summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/common_time/Android.mk3
-rw-r--r--services/input/Android.mk1
-rw-r--r--services/input/EventHub.cpp42
-rw-r--r--services/input/EventHub.h17
-rw-r--r--services/input/InputDispatcher.cpp4
-rw-r--r--services/input/InputDispatcher.h2
-rw-r--r--services/input/InputReader.cpp213
-rw-r--r--services/input/InputReader.h31
-rw-r--r--services/input/SpriteController.cpp26
-rw-r--r--services/input/tests/Android.mk1
-rw-r--r--services/java/com/android/server/AppOpsService.java860
-rw-r--r--services/java/com/android/server/AppWidgetService.java157
-rw-r--r--services/java/com/android/server/AppWidgetServiceImpl.java105
-rw-r--r--services/java/com/android/server/AttributeCache.java47
-rw-r--r--services/java/com/android/server/BackupManagerService.java240
-rw-r--r--services/java/com/android/server/BatteryService.java9
-rw-r--r--services/java/com/android/server/BluetoothManagerService.java163
-rw-r--r--services/java/com/android/server/BootReceiver.java29
-rw-r--r--services/java/com/android/server/ClipboardService.java54
-rw-r--r--services/java/com/android/server/ConnectivityService.java974
-rw-r--r--services/java/com/android/server/DevicePolicyManagerService.java179
-rw-r--r--services/java/com/android/server/DeviceStorageMonitorService.java87
-rw-r--r--services/java/com/android/server/EntropyMixer.java26
-rw-r--r--services/java/com/android/server/EventLogTags.logtags17
-rw-r--r--services/java/com/android/server/IdleMaintenanceService.java296
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java1130
-rw-r--r--services/java/com/android/server/IntentResolver.java16
-rw-r--r--services/java/com/android/server/IntentResolverOld.java12
-rw-r--r--services/java/com/android/server/LocationManagerService.java511
-rw-r--r--services/java/com/android/server/LockSettingsService.java462
-rw-r--r--services/java/com/android/server/MountService.java56
-rw-r--r--services/java/com/android/server/NativeDaemonConnector.java148
-rw-r--r--services/java/com/android/server/NetworkManagementService.java181
-rw-r--r--services/java/com/android/server/NetworkTimeUpdateService.java51
-rw-r--r--services/java/com/android/server/NotificationManagerService.java1103
-rw-r--r--services/java/com/android/server/PreferredComponent.java20
-rw-r--r--services/java/com/android/server/SerialService.java7
-rw-r--r--services/java/com/android/server/ServiceWatcher.java176
-rw-r--r--services/java/com/android/server/StatusBarManagerService.java2
-rw-r--r--services/java/com/android/server/SystemServer.java54
-rw-r--r--services/java/com/android/server/TextServicesManagerService.java3
-rw-r--r--services/java/com/android/server/ThrottleService.java1160
-rw-r--r--services/java/com/android/server/UiModeManagerService.java2
-rw-r--r--services/java/com/android/server/VibratorService.java113
-rw-r--r--services/java/com/android/server/WallpaperManagerService.java3
-rw-r--r--services/java/com/android/server/Watchdog.java65
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityInputFilter.java234
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java1600
-rw-r--r--services/java/com/android/server/accessibility/EventStreamTransformation.java2
-rw-r--r--services/java/com/android/server/accessibility/ScreenMagnifier.java1317
-rw-r--r--services/java/com/android/server/accounts/AccountAuthenticatorCache.java94
-rw-r--r--services/java/com/android/server/accounts/AccountManagerService.java2975
-rw-r--r--services/java/com/android/server/accounts/IAccountAuthenticatorCache.java67
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java508
-rw-r--r--services/java/com/android/server/am/ActivityRecord.java62
-rw-r--r--services/java/com/android/server/am/ActivityStack.java168
-rw-r--r--services/java/com/android/server/am/BatteryStatsService.java14
-rw-r--r--services/java/com/android/server/am/BroadcastQueue.java26
-rw-r--r--services/java/com/android/server/am/BroadcastRecord.java17
-rw-r--r--services/java/com/android/server/am/NativeCrashListener.java278
-rw-r--r--services/java/com/android/server/am/PendingIntentRecord.java7
-rw-r--r--services/java/com/android/server/am/ProcessList.java4
-rw-r--r--services/java/com/android/server/am/ProcessRecord.java3
-rw-r--r--services/java/com/android/server/am/ServiceRecord.java53
-rw-r--r--services/java/com/android/server/connectivity/Nat464Xlat.java193
-rw-r--r--services/java/com/android/server/connectivity/Tethering.java17
-rw-r--r--services/java/com/android/server/connectivity/Vpn.java69
-rw-r--r--services/java/com/android/server/content/ContentService.java855
-rw-r--r--services/java/com/android/server/content/SyncManager.java2822
-rw-r--r--services/java/com/android/server/content/SyncOperation.java224
-rw-r--r--services/java/com/android/server/content/SyncQueue.java225
-rw-r--r--services/java/com/android/server/content/SyncStorageEngine.java2303
-rw-r--r--services/java/com/android/server/display/DisplayDevice.java7
-rw-r--r--services/java/com/android/server/display/DisplayManagerService.java26
-rw-r--r--services/java/com/android/server/display/LocalDisplayAdapter.java27
-rw-r--r--services/java/com/android/server/display/LogicalDisplay.java13
-rw-r--r--services/java/com/android/server/display/OverlayDisplayAdapter.java3
-rw-r--r--services/java/com/android/server/display/WifiDisplayAdapter.java3
-rw-r--r--services/java/com/android/server/display/WifiDisplayController.java2
-rw-r--r--services/java/com/android/server/dreams/DreamController.java4
-rw-r--r--services/java/com/android/server/firewall/AndFilter.java46
-rw-r--r--services/java/com/android/server/firewall/CategoryFilter.java58
-rw-r--r--services/java/com/android/server/firewall/Filter.java39
-rw-r--r--services/java/com/android/server/firewall/FilterFactory.java40
-rw-r--r--services/java/com/android/server/firewall/FilterList.java41
-rw-r--r--services/java/com/android/server/firewall/IntentFirewall.java512
-rw-r--r--services/java/com/android/server/firewall/NotFilter.java59
-rw-r--r--services/java/com/android/server/firewall/OrFilter.java46
-rw-r--r--services/java/com/android/server/firewall/PortFilter.java110
-rw-r--r--services/java/com/android/server/firewall/SenderFilter.java111
-rw-r--r--services/java/com/android/server/firewall/SenderPermissionFilter.java57
-rw-r--r--services/java/com/android/server/firewall/StringFilter.java342
-rw-r--r--services/java/com/android/server/location/GeocoderProxy.java15
-rw-r--r--services/java/com/android/server/location/GeofenceManager.java19
-rw-r--r--services/java/com/android/server/location/GeofenceProxy.java157
-rw-r--r--services/java/com/android/server/location/GeofenceState.java6
-rw-r--r--services/java/com/android/server/location/GpsLocationProvider.java291
-rw-r--r--services/java/com/android/server/location/LocationBlacklist.java4
-rw-r--r--services/java/com/android/server/location/LocationProviderInterface.java2
-rw-r--r--services/java/com/android/server/location/LocationProviderProxy.java23
-rw-r--r--services/java/com/android/server/location/MockProvider.java5
-rw-r--r--services/java/com/android/server/location/PassiveProvider.java5
-rw-r--r--services/java/com/android/server/net/LockdownVpnTracker.java25
-rw-r--r--services/java/com/android/server/net/NetworkPolicyManagerService.java64
-rw-r--r--services/java/com/android/server/os/SchedulingPolicyService.java64
-rw-r--r--services/java/com/android/server/pm/Installer.java31
-rw-r--r--services/java/com/android/server/pm/PackageManagerService.java1555
-rw-r--r--services/java/com/android/server/pm/PackageSettingBase.java13
-rw-r--r--services/java/com/android/server/pm/PreferredActivity.java4
-rw-r--r--services/java/com/android/server/pm/PreferredIntentResolver.java4
-rw-r--r--services/java/com/android/server/pm/Settings.java469
-rw-r--r--services/java/com/android/server/pm/UserManagerService.java281
-rw-r--r--services/java/com/android/server/power/ElectronBeam.java215
-rw-r--r--services/java/com/android/server/power/PowerManagerService.java101
-rw-r--r--services/java/com/android/server/search/SearchManagerService.java279
-rw-r--r--services/java/com/android/server/search/Searchables.java464
-rw-r--r--services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java42
-rw-r--r--services/java/com/android/server/updates/IntentFirewallInstallReceiver.java27
-rw-r--r--services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java155
-rw-r--r--services/java/com/android/server/updates/TZInfoInstallReceiver.java34
-rw-r--r--services/java/com/android/server/usb/UsbDebuggingManager.java28
-rw-r--r--services/java/com/android/server/usb/UsbDeviceManager.java13
-rw-r--r--services/java/com/android/server/usb/UsbService.java6
-rw-r--r--services/java/com/android/server/usb/UsbSettingsManager.java16
-rw-r--r--services/java/com/android/server/wifi/README.txt19
-rw-r--r--services/java/com/android/server/wifi/WifiController.java737
-rw-r--r--services/java/com/android/server/wifi/WifiNotificationController.java297
-rw-r--r--services/java/com/android/server/wifi/WifiService.java (renamed from services/java/com/android/server/WifiService.java)976
-rw-r--r--services/java/com/android/server/wifi/WifiSettingsStore.java197
-rw-r--r--services/java/com/android/server/wifi/WifiTrafficPoller.java191
-rw-r--r--services/java/com/android/server/wm/AppTransition.java767
-rw-r--r--services/java/com/android/server/wm/AppWindowAnimator.java51
-rw-r--r--services/java/com/android/server/wm/AppWindowToken.java31
-rw-r--r--services/java/com/android/server/wm/BlackFrame.java15
-rw-r--r--services/java/com/android/server/wm/DimAnimator.java248
-rw-r--r--services/java/com/android/server/wm/DimLayer.java269
-rw-r--r--services/java/com/android/server/wm/DimSurface.java127
-rw-r--r--services/java/com/android/server/wm/DisplayContent.java11
-rw-r--r--services/java/com/android/server/wm/DisplayMagnifier.java756
-rw-r--r--services/java/com/android/server/wm/DisplaySettings.java224
-rw-r--r--services/java/com/android/server/wm/DragState.java21
-rw-r--r--services/java/com/android/server/wm/MagnificationSpec.java46
-rw-r--r--services/java/com/android/server/wm/ScreenRotationAnimation.java107
-rw-r--r--services/java/com/android/server/wm/Session.java26
-rw-r--r--services/java/com/android/server/wm/StrictModeFlash.java39
-rw-r--r--services/java/com/android/server/wm/Watermark.java51
-rw-r--r--services/java/com/android/server/wm/WindowAnimator.java506
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java2590
-rw-r--r--services/java/com/android/server/wm/WindowState.java293
-rw-r--r--services/java/com/android/server/wm/WindowStateAnimator.java257
-rw-r--r--services/jni/Android.mk1
-rw-r--r--services/jni/com_android_server_BatteryService.cpp227
-rw-r--r--services/jni/com_android_server_input_InputApplicationHandle.cpp4
-rw-r--r--services/jni/com_android_server_input_InputManagerService.cpp7
-rw-r--r--services/jni/com_android_server_input_InputWindowHandle.cpp4
-rw-r--r--services/jni/com_android_server_location_GpsLocationProvider.cpp179
-rw-r--r--services/tests/servicestests/Android.mk2
-rw-r--r--services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/EntropyMixerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java353
-rw-r--r--services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java262
-rw-r--r--services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java100
-rw-r--r--services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java105
-rw-r--r--services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java470
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/search/SearchablesTest.java449
170 files changed, 30200 insertions, 9916 deletions
diff --git a/services/common_time/Android.mk b/services/common_time/Android.mk
index 0606ab4..75eb528 100644
--- a/services/common_time/Android.mk
+++ b/services/common_time/Android.mk
@@ -27,7 +27,8 @@ endif
LOCAL_SHARED_LIBRARIES := \
libbinder \
libcommon_time_client \
- libutils
+ libutils \
+ liblog
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := common_time
diff --git a/services/input/Android.mk b/services/input/Android.mk
index 159800f..5d913f3 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -29,6 +29,7 @@ LOCAL_SRC_FILES:= \
LOCAL_SHARED_LIBRARIES := \
libcutils \
+ liblog \
libandroidfw \
libutils \
libhardware \
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 1c9520d..f4e1cec 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -40,7 +40,6 @@
#include <androidfw/KeyCharacterMap.h>
#include <androidfw/VirtualKeyMap.h>
-#include <sha1.h>
#include <string.h>
#include <stdint.h>
#include <dirent.h>
@@ -49,6 +48,7 @@
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/limits.h>
+#include <sys/sha1.h>
/* this macro is used to tell if "bit" is set in "array"
* it selects a byte from the array, and does a boolean AND
@@ -162,7 +162,8 @@ EventHub::Device::Device(int fd, int32_t id, const String8& path,
next(NULL),
fd(fd), id(id), path(path), identifier(identifier),
classes(0), configuration(NULL), virtualKeyMap(NULL),
- ffEffectPlaying(false), ffEffectId(-1) {
+ ffEffectPlaying(false), ffEffectId(-1),
+ timestampOverrideSec(0), timestampOverrideUsec(0) {
memset(keyBitmask, 0, sizeof(keyBitmask));
memset(absBitmask, 0, sizeof(absBitmask));
memset(relBitmask, 0, sizeof(relBitmask));
@@ -766,12 +767,37 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz
size_t count = size_t(readSize) / sizeof(struct input_event);
for (size_t i = 0; i < count; i++) {
- const struct input_event& iev = readBuffer[i];
- ALOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, value=%d",
+ struct input_event& iev = readBuffer[i];
+ ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",
device->path.string(),
(int) iev.time.tv_sec, (int) iev.time.tv_usec,
iev.type, iev.code, iev.value);
+ // Some input devices may have a better concept of the time
+ // when an input event was actually generated than the kernel
+ // which simply timestamps all events on entry to evdev.
+ // This is a custom Android extension of the input protocol
+ // mainly intended for use with uinput based device drivers.
+ if (iev.type == EV_MSC) {
+ if (iev.code == MSC_ANDROID_TIME_SEC) {
+ device->timestampOverrideSec = iev.value;
+ continue;
+ } else if (iev.code == MSC_ANDROID_TIME_USEC) {
+ device->timestampOverrideUsec = iev.value;
+ continue;
+ }
+ }
+ if (device->timestampOverrideSec || device->timestampOverrideUsec) {
+ iev.time.tv_sec = device->timestampOverrideSec;
+ iev.time.tv_usec = device->timestampOverrideUsec;
+ if (iev.type == EV_SYN && iev.code == SYN_REPORT) {
+ device->timestampOverrideSec = 0;
+ device->timestampOverrideUsec = 0;
+ }
+ ALOGV("applied override time %d.%06d",
+ int(iev.time.tv_sec), int(iev.time.tv_usec));
+ }
+
#ifdef HAVE_POSIX_CLOCKS
// Use the time specified in the event instead of the current time
// so that downstream code can get more accurate estimates of
@@ -829,8 +855,8 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz
event->code = iev.code;
event->value = iev.value;
event += 1;
+ capacity -= 1;
}
- capacity -= count;
if (capacity == 0) {
// The result buffer is full. Reset the pending event index
// so we will try to read the device again on the next iteration.
@@ -1183,6 +1209,12 @@ status_t EventHub::openDeviceLocked(const char *devicePath) {
break;
}
}
+
+ // Disable kernel key repeat since we handle it ourselves
+ unsigned int repeatRate[] = {0,0};
+ if (ioctl(fd, EVIOCSREP, repeatRate)) {
+ ALOGW("Unable to disable kernel key repeat for %s: %s", devicePath, strerror(errno));
+ }
}
// If the device isn't recognized as something we handle, don't monitor it.
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index afc12ef..c93fc7a 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -42,6 +42,20 @@
#define BTN_FIRST 0x100 // first button code
#define BTN_LAST 0x15f // last button code
+/*
+ * These constants are used privately in Android to pass raw timestamps
+ * through evdev from uinput device drivers because there is currently no
+ * other way to transfer this information. The evdev driver automatically
+ * timestamps all input events with the time they were posted and clobbers
+ * whatever information was passed in.
+ *
+ * For the purposes of this hack, the timestamp is specified in the
+ * CLOCK_MONOTONIC timebase and is split into two EV_MSC events specifying
+ * seconds and microseconds.
+ */
+#define MSC_ANDROID_TIME_SEC 0x6
+#define MSC_ANDROID_TIME_USEC 0x7
+
namespace android {
enum {
@@ -329,6 +343,9 @@ private:
bool ffEffectPlaying;
int16_t ffEffectId; // initially -1
+ int32_t timestampOverrideSec;
+ int32_t timestampOverrideUsec;
+
Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier);
~Device();
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 0465215..23a846b 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -538,7 +538,9 @@ void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropR
}
bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) {
- return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL;
+ return keyCode == AKEYCODE_HOME
+ || keyCode == AKEYCODE_ENDCALL
+ || keyCode == AKEYCODE_APP_SWITCH;
}
bool InputDispatcher::isAppSwitchKeyEventLocked(KeyEntry* keyEntry) {
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index d4f932e..430721e 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -165,6 +165,8 @@ struct InputTarget {
* Input dispatcher configuration.
*
* Specifies various options that modify the behavior of the input dispatcher.
+ * The values provided here are merely defaults. The actual values will come from ViewConfiguration
+ * and are passed into the dispatcher during initialization.
*/
struct InputDispatcherConfiguration {
// The key repeat initial timeout.
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index bc8df18..c774763 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -882,8 +882,9 @@ void InputDevice::dump(String8& dump) {
snprintf(name, sizeof(name), "%d", range.axis);
}
dump.appendFormat(INDENT3 "%s: source=0x%08x, "
- "min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n",
- name, range.source, range.min, range.max, range.flat, range.fuzz);
+ "min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f, resolution=%0.3f\n",
+ name, range.source, range.min, range.max, range.flat, range.fuzz,
+ range.resolution);
}
}
@@ -2247,20 +2248,20 @@ void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
if (mParameters.mode == Parameters::MODE_POINTER) {
float minX, minY, maxX, maxY;
if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
- info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f);
- info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f);
}
} else {
- info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale);
- info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale);
+ info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f);
}
- info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
if (mCursorScrollAccumulator.haveRelativeVWheel()) {
- info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
}
if (mCursorScrollAccumulator.haveRelativeHWheel()) {
- info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
}
}
@@ -2611,10 +2612,24 @@ void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
}
if (mCursorScrollAccumulator.haveRelativeVWheel()) {
- info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f);
}
if (mCursorScrollAccumulator.haveRelativeHWheel()) {
- info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f);
+ }
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+ const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
+ const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat,
+ x.fuzz, x.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat,
+ y.fuzz, y.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat,
+ x.fuzz, x.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat,
+ y.fuzz, y.resolution);
}
}
}
@@ -2788,6 +2803,8 @@ void TouchInputMapper::configureParameters() {
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
} else if (deviceTypeString == "touchPad") {
mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+ } else if (deviceTypeString == "touchNavigation") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_NAVIGATION;
} else if (deviceTypeString == "pointer") {
mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
} else if (deviceTypeString != "default") {
@@ -2832,6 +2849,9 @@ void TouchInputMapper::dumpParameters(String8& dump) {
case Parameters::DEVICE_TYPE_TOUCH_PAD:
dump.append(INDENT4 "DeviceType: touchPad\n");
break;
+ case Parameters::DEVICE_TYPE_TOUCH_NAVIGATION:
+ dump.append(INDENT4 "DeviceType: touchNavigation\n");
+ break;
case Parameters::DEVICE_TYPE_POINTER:
dump.append(INDENT4 "DeviceType: pointer\n");
break;
@@ -2885,6 +2905,9 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
if (hasStylus()) {
mSource |= AINPUT_SOURCE_STYLUS;
}
+ } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_NAVIGATION) {
+ mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
+ mDeviceMode = DEVICE_MODE_NAVIGATION;
} else {
mSource = AINPUT_SOURCE_TOUCHPAD;
mDeviceMode = DEVICE_MODE_UNSCALED;
@@ -3055,6 +3078,7 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.touchMajor.max = diagonalSize;
mOrientedRanges.touchMajor.flat = 0;
mOrientedRanges.touchMajor.fuzz = 0;
+ mOrientedRanges.touchMajor.resolution = 0;
mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
@@ -3065,6 +3089,7 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.toolMajor.max = diagonalSize;
mOrientedRanges.toolMajor.flat = 0;
mOrientedRanges.toolMajor.fuzz = 0;
+ mOrientedRanges.toolMajor.resolution = 0;
mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
@@ -3075,6 +3100,7 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.size.max = 1.0;
mOrientedRanges.size.flat = 0;
mOrientedRanges.size.fuzz = 0;
+ mOrientedRanges.size.resolution = 0;
} else {
mSizeScale = 0.0f;
}
@@ -3098,6 +3124,7 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.pressure.max = 1.0;
mOrientedRanges.pressure.flat = 0;
mOrientedRanges.pressure.fuzz = 0;
+ mOrientedRanges.pressure.resolution = 0;
// Tilt
mTiltXCenter = 0;
@@ -3121,6 +3148,7 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.tilt.max = M_PI_2;
mOrientedRanges.tilt.flat = 0;
mOrientedRanges.tilt.fuzz = 0;
+ mOrientedRanges.tilt.resolution = 0;
}
// Orientation
@@ -3134,6 +3162,7 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.orientation.max = M_PI;
mOrientedRanges.orientation.flat = 0;
mOrientedRanges.orientation.fuzz = 0;
+ mOrientedRanges.orientation.resolution = 0;
} else if (mCalibration.orientationCalibration !=
Calibration::ORIENTATION_CALIBRATION_NONE) {
if (mCalibration.orientationCalibration
@@ -3157,6 +3186,7 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.orientation.max = M_PI_2;
mOrientedRanges.orientation.flat = 0;
mOrientedRanges.orientation.fuzz = 0;
+ mOrientedRanges.orientation.resolution = 0;
}
// Distance
@@ -3182,6 +3212,7 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.distance.flat = 0;
mOrientedRanges.distance.fuzz =
mRawPointerAxes.distance.fuzz * mDistanceScale;
+ mOrientedRanges.distance.resolution = 0;
}
// Compute oriented precision, scales and ranges.
@@ -3196,12 +3227,14 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.x.min = mYTranslate;
mOrientedRanges.x.max = mSurfaceHeight + mYTranslate - 1;
mOrientedRanges.x.flat = 0;
- mOrientedRanges.x.fuzz = mYScale;
+ mOrientedRanges.x.fuzz = 0;
+ mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
mOrientedRanges.y.min = mXTranslate;
mOrientedRanges.y.max = mSurfaceWidth + mXTranslate - 1;
mOrientedRanges.y.flat = 0;
- mOrientedRanges.y.fuzz = mXScale;
+ mOrientedRanges.y.fuzz = 0;
+ mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
break;
default:
@@ -3211,17 +3244,19 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
mOrientedRanges.x.min = mXTranslate;
mOrientedRanges.x.max = mSurfaceWidth + mXTranslate - 1;
mOrientedRanges.x.flat = 0;
- mOrientedRanges.x.fuzz = mXScale;
+ mOrientedRanges.x.fuzz = 0;
+ mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
mOrientedRanges.y.min = mYTranslate;
mOrientedRanges.y.max = mSurfaceHeight + mYTranslate - 1;
mOrientedRanges.y.flat = 0;
- mOrientedRanges.y.fuzz = mYScale;
+ mOrientedRanges.y.fuzz = 0;
+ mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
break;
}
- // Compute pointer gesture detection parameters.
if (mDeviceMode == DEVICE_MODE_POINTER) {
+ // Compute pointer gesture detection parameters.
float rawDiagonal = hypotf(rawWidth, rawHeight);
float displayDiagonal = hypotf(mSurfaceWidth, mSurfaceHeight);
@@ -3246,10 +3281,10 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
// translated into freeform gestures.
mPointerGestureMaxSwipeWidth =
mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal;
- }
- // Abort current pointer usages because the state has changed.
- abortPointerUsage(when, 0 /*policyFlags*/);
+ // Abort current pointer usages because the state has changed.
+ abortPointerUsage(when, 0 /*policyFlags*/);
+ }
// Inform the dispatcher about the changes.
*outResetNeeded = true;
@@ -3425,6 +3460,19 @@ void TouchInputMapper::parseCalibration() {
out.haveDistanceScale = in.tryGetProperty(String8("touch.distance.scale"),
out.distanceScale);
+
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_DEFAULT;
+ String8 coverageCalibrationString;
+ if (in.tryGetProperty(String8("touch.coverage.calibration"), coverageCalibrationString)) {
+ if (coverageCalibrationString == "none") {
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+ } else if (coverageCalibrationString == "box") {
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_BOX;
+ } else if (coverageCalibrationString != "default") {
+ ALOGW("Invalid value for touch.coverage.calibration: '%s'",
+ coverageCalibrationString.string());
+ }
+ }
}
void TouchInputMapper::resolveCalibration() {
@@ -3463,6 +3511,11 @@ void TouchInputMapper::resolveCalibration() {
} else {
mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
}
+
+ // Coverage
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_DEFAULT) {
+ mCalibration.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+ }
}
void TouchInputMapper::dumpCalibration(String8& dump) {
@@ -3555,6 +3608,17 @@ void TouchInputMapper::dumpCalibration(String8& dump) {
dump.appendFormat(INDENT4 "touch.distance.scale: %0.3f\n",
mCalibration.distanceScale);
}
+
+ switch (mCalibration.coverageCalibration) {
+ case Calibration::COVERAGE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.coverage.calibration: none\n");
+ break;
+ case Calibration::COVERAGE_CALIBRATION_BOX:
+ dump.append(INDENT4 "touch.coverage.calibration: box\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
}
void TouchInputMapper::reset(nsecs_t when) {
@@ -4162,13 +4226,31 @@ void TouchInputMapper::cookPointerData() {
distance = 0;
}
- // X and Y
+ // Coverage
+ int32_t rawLeft, rawTop, rawRight, rawBottom;
+ switch (mCalibration.coverageCalibration) {
+ case Calibration::COVERAGE_CALIBRATION_BOX:
+ rawLeft = (in.toolMinor & 0xffff0000) >> 16;
+ rawRight = in.toolMinor & 0x0000ffff;
+ rawBottom = in.toolMajor & 0x0000ffff;
+ rawTop = (in.toolMajor & 0xffff0000) >> 16;
+ break;
+ default:
+ rawLeft = rawTop = rawRight = rawBottom = 0;
+ break;
+ }
+
+ // X, Y, and the bounding box for coverage information
// Adjust coords for surface orientation.
- float x, y;
+ float x, y, left, top, right, bottom;
switch (mSurfaceOrientation) {
case DISPLAY_ORIENTATION_90:
x = float(in.y - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
y = float(mRawPointerAxes.x.maxValue - in.x) * mXScale + mXTranslate;
+ left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ right = float(rawBottom- mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
+ top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
orientation -= M_PI_2;
if (orientation < - M_PI_2) {
orientation += M_PI;
@@ -4177,10 +4259,18 @@ void TouchInputMapper::cookPointerData() {
case DISPLAY_ORIENTATION_180:
x = float(mRawPointerAxes.x.maxValue - in.x) * mXScale + mXTranslate;
y = float(mRawPointerAxes.y.maxValue - in.y) * mYScale + mYTranslate;
+ left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
+ right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
+ bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
+ top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
break;
case DISPLAY_ORIENTATION_270:
x = float(mRawPointerAxes.y.maxValue - in.y) * mYScale + mYTranslate;
y = float(in.x - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
+ right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
+ bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
orientation += M_PI_2;
if (orientation > M_PI_2) {
orientation -= M_PI;
@@ -4189,6 +4279,10 @@ void TouchInputMapper::cookPointerData() {
default:
x = float(in.x - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
y = float(in.y - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
break;
}
@@ -4201,11 +4295,18 @@ void TouchInputMapper::cookPointerData() {
out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size);
out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor);
out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor);
- out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
- out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt);
out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom);
+ } else {
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
+ }
// Write output properties.
PointerProperties& properties = mCurrentCookedPointerData.pointerProperties[i];
@@ -6036,15 +6137,42 @@ void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
for (size_t i = 0; i < mAxes.size(); i++) {
const Axis& axis = mAxes.valueAt(i);
- info->addMotionRange(axis.axisInfo.axis, AINPUT_SOURCE_JOYSTICK,
- axis.min, axis.max, axis.flat, axis.fuzz);
+ addMotionRange(axis.axisInfo.axis, axis, info);
+
if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
- info->addMotionRange(axis.axisInfo.highAxis, AINPUT_SOURCE_JOYSTICK,
- axis.min, axis.max, axis.flat, axis.fuzz);
+ addMotionRange(axis.axisInfo.highAxis, axis, info);
+
}
}
}
+void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis,
+ InputDeviceInfo* info) {
+ info->addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK,
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ /* In order to ease the transition for developers from using the old axes
+ * to the newer, more semantically correct axes, we'll continue to register
+ * the old axes as duplicates of their corresponding new ones. */
+ int32_t compatAxis = getCompatAxis(axisId);
+ if (compatAxis >= 0) {
+ info->addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK,
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ }
+}
+
+/* A mapping from axes the joystick actually has to the axes that should be
+ * artificially created for compatibility purposes.
+ * Returns -1 if no compatibility axis is needed. */
+int32_t JoystickInputMapper::getCompatAxis(int32_t axis) {
+ switch(axis) {
+ case AMOTION_EVENT_AXIS_LTRIGGER:
+ return AMOTION_EVENT_AXIS_BRAKE;
+ case AMOTION_EVENT_AXIS_RTRIGGER:
+ return AMOTION_EVENT_AXIS_GAS;
+ }
+ return -1;
+}
+
void JoystickInputMapper::dump(String8& dump) {
dump.append(INDENT2 "Joystick Input Mapper:\n");
@@ -6070,8 +6198,8 @@ void JoystickInputMapper::dump(String8& dump) {
dump.append(" (invert)");
}
- dump.appendFormat(": min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f\n",
- axis.min, axis.max, axis.flat, axis.fuzz);
+ dump.appendFormat(": min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f, resolution=%0.5f\n",
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
dump.appendFormat(INDENT4 " scale=%0.5f, offset=%0.5f, "
"highScale=%0.5f, highOffset=%0.5f\n",
axis.scale, axis.offset, axis.highScale, axis.highOffset);
@@ -6117,18 +6245,21 @@ void JoystickInputMapper::configure(nsecs_t when,
float highScale = 1.0f / (rawAxisInfo.maxValue - axisInfo.splitValue);
axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
scale, 0.0f, highScale, 0.0f,
- 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale);
+ 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
} else if (isCenteredAxis(axisInfo.axis)) {
float scale = 2.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
float offset = avg(rawAxisInfo.minValue, rawAxisInfo.maxValue) * -scale;
axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
scale, offset, scale, offset,
- -1.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale);
+ -1.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
} else {
float scale = 1.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
scale, 0.0f, scale, 0.0f,
- 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale);
+ 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
}
// To eliminate noise while the joystick is at rest, filter out small variations
@@ -6296,9 +6427,10 @@ void JoystickInputMapper::sync(nsecs_t when, bool force) {
size_t numAxes = mAxes.size();
for (size_t i = 0; i < numAxes; i++) {
const Axis& axis = mAxes.valueAt(i);
- pointerCoords.setAxisValue(axis.axisInfo.axis, axis.currentValue);
+ setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.axis, axis.currentValue);
if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
- pointerCoords.setAxisValue(axis.axisInfo.highAxis, axis.highCurrentValue);
+ setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.highAxis,
+ axis.highCurrentValue);
}
}
@@ -6314,6 +6446,19 @@ void JoystickInputMapper::sync(nsecs_t when, bool force) {
getListener()->notifyMotion(&args);
}
+void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords,
+ int32_t axis, float value) {
+ pointerCoords->setAxisValue(axis, value);
+ /* In order to ease the transition for developers from using the old axes
+ * to the newer, more semantically correct axes, we'll continue to produce
+ * values for the old axes as mirrors of the value of their corresponding
+ * new axes. */
+ int32_t compatAxis = getCompatAxis(axis);
+ if (compatAxis >= 0) {
+ pointerCoords->setAxisValue(compatAxis, value);
+ }
+}
+
bool JoystickInputMapper::filterAxes(bool force) {
bool atLeastOneSignificantChange = force;
size_t numAxes = mAxes.size();
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 61b21e2..0189ba7 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -791,6 +791,10 @@ struct CookedPointerData {
void clear();
void copyFrom(const CookedPointerData& other);
+ inline const PointerCoords& pointerCoordsForId(uint32_t id) const {
+ return pointerCoords[idToIndex[id]];
+ }
+
inline bool isHovering(uint32_t pointerIndex) {
return hoveringIdBits.hasBit(pointerProperties[pointerIndex].id);
}
@@ -1180,6 +1184,7 @@ protected:
DEVICE_MODE_DISABLED, // input is disabled
DEVICE_MODE_DIRECT, // direct mapping (touchscreen)
DEVICE_MODE_UNSCALED, // unscaled mapping (touchpad)
+ DEVICE_MODE_NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
DEVICE_MODE_POINTER, // pointer mapping (pointer)
};
DeviceMode mDeviceMode;
@@ -1192,6 +1197,7 @@ protected:
enum DeviceType {
DEVICE_TYPE_TOUCH_SCREEN,
DEVICE_TYPE_TOUCH_PAD,
+ DEVICE_TYPE_TOUCH_NAVIGATION,
DEVICE_TYPE_POINTER,
};
@@ -1261,6 +1267,14 @@ protected:
bool haveDistanceScale;
float distanceScale;
+ enum CoverageCalibration {
+ COVERAGE_CALIBRATION_DEFAULT,
+ COVERAGE_CALIBRATION_NONE,
+ COVERAGE_CALIBRATION_BOX,
+ };
+
+ CoverageCalibration coverageCalibration;
+
inline void applySizeScaleAndBias(float* outSize) const {
if (haveSizeScale) {
*outSize *= sizeScale;
@@ -1729,10 +1743,11 @@ private:
float highScale; // scale factor from raw to normalized values of high split
float highOffset; // offset to add after scaling for normalization of high split
- float min; // normalized inclusive minimum
- float max; // normalized inclusive maximum
- float flat; // normalized flat region size
- float fuzz; // normalized error tolerance
+ float min; // normalized inclusive minimum
+ float max; // normalized inclusive maximum
+ float flat; // normalized flat region size
+ float fuzz; // normalized error tolerance
+ float resolution; // normalized resolution in units/mm
float filter; // filter out small variations of this size
float currentValue; // current value
@@ -1743,7 +1758,7 @@ private:
void initialize(const RawAbsoluteAxisInfo& rawAxisInfo, const AxisInfo& axisInfo,
bool explicitlyMapped, float scale, float offset,
float highScale, float highOffset,
- float min, float max, float flat, float fuzz) {
+ float min, float max, float flat, float fuzz, float resolution) {
this->rawAxisInfo = rawAxisInfo;
this->axisInfo = axisInfo;
this->explicitlyMapped = explicitlyMapped;
@@ -1755,6 +1770,7 @@ private:
this->max = max;
this->flat = flat;
this->fuzz = fuzz;
+ this->resolution = resolution;
this->filter = 0;
resetValue();
}
@@ -1782,6 +1798,11 @@ private:
float newValue, float currentValue, float thresholdValue);
static bool isCenteredAxis(int32_t axis);
+ static int32_t getCompatAxis(int32_t axis);
+
+ static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info);
+ static void setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis,
+ float value);
};
} // namespace android
diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp
index 1f3d2cf..fd9c66b 100644
--- a/services/input/SpriteController.cpp
+++ b/services/input/SpriteController.cpp
@@ -22,12 +22,14 @@
#include <cutils/log.h>
#include <utils/String8.h>
+#include <gui/Surface.h>
#include <SkBitmap.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkPaint.h>
#include <SkXfermode.h>
+#include <android/native_window.h>
namespace android {
@@ -197,33 +199,32 @@ void SpriteController::doUpdateSprites() {
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);
+ ANativeWindow_Buffer outBuffer;
+ status_t status = surface->lock(&outBuffer, NULL);
if (status) {
ALOGE("Error %d locking sprite surface before drawing.", status);
} else {
SkBitmap surfaceBitmap;
- ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format);
+ ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config,
- surfaceInfo.w, surfaceInfo.h, bpr);
- surfaceBitmap.setPixels(surfaceInfo.bits);
+ outBuffer.width, outBuffer.height, bpr);
+ surfaceBitmap.setPixels(outBuffer.bits);
- SkCanvas surfaceCanvas;
- surfaceCanvas.setBitmapDevice(surfaceBitmap);
+ SkCanvas surfaceCanvas(surfaceBitmap);
SkPaint paint;
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
- if (surfaceInfo.w > uint32_t(update.state.icon.bitmap.width())) {
+ if (outBuffer.width > uint32_t(update.state.icon.bitmap.width())) {
paint.setColor(0); // transparent fill color
surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
- surfaceInfo.w, update.state.icon.bitmap.height(), paint);
+ outBuffer.width, update.state.icon.bitmap.height(), paint);
}
- if (surfaceInfo.h > uint32_t(update.state.icon.bitmap.height())) {
+ if (outBuffer.height > uint32_t(update.state.icon.bitmap.height())) {
paint.setColor(0); // transparent fill color
surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
- surfaceInfo.w, surfaceInfo.h, paint);
+ outBuffer.width, outBuffer.height, paint);
}
status = surface->unlockAndPost();
@@ -371,8 +372,7 @@ sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height
sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eHidden);
- if (surfaceControl == NULL || !surfaceControl->isValid()
- || !surfaceControl->getSurface()->isValid()) {
+ if (surfaceControl == NULL || !surfaceControl->isValid()) {
ALOGE("Error creating sprite surface.");
return NULL;
}
diff --git a/services/input/tests/Android.mk b/services/input/tests/Android.mk
index 8f8c34b..211e64b 100644
--- a/services/input/tests/Android.mk
+++ b/services/input/tests/Android.mk
@@ -9,6 +9,7 @@ test_src_files := \
shared_libraries := \
libcutils \
+ liblog \
libandroidfw \
libutils \
libhardware \
diff --git a/services/java/com/android/server/AppOpsService.java b/services/java/com/android/server/AppOpsService.java
new file mode 100644
index 0000000..a402642
--- /dev/null
+++ b/services/java/com/android/server/AppOpsService.java
@@ -0,0 +1,860 @@
+/*
+ * Copyright (C) 2012 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;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+public class AppOpsService extends IAppOpsService.Stub {
+ static final String TAG = "AppOps";
+ static final boolean DEBUG = false;
+
+ // Write at most every 30 minutes.
+ static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
+
+ Context mContext;
+ final AtomicFile mFile;
+ final Handler mHandler;
+
+ boolean mWriteScheduled;
+ final Runnable mWriteRunner = new Runnable() {
+ public void run() {
+ synchronized (AppOpsService.this) {
+ mWriteScheduled = false;
+ AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+ @Override protected Void doInBackground(Void... params) {
+ writeState();
+ return null;
+ }
+ };
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
+ }
+ }
+ };
+
+ final SparseArray<HashMap<String, Ops>> mUidOps
+ = new SparseArray<HashMap<String, Ops>>();
+
+ public final static class Ops extends SparseArray<Op> {
+ public final String packageName;
+ public final int uid;
+
+ public Ops(String _packageName, int _uid) {
+ packageName = _packageName;
+ uid = _uid;
+ }
+ }
+
+ public final static class Op {
+ public final int op;
+ public int mode;
+ public int duration;
+ public long time;
+ public long rejectTime;
+ public int nesting;
+
+ public Op(int _op) {
+ op = _op;
+ mode = AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ final SparseArray<ArrayList<Callback>> mOpModeWatchers
+ = new SparseArray<ArrayList<Callback>>();
+ final HashMap<String, ArrayList<Callback>> mPackageModeWatchers
+ = new HashMap<String, ArrayList<Callback>>();
+ final HashMap<IBinder, Callback> mModeWatchers
+ = new HashMap<IBinder, Callback>();
+
+ public final class Callback implements DeathRecipient {
+ final IAppOpsCallback mCallback;
+
+ public Callback(IAppOpsCallback callback) {
+ mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void unlinkToDeath() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingMode(mCallback);
+ }
+ }
+
+ public AppOpsService(File storagePath) {
+ mFile = new AtomicFile(storagePath);
+ mHandler = new Handler();
+ readState();
+ }
+
+ public void publish(Context context) {
+ mContext = context;
+ ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
+ }
+
+ public void systemReady() {
+ synchronized (this) {
+ boolean changed = false;
+ for (int i=0; i<mUidOps.size(); i++) {
+ HashMap<String, Ops> pkgs = mUidOps.valueAt(i);
+ Iterator<Ops> it = pkgs.values().iterator();
+ while (it.hasNext()) {
+ Ops ops = it.next();
+ int curUid;
+ try {
+ curUid = mContext.getPackageManager().getPackageUid(ops.packageName,
+ UserHandle.getUserId(ops.uid));
+ } catch (NameNotFoundException e) {
+ curUid = -1;
+ }
+ if (curUid != ops.uid) {
+ Slog.i(TAG, "Pruning old package " + ops.packageName
+ + "/" + ops.uid + ": new uid=" + curUid);
+ it.remove();
+ changed = true;
+ }
+ }
+ if (pkgs.size() <= 0) {
+ mUidOps.removeAt(i);
+ }
+ }
+ if (changed) {
+ scheduleWriteLocked();
+ }
+ }
+ }
+
+ public void packageRemoved(int uid, String packageName) {
+ synchronized (this) {
+ HashMap<String, Ops> pkgs = mUidOps.get(uid);
+ if (pkgs != null) {
+ if (pkgs.remove(packageName) != null) {
+ if (pkgs.size() <= 0) {
+ mUidOps.remove(uid);
+ }
+ scheduleWriteLocked();
+ }
+ }
+ }
+ }
+
+ public void uidRemoved(int uid) {
+ synchronized (this) {
+ if (mUidOps.indexOfKey(uid) >= 0) {
+ mUidOps.remove(uid);
+ scheduleWriteLocked();
+ }
+ }
+ }
+
+ public void shutdown() {
+ Slog.w(TAG, "Writing app ops before shutdown...");
+ boolean doWrite = false;
+ synchronized (this) {
+ if (mWriteScheduled) {
+ mWriteScheduled = false;
+ doWrite = true;
+ }
+ }
+ if (doWrite) {
+ writeState();
+ }
+ }
+
+ private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+ ArrayList<AppOpsManager.OpEntry> resOps = null;
+ if (ops == null) {
+ resOps = new ArrayList<AppOpsManager.OpEntry>();
+ for (int j=0; j<pkgOps.size(); j++) {
+ Op curOp = pkgOps.valueAt(j);
+ resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
+ curOp.rejectTime, curOp.duration));
+ }
+ } else {
+ for (int j=0; j<ops.length; j++) {
+ Op curOp = pkgOps.get(ops[j]);
+ if (curOp != null) {
+ if (resOps == null) {
+ resOps = new ArrayList<AppOpsManager.OpEntry>();
+ }
+ resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
+ curOp.rejectTime, curOp.duration));
+ }
+ }
+ }
+ return resOps;
+ }
+
+ @Override
+ public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ ArrayList<AppOpsManager.PackageOps> res = null;
+ synchronized (this) {
+ for (int i=0; i<mUidOps.size(); i++) {
+ HashMap<String, Ops> packages = mUidOps.valueAt(i);
+ for (Ops pkgOps : packages.values()) {
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+ if (resOps != null) {
+ if (res == null) {
+ res = new ArrayList<AppOpsManager.PackageOps>();
+ }
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uid, resOps);
+ res.add(resPackage);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ @Override
+ public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+ int[] ops) {
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ synchronized (this) {
+ Ops pkgOps = getOpsLocked(uid, packageName, false);
+ if (pkgOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+ if (resOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uid, resOps);
+ res.add(resPackage);
+ return res;
+ }
+ }
+
+ @Override
+ public void setMode(int code, int uid, String packageName, int mode) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ ArrayList<Callback> repCbs = null;
+ code = AppOpsManager.opToSwitch(code);
+ synchronized (this) {
+ Op op = getOpLocked(code, uid, packageName, true);
+ if (op != null) {
+ if (op.mode != mode) {
+ op.mode = mode;
+ ArrayList<Callback> cbs = mOpModeWatchers.get(code);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArrayList<Callback>();
+ }
+ repCbs.addAll(cbs);
+ }
+ cbs = mPackageModeWatchers.get(packageName);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArrayList<Callback>();
+ }
+ repCbs.addAll(cbs);
+ }
+ if (mode == AppOpsManager.MODE_ALLOWED) {
+ // If going into the default mode, prune this op
+ // if there is nothing else interesting in it.
+ if (op.time == 0 && op.rejectTime == 0) {
+ Ops ops = getOpsLocked(uid, packageName, false);
+ if (ops != null) {
+ ops.remove(op.op);
+ if (ops.size() <= 0) {
+ HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+ if (pkgOps != null) {
+ pkgOps.remove(ops.packageName);
+ if (pkgOps.size() <= 0) {
+ mUidOps.remove(uid);
+ }
+ }
+ }
+ }
+ }
+ }
+ scheduleWriteNowLocked();
+ }
+ }
+ }
+ if (repCbs != null) {
+ for (int i=0; i<repCbs.size(); i++) {
+ try {
+ repCbs.get(i).mCallback.opChanged(code, packageName);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
+ synchronized (this) {
+ op = AppOpsManager.opToSwitch(op);
+ Callback cb = mModeWatchers.get(callback.asBinder());
+ if (cb == null) {
+ cb = new Callback(callback);
+ mModeWatchers.put(callback.asBinder(), cb);
+ }
+ if (op != AppOpsManager.OP_NONE) {
+ ArrayList<Callback> cbs = mOpModeWatchers.get(op);
+ if (cbs == null) {
+ cbs = new ArrayList<Callback>();
+ mOpModeWatchers.put(op, cbs);
+ }
+ cbs.add(cb);
+ }
+ if (packageName != null) {
+ ArrayList<Callback> cbs = mPackageModeWatchers.get(packageName);
+ if (cbs == null) {
+ cbs = new ArrayList<Callback>();
+ mPackageModeWatchers.put(packageName, cbs);
+ }
+ cbs.add(cb);
+ }
+ }
+ }
+
+ @Override
+ public void stopWatchingMode(IAppOpsCallback callback) {
+ synchronized (this) {
+ Callback cb = mModeWatchers.remove(callback.asBinder());
+ if (cb != null) {
+ cb.unlinkToDeath();
+ for (int i=0; i<mOpModeWatchers.size(); i++) {
+ ArrayList<Callback> cbs = mOpModeWatchers.valueAt(i);
+ cbs.remove(cb);
+ if (cbs.size() <= 0) {
+ mOpModeWatchers.removeAt(i);
+ }
+ }
+ if (mPackageModeWatchers.size() > 0) {
+ Iterator<ArrayList<Callback>> it = mPackageModeWatchers.values().iterator();
+ while (it.hasNext()) {
+ ArrayList<Callback> cbs = it.next();
+ cbs.remove(cb);
+ if (cbs.size() <= 0) {
+ it.remove();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public int checkOperation(int code, int uid, String packageName) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ synchronized (this) {
+ Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);
+ if (op == null) {
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ return op.mode;
+ }
+ }
+
+ @Override
+ public int noteOperation(int code, int uid, String packageName) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ synchronized (this) {
+ Ops ops = getOpsLocked(uid, packageName, true);
+ if (ops == null) {
+ if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName);
+ return AppOpsManager.MODE_IGNORED;
+ }
+ Op op = getOpLocked(ops, code, true);
+ if (op.duration == -1) {
+ Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
+ + " code " + code + " time=" + op.time + " duration=" + op.duration);
+ }
+ op.duration = 0;
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
+ if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
+ op.rejectTime = System.currentTimeMillis();
+ return switchOp.mode;
+ }
+ if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName);
+ op.time = System.currentTimeMillis();
+ op.rejectTime = 0;
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ @Override
+ public int startOperation(int code, int uid, String packageName) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ synchronized (this) {
+ Ops ops = getOpsLocked(uid, packageName, true);
+ if (ops == null) {
+ if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName);
+ return AppOpsManager.MODE_IGNORED;
+ }
+ Op op = getOpLocked(ops, code, true);
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
+ if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) Log.d(TAG, "startOperation: reject #" + op.mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
+ op.rejectTime = System.currentTimeMillis();
+ return switchOp.mode;
+ }
+ if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName);
+ if (op.nesting == 0) {
+ op.time = System.currentTimeMillis();
+ op.rejectTime = 0;
+ op.duration = -1;
+ }
+ op.nesting++;
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ @Override
+ public void finishOperation(int code, int uid, String packageName) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ synchronized (this) {
+ Op op = getOpLocked(code, uid, packageName, true);
+ if (op == null) {
+ return;
+ }
+ if (op.nesting <= 1) {
+ if (op.nesting == 1) {
+ op.duration = (int)(System.currentTimeMillis() - op.time);
+ op.time += op.duration;
+ } else {
+ Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg " + packageName
+ + " code " + code + " time=" + op.time + " duration=" + op.duration
+ + " nesting=" + op.nesting);
+ }
+ op.nesting = 0;
+ } else {
+ op.nesting--;
+ }
+ }
+ }
+
+ private void verifyIncomingUid(int uid) {
+ if (uid == Binder.getCallingUid()) {
+ return;
+ }
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+
+ private void verifyIncomingOp(int op) {
+ if (op >= 0 && op < AppOpsManager._NUM_OP) {
+ return;
+ }
+ throw new IllegalArgumentException("Bad operation #" + op);
+ }
+
+ private Ops getOpsLocked(int uid, String packageName, boolean edit) {
+ HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+ if (pkgOps == null) {
+ if (!edit) {
+ return null;
+ }
+ pkgOps = new HashMap<String, Ops>();
+ mUidOps.put(uid, pkgOps);
+ }
+ if (uid == 0) {
+ packageName = "root";
+ } else if (uid == Process.SHELL_UID) {
+ packageName = "com.android.shell";
+ }
+ Ops ops = pkgOps.get(packageName);
+ if (ops == null) {
+ if (!edit) {
+ return null;
+ }
+ // This is the first time we have seen this package name under this uid,
+ // so let's make sure it is valid.
+ if (uid != 0) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ int pkgUid = -1;
+ try {
+ pkgUid = mContext.getPackageManager().getPackageUid(packageName,
+ UserHandle.getUserId(uid));
+ } catch (NameNotFoundException e) {
+ }
+ if (pkgUid != uid) {
+ // Oops! The package name is not valid for the uid they are calling
+ // under. Abort.
+ Slog.w(TAG, "Bad call: specified package " + packageName
+ + " under uid " + uid + " but it is really " + pkgUid);
+ return null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ ops = new Ops(packageName, uid);
+ pkgOps.put(packageName, ops);
+ }
+ return ops;
+ }
+
+ private void scheduleWriteLocked() {
+ if (!mWriteScheduled) {
+ mWriteScheduled = true;
+ mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+ }
+ }
+
+ private void scheduleWriteNowLocked() {
+ if (!mWriteScheduled) {
+ mWriteScheduled = true;
+ }
+ mHandler.removeCallbacks(mWriteRunner);
+ mHandler.post(mWriteRunner);
+ }
+
+ private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
+ Ops ops = getOpsLocked(uid, packageName, edit);
+ if (ops == null) {
+ return null;
+ }
+ return getOpLocked(ops, code, edit);
+ }
+
+ private Op getOpLocked(Ops ops, int code, boolean edit) {
+ Op op = ops.get(code);
+ if (op == null) {
+ if (!edit) {
+ return null;
+ }
+ op = new Op(code);
+ ops.put(code, op);
+ }
+ if (edit) {
+ scheduleWriteLocked();
+ }
+ return op;
+ }
+
+ void readState() {
+ synchronized (mFile) {
+ synchronized (this) {
+ FileInputStream stream;
+ try {
+ stream = mFile.openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+ return;
+ }
+ boolean success = false;
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("pkg")) {
+ readPackage(parser);
+ } else {
+ Slog.w(TAG, "Unknown element under <app-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ success = true;
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } finally {
+ if (!success) {
+ mUidOps.clear();
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ }
+
+ void readPackage(XmlPullParser parser) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ String pkgName = parser.getAttributeValue(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("uid")) {
+ readUid(parser, pkgName);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n")));
+ String mode = parser.getAttributeValue(null, "m");
+ if (mode != null) {
+ op.mode = Integer.parseInt(mode);
+ }
+ String time = parser.getAttributeValue(null, "t");
+ if (time != null) {
+ op.time = Long.parseLong(time);
+ }
+ time = parser.getAttributeValue(null, "r");
+ if (time != null) {
+ op.rejectTime = Long.parseLong(time);
+ }
+ String dur = parser.getAttributeValue(null, "d");
+ if (dur != null) {
+ op.duration = Integer.parseInt(dur);
+ }
+ HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+ if (pkgOps == null) {
+ pkgOps = new HashMap<String, Ops>();
+ mUidOps.put(uid, pkgOps);
+ }
+ Ops ops = pkgOps.get(pkgName);
+ if (ops == null) {
+ ops = new Ops(pkgName, uid);
+ pkgOps.put(pkgName, ops);
+ }
+ ops.put(op.op, op);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ void writeState() {
+ synchronized (mFile) {
+ List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+ FileOutputStream stream;
+ try {
+ stream = mFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state: " + e);
+ return;
+ }
+
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, "app-ops");
+
+ if (allOps != null) {
+ String lastPkg = null;
+ for (int i=0; i<allOps.size(); i++) {
+ AppOpsManager.PackageOps pkg = allOps.get(i);
+ if (!pkg.getPackageName().equals(lastPkg)) {
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ lastPkg = pkg.getPackageName();
+ out.startTag(null, "pkg");
+ out.attribute(null, "n", lastPkg);
+ }
+ out.startTag(null, "uid");
+ out.attribute(null, "n", Integer.toString(pkg.getUid()));
+ List<AppOpsManager.OpEntry> ops = pkg.getOps();
+ for (int j=0; j<ops.size(); j++) {
+ AppOpsManager.OpEntry op = ops.get(j);
+ out.startTag(null, "op");
+ out.attribute(null, "n", Integer.toString(op.getOp()));
+ if (op.getMode() != AppOpsManager.MODE_ALLOWED) {
+ out.attribute(null, "m", Integer.toString(op.getMode()));
+ }
+ long time = op.getTime();
+ if (time != 0) {
+ out.attribute(null, "t", Long.toString(time));
+ }
+ time = op.getRejectTime();
+ if (time != 0) {
+ out.attribute(null, "r", Long.toString(time));
+ }
+ int dur = op.getDuration();
+ if (dur != 0) {
+ out.attribute(null, "d", Integer.toString(dur));
+ }
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "uid");
+ }
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ }
+
+ out.endTag(null, "app-ops");
+ out.endDocument();
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state, restoring backup.", e);
+ mFile.failWrite(stream);
+ }
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump ApOps service from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ synchronized (this) {
+ pw.println("Current AppOps Service state:");
+ final long now = System.currentTimeMillis();
+ for (int i=0; i<mUidOps.size(); i++) {
+ pw.print(" Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":");
+ HashMap<String, Ops> pkgOps = mUidOps.valueAt(i);
+ for (Ops ops : pkgOps.values()) {
+ pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
+ for (int j=0; j<ops.size(); j++) {
+ Op op = ops.valueAt(j);
+ pw.print(" "); pw.print(AppOpsManager.opToName(op.op));
+ pw.print(": mode="); pw.print(op.mode);
+ if (op.time != 0) {
+ pw.print("; time="); TimeUtils.formatDuration(now-op.time, pw);
+ pw.print(" ago");
+ }
+ if (op.rejectTime != 0) {
+ pw.print("; rejectTime="); TimeUtils.formatDuration(now-op.rejectTime, pw);
+ pw.print(" ago");
+ }
+ if (op.duration == -1) {
+ pw.println(" (running)");
+ } else {
+ pw.print("; duration=");
+ TimeUtils.formatDuration(op.duration, pw);
+ pw.println();
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 06aeb29..d5715a5 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -16,7 +16,7 @@
package com.android.server;
-import android.app.ActivityManagerNative;
+import android.app.ActivityManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -121,107 +121,67 @@ class AppWidgetService extends IAppWidgetService.Stub
}, userFilter);
}
- /**
- * This returns the user id of the caller, if the caller is not the system process,
- * otherwise it assumes that the calls are from the lockscreen and hence are meant for the
- * current user. TODO: Instead, have lockscreen make explicit calls with userId
- */
- private int getCallingOrCurrentUserId() {
- int callingUid = Binder.getCallingUid();
- // Also check the PID because Settings (power control widget) also runs as System UID
- if (callingUid == android.os.Process.myUid()
- && Binder.getCallingPid() == android.os.Process.myPid()) {
- try {
- return ActivityManagerNative.getDefault().getCurrentUser().id;
- } catch (RemoteException re) {
- return UserHandle.getUserId(callingUid);
- }
- } else {
- return UserHandle.getUserId(callingUid);
- }
- }
-
@Override
- public int allocateAppWidgetId(String packageName, int hostId) throws RemoteException {
- return getImplForUser(getCallingOrCurrentUserId()).allocateAppWidgetId(
- packageName, hostId);
+ public int allocateAppWidgetId(String packageName, int hostId, int userId)
+ throws RemoteException {
+ return getImplForUser(userId).allocateAppWidgetId(packageName, hostId);
}
@Override
- public int[] getAppWidgetIdsForHost(int hostId) throws RemoteException {
- return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetIdsForHost(hostId);
+ public int[] getAppWidgetIdsForHost(int hostId, int userId) throws RemoteException {
+ return getImplForUser(userId).getAppWidgetIdsForHost(hostId);
}
-
+
@Override
- public void deleteAppWidgetId(int appWidgetId) throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).deleteAppWidgetId(appWidgetId);
+ public void deleteAppWidgetId(int appWidgetId, int userId) throws RemoteException {
+ getImplForUser(userId).deleteAppWidgetId(appWidgetId);
}
@Override
- public void deleteHost(int hostId) throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).deleteHost(hostId);
+ public void deleteHost(int hostId, int userId) throws RemoteException {
+ getImplForUser(userId).deleteHost(hostId);
}
@Override
- public void deleteAllHosts() throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).deleteAllHosts();
+ public void deleteAllHosts(int userId) throws RemoteException {
+ getImplForUser(userId).deleteAllHosts();
}
@Override
- public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options)
+ public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options, int userId)
throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).bindAppWidgetId(appWidgetId, provider,
- options);
+ getImplForUser(userId).bindAppWidgetId(appWidgetId, provider, options);
}
@Override
public boolean bindAppWidgetIdIfAllowed(
- String packageName, int appWidgetId, ComponentName provider, Bundle options)
+ String packageName, int appWidgetId, ComponentName provider, Bundle options, int userId)
throws RemoteException {
- return getImplForUser(getCallingOrCurrentUserId()).bindAppWidgetIdIfAllowed(
+ return getImplForUser(userId).bindAppWidgetIdIfAllowed(
packageName, appWidgetId, provider, options);
}
@Override
- public boolean hasBindAppWidgetPermission(String packageName) throws RemoteException {
- return getImplForUser(getCallingOrCurrentUserId()).hasBindAppWidgetPermission(
- packageName);
+ public boolean hasBindAppWidgetPermission(String packageName, int userId)
+ throws RemoteException {
+ return getImplForUser(userId).hasBindAppWidgetPermission(packageName);
}
@Override
- public void setBindAppWidgetPermission(String packageName, boolean permission)
+ public void setBindAppWidgetPermission(String packageName, boolean permission, int userId)
throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).setBindAppWidgetPermission(
- packageName, permission);
+ getImplForUser(userId).setBindAppWidgetPermission(packageName, permission);
}
@Override
public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection,
int userId) throws RemoteException {
- if (Binder.getCallingPid() != android.os.Process.myPid()
- && userId != UserHandle.getCallingUserId()) {
- throw new SecurityException("Call from non-system process. Calling uid = "
- + Binder.getCallingUid());
- }
- getImplForUser(userId).bindRemoteViewsService(
- appWidgetId, intent, connection);
+ getImplForUser(userId).bindRemoteViewsService(appWidgetId, intent, connection);
}
@Override
public int[] startListening(IAppWidgetHost host, String packageName, int hostId,
- List<RemoteViews> updatedViews) throws RemoteException {
- return getImplForUser(getCallingOrCurrentUserId()).startListening(host,
- packageName, hostId, updatedViews);
- }
-
- @Override
- public int[] startListeningAsUser(IAppWidgetHost host, String packageName, int hostId,
List<RemoteViews> updatedViews, int userId) throws RemoteException {
- if (Binder.getCallingPid() != android.os.Process.myPid()
- && userId != UserHandle.getCallingUserId()) {
- throw new SecurityException("Call from non-system process. Calling uid = "
- + Binder.getCallingUid());
- }
return getImplForUser(userId).startListening(host, packageName, hostId, updatedViews);
}
@@ -250,7 +210,19 @@ class AppWidgetService extends IAppWidgetService.Stub
}
}
+ private void checkPermission(int userId) {
+ int realUserId = ActivityManager.handleIncomingUser(
+ Binder.getCallingPid(),
+ Binder.getCallingUid(),
+ userId,
+ false, /* allowAll */
+ true, /* requireFull */
+ this.getClass().getSimpleName(),
+ this.getClass().getPackage().getName());
+ }
+
private AppWidgetServiceImpl getImplForUser(int userId) {
+ checkPermission(userId);
boolean sendInitial = false;
AppWidgetServiceImpl service;
synchronized (mAppWidgetServices) {
@@ -272,86 +244,73 @@ class AppWidgetService extends IAppWidgetService.Stub
}
@Override
- public int[] getAppWidgetIds(ComponentName provider) throws RemoteException {
- return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetIds(provider);
+ public int[] getAppWidgetIds(ComponentName provider, int userId) throws RemoteException {
+ return getImplForUser(userId).getAppWidgetIds(provider);
}
@Override
- public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) throws RemoteException {
- return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetInfo(appWidgetId);
+ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId, int userId)
+ throws RemoteException {
+ return getImplForUser(userId).getAppWidgetInfo(appWidgetId);
}
@Override
- public RemoteViews getAppWidgetViews(int appWidgetId) throws RemoteException {
- return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetViews(appWidgetId);
+ public RemoteViews getAppWidgetViews(int appWidgetId, int userId) throws RemoteException {
+ return getImplForUser(userId).getAppWidgetViews(appWidgetId);
}
@Override
- public void updateAppWidgetOptions(int appWidgetId, Bundle options) {
- getImplForUser(getCallingOrCurrentUserId()).updateAppWidgetOptions(appWidgetId, options);
+ public void updateAppWidgetOptions(int appWidgetId, Bundle options, int userId) {
+ getImplForUser(userId).updateAppWidgetOptions(appWidgetId, options);
}
@Override
- public Bundle getAppWidgetOptions(int appWidgetId) {
- return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetOptions(appWidgetId);
+ public Bundle getAppWidgetOptions(int appWidgetId, int userId) {
+ return getImplForUser(userId).getAppWidgetOptions(appWidgetId);
}
@Override
- public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter)
+ public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int userId)
throws RemoteException {
- return getImplForUser(getCallingOrCurrentUserId()).getInstalledProviders(categoryFilter);
+ return getImplForUser(userId).getInstalledProviders(categoryFilter);
}
@Override
- public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId)
+ public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId, int userId)
throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).notifyAppWidgetViewDataChanged(
+ getImplForUser(userId).notifyAppWidgetViewDataChanged(
appWidgetIds, viewId);
}
@Override
- public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views)
+ public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).partiallyUpdateAppWidgetIds(
+ getImplForUser(userId).partiallyUpdateAppWidgetIds(
appWidgetIds, views);
}
@Override
- public void stopListening(int hostId) throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).stopListening(hostId);
- }
-
- @Override
- public void stopListeningAsUser(int hostId, int userId) throws RemoteException {
- if (Binder.getCallingPid() != android.os.Process.myPid()
- && userId != UserHandle.getCallingUserId()) {
- throw new SecurityException("Call from non-system process. Calling uid = "
- + Binder.getCallingUid());
- }
+ public void stopListening(int hostId, int userId) throws RemoteException {
getImplForUser(userId).stopListening(hostId);
}
@Override
public void unbindRemoteViewsService(int appWidgetId, Intent intent, int userId)
throws RemoteException {
- if (Binder.getCallingPid() != android.os.Process.myPid()
- && userId != UserHandle.getCallingUserId()) {
- throw new SecurityException("Call from non-system process. Calling uid = "
- + Binder.getCallingUid());
- }
getImplForUser(userId).unbindRemoteViewsService(
appWidgetId, intent);
}
@Override
- public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).updateAppWidgetIds(appWidgetIds, views);
+ public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views, int userId)
+ throws RemoteException {
+ getImplForUser(userId).updateAppWidgetIds(appWidgetIds, views);
}
@Override
- public void updateAppWidgetProvider(ComponentName provider, RemoteViews views)
+ public void updateAppWidgetProvider(ComponentName provider, RemoteViews views, int userId)
throws RemoteException {
- getImplForUser(getCallingOrCurrentUserId()).updateAppWidgetProvider(provider, views);
+ getImplForUser(userId).updateAppWidgetProvider(provider, views);
}
@Override
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index e1e9eaf..fb2828b 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -177,18 +177,20 @@ class AppWidgetServiceImpl {
// Manages persistent references to RemoteViewsServices from different App Widgets
private final HashMap<FilterComparison, HashSet<Integer>> mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>();
- Context mContext;
+ final Context mContext;
+ final IPackageManager mPm;
+ final AlarmManager mAlarmManager;
+ final ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>();
+ final int mUserId;
+ final boolean mHasFeature;
+
Locale mLocale;
- IPackageManager mPm;
- AlarmManager mAlarmManager;
- ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>();
int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1;
final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>();
- ArrayList<Host> mHosts = new ArrayList<Host>();
+ final ArrayList<Host> mHosts = new ArrayList<Host>();
// set of package names
- HashSet<String> mPackagesWithBindWidgetPermission = new HashSet<String>();
+ final HashSet<String> mPackagesWithBindWidgetPermission = new HashSet<String>();
boolean mSafeMode;
- int mUserId;
boolean mStateLoaded;
int mMaxWidgetBitmapMemory;
@@ -204,6 +206,8 @@ class AppWidgetServiceImpl {
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
mUserId = userId;
mSaveStateHandler = saveStateHandler;
+ mHasFeature = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_APP_WIDGETS);
computeMaximumWidgetBitmapMemory();
}
@@ -426,6 +430,9 @@ class AppWidgetServiceImpl {
private void ensureStateLoadedLocked() {
if (!mStateLoaded) {
+ if (!mHasFeature) {
+ return;
+ }
loadAppWidgetListLocked();
loadStateLocked();
mStateLoaded = true;
@@ -435,6 +442,9 @@ class AppWidgetServiceImpl {
public int allocateAppWidgetId(String packageName, int hostId) {
int callingUid = enforceSystemOrCallingUid(packageName);
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return -1;
+ }
ensureStateLoadedLocked();
int appWidgetId = mNextAppWidgetId++;
@@ -456,6 +466,9 @@ class AppWidgetServiceImpl {
public void deleteAppWidgetId(int appWidgetId) {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
ensureStateLoadedLocked();
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
if (id != null) {
@@ -467,6 +480,9 @@ class AppWidgetServiceImpl {
public void deleteHost(int hostId) {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
ensureStateLoadedLocked();
int callingUid = Binder.getCallingUid();
Host host = lookupHostLocked(callingUid, hostId);
@@ -479,6 +495,9 @@ class AppWidgetServiceImpl {
public void deleteAllHosts() {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
ensureStateLoadedLocked();
int callingUid = Binder.getCallingUid();
final int N = mHosts.size();
@@ -561,6 +580,9 @@ class AppWidgetServiceImpl {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
options = cloneIfLocalBinder(options);
ensureStateLoadedLocked();
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
@@ -622,6 +644,9 @@ class AppWidgetServiceImpl {
public boolean bindAppWidgetIdIfAllowed(
String packageName, int appWidgetId, ComponentName provider, Bundle options) {
+ if (!mHasFeature) {
+ return false;
+ }
try {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET, null);
} catch (SecurityException se) {
@@ -649,6 +674,9 @@ class AppWidgetServiceImpl {
}
public boolean hasBindAppWidgetPermission(String packageName) {
+ if (!mHasFeature) {
+ return false;
+ }
mContext.enforceCallingPermission(
android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
"hasBindAppWidgetPermission packageName=" + packageName);
@@ -660,6 +688,9 @@ class AppWidgetServiceImpl {
}
public void setBindAppWidgetPermission(String packageName, boolean permission) {
+ if (!mHasFeature) {
+ return;
+ }
mContext.enforceCallingPermission(
android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
"setBindAppWidgetPermission packageName=" + packageName);
@@ -678,6 +709,9 @@ class AppWidgetServiceImpl {
// Binds to a specific RemoteViewsService
public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
ensureStateLoadedLocked();
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
if (id == null) {
@@ -718,7 +752,8 @@ class AppWidgetServiceImpl {
final long token = Binder.clearCallingIdentity();
try {
conn = new ServiceConnectionProxy(key, connection);
- mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE, userId);
+ mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
+ new UserHandle(userId));
mBoundRemoteViewsServices.put(key, conn);
} finally {
Binder.restoreCallingIdentity(token);
@@ -734,6 +769,9 @@ class AppWidgetServiceImpl {
// Unbinds from a specific RemoteViewsService
public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
ensureStateLoadedLocked();
// Unbind from the RemoteViewsService (which will trigger a callback to the bound
// RemoteViewsAdapter)
@@ -806,7 +844,8 @@ class AppWidgetServiceImpl {
// RemoteViewsService.
final long token = Binder.clearCallingIdentity();
try {
- mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE, userId);
+ mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
+ new UserHandle(userId));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -844,6 +883,9 @@ class AppWidgetServiceImpl {
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return null;
+ }
ensureStateLoadedLocked();
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
if (id != null && id.provider != null && !id.provider.zombie) {
@@ -856,6 +898,9 @@ class AppWidgetServiceImpl {
public RemoteViews getAppWidgetViews(int appWidgetId) {
if (DBG) log("getAppWidgetViews id=" + appWidgetId);
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return null;
+ }
ensureStateLoadedLocked();
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
if (id != null) {
@@ -868,6 +913,9 @@ class AppWidgetServiceImpl {
public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter) {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return new ArrayList<AppWidgetProviderInfo>(0);
+ }
ensureStateLoadedLocked();
final int N = mInstalledProviders.size();
ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N);
@@ -882,6 +930,9 @@ class AppWidgetServiceImpl {
}
public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
+ if (!mHasFeature) {
+ return;
+ }
if (appWidgetIds == null) {
return;
}
@@ -927,6 +978,9 @@ class AppWidgetServiceImpl {
public void updateAppWidgetOptions(int appWidgetId, Bundle options) {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
options = cloneIfLocalBinder(options);
ensureStateLoadedLocked();
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
@@ -951,6 +1005,9 @@ class AppWidgetServiceImpl {
public Bundle getAppWidgetOptions(int appWidgetId) {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return Bundle.EMPTY;
+ }
ensureStateLoadedLocked();
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
if (id != null && id.options != null) {
@@ -962,6 +1019,9 @@ class AppWidgetServiceImpl {
}
public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
+ if (!mHasFeature) {
+ return;
+ }
if (appWidgetIds == null) {
return;
}
@@ -985,6 +1045,9 @@ class AppWidgetServiceImpl {
}
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
+ if (!mHasFeature) {
+ return;
+ }
if (appWidgetIds == null) {
return;
}
@@ -1003,6 +1066,9 @@ class AppWidgetServiceImpl {
}
public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) {
+ if (!mHasFeature) {
+ return;
+ }
synchronized (mAppWidgetIds) {
ensureStateLoadedLocked();
Provider p = lookupProviderLocked(provider);
@@ -1044,7 +1110,7 @@ class AppWidgetServiceImpl {
if (id.host.callbacks != null) {
try {
// the lock is held, but this is a oneway call
- id.host.callbacks.updateAppWidget(id.appWidgetId, views);
+ id.host.callbacks.updateAppWidget(id.appWidgetId, views, mUserId);
} catch (RemoteException e) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this instance.
@@ -1063,7 +1129,7 @@ class AppWidgetServiceImpl {
if (id.host.callbacks != null) {
try {
// the lock is held, but this is a oneway call
- id.host.callbacks.viewDataChanged(id.appWidgetId, viewId);
+ id.host.callbacks.viewDataChanged(id.appWidgetId, viewId, mUserId);
} catch (RemoteException e) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this instance.
@@ -1104,7 +1170,8 @@ class AppWidgetServiceImpl {
// Bind to the service and call onDataSetChanged()
final long token = Binder.clearCallingIdentity();
try {
- mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE, userId);
+ mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE,
+ new UserHandle(userId));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1144,6 +1211,9 @@ class AppWidgetServiceImpl {
public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
List<RemoteViews> updatedViews) {
+ if (!mHasFeature) {
+ return new int[0];
+ }
int callingUid = enforceCallingUid(packageName);
synchronized (mAppWidgetIds) {
ensureStateLoadedLocked();
@@ -1166,6 +1236,9 @@ class AppWidgetServiceImpl {
public void stopListening(int hostId) {
synchronized (mAppWidgetIds) {
+ if (!mHasFeature) {
+ return;
+ }
ensureStateLoadedLocked();
Host host = lookupHostLocked(Binder.getCallingUid(), hostId);
if (host != null) {
@@ -1555,6 +1628,9 @@ class AppWidgetServiceImpl {
}
void saveStateLocked() {
+ if (!mHasFeature) {
+ return;
+ }
AtomicFile file = savedStateFile();
FileOutputStream stream;
try {
@@ -1931,7 +2007,8 @@ class AppWidgetServiceImpl {
id.views = null;
if (id.host != null && id.host.callbacks != null) {
try {
- id.host.callbacks.providerChanged(id.appWidgetId, p.info);
+ id.host.callbacks.providerChanged(id.appWidgetId, p.info,
+ mUserId);
} catch (RemoteException ex) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this
@@ -1998,7 +2075,7 @@ class AppWidgetServiceImpl {
Host host = mHosts.get(i);
try {
if (host.callbacks != null) {
- host.callbacks.providersChanged();
+ host.callbacks.providersChanged(mUserId);
}
} catch (RemoteException ex) {
// It failed; remove the callback. No need to prune because
diff --git a/services/java/com/android/server/AttributeCache.java b/services/java/com/android/server/AttributeCache.java
index 81613c6..427dbc0 100644
--- a/services/java/com/android/server/AttributeCache.java
+++ b/services/java/com/android/server/AttributeCache.java
@@ -35,54 +35,52 @@ import java.util.WeakHashMap;
*/
public final class AttributeCache {
private static AttributeCache sInstance = null;
-
+
private final Context mContext;
- private final SparseArray<WeakHashMap<String, Package>> mPackages =
- new SparseArray<WeakHashMap<String, Package>>();
+ private final WeakHashMap<String, Package> mPackages =
+ new WeakHashMap<String, Package>();
private final Configuration mConfiguration = new Configuration();
-
+
public final static class Package {
public final Context context;
private final SparseArray<HashMap<int[], Entry>> mMap
= new SparseArray<HashMap<int[], Entry>>();
-
+
public Package(Context c) {
context = c;
}
}
-
+
public final static class Entry {
public final Context context;
public final TypedArray array;
-
+
public Entry(Context c, TypedArray ta) {
context = c;
array = ta;
}
}
-
+
public static void init(Context context) {
if (sInstance == null) {
sInstance = new AttributeCache(context);
}
}
-
+
public static AttributeCache instance() {
return sInstance;
}
-
+
public AttributeCache(Context context) {
mContext = context;
}
-
+
public void removePackage(String packageName) {
synchronized (this) {
- for (int i=0; i<mPackages.size(); i++) {
- mPackages.valueAt(i).remove(packageName);
- }
+ mPackages.remove(packageName);
}
}
-
+
public void updateConfiguration(Configuration config) {
synchronized (this) {
int changes = mConfiguration.updateFrom(config);
@@ -96,21 +94,10 @@ public final class AttributeCache {
}
}
}
-
- public void removeUser(int userId) {
- synchronized (this) {
- mPackages.remove(userId);
- }
- }
-
- public Entry get(int userId, String packageName, int resId, int[] styleable) {
+
+ public Entry get(String packageName, int resId, int[] styleable, int userId) {
synchronized (this) {
- WeakHashMap<String, Package> packages = mPackages.get(userId);
- if (packages == null) {
- packages = new WeakHashMap<String, Package>();
- mPackages.put(userId, packages);
- }
- Package pkg = packages.get(packageName);
+ Package pkg = mPackages.get(packageName);
HashMap<int[], Entry> map = null;
Entry ent = null;
if (pkg != null) {
@@ -133,7 +120,7 @@ public final class AttributeCache {
return null;
}
pkg = new Package(context);
- packages.put(packageName, pkg);
+ mPackages.put(packageName, pkg);
}
if (map == null) {
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 8be3b32..a537e99 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -79,6 +79,7 @@ import android.util.StringBuilderPrinter;
import com.android.internal.backup.BackupConstants;
import com.android.internal.backup.IBackupTransport;
+import com.android.internal.backup.IObbBackupService;
import com.android.internal.backup.LocalTransport;
import com.android.server.PackageManagerBackupAgent.Metadata;
@@ -134,7 +135,7 @@ import javax.crypto.spec.SecretKeySpec;
class BackupManagerService extends IBackupManager.Stub {
private static final String TAG = "BackupManagerService";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final boolean MORE_DEBUG = false;
// Name and current contents version of the full-backup manifest file
@@ -364,15 +365,17 @@ class BackupManagerService extends IBackupManager.Stub {
class FullBackupParams extends FullParams {
public boolean includeApks;
+ public boolean includeObbs;
public boolean includeShared;
public boolean allApps;
public boolean includeSystem;
public String[] packages;
- FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveShared,
- boolean doAllApps, boolean doSystem, String[] pkgList) {
+ FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveObbs,
+ boolean saveShared, boolean doAllApps, boolean doSystem, String[] pkgList) {
fd = output;
includeApks = saveApks;
+ includeObbs = saveObbs;
includeShared = saveShared;
allApps = doAllApps;
includeSystem = doSystem;
@@ -551,7 +554,7 @@ class BackupManagerService extends IBackupManager.Stub {
// similar to normal backup/restore.
FullBackupParams params = (FullBackupParams)msg.obj;
PerformFullBackupTask task = new PerformFullBackupTask(params.fd,
- params.observer, params.includeApks,
+ params.observer, params.includeApks, params.includeObbs,
params.includeShared, params.curPassword, params.encryptPassword,
params.allApps, params.includeSystem, params.packages, params.latch);
(new Thread(task)).start();
@@ -840,8 +843,8 @@ class BackupManagerService extends IBackupManager.Stub {
if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
if (DEBUG) Slog.v(TAG, "Binding to Google transport");
Intent intent = new Intent().setComponent(transportComponent);
- context.bindService(intent, mGoogleConnection, Context.BIND_AUTO_CREATE,
- UserHandle.USER_OWNER);
+ context.bindServiceAsUser(intent, mGoogleConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.OWNER);
} else {
Slog.w(TAG, "Possible Google transport spoof: ignoring " + info);
}
@@ -2314,13 +2317,132 @@ class BackupManagerService extends IBackupManager.Stub {
}
- // ----- Full backup to a file/socket -----
+ // ----- Full backup/restore to a file/socket -----
- class PerformFullBackupTask implements Runnable {
+ abstract class ObbServiceClient {
+ public IObbBackupService mObbService;
+ public void setObbBinder(IObbBackupService binder) {
+ mObbService = binder;
+ }
+ }
+
+ class FullBackupObbConnection implements ServiceConnection {
+ volatile IObbBackupService mService;
+
+ FullBackupObbConnection() {
+ mService = null;
+ }
+
+ public void establish() {
+ if (DEBUG) Slog.i(TAG, "Initiating bind of OBB service on " + this);
+ Intent obbIntent = new Intent().setComponent(new ComponentName(
+ "com.android.sharedstoragebackup",
+ "com.android.sharedstoragebackup.ObbBackupService"));
+ BackupManagerService.this.mContext.bindService(
+ obbIntent, this, Context.BIND_AUTO_CREATE);
+ }
+
+ public void tearDown() {
+ BackupManagerService.this.mContext.unbindService(this);
+ }
+
+ public boolean backupObbs(PackageInfo pkg, OutputStream out) {
+ boolean success = false;
+ waitForConnection();
+
+ ParcelFileDescriptor[] pipes = null;
+ try {
+ pipes = ParcelFileDescriptor.createPipe();
+ int token = generateToken();
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
+ mService.backupObbs(pkg.packageName, pipes[1], token, mBackupManagerBinder);
+ routeSocketDataToOutput(pipes[0], out);
+ success = waitUntilOperationComplete(token);
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to back up OBBs for " + pkg, e);
+ } finally {
+ try {
+ out.flush();
+ if (pipes != null) {
+ if (pipes[0] != null) pipes[0].close();
+ if (pipes[1] != null) pipes[1].close();
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "I/O error closing down OBB backup", e);
+ }
+ }
+ return success;
+ }
+
+ public void restoreObbFile(String pkgName, ParcelFileDescriptor data,
+ long fileSize, int type, String path, long mode, long mtime,
+ int token, IBackupManager callbackBinder) {
+ waitForConnection();
+
+ try {
+ mService.restoreObbFile(pkgName, data, fileSize, type, path, mode, mtime,
+ token, callbackBinder);
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to restore OBBs for " + pkgName, e);
+ }
+ }
+
+ private void waitForConnection() {
+ synchronized (this) {
+ while (mService == null) {
+ if (DEBUG) Slog.i(TAG, "...waiting for OBB service binding...");
+ try {
+ this.wait();
+ } catch (InterruptedException e) { /* never interrupted */ }
+ }
+ if (DEBUG) Slog.i(TAG, "Connected to OBB service; continuing");
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ mService = IObbBackupService.Stub.asInterface(service);
+ if (DEBUG) Slog.i(TAG, "OBB service connection " + mService
+ + " connected on " + this);
+ this.notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (this) {
+ mService = null;
+ if (DEBUG) Slog.i(TAG, "OBB service connection disconnected on " + this);
+ this.notifyAll();
+ }
+ }
+
+ }
+
+ private void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
+ throws IOException {
+ FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
+ DataInputStream in = new DataInputStream(raw);
+
+ byte[] buffer = new byte[32 * 1024];
+ int chunkTotal;
+ while ((chunkTotal = in.readInt()) > 0) {
+ while (chunkTotal > 0) {
+ int toRead = (chunkTotal > buffer.length) ? buffer.length : chunkTotal;
+ int nRead = in.read(buffer, 0, toRead);
+ out.write(buffer, 0, nRead);
+ chunkTotal -= nRead;
+ }
+ }
+ }
+
+ class PerformFullBackupTask extends ObbServiceClient implements Runnable {
ParcelFileDescriptor mOutputFile;
DeflaterOutputStream mDeflater;
IFullBackupRestoreObserver mObserver;
boolean mIncludeApks;
+ boolean mIncludeObbs;
boolean mIncludeShared;
boolean mAllApps;
final boolean mIncludeSystem;
@@ -2330,6 +2452,7 @@ class BackupManagerService extends IBackupManager.Stub {
AtomicBoolean mLatchObject;
File mFilesDir;
File mManifestFile;
+
class FullBackupRunner implements Runnable {
PackageInfo mPackage;
@@ -2385,12 +2508,13 @@ class BackupManagerService extends IBackupManager.Stub {
}
PerformFullBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
- boolean includeApks, boolean includeShared, String curPassword,
- String encryptPassword, boolean doAllApps, boolean doSystem, String[] packages,
- AtomicBoolean latch) {
+ boolean includeApks, boolean includeObbs, boolean includeShared,
+ String curPassword, String encryptPassword, boolean doAllApps,
+ boolean doSystem, String[] packages, AtomicBoolean latch) {
mOutputFile = fd;
mObserver = observer;
mIncludeApks = includeApks;
+ mIncludeObbs = includeObbs;
mIncludeShared = includeShared;
mAllApps = doAllApps;
mIncludeSystem = doSystem;
@@ -2413,9 +2537,12 @@ class BackupManagerService extends IBackupManager.Stub {
@Override
public void run() {
+ Slog.i(TAG, "--- Performing full-dataset backup ---");
+
List<PackageInfo> packagesToBackup = new ArrayList<PackageInfo>();
+ FullBackupObbConnection obbConnection = new FullBackupObbConnection();
+ obbConnection.establish(); // we'll want this later
- Slog.i(TAG, "--- Performing full-dataset backup ---");
sendStartBackup();
// doAllApps supersedes the package set if any
@@ -2565,6 +2692,15 @@ class BackupManagerService extends IBackupManager.Stub {
for (int i = 0; i < N; i++) {
pkg = packagesToBackup.get(i);
backupOnePackage(pkg, out);
+
+ // after the app's agent runs to handle its private filesystem
+ // contents, back up any OBB content it has on its behalf.
+ if (mIncludeObbs) {
+ boolean obbOkay = obbConnection.backupObbs(pkg, out);
+ if (!obbOkay) {
+ throw new RuntimeException("Failure writing OBB stack for " + pkg);
+ }
+ }
}
// Done!
@@ -2589,6 +2725,7 @@ class BackupManagerService extends IBackupManager.Stub {
mLatchObject.notifyAll();
}
sendEndBackup();
+ obbConnection.tearDown();
if (DEBUG) Slog.d(TAG, "Full backup pass complete.");
mWakelock.release();
}
@@ -2696,20 +2833,7 @@ class BackupManagerService extends IBackupManager.Stub {
// Now pull data from the app and stuff it into the compressor
try {
- FileInputStream raw = new FileInputStream(pipes[0].getFileDescriptor());
- DataInputStream in = new DataInputStream(raw);
-
- byte[] buffer = new byte[16 * 1024];
- int chunkTotal;
- while ((chunkTotal = in.readInt()) > 0) {
- while (chunkTotal > 0) {
- int toRead = (chunkTotal > buffer.length)
- ? buffer.length : chunkTotal;
- int nRead = in.read(buffer, 0, toRead);
- out.write(buffer, 0, nRead);
- chunkTotal -= nRead;
- }
- }
+ routeSocketDataToOutput(pipes[0], out);
} catch (IOException e) {
Slog.i(TAG, "Caught exception reading from agent", e);
}
@@ -2908,7 +3032,7 @@ class BackupManagerService extends IBackupManager.Stub {
ACCEPT_IF_APK
}
- class PerformFullRestoreTask implements Runnable {
+ class PerformFullRestoreTask extends ObbServiceClient implements Runnable {
ParcelFileDescriptor mInputFile;
String mCurrentPassword;
String mDecryptPassword;
@@ -2917,6 +3041,7 @@ class BackupManagerService extends IBackupManager.Stub {
IBackupAgent mAgent;
String mAgentPackage;
ApplicationInfo mTargetApp;
+ FullBackupObbConnection mObbConnection = null;
ParcelFileDescriptor[] mPipes = null;
long mBytes;
@@ -2945,6 +3070,7 @@ class BackupManagerService extends IBackupManager.Stub {
mAgent = null;
mAgentPackage = null;
mTargetApp = null;
+ mObbConnection = new FullBackupObbConnection();
// Which packages we've already wiped data on. We prepopulate this
// with a whitelist of packages known to be unclearable.
@@ -2988,6 +3114,7 @@ class BackupManagerService extends IBackupManager.Stub {
@Override
public void run() {
Slog.i(TAG, "--- Performing full-dataset restore ---");
+ mObbConnection.establish();
sendStartRestore();
// Are we able to restore shared-storage data?
@@ -3075,6 +3202,7 @@ class BackupManagerService extends IBackupManager.Stub {
mLatchObject.set(true);
mLatchObject.notifyAll();
}
+ mObbConnection.tearDown();
sendEndRestore();
Slog.d(TAG, "Full restore pass complete.");
mWakelock.release();
@@ -3327,22 +3455,30 @@ class BackupManagerService extends IBackupManager.Stub {
long toCopy = info.size;
final int token = generateToken();
try {
- if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
- + info.path);
prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, null);
- // fire up the app's agent listening on the socket. If
- // the agent is running in the system process we can't
- // just invoke it asynchronously, so we provide a thread
- // for it here.
- if (mTargetApp.processName.equals("system")) {
- Slog.d(TAG, "system process agent - spinning a thread");
- RestoreFileRunnable runner = new RestoreFileRunnable(
- mAgent, info, mPipes[0], token);
- new Thread(runner).start();
+ if (info.domain.equals(FullBackup.OBB_TREE_TOKEN)) {
+ if (DEBUG) Slog.d(TAG, "Restoring OBB file for " + pkg
+ + " : " + info.path);
+ mObbConnection.restoreObbFile(pkg, mPipes[0],
+ info.size, info.type, info.path, info.mode,
+ info.mtime, token, mBackupManagerBinder);
} else {
- mAgent.doRestoreFile(mPipes[0], info.size, info.type,
- info.domain, info.path, info.mode, info.mtime,
- token, mBackupManagerBinder);
+ if (DEBUG) Slog.d(TAG, "Invoking agent to restore file "
+ + info.path);
+ // fire up the app's agent listening on the socket. If
+ // the agent is running in the system process we can't
+ // just invoke it asynchronously, so we provide a thread
+ // for it here.
+ if (mTargetApp.processName.equals("system")) {
+ Slog.d(TAG, "system process agent - spinning a thread");
+ RestoreFileRunnable runner = new RestoreFileRunnable(
+ mAgent, info, mPipes[0], token);
+ new Thread(runner).start();
+ } else {
+ mAgent.doRestoreFile(mPipes[0], info.size, info.type,
+ info.domain, info.path, info.mode, info.mtime,
+ token, mBackupManagerBinder);
+ }
}
} catch (IOException e) {
// couldn't dup the socket for a process-local restore
@@ -3350,7 +3486,7 @@ class BackupManagerService extends IBackupManager.Stub {
agentSuccess = false;
okay = false;
} catch (RemoteException e) {
- // whoops, remote agent went away. We'll eat the content
+ // whoops, remote entity went away. We'll eat the content
// ourselves, then, and not copy it over.
Slog.e(TAG, "Agent crashed during full restore");
agentSuccess = false;
@@ -3908,17 +4044,6 @@ class BackupManagerService extends IBackupManager.Stub {
slash = info.path.indexOf('/');
if (slash < 0) throw new IOException("Illegal semantic path in non-manifest " + info.path);
info.domain = info.path.substring(0, slash);
- // validate that it's one of the domains we understand
- if (!info.domain.equals(FullBackup.APK_TREE_TOKEN)
- && !info.domain.equals(FullBackup.DATA_TREE_TOKEN)
- && !info.domain.equals(FullBackup.DATABASE_TREE_TOKEN)
- && !info.domain.equals(FullBackup.ROOT_TREE_TOKEN)
- && !info.domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)
- && !info.domain.equals(FullBackup.OBB_TREE_TOKEN)
- && !info.domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
- throw new IOException("Unrecognized domain " + info.domain);
- }
-
info.path = info.path.substring(slash + 1);
}
}
@@ -5009,7 +5134,8 @@ class BackupManagerService extends IBackupManager.Stub {
// Run a *full* backup pass for the given package, writing the resulting data stream
// to the supplied file descriptor. This method is synchronous and does not return
// to the caller until the backup has been completed.
- public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeShared,
+ public void fullBackup(ParcelFileDescriptor fd, boolean includeApks,
+ boolean includeObbs, boolean includeShared,
boolean doAllApps, boolean includeSystem, String[] pkgList) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup");
@@ -5040,12 +5166,12 @@ class BackupManagerService extends IBackupManager.Stub {
}
if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks
- + " shared=" + includeShared + " all=" + doAllApps
+ + " obb=" + includeObbs + " shared=" + includeShared + " all=" + doAllApps
+ " pkgs=" + pkgList);
Slog.i(TAG, "Beginning full backup...");
- FullBackupParams params = new FullBackupParams(fd, includeApks, includeShared,
- doAllApps, includeSystem, pkgList);
+ FullBackupParams params = new FullBackupParams(fd, includeApks, includeObbs,
+ includeShared, doAllApps, includeSystem, pkgList);
final int token = generateToken();
synchronized (mFullConfirmations) {
mFullConfirmations.put(token, params);
diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java
index dbffa97..1f2947d 100644
--- a/services/java/com/android/server/BatteryService.java
+++ b/services/java/com/android/server/BatteryService.java
@@ -236,6 +236,15 @@ public final class BatteryService extends Binder {
}
}
+ /**
+ * Returns a non-zero value if an unsupported charger is attached.
+ */
+ public int getInvalidCharger() {
+ synchronized (mLock) {
+ return mInvalidCharger;
+ }
+ }
+
private void shutdownIfNoPowerLocked() {
// shut down gracefully if our battery is critically low and we are not powered.
// wait until the system has booted before attempting to display the shutdown dialog.
diff --git a/services/java/com/android/server/BluetoothManagerService.java b/services/java/com/android/server/BluetoothManagerService.java
index 5a2088c..bea2cca 100644
--- a/services/java/com/android/server/BluetoothManagerService.java
+++ b/services/java/com/android/server/BluetoothManagerService.java
@@ -19,6 +19,7 @@ package com.android.server;
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothCallback;
import android.bluetooth.IBluetoothManager;
import android.bluetooth.IBluetoothManagerCallback;
@@ -30,6 +31,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -58,6 +60,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private static final int TIMEOUT_SAVE_MS = 500; //Maximum msec to wait for a save
//Maximum msec to wait for service restart
private static final int SERVICE_RESTART_TIME_MS = 200;
+ //Maximum msec to wait for restart due to error
+ private static final int ERROR_RESTART_TIME_MS = 3000;
//Maximum msec to delay MESSAGE_USER_SWITCHED
private static final int USER_SWITCHED_TIME_MS = 200;
@@ -77,6 +81,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201;
private static final int MESSAGE_USER_SWITCHED = 300;
private static final int MAX_SAVE_RETRIES=3;
+ private static final int MAX_ERROR_RESTART_RETRIES=6;
+
// Bluetooth persisted setting is off
private static final int BLUETOOTH_OFF=0;
// Bluetooth persisted setting is on
@@ -87,6 +93,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
// and Airplane mode will have higher priority.
private static final int BLUETOOTH_ON_AIRPLANE=2;
+ private static final int SERVICE_IBLUETOOTH = 1;
+ private static final int SERVICE_IBLUETOOTHGATT = 2;
+
private final Context mContext;
// Locks are not provided for mName and mAddress.
@@ -97,6 +106,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private final RemoteCallbackList<IBluetoothManagerCallback> mCallbacks;
private final RemoteCallbackList<IBluetoothStateChangeCallback> mStateChangeCallbacks;
private IBluetooth mBluetooth;
+ private IBluetoothGatt mBluetoothGatt;
private boolean mBinding;
private boolean mUnbinding;
// used inside handler thread
@@ -112,6 +122,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private int mState;
private HandlerThread mThread;
private final BluetoothHandler mHandler;
+ private int mErrorRecoveryRetryCounter;
private void registerForAirplaneMode(IntentFilter filter) {
final ContentResolver resolver = mContext.getContentResolver();
@@ -197,6 +208,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
mEnableExternal = false;
mAddress = null;
mName = null;
+ mErrorRecoveryRetryCounter = 0;
mContentResolver = context.getContentResolver();
mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
@@ -463,6 +475,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
}
}
+ public IBluetoothGatt getBluetoothGatt() {
+ // sync protection
+ return mBluetoothGatt;
+ }
+
private void sendBluetoothStateCallback(boolean isUp) {
int n = mStateChangeCallbacks.beginBroadcast();
if (DBG) Log.d(TAG,"Broadcasting onBluetoothStateChange("+isUp+") to " + n + " receivers.");
@@ -575,16 +592,35 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
}
public void onServiceConnected(ComponentName className, IBinder service) {
- if (DBG) Log.d(TAG, "BluetoothServiceConnection: connected to AdapterService");
+ if (DBG) Log.d(TAG, "BluetoothServiceConnection: " + className.getClassName());
Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);
+ // TBD if (className.getClassName().equals(IBluetooth.class.getName())) {
+ if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) {
+ msg.arg1 = SERVICE_IBLUETOOTH;
+ // } else if (className.getClassName().equals(IBluetoothGatt.class.getName())) {
+ } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) {
+ msg.arg1 = SERVICE_IBLUETOOTHGATT;
+ } else {
+ Log.e(TAG, "Unknown service connected: " + className.getClassName());
+ return;
+ }
msg.obj = service;
mHandler.sendMessage(msg);
}
public void onServiceDisconnected(ComponentName className) {
// Called if we unexpected disconnected.
- if (DBG) Log.d(TAG, "BluetoothServiceConnection: disconnected from AdapterService");
+ if (DBG) Log.d(TAG, "BluetoothServiceConnection, disconnected: " +
+ className.getClassName());
Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED);
+ if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) {
+ msg.arg1 = SERVICE_IBLUETOOTH;
+ } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) {
+ msg.arg1 = SERVICE_IBLUETOOTHGATT;
+ } else {
+ Log.e(TAG, "Unknown service disconnected: " + className.getClassName());
+ return;
+ }
mHandler.sendMessage(msg);
}
}
@@ -611,8 +647,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
Intent i = new Intent(IBluetooth.class.getName());
- if (!mContext.bindService(i, mConnection,
- Context.BIND_AUTO_CREATE, UserHandle.USER_CURRENT)) {
+ if (!mContext.bindServiceAsUser(i, mConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
Log.e(TAG, "fail to bind to: " + IBluetooth.class.getName());
} else {
@@ -746,13 +782,18 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
}
case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:
{
- if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED");
-
- //Remove timeout
- mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+ if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1);
IBinder service = (IBinder) msg.obj;
synchronized(mConnection) {
+ if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
+ mBluetoothGatt = IBluetoothGatt.Stub.asInterface(service);
+ break;
+ } // else must be SERVICE_IBLUETOOTH
+
+ //Remove timeout
+ mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+
mBinding = false;
mBluetooth = IBluetooth.Stub.asInterface(service);
@@ -812,15 +853,37 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
if (DBG) Log.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: prevState = " + prevState + ", newState=" + newState);
mState = newState;
bluetoothStateChangeHandler(prevState, newState);
+ // handle error state transition case from TURNING_ON to OFF
+ // unbind and rebind bluetooth service and enable bluetooth
+ if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
+ (newState == BluetoothAdapter.STATE_OFF) &&
+ (mBluetooth != null) && mEnable) {
+ recoverBluetoothServiceFromError();
+ }
+ if (newState == BluetoothAdapter.STATE_ON) {
+ // bluetooth is working, reset the counter
+ if (mErrorRecoveryRetryCounter != 0) {
+ Log.w(TAG, "bluetooth is recovered from error");
+ mErrorRecoveryRetryCounter = 0;
+ }
+ }
break;
}
case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED:
{
- Log.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED");
+ Log.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: " + msg.arg1);
synchronized(mConnection) {
- // if service is unbinded already, do nothing and return
- if (mBluetooth == null) return;
- mBluetooth = null;
+ if (msg.arg1 == SERVICE_IBLUETOOTH) {
+ // if service is unbinded already, do nothing and return
+ if (mBluetooth == null) break;
+ mBluetooth = null;
+ } else if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
+ mBluetoothGatt = null;
+ break;
+ } else {
+ Log.e(TAG, "Bad msg.arg1: " + msg.arg1);
+ break;
+ }
}
if (mEnable) {
@@ -959,8 +1022,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
mConnection.setGetNameAddressOnly(false);
Intent i = new Intent(IBluetooth.class.getName());
- if (!mContext.bindService(i, mConnection,Context.BIND_AUTO_CREATE,
- UserHandle.USER_CURRENT)) {
+ if (!mContext.bindServiceAsUser(i, mConnection,Context.BIND_AUTO_CREATE,
+ UserHandle.CURRENT)) {
mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
Log.e(TAG, "Fail to bind to: " + IBluetooth.class.getName());
} else {
@@ -1048,10 +1111,22 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
boolean isUp = (newState==BluetoothAdapter.STATE_ON);
sendBluetoothStateCallback(isUp);
- //If Bluetooth is off, send service down event to proxy objects, and unbind
- if (!isUp && canUnbindBluetoothService()) {
- sendBluetoothServiceDownCallback();
- unbindAndFinish();
+ if (isUp) {
+ // connect to GattService
+ if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Intent i = new Intent(IBluetoothGatt.class.getName());
+ if (!mContext.bindServiceAsUser(i, mConnection, Context.BIND_AUTO_CREATE,
+ UserHandle.CURRENT)) {
+ Log.e(TAG, "Fail to bind to: " + IBluetoothGatt.class.getName());
+ }
+ }
+ } else {
+ //If Bluetooth is off, send service down event to proxy objects, and unbind
+ if (!isUp && canUnbindBluetoothService()) {
+ sendBluetoothServiceDownCallback();
+ unbindAndFinish();
+ }
}
}
@@ -1081,9 +1156,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
if (mBluetooth.getState() == BluetoothAdapter.STATE_ON) return true;
} else if (off) {
if (mBluetooth.getState() == BluetoothAdapter.STATE_OFF) return true;
- } else {
+ } else {
if (mBluetooth.getState() != BluetoothAdapter.STATE_ON) return true;
- }
+ }
} catch (RemoteException e) {
Log.e(TAG, "getState()", e);
break;
@@ -1091,9 +1166,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
}
if (on || off) {
SystemClock.sleep(300);
- } else {
+ } else {
SystemClock.sleep(50);
- }
+ }
i++;
}
Log.e(TAG,"waitForOnOff time out");
@@ -1126,4 +1201,48 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
}
return false;
}
+
+ private void recoverBluetoothServiceFromError() {
+ Log.e(TAG,"recoverBluetoothServiceFromError");
+ synchronized (mConnection) {
+ if (mBluetooth != null) {
+ //Unregister callback object
+ try {
+ mBluetooth.unregisterCallback(mBluetoothCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to unregister",re);
+ }
+ }
+ }
+
+ SystemClock.sleep(500);
+
+ // disable
+ handleDisable();
+
+ waitForOnOff(false, true);
+
+ sendBluetoothServiceDownCallback();
+ synchronized (mConnection) {
+ if (mBluetooth != null) {
+ mBluetooth = null;
+ //Unbind
+ mContext.unbindService(mConnection);
+ }
+ }
+
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mState = BluetoothAdapter.STATE_OFF;
+
+ mEnable = false;
+
+ if (mErrorRecoveryRetryCounter++ < MAX_ERROR_RESTART_RETRIES) {
+ // Send a Bluetooth Restart message to reenable bluetooth
+ Message restartMsg = mHandler.obtainMessage(
+ MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ mHandler.sendMessageDelayed(restartMsg, ERROR_RESTART_TIME_MS);
+ } else {
+ // todo: notify user to power down and power up phone to make bluetooth work.
+ }
+ }
}
diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java
index 235c662..3dade37 100644
--- a/services/java/com/android/server/BootReceiver.java
+++ b/services/java/com/android/server/BootReceiver.java
@@ -126,6 +126,7 @@ public class BootReceiver extends BroadcastReceiver {
-LOG_SIZE, "APANIC_CONSOLE");
addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_threads",
-LOG_SIZE, "APANIC_THREADS");
+ addAuditErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_AUDIT");
} else {
if (db != null) db.addText("SYSTEM_RESTART", headers);
}
@@ -174,4 +175,32 @@ public class BootReceiver extends BroadcastReceiver {
Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"));
}
+
+ private static void addAuditErrorsToDropBox(DropBoxManager db, SharedPreferences prefs,
+ String headers, int maxSize, String tag) throws IOException {
+ if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled
+ Slog.i(TAG, "Copying audit failures to DropBox");
+
+ File file = new File("/proc/last_kmsg");
+ long fileTime = file.lastModified();
+ if (fileTime <= 0) return; // File does not exist
+
+ if (prefs != null) {
+ long lastTime = prefs.getLong(tag, 0);
+ if (lastTime == fileTime) return; // Already logged this particular file
+ // TODO: move all these SharedPreferences Editor commits
+ // outside this function to the end of logBootEvents
+ prefs.edit().putLong(tag, fileTime).apply();
+ }
+
+ String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
+ StringBuilder sb = new StringBuilder();
+ for (String line : log.split("\n")) {
+ if (line.contains("audit")) {
+ sb.append(line + "\n");
+ }
+ }
+ Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox");
+ db.addText(tag, headers + sb.toString());
+ }
}
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index 74ec6e2..058857d 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -18,6 +18,7 @@ package com.android.server;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.ClipData;
@@ -55,8 +56,18 @@ public class ClipboardService extends IClipboard.Stub {
private final Context mContext;
private final IActivityManager mAm;
private final PackageManager mPm;
+ private final AppOpsManager mAppOps;
private final IBinder mPermissionOwner;
+ private class ListenerInfo {
+ final int mUid;
+ final String mPackageName;
+ ListenerInfo(int uid, String packageName) {
+ mUid = uid;
+ mPackageName = packageName;
+ }
+ }
+
private class PerUserClipboard {
final int userId;
@@ -82,6 +93,7 @@ public class ClipboardService extends IClipboard.Stub {
mContext = context;
mAm = ActivityManagerNative.getDefault();
mPm = context.getPackageManager();
+ mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
IBinder permOwner = null;
try {
permOwner = mAm.newUriPermissionOwner("clipboard");
@@ -137,11 +149,15 @@ public class ClipboardService extends IClipboard.Stub {
}
}
- public void setPrimaryClip(ClipData clip) {
+ public void setPrimaryClip(ClipData clip, String callingPackage) {
synchronized (this) {
if (clip != null && clip.getItemCount() <= 0) {
throw new IllegalArgumentException("No items");
}
+ if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
checkDataOwnerLocked(clip, Binder.getCallingUid());
clearActiveOwnersLocked();
PerUserClipboard clipboard = getClipboard();
@@ -149,7 +165,13 @@ public class ClipboardService extends IClipboard.Stub {
final int n = clipboard.primaryClipListeners.beginBroadcast();
for (int i = 0; i < n; i++) {
try {
- clipboard.primaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged();
+ ListenerInfo li = (ListenerInfo)
+ clipboard.primaryClipListeners.getBroadcastCookie(i);
+ if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
+ li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
+ clipboard.primaryClipListeners.getBroadcastItem(i)
+ .dispatchPrimaryClipChanged();
+ }
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
@@ -162,27 +184,41 @@ public class ClipboardService extends IClipboard.Stub {
public ClipData getPrimaryClip(String pkg) {
synchronized (this) {
+ if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ pkg) != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
addActiveOwnerLocked(Binder.getCallingUid(), pkg);
return getClipboard().primaryClip;
}
}
- public ClipDescription getPrimaryClipDescription() {
+ public ClipDescription getPrimaryClipDescription(String callingPackage) {
synchronized (this) {
+ if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
PerUserClipboard clipboard = getClipboard();
return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
}
}
- public boolean hasPrimaryClip() {
+ public boolean hasPrimaryClip(String callingPackage) {
synchronized (this) {
+ if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
return getClipboard().primaryClip != null;
}
}
- public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
+ public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
+ String callingPackage) {
synchronized (this) {
- getClipboard().primaryClipListeners.register(listener);
+ getClipboard().primaryClipListeners.register(listener,
+ new ListenerInfo(Binder.getCallingUid(), callingPackage));
}
}
@@ -192,8 +228,12 @@ public class ClipboardService extends IClipboard.Stub {
}
}
- public boolean hasClipboardText() {
+ public boolean hasClipboardText(String callingPackage) {
synchronized (this) {
+ if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
PerUserClipboard clipboard = getClipboard();
if (clipboard.primaryClip != null) {
CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index e5cfdf6..29c546e 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,7 +16,6 @@
package com.android.server;
-import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
@@ -32,7 +31,9 @@ import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
-import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -54,11 +55,13 @@ import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.Uri;
import android.net.LinkProperties.CompareResult;
import android.net.MobileDataStateTracker;
import android.net.NetworkConfig;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
import android.net.NetworkQuotaInfo;
import android.net.NetworkState;
import android.net.NetworkStateTracker;
@@ -68,6 +71,7 @@ import android.net.ProxyProperties;
import android.net.RouteInfo;
import android.net.wifi.WifiStateTracker;
import android.net.wimax.WimaxManagerConstants;
+import android.os.AsyncTask;
import android.os.Binder;
import android.os.FileUtils;
import android.os.Handler;
@@ -76,10 +80,12 @@ import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
+import android.os.Messenger;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -87,18 +93,21 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.security.Credentials;
import android.security.KeyStore;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.EventLog;
import android.util.Slog;
import android.util.SparseIntArray;
+import com.android.internal.R;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
+import com.android.internal.telephony.DctConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.am.BatteryStatsService;
+import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.BaseNetworkObserver;
@@ -112,9 +121,11 @@ import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
+import java.net.HttpURLConnection;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -122,6 +133,8 @@ import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
@@ -142,6 +155,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
"android.telephony.apn-restore";
+ // Default value if FAIL_FAST_TIME_MS is not set
+ private static final int DEFAULT_FAIL_FAST_TIME_MS = 1 * 60 * 1000;
+ // system property that can override DEFAULT_FAIL_FAST_TIME_MS
+ private static final String FAIL_FAST_TIME_MS =
+ "persist.radio.fail_fast_time_ms";
+
// used in recursive route setting to add gateways for the host for which
// a host route was requested.
private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10;
@@ -157,6 +176,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private boolean mLockdownEnabled;
private LockdownVpnTracker mLockdownTracker;
+ private Nat464Xlat mClat;
+
/** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
private Object mRulesLock = new Object();
/** Currently active network rules by UID. */
@@ -183,7 +204,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* A per Net list of the PID's that requested access to the net
* used both as a refcount and for per-PID DNS selection
*/
- private List mNetRequestersPids[];
+ private List<Integer> mNetRequestersPids[];
// priority order of the nettrackers
// (excluding dynamically set mNetworkPreference)
@@ -291,6 +312,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private static final int EVENT_VPN_STATE_CHANGED = 14;
+ /**
+ * Used internally to disable fail fast of mobile data
+ */
+ private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 15;
+
/** Handler used for internal events. */
private InternalHandler mHandler;
/** Handler used for incoming {@link NetworkStateTracker} events. */
@@ -320,12 +346,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// track the current default http proxy - tell the world if we get a new one (real change)
private ProxyProperties mDefaultProxy = null;
- private Object mDefaultProxyLock = new Object();
+ private Object mProxyLock = new Object();
private boolean mDefaultProxyDisabled = false;
// track the global proxy.
private ProxyProperties mGlobalProxy = null;
- private final Object mGlobalProxyLock = new Object();
private SettingsObserver mSettingsObserver;
@@ -346,6 +371,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// the set of network types that can only be enabled by system/sig apps
List mProtectedNetworks;
+ private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
+
+ TelephonyManager mTelephonyManager;
+
public ConnectivityService(Context context, INetworkManagementService netd,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
// Currently, omitting a NetworkFactory will create one internally
@@ -394,6 +423,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mNetd = checkNotNull(netManager, "missing INetworkManagementService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
mKeyStore = KeyStore.getInstance();
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
try {
mPolicyManager.registerListener(mPolicyListener);
@@ -412,8 +442,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
ConnectivityManager.MAX_NETWORK_TYPE+1];
mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1];
- mNetworkPreference = getPersistedNetworkPreference();
-
mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1];
mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
@@ -434,6 +462,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mRadioAttributes[r.mType] = r;
}
+ // TODO: What is the "correct" way to do determine if this is a wifi only device?
+ boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
+ log("wifiOnly=" + wifiOnly);
String[] naStrings = context.getResources().getStringArray(
com.android.internal.R.array.networkAttributes);
for (String naString : naStrings) {
@@ -444,6 +475,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
n.type);
continue;
}
+ if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
+ log("networkAttributes - ignoring mobile as this dev is wifiOnly " +
+ n.type);
+ continue;
+ }
if (mNetConfigs[n.type] != null) {
loge("Error in networkAttributes - ignoring attempt to redefine type " +
n.type);
@@ -495,15 +531,29 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- mNetRequestersPids = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
+ // Update mNetworkPreference according to user mannually first then overlay config.xml
+ mNetworkPreference = getPersistedNetworkPreference();
+ if (mNetworkPreference == -1) {
+ for (int n : mPriorityList) {
+ if (mNetConfigs[n].isDefault() && ConnectivityManager.isNetworkTypeValid(n)) {
+ mNetworkPreference = n;
+ break;
+ }
+ }
+ if (mNetworkPreference == -1) {
+ throw new IllegalStateException(
+ "You should set at least one default Network in config.xml!");
+ }
+ }
+
+ mNetRequestersPids =
+ (List<Integer> [])new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
for (int i : mPriorityList) {
- mNetRequestersPids[i] = new ArrayList();
+ mNetRequestersPids[i] = new ArrayList<Integer>();
}
mFeatureUsers = new ArrayList<FeatureUser>();
- mNumDnsEntries = 0;
-
mTestMode = SystemProperties.get("cm.test.mode").equals("true")
&& SystemProperties.get("ro.build.type").equals("eng");
@@ -532,12 +582,15 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mTethering.getTetherableBluetoothRegexs().length != 0) &&
mTethering.getUpstreamIfaceTypes().length != 0);
- mVpn = new Vpn(mContext, mVpnCallback, mNetd);
+ mVpn = new Vpn(mContext, mVpnCallback, mNetd, this);
mVpn.startMonitoring(mContext, mTrackerHandler);
+ mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler);
+
try {
mNetd.registerObserver(mTethering);
mNetd.registerObserver(mDataActivityObserver);
+ mNetd.registerObserver(mClat);
} catch (RemoteException e) {
loge("Error registering observer :" + e);
}
@@ -726,11 +779,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
final int networkPrefSetting = Settings.Global
.getInt(cr, Settings.Global.NETWORK_PREFERENCE, -1);
- if (networkPrefSetting != -1) {
- return networkPrefSetting;
- }
- return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE;
+ return networkPrefSetting;
}
/**
@@ -1306,7 +1356,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (usedNetworkType != networkType) {
Integer currentPid = new Integer(pid);
mNetRequestersPids[usedNetworkType].remove(currentPid);
- reassessPidDns(pid, true);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ reassessPidDns(pid, true);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ flushVmDnsCache();
if (mNetRequestersPids[usedNetworkType].size() != 0) {
if (VDBG) {
log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
@@ -1381,8 +1438,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
tracker.isTeardownRequested()) {
if (VDBG) {
- log("requestRouteToHostAddress on down network " +
- "(" + networkType + ") - dropped");
+ log("requestRouteToHostAddress on down network "
+ + "(" + networkType + ") - dropped"
+ + " tracker=" + tracker
+ + " netState=" + netState
+ + " isTeardownRequested="
+ + ((tracker != null) ? tracker.isTeardownRequested() : "tracker:null"));
}
return false;
}
@@ -1390,21 +1451,24 @@ public class ConnectivityService extends IConnectivityManager.Stub {
try {
InetAddress addr = InetAddress.getByAddress(hostAddress);
LinkProperties lp = tracker.getLinkProperties();
- return addRouteToAddress(lp, addr);
+ boolean ok = addRouteToAddress(lp, addr);
+ if (DBG) log("requestRouteToHostAddress ok=" + ok);
+ return ok;
} catch (UnknownHostException e) {
if (DBG) log("requestRouteToHostAddress got " + e.toString());
} finally {
Binder.restoreCallingIdentity(token);
}
+ if (DBG) log("requestRouteToHostAddress X bottom return false");
return false;
}
private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
- return modifyRoute(p.getInterfaceName(), p, r, 0, ADD, toDefaultTable);
+ return modifyRoute(p, r, 0, ADD, toDefaultTable);
}
private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
- return modifyRoute(p.getInterfaceName(), p, r, 0, REMOVE, toDefaultTable);
+ return modifyRoute(p, r, 0, REMOVE, toDefaultTable);
}
private boolean addRouteToAddress(LinkProperties lp, InetAddress addr) {
@@ -1417,26 +1481,27 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
boolean toDefaultTable) {
- RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), addr);
+ RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
if (bestRoute == null) {
- bestRoute = RouteInfo.makeHostRoute(addr);
+ bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
} else {
+ String iface = bestRoute.getInterface();
if (bestRoute.getGateway().equals(addr)) {
// if there is no better route, add the implied hostroute for our gateway
- bestRoute = RouteInfo.makeHostRoute(addr);
+ bestRoute = RouteInfo.makeHostRoute(addr, iface);
} else {
// if we will connect to this through another route, add a direct route
// to it's gateway
- bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway());
+ bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
}
}
- return modifyRoute(lp.getInterfaceName(), lp, bestRoute, 0, doAdd, toDefaultTable);
+ return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable);
}
- private boolean modifyRoute(String ifaceName, LinkProperties lp, RouteInfo r, int cycleCount,
- boolean doAdd, boolean toDefaultTable) {
- if ((ifaceName == null) || (lp == null) || (r == null)) {
- if (DBG) log("modifyRoute got unexpected null: " + ifaceName + ", " + lp + ", " + r);
+ private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
+ boolean toDefaultTable) {
+ if ((lp == null) || (r == null)) {
+ if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
return false;
}
@@ -1445,18 +1510,25 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return false;
}
- if (r.isHostRoute() == false) {
- RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), r.getGateway());
+ String ifaceName = r.getInterface();
+ if(ifaceName == null) {
+ loge("Error modifying route - no interface name");
+ return false;
+ }
+ if (r.hasGateway()) {
+ RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), r.getGateway());
if (bestRoute != null) {
if (bestRoute.getGateway().equals(r.getGateway())) {
// if there is no better route, add the implied hostroute for our gateway
- bestRoute = RouteInfo.makeHostRoute(r.getGateway());
+ bestRoute = RouteInfo.makeHostRoute(r.getGateway(), ifaceName);
} else {
// if we will connect to our gateway through another route, add a direct
// route to it's gateway
- bestRoute = RouteInfo.makeHostRoute(r.getGateway(), bestRoute.getGateway());
+ bestRoute = RouteInfo.makeHostRoute(r.getGateway(),
+ bestRoute.getGateway(),
+ ifaceName);
}
- modifyRoute(ifaceName, lp, bestRoute, cycleCount+1, doAdd, toDefaultTable);
+ modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable);
}
}
if (doAdd) {
@@ -1688,9 +1760,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* in accordance with network preference policies.
*/
if (!mNetConfigs[prevNetType].isDefault()) {
- List pids = mNetRequestersPids[prevNetType];
- for (int i = 0; i<pids.size(); i++) {
- Integer pid = (Integer)pids.get(i);
+ List<Integer> pids = mNetRequestersPids[prevNetType];
+ for (Integer pid : pids) {
// will remove them because the net's no longer connected
// need to do this now as only now do we know the pids and
// can properly null things that are no longer referenced.
@@ -1811,6 +1882,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
public void sendConnectedBroadcast(NetworkInfo info) {
+ enforceConnectivityInternalPermission();
sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
@@ -2095,6 +2167,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
/** @hide */
public void captivePortalCheckComplete(NetworkInfo info) {
+ enforceConnectivityInternalPermission();
mNetTrackers[info.getType()].captivePortalCheckComplete();
}
@@ -2225,33 +2298,55 @@ public class ConnectivityService extends IConnectivityManager.Stub {
boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault());
if (resetMask != 0 || resetDns) {
- LinkProperties linkProperties = mNetTrackers[netType].getLinkProperties();
- if (linkProperties != null) {
- String iface = linkProperties.getInterfaceName();
- if (TextUtils.isEmpty(iface) == false) {
- if (resetMask != 0) {
- if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")");
- NetworkUtils.resetConnections(iface, resetMask);
-
- // Tell VPN the interface is down. It is a temporary
- // but effective fix to make VPN aware of the change.
- if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) {
- mVpn.interfaceStatusChanged(iface, false);
+ if (curLp != null) {
+ for (String iface : curLp.getAllInterfaceNames()) {
+ if (TextUtils.isEmpty(iface) == false) {
+ if (resetMask != 0) {
+ if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")");
+ NetworkUtils.resetConnections(iface, resetMask);
+
+ // Tell VPN the interface is down. It is a temporary
+ // but effective fix to make VPN aware of the change.
+ if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) {
+ mVpn.interfaceStatusChanged(iface, false);
+ }
}
- }
- if (resetDns) {
- if (VDBG) log("resetting DNS cache for " + iface);
- try {
- mNetd.flushInterfaceDnsCache(iface);
- } catch (Exception e) {
- // never crash - catch them all
- if (DBG) loge("Exception resetting dns cache: " + e);
+ if (resetDns) {
+ flushVmDnsCache();
+ if (VDBG) log("resetting DNS cache for " + iface);
+ try {
+ mNetd.flushInterfaceDnsCache(iface);
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (DBG) loge("Exception resetting dns cache: " + e);
+ }
}
+ } else {
+ loge("Can't reset connection for type "+netType);
}
}
}
}
+ // Update 464xlat state.
+ NetworkStateTracker tracker = mNetTrackers[netType];
+ if (mClat.requiresClat(netType, tracker)) {
+ // If the connection was previously using clat, but is not using it now, stop the clat
+ // daemon. Normally, this happens automatically when the connection disconnects, but if
+ // the disconnect is not reported, or if the connection's LinkProperties changed for
+ // some other reason (e.g., handoff changes the IP addresses on the link), it would
+ // still be running. If it's not running, then stopping it is a no-op.
+ if (Nat464Xlat.isRunningClat(curLp) && !Nat464Xlat.isRunningClat(newLp)) {
+ mClat.stopClat();
+ }
+ // If the link requires clat to be running, then start the daemon now.
+ if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
+ mClat.startClat(tracker);
+ } else {
+ mClat.stopClat();
+ }
+ }
+
// TODO: Temporary notifying upstread change to Tethering.
// @see bug/4455071
/** Notify TetheringService if interface name has been changed. */
@@ -2281,7 +2376,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
routeDiff = curLp.compareRoutes(newLp);
dnsDiff = curLp.compareDnses(newLp);
} else if (newLp != null) {
- routeDiff.added = newLp.getRoutes();
+ routeDiff.added = newLp.getAllRoutes();
dnsDiff.added = newLp.getDnses();
}
@@ -2297,28 +2392,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- for (RouteInfo r : routeDiff.added) {
- if (isLinkDefault || ! r.isDefaultRoute()) {
- addRoute(newLp, r, TO_DEFAULT_TABLE);
- } else {
- // add to a secondary route table
- addRoute(newLp, r, TO_SECONDARY_TABLE);
-
- // many radios add a default route even when we don't want one.
- // remove the default route unless somebody else has asked for it
- String ifaceName = newLp.getInterfaceName();
- if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) {
- if (VDBG) log("Removing " + r + " for interface " + ifaceName);
- try {
- mNetd.removeRoute(ifaceName, r);
- } catch (Exception e) {
- // never crash - catch them all
- if (DBG) loge("Exception trying to remove a route: " + e);
- }
- }
- }
- }
-
if (!isLinkDefault) {
// handle DNS routes
if (routesChanged) {
@@ -2343,6 +2416,29 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
}
+
+ for (RouteInfo r : routeDiff.added) {
+ if (isLinkDefault || ! r.isDefaultRoute()) {
+ addRoute(newLp, r, TO_DEFAULT_TABLE);
+ } else {
+ // add to a secondary route table
+ addRoute(newLp, r, TO_SECONDARY_TABLE);
+
+ // many radios add a default route even when we don't want one.
+ // remove the default route unless somebody else has asked for it
+ String ifaceName = newLp.getInterfaceName();
+ if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) {
+ if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+ try {
+ mNetd.removeRoute(ifaceName, r);
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (DBG) loge("Exception trying to remove a route: " + e);
+ }
+ }
+ }
+ }
+
return routesChanged;
}
@@ -2352,7 +2448,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
* wide use
*/
- public void updateNetworkSettings(NetworkStateTracker nt) {
+ private void updateNetworkSettings(NetworkStateTracker nt) {
String key = nt.getTcpBufferSizesPropName();
String bufferSizes = key == null ? null : SystemProperties.get(key);
@@ -2406,9 +2502,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* on the highest priority active net which this process requested.
* If there aren't any, clear it out
*/
- private void reassessPidDns(int myPid, boolean doBump)
+ private void reassessPidDns(int pid, boolean doBump)
{
- if (VDBG) log("reassessPidDns for pid " + myPid);
+ if (VDBG) log("reassessPidDns for pid " + pid);
+ Integer myPid = new Integer(pid);
for(int i : mPriorityList) {
if (mNetConfigs[i].isDefault()) {
continue;
@@ -2418,61 +2515,25 @@ public class ConnectivityService extends IConnectivityManager.Stub {
!nt.isTeardownRequested()) {
LinkProperties p = nt.getLinkProperties();
if (p == null) continue;
- List pids = mNetRequestersPids[i];
- for (int j=0; j<pids.size(); j++) {
- Integer pid = (Integer)pids.get(j);
- if (pid.intValue() == myPid) {
- Collection<InetAddress> dnses = p.getDnses();
- writePidDns(dnses, myPid);
- if (doBump) {
- bumpDns();
- }
- return;
+ if (mNetRequestersPids[i].contains(myPid)) {
+ try {
+ mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception reasseses pid dns: " + e);
}
+ return;
}
}
}
// nothing found - delete
- for (int i = 1; ; i++) {
- String prop = "net.dns" + i + "." + myPid;
- if (SystemProperties.get(prop).length() == 0) {
- if (doBump) {
- bumpDns();
- }
- return;
- }
- SystemProperties.set(prop, "");
- }
- }
-
- // return true if results in a change
- private boolean writePidDns(Collection <InetAddress> dnses, int pid) {
- int j = 1;
- boolean changed = false;
- for (InetAddress dns : dnses) {
- String dnsString = dns.getHostAddress();
- if (changed || !dnsString.equals(SystemProperties.get("net.dns" + j + "." + pid))) {
- changed = true;
- SystemProperties.set("net.dns" + j + "." + pid, dns.getHostAddress());
- }
- j++;
+ try {
+ mNetd.clearDnsInterfaceForPid(pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception clear interface from pid: " + e);
}
- return changed;
}
- private void bumpDns() {
- /*
- * Bump the property that tells the name resolver library to reread
- * the DNS server list from the properties.
- */
- String propVal = SystemProperties.get("net.dnschange");
- int n = 0;
- if (propVal.length() != 0) {
- try {
- n = Integer.parseInt(propVal);
- } catch (NumberFormatException e) {}
- }
- SystemProperties.set("net.dnschange", "" + (n+1));
+ private void flushVmDnsCache() {
/*
* Tell the VMs to toss their DNS caches
*/
@@ -2491,56 +2552,34 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
// Caller must grab mDnsLock.
- private boolean updateDns(String network, String iface,
+ private void updateDnsLocked(String network, String iface,
Collection<InetAddress> dnses, String domains) {
- boolean changed = false;
int last = 0;
if (dnses.size() == 0 && mDefaultDns != null) {
- ++last;
- String value = mDefaultDns.getHostAddress();
- if (!value.equals(SystemProperties.get("net.dns1"))) {
- if (DBG) {
- loge("no dns provided for " + network + " - using " + value);
- }
- changed = true;
- SystemProperties.set("net.dns1", value);
+ dnses = new ArrayList();
+ dnses.add(mDefaultDns);
+ if (DBG) {
+ loge("no dns provided for " + network + " - using " + mDefaultDns.getHostAddress());
}
- } else {
+ }
+
+ try {
+ mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains);
+ mNetd.setDefaultInterfaceForDns(iface);
for (InetAddress dns : dnses) {
++last;
String key = "net.dns" + last;
String value = dns.getHostAddress();
- if (!changed && value.equals(SystemProperties.get(key))) {
- continue;
- }
- if (VDBG) {
- log("adding dns " + value + " for " + network);
- }
- changed = true;
SystemProperties.set(key, value);
}
- }
- for (int i = last + 1; i <= mNumDnsEntries; ++i) {
- String key = "net.dns" + i;
- if (VDBG) log("erasing " + key);
- changed = true;
- SystemProperties.set(key, "");
- }
- mNumDnsEntries = last;
-
- if (changed) {
- try {
- mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses));
- mNetd.setDefaultInterfaceForDns(iface);
- } catch (Exception e) {
- if (DBG) loge("exception setting default dns interface: " + e);
+ for (int i = last + 1; i <= mNumDnsEntries; ++i) {
+ String key = "net.dns" + i;
+ SystemProperties.set(key, "");
}
+ mNumDnsEntries = last;
+ } catch (Exception e) {
+ if (DBG) loge("exception setting default dns interface: " + e);
}
- if (!domains.equals(SystemProperties.get("net.dns.search"))) {
- SystemProperties.set("net.dns.search", domains);
- changed = true;
- }
- return changed;
}
private void handleDnsConfigurationChange(int netType) {
@@ -2550,29 +2589,31 @@ public class ConnectivityService extends IConnectivityManager.Stub {
LinkProperties p = nt.getLinkProperties();
if (p == null) return;
Collection<InetAddress> dnses = p.getDnses();
- boolean changed = false;
if (mNetConfigs[netType].isDefault()) {
String network = nt.getNetworkInfo().getTypeName();
synchronized (mDnsLock) {
if (!mDnsOverridden) {
- changed = updateDns(network, p.getInterfaceName(), dnses, "");
+ updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains());
}
}
} else {
try {
mNetd.setDnsServersForInterface(p.getInterfaceName(),
- NetworkUtils.makeStrings(dnses));
+ NetworkUtils.makeStrings(dnses), p.getDomains());
} catch (Exception e) {
if (DBG) loge("exception setting dns servers: " + e);
}
// set per-pid dns for attached secondary nets
- List pids = mNetRequestersPids[netType];
- for (int y=0; y< pids.size(); y++) {
- Integer pid = (Integer)pids.get(y);
- changed = writePidDns(dnses, pid.intValue());
+ List<Integer> pids = mNetRequestersPids[netType];
+ for (Integer pid : pids) {
+ try {
+ mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception setting interface for pid: " + e);
+ }
}
}
- if (changed) bumpDns();
+ flushVmDnsCache();
}
}
@@ -2631,7 +2672,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
pw.increaseIndent();
for (int net : mPriorityList) {
String pidString = net + ": ";
- for (Object pid : mNetRequestersPids[net]) {
+ for (Integer pid : mNetRequestersPids[net]) {
pidString = pidString + pid.toString() + ", ";
}
pw.println(pidString);
@@ -2820,6 +2861,19 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
break;
}
+ case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: {
+ int tag = mEnableFailFastMobileDataTag.get();
+ if (msg.arg1 == tag) {
+ MobileDataStateTracker mobileDst =
+ (MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+ if (mobileDst != null) {
+ mobileDst.setEnableFailFastMobileData(msg.arg2);
+ }
+ } else {
+ log("EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: stale arg1:" + msg.arg1
+ + " != tag:" + tag);
+ }
+ }
}
}
}
@@ -2886,7 +2940,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
public int setUsbTethering(boolean enable) {
- enforceTetherAccessPermission();
+ enforceTetherChangePermission();
if (isTetheringSupported()) {
return mTethering.setUsbTethering(enable);
} else {
@@ -3039,14 +3093,19 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
public ProxyProperties getProxy() {
- synchronized (mDefaultProxyLock) {
- return mDefaultProxyDisabled ? null : mDefaultProxy;
+ // this information is already available as a world read/writable jvm property
+ // so this API change wouldn't have a benifit. It also breaks the passing
+ // of proxy info to all the JVMs.
+ // enforceAccessPermission();
+ synchronized (mProxyLock) {
+ if (mGlobalProxy != null) return mGlobalProxy;
+ return (mDefaultProxyDisabled ? null : mDefaultProxy);
}
}
public void setGlobalProxy(ProxyProperties proxyProperties) {
- enforceChangePermission();
- synchronized (mGlobalProxyLock) {
+ enforceConnectivityInternalPermission();
+ synchronized (mProxyLock) {
if (proxyProperties == mGlobalProxy) return;
if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return;
if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return;
@@ -3063,16 +3122,21 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mGlobalProxy = null;
}
ContentResolver res = mContext.getContentResolver();
- Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, host);
- Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port);
- Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
- exclList);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, host);
+ Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port);
+ Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ exclList);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
if (mGlobalProxy == null) {
proxyProperties = mDefaultProxy;
}
- //sendProxyBroadcast(proxyProperties);
+ sendProxyBroadcast(proxyProperties);
}
private void loadGlobalProxy() {
@@ -3083,14 +3147,18 @@ public class ConnectivityService extends IConnectivityManager.Stub {
Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
if (!TextUtils.isEmpty(host)) {
ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList);
- synchronized (mGlobalProxyLock) {
+ synchronized (mProxyLock) {
mGlobalProxy = proxyProperties;
}
}
}
public ProxyProperties getGlobalProxy() {
- synchronized (mGlobalProxyLock) {
+ // this information is already available as a world read/writable jvm property
+ // so this API change wouldn't have a benifit. It also breaks the passing
+ // of proxy info to all the JVMs.
+ // enforceAccessPermission();
+ synchronized (mProxyLock) {
return mGlobalProxy;
}
}
@@ -3099,11 +3167,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (proxy != null && TextUtils.isEmpty(proxy.getHost())) {
proxy = null;
}
- synchronized (mDefaultProxyLock) {
+ synchronized (mProxyLock) {
if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
- if (mDefaultProxy == proxy) return;
+ if (mDefaultProxy == proxy) return; // catches repeated nulls
mDefaultProxy = proxy;
+ if (mGlobalProxy != null) return;
if (!mDefaultProxyDisabled) {
sendProxyBroadcast(proxy);
}
@@ -3225,7 +3294,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
throwIfLockdownEnabled();
try {
int type = mActiveDefaultNetwork;
- if (ConnectivityManager.isNetworkTypeValid(type)) {
+ if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
mVpn.protect(socket, mNetTrackers[type].getLinkProperties().getInterfaceName());
return true;
}
@@ -3341,19 +3410,15 @@ public class ConnectivityService extends IConnectivityManager.Stub {
String domains = buffer.toString().trim();
// Apply DNS changes.
- boolean changed = false;
synchronized (mDnsLock) {
- changed = updateDns("VPN", "VPN", addresses, domains);
+ updateDnsLocked("VPN", "VPN", addresses, domains);
mDnsOverridden = true;
}
- if (changed) {
- bumpDns();
- }
- // Temporarily disable the default proxy.
- synchronized (mDefaultProxyLock) {
+ // Temporarily disable the default proxy (not global).
+ synchronized (mProxyLock) {
mDefaultProxyDisabled = true;
- if (mDefaultProxy != null) {
+ if (mGlobalProxy == null && mDefaultProxy != null) {
sendProxyBroadcast(null);
}
}
@@ -3368,9 +3433,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mHandler.sendEmptyMessage(EVENT_RESTORE_DNS);
}
}
- synchronized (mDefaultProxyLock) {
+ synchronized (mProxyLock) {
mDefaultProxyDisabled = false;
- if (mDefaultProxy != null) {
+ if (mGlobalProxy == null && mDefaultProxy != null) {
sendProxyBroadcast(mDefaultProxy);
}
}
@@ -3379,7 +3444,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
@Override
public boolean updateLockdownVpn() {
- enforceSystemUid();
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ Slog.w(TAG, "Lockdown VPN only available to AID_SYSTEM");
+ return false;
+ }
// Tear down existing lockdown if profile was removed
mLockdownEnabled = LockdownVpnTracker.isEnabled();
@@ -3415,6 +3483,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
try {
if (tracker != null) {
mNetd.setFirewallEnabled(true);
+ mNetd.setFirewallInterfaceRule("lo", true);
mLockdownTracker = tracker;
mLockdownTracker.init();
} else {
@@ -3431,10 +3500,495 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- private static void enforceSystemUid() {
- final int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID) {
- throw new SecurityException("Only available to AID_SYSTEM");
+ public void supplyMessenger(int networkType, Messenger messenger) {
+ enforceConnectivityInternalPermission();
+
+ if (isNetworkTypeValid(networkType) && mNetTrackers[networkType] != null) {
+ mNetTrackers[networkType].supplyMessenger(messenger);
+ }
+ }
+
+ public int findConnectionTypeForIface(String iface) {
+ enforceConnectivityInternalPermission();
+
+ if (TextUtils.isEmpty(iface)) return ConnectivityManager.TYPE_NONE;
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ LinkProperties lp = tracker.getLinkProperties();
+ if (lp != null && iface.equals(lp.getInterfaceName())) {
+ return tracker.getNetworkInfo().getType();
+ }
+ }
+ }
+ return ConnectivityManager.TYPE_NONE;
+ }
+
+ /**
+ * Have mobile data fail fast if enabled.
+ *
+ * @param enabled DctConstants.ENABLED/DISABLED
+ */
+ private void setEnableFailFastMobileData(int enabled) {
+ int tag;
+
+ if (enabled == DctConstants.ENABLED) {
+ tag = mEnableFailFastMobileDataTag.incrementAndGet();
+ } else {
+ tag = mEnableFailFastMobileDataTag.get();
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_ENABLE_FAIL_FAST_MOBILE_DATA, tag,
+ enabled));
+ }
+
+ @Override
+ public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs,
+ final ResultReceiver resultReceiver) {
+ log("checkMobileProvisioning: E sendNotification=" + sendNotification
+ + " suggestedTimeOutMs=" + suggestedTimeOutMs
+ + " resultReceiver=" + resultReceiver);
+ enforceChangePermission();
+
+ int timeOutMs = suggestedTimeOutMs;
+ if (suggestedTimeOutMs > CheckMp.MAX_TIMEOUT_MS) {
+ timeOutMs = CheckMp.MAX_TIMEOUT_MS;
+ }
+
+ // Check that mobile networks are supported
+ if (!isNetworkSupported(ConnectivityManager.TYPE_MOBILE)
+ || !isNetworkSupported(ConnectivityManager.TYPE_MOBILE_HIPRI)) {
+ log("checkMobileProvisioning: X no mobile network");
+ if (resultReceiver != null) {
+ resultReceiver.send(ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION, null);
+ }
+ return timeOutMs;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ CheckMp checkMp = new CheckMp(mContext, this);
+ CheckMp.CallBack cb = new CheckMp.CallBack() {
+ @Override
+ void onComplete(Integer result) {
+ log("CheckMp.onComplete: result=" + result);
+ if (resultReceiver != null) {
+ log("CheckMp.onComplete: send result");
+ resultReceiver.send(result, null);
+ }
+ NetworkInfo ni =
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo();
+ switch(result) {
+ case ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE:
+ case ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION: {
+ log("CheckMp.onComplete: ignore, connected or no connection");
+ break;
+ }
+ case ConnectivityManager.CMP_RESULT_CODE_REDIRECTED: {
+ log("CheckMp.onComplete: warm sim");
+ String url = getProvisioningUrl();
+ if (TextUtils.isEmpty(url)) {
+ url = mContext.getResources()
+ .getString(R.string.mobile_redirected_provisioning_url);
+ }
+ if (TextUtils.isEmpty(url) == false) {
+ log("CheckMp.onComplete: warm sim (redirected), url=" + url);
+ setNotificationVisible(true, ni, url);
+ } else {
+ log("CheckMp.onComplete: warm sim (redirected), no url");
+ }
+ break;
+ }
+ case ConnectivityManager.CMP_RESULT_CODE_NO_DNS:
+ case ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION: {
+ String url = getProvisioningUrl();
+ if (TextUtils.isEmpty(url) == false) {
+ log("CheckMp.onComplete: warm sim (no dns/tcp), url=" + url);
+ setNotificationVisible(true, ni, url);
+ } else {
+ log("CheckMp.onComplete: warm sim (no dns/tcp), no url");
+ }
+ break;
+ }
+ default: {
+ loge("CheckMp.onComplete: ignore unexpected result=" + result);
+ break;
+ }
+ }
+ }
+ };
+ CheckMp.Params params =
+ new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb);
+ log("checkMobileProvisioning: params=" + params);
+ setNotificationVisible(false, null, null);
+ checkMp.execute(params);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ log("checkMobileProvisioning: X");
+ }
+ return timeOutMs;
+ }
+
+ static class CheckMp extends
+ AsyncTask<CheckMp.Params, Void, Integer> {
+ private static final String CHECKMP_TAG = "CheckMp";
+ public static final int MAX_TIMEOUT_MS = 60000;
+ private static final int SOCKET_TIMEOUT_MS = 5000;
+ private Context mContext;
+ private ConnectivityService mCs;
+ private TelephonyManager mTm;
+ private Params mParams;
+
+ /**
+ * Parameters for AsyncTask.execute
+ */
+ static class Params {
+ private String mUrl;
+ private long mTimeOutMs;
+ private CallBack mCb;
+
+ Params(String url, long timeOutMs, CallBack cb) {
+ mUrl = url;
+ mTimeOutMs = timeOutMs;
+ mCb = cb;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + " url=" + mUrl + " mTimeOutMs=" + mTimeOutMs + " mCb=" + mCb + "}";
+ }
+ }
+
+ /**
+ * The call back object passed in Params. onComplete will be called
+ * on the main thread.
+ */
+ abstract static class CallBack {
+ // Called on the main thread.
+ abstract void onComplete(Integer result);
+ }
+
+ public CheckMp(Context context, ConnectivityService cs) {
+ mContext = context;
+ mCs = cs;
+
+ // Setup access to TelephonyService we'll be using.
+ mTm = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ }
+
+ /**
+ * Get the default url to use for the test.
+ */
+ public String getDefaultUrl() {
+ // See http://go/clientsdns for usage approval
+ String server = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_SERVER);
+ if (server == null) {
+ server = "clients3.google.com";
+ }
+ return "http://" + server + "/generate_204";
+ }
+
+ /**
+ * Detect if its possible to connect to the http url. DNS based detection techniques
+ * do not work at all hotspots. The best way to check is to perform a request to
+ * a known address that fetches the data we expect.
+ */
+ private synchronized Integer isMobileOk(Params params) {
+ Integer result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+ Uri orgUri = Uri.parse(params.mUrl);
+ Random rand = new Random();
+ mParams = params;
+
+ try {
+ if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
+ log("isMobileOk: not mobile capable");
+ result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+ return result;
+ }
+
+ // Enable fail fast as we'll do retries here and use a
+ // hipri connection so the default connection stays active.
+ log("isMobileOk: start hipri url=" + params.mUrl);
+ mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
+ mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ Phone.FEATURE_ENABLE_HIPRI, new Binder());
+
+ // Continue trying to connect until time has run out
+ long endTime = SystemClock.elapsedRealtime() + params.mTimeOutMs;
+ while(SystemClock.elapsedRealtime() < endTime) {
+ try {
+ // Wait for hipri to connect.
+ // TODO: Don't poll and handle situation where hipri fails
+ // because default is retrying. See b/9569540
+ NetworkInfo.State state = mCs
+ .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
+ if (state != NetworkInfo.State.CONNECTED) {
+ log("isMobileOk: not connected ni=" +
+ mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+ sleep(1);
+ result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+ continue;
+ }
+
+ // Get of the addresses associated with the url host. We need to use the
+ // address otherwise HttpURLConnection object will use the name to get
+ // the addresses and is will try every address but that will bypass the
+ // route to host we setup and the connection could succeed as the default
+ // interface might be connected to the internet via wifi or other interface.
+ InetAddress[] addresses;
+ try {
+ addresses = InetAddress.getAllByName(orgUri.getHost());
+ } catch (UnknownHostException e) {
+ log("isMobileOk: UnknownHostException");
+ result = ConnectivityManager.CMP_RESULT_CODE_NO_DNS;
+ return result;
+ }
+ log("isMobileOk: addresses=" + inetAddressesToString(addresses));
+
+ // Get the type of addresses supported by this link
+ LinkProperties lp = mCs.getLinkProperties(
+ ConnectivityManager.TYPE_MOBILE_HIPRI);
+ boolean linkHasIpv4 = hasIPv4Address(lp);
+ boolean linkHasIpv6 = hasIPv6Address(lp);
+ log("isMobileOk: linkHasIpv4=" + linkHasIpv4
+ + " linkHasIpv6=" + linkHasIpv6);
+
+ // Loop through at most 3 valid addresses or all of the address or until
+ // we run out of time
+ int loops = Math.min(3, addresses.length);
+ for(int validAddr=0, addrTried=0;
+ (validAddr < loops) && (addrTried < addresses.length)
+ && (SystemClock.elapsedRealtime() < endTime);
+ addrTried ++) {
+
+ // Choose the address at random but make sure its type is supported
+ InetAddress hostAddr = addresses[rand.nextInt(addresses.length)];
+ if (((hostAddr instanceof Inet4Address) && linkHasIpv4)
+ || ((hostAddr instanceof Inet6Address) && linkHasIpv6)) {
+ // Valid address, so use it
+ validAddr += 1;
+ } else {
+ // Invalid address so try next address
+ continue;
+ }
+
+ // Make a route to host so we check the specific interface.
+ if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
+ hostAddr.getAddress())) {
+ // Wait a short time to be sure the route is established ??
+ log("isMobileOk:"
+ + " wait to establish route to hostAddr=" + hostAddr);
+ sleep(3);
+ } else {
+ log("isMobileOk:"
+ + " could not establish route to hostAddr=" + hostAddr);
+ continue;
+ }
+
+ // Rewrite the url to have numeric address to use the specific route.
+ // I also set the "Connection" to "Close" as by default "Keep-Alive"
+ // is used which is useless in this case.
+ URL newUrl = new URL(orgUri.getScheme() + "://"
+ + hostAddr.getHostAddress() + orgUri.getPath());
+ log("isMobileOk: newUrl=" + newUrl);
+
+ HttpURLConnection urlConn = null;
+ try {
+ // Open the connection set the request header and get the response
+ urlConn = (HttpURLConnection) newUrl.openConnection(
+ java.net.Proxy.NO_PROXY);
+ urlConn.setInstanceFollowRedirects(false);
+ urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS);
+ urlConn.setReadTimeout(SOCKET_TIMEOUT_MS);
+ urlConn.setUseCaches(false);
+ urlConn.setAllowUserInteraction(false);
+ urlConn.setRequestProperty("Connection", "close");
+ int responseCode = urlConn.getResponseCode();
+ if (responseCode == 204) {
+ result = ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE;
+ } else {
+ result = ConnectivityManager.CMP_RESULT_CODE_REDIRECTED;
+ }
+ log("isMobileOk: connected responseCode=" + responseCode);
+ urlConn.disconnect();
+ urlConn = null;
+ return result;
+ } catch (Exception e) {
+ log("isMobileOk: HttpURLConnection Exception e=" + e);
+ if (urlConn != null) {
+ urlConn.disconnect();
+ urlConn = null;
+ }
+ }
+ }
+ result = ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION;
+ log("isMobileOk: loops|timed out");
+ return result;
+ } catch (Exception e) {
+ log("isMobileOk: Exception e=" + e);
+ continue;
+ }
+ }
+ log("isMobileOk: timed out");
+ } finally {
+ log("isMobileOk: F stop hipri");
+ mCs.setEnableFailFastMobileData(DctConstants.DISABLED);
+ mCs.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ Phone.FEATURE_ENABLE_HIPRI);
+ log("isMobileOk: X result=" + result);
+ }
+ return result;
+ }
+
+ @Override
+ protected Integer doInBackground(Params... params) {
+ return isMobileOk(params[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ log("onPostExecute: result=" + result);
+ if ((mParams != null) && (mParams.mCb != null)) {
+ mParams.mCb.onComplete(result);
+ }
+ }
+
+ private String inetAddressesToString(InetAddress[] addresses) {
+ StringBuffer sb = new StringBuffer();
+ boolean firstTime = true;
+ for(InetAddress addr : addresses) {
+ if (firstTime) {
+ firstTime = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append(addr);
+ }
+ return sb.toString();
+ }
+
+ private void printNetworkInfo() {
+ boolean hasIccCard = mTm.hasIccCard();
+ int simState = mTm.getSimState();
+ log("hasIccCard=" + hasIccCard
+ + " simState=" + simState);
+ NetworkInfo[] ni = mCs.getAllNetworkInfo();
+ if (ni != null) {
+ log("ni.length=" + ni.length);
+ for (NetworkInfo netInfo: ni) {
+ log("netInfo=" + netInfo.toString());
+ }
+ } else {
+ log("no network info ni=null");
+ }
+ }
+
+ /**
+ * Sleep for a few seconds then return.
+ * @param seconds
+ */
+ private static void sleep(int seconds) {
+ try {
+ Thread.sleep(seconds * 1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public boolean hasIPv4Address(LinkProperties lp) {
+ return lp.hasIPv4Address();
+ }
+
+ // Not implemented in LinkProperties, do it here.
+ public boolean hasIPv6Address(LinkProperties lp) {
+ for (LinkAddress address : lp.getLinkAddresses()) {
+ if (address.getAddress() instanceof Inet6Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void log(String s) {
+ Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s);
+ }
+ }
+
+ private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
+
+ private void setNotificationVisible(boolean visible, NetworkInfo networkInfo, String url) {
+ log("setNotificationVisible: E visible=" + visible + " ni=" + networkInfo + " url=" + url);
+
+ Resources r = Resources.getSystem();
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (visible) {
+ CharSequence title;
+ CharSequence details;
+ int icon;
+ switch (networkInfo.getType()) {
+ case ConnectivityManager.TYPE_WIFI:
+ log("setNotificationVisible: TYPE_WIFI");
+ title = r.getString(R.string.wifi_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ networkInfo.getExtraInfo());
+ icon = R.drawable.stat_notify_wifi_in_range;
+ break;
+ case ConnectivityManager.TYPE_MOBILE:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ log("setNotificationVisible: TYPE_MOBILE|HIPRI");
+ title = r.getString(R.string.network_available_sign_in, 0);
+ // TODO: Change this to pull from NetworkInfo once a printable
+ // name has been added to it
+ details = mTelephonyManager.getNetworkOperatorName();
+ icon = R.drawable.stat_notify_rssi_in_range;
+ break;
+ default:
+ log("setNotificationVisible: other type=" + networkInfo.getType());
+ title = r.getString(R.string.network_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ networkInfo.getExtraInfo());
+ icon = R.drawable.stat_notify_rssi_in_range;
+ break;
+ }
+
+ Notification notification = new Notification();
+ notification.when = 0;
+ notification.icon = icon;
+ notification.flags = Notification.FLAG_AUTO_CANCEL;
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ notification.tickerText = title;
+ notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
+
+ log("setNotificaitionVisible: notify notificaiton=" + notification);
+ notificationManager.notify(NOTIFICATION_ID, 1, notification);
+ } else {
+ log("setNotificaitionVisible: cancel");
+ notificationManager.cancel(NOTIFICATION_ID, 1);
}
+ log("setNotificationVisible: X visible=" + visible + " ni=" + networkInfo + " url=" + url);
+ }
+
+ private String getProvisioningUrl() {
+ String url = mContext.getResources().getString(R.string.mobile_provisioning_url);
+ log("getProvisioningUrl: mobile_provisioning_url=" + url);
+
+ // populate the iccid, imei and phone number in the provisioning url.
+ if (!TextUtils.isEmpty(url)) {
+ String phoneNumber = mTelephonyManager.getLine1Number();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ phoneNumber = "0000000000";
+ }
+ url = String.format(url,
+ mTelephonyManager.getSimSerialNumber() /* ICCID */,
+ mTelephonyManager.getDeviceId() /* IMEI */,
+ phoneNumber /* Phone numer */);
+ }
+
+ return url;
}
}
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 6a62809..ab70e6f 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -41,10 +41,14 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -62,6 +66,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.AtomicFile;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
@@ -88,10 +93,11 @@ import java.util.Set;
* Implementation of the device policy APIs.
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
- private static final String DEVICE_POLICIES_XML = "device_policies.xml";
private static final String TAG = "DevicePolicyManagerService";
+ private static final String DEVICE_POLICIES_XML = "device_policies.xml";
+
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
private static final long MS_PER_DAY = 86400 * 1000;
@@ -109,6 +115,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
IPowerManager mIPowerManager;
IWindowManager mIWindowManager;
+ private DeviceOwner mDeviceOwner;
+
public static class DevicePolicyData {
int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
int mActivePasswordLength = 0;
@@ -507,6 +515,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
context.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
}
@@ -545,6 +554,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ void loadDeviceOwner() {
+ synchronized (this) {
+ if (DeviceOwner.isRegistered()) {
+ mDeviceOwner = new DeviceOwner();
+ }
+ }
+ }
+
/**
* Set an alarm for an upcoming event - expiration warning, expiration, or post-expiration
* reminders. Clears alarm if no expirations are configured.
@@ -709,7 +726,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Intent resolveIntent = new Intent();
resolveIntent.setComponent(adminName);
List<ResolveInfo> infos = mContext.getPackageManager().queryBroadcastReceivers(
- resolveIntent, PackageManager.GET_META_DATA, userHandle);
+ resolveIntent,
+ PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+ userHandle);
if (infos == null || infos.size() <= 0) {
throw new IllegalArgumentException("Unknown admin: " + adminName);
}
@@ -994,6 +1013,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public void systemReady() {
synchronized (this) {
loadSettingsLocked(getUserData(UserHandle.USER_OWNER), UserHandle.USER_OWNER);
+ loadDeviceOwner();
}
}
@@ -1052,6 +1072,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (replaceIndex == -1) {
policy.mAdminList.add(newAdmin);
+ enableIfNecessary(info.getPackageName(), userHandle);
} else {
policy.mAdminList.set(replaceIndex, newAdmin);
}
@@ -1119,6 +1140,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
if (admin.getUid() != Binder.getCallingUid()) {
+ // If trying to remove device owner, refuse when the caller is not the owner.
+ if (mDeviceOwner != null
+ && adminReceiver.getPackageName().equals(mDeviceOwner.getPackageName())) {
+ return;
+ }
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
}
@@ -1889,7 +1915,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mHandler.post(new Runnable() {
public void run() {
try {
- ActivityManagerNative.getDefault().switchUser(0);
+ ActivityManagerNative.getDefault().switchUser(UserHandle.USER_OWNER);
((UserManager) mContext.getSystemService(Context.USER_SERVICE))
.removeUser(userHandle);
} catch (RemoteException re) {
@@ -2351,6 +2377,49 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ @Override
+ public boolean setDeviceOwner(String packageName) {
+ if (packageName == null
+ || !DeviceOwner.isInstalled(packageName, mContext.getPackageManager())) {
+ throw new IllegalArgumentException("Invalid package name " + packageName
+ + " for device owner");
+ }
+ synchronized (this) {
+ if (mDeviceOwner == null && !isDeviceProvisioned()) {
+ mDeviceOwner = new DeviceOwner(packageName);
+ mDeviceOwner.writeOwnerFile();
+ return true;
+ } else {
+ throw new IllegalStateException("Trying to set device owner to " + packageName
+ + ", owner=" + mDeviceOwner.getPackageName()
+ + ", device_provisioned=" + isDeviceProvisioned());
+ }
+ }
+ }
+
+ @Override
+ public boolean isDeviceOwner(String packageName) {
+ synchronized (this) {
+ return mDeviceOwner != null
+ && mDeviceOwner.getPackageName().equals(packageName);
+ }
+ }
+
+ @Override
+ public String getDeviceOwner() {
+ synchronized (this) {
+ if (mDeviceOwner != null) {
+ return mDeviceOwner.getPackageName();
+ }
+ }
+ return null;
+ }
+
+ private boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) > 0;
+ }
+
private void enforceCrossUserPermission(int userHandle) {
if (userHandle < 0) {
throw new IllegalArgumentException("Invalid userId " + userHandle);
@@ -2364,6 +2433,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private void enableIfNecessary(String packageName, int userId) {
+ try {
+ IPackageManager ipm = AppGlobals.getPackageManager();
+ ApplicationInfo ai = ipm.getApplicationInfo(packageName,
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+ userId);
+ if (ai.enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ ipm.setApplicationEnabledSetting(packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP, userId, "DevicePolicyManager");
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -2399,4 +2484,92 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
+
+ static class DeviceOwner {
+ private static final String DEVICE_OWNER_XML = "device_owner.xml";
+ private static final String TAG_DEVICE_OWNER = "device-owner";
+ private static final String ATTR_PACKAGE = "package";
+ private String mPackageName;
+
+ DeviceOwner() {
+ readOwnerFile();
+ }
+
+ DeviceOwner(String packageName) {
+ this.mPackageName = packageName;
+ }
+
+ static boolean isRegistered() {
+ return new File(Environment.getSystemSecureDirectory(),
+ DEVICE_OWNER_XML).exists();
+ }
+
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ static boolean isInstalled(String packageName, PackageManager pm) {
+ try {
+ PackageInfo pi;
+ if ((pi = pm.getPackageInfo(packageName, 0)) != null) {
+ if ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+ }
+ } catch (NameNotFoundException nnfe) {
+ Slog.w(TAG, "Device Owner package " + packageName + " not installed.");
+ }
+ return false;
+ }
+
+ void readOwnerFile() {
+ AtomicFile file = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
+ DEVICE_OWNER_XML));
+ try {
+ FileInputStream input = file.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(input, null);
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+ String tag = parser.getName();
+ if (!TAG_DEVICE_OWNER.equals(tag)) {
+ throw new XmlPullParserException(
+ "Device Owner file does not start with device-owner tag: found " + tag);
+ }
+ mPackageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+ input.close();
+ } catch (XmlPullParserException xppe) {
+ Slog.e(TAG, "Error parsing device-owner file\n" + xppe);
+ } catch (IOException ioe) {
+ Slog.e(TAG, "IO Exception when reading device-owner file\n" + ioe);
+ }
+ }
+
+ void writeOwnerFile() {
+ synchronized (this) {
+ writeOwnerFileLocked();
+ }
+ }
+
+ private void writeOwnerFileLocked() {
+ AtomicFile file = new AtomicFile(new File(Environment.getSystemSecureDirectory(),
+ DEVICE_OWNER_XML));
+ try {
+ FileOutputStream output = file.startWrite();
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(output, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, TAG_DEVICE_OWNER);
+ out.attribute(null, ATTR_PACKAGE, mPackageName);
+ out.endTag(null, TAG_DEVICE_OWNER);
+ out.endDocument();
+ out.flush();
+ file.finishWrite(output);
+ } catch (IOException ioe) {
+ Slog.e(TAG, "IO Exception when writing device-owner file\n" + ioe);
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java
index 94a087a..016c561 100644
--- a/services/java/com/android/server/DeviceStorageMonitorService.java
+++ b/services/java/com/android/server/DeviceStorageMonitorService.java
@@ -16,9 +16,6 @@
package com.android.server;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -40,12 +37,17 @@ import android.os.StatFs;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.storage.StorageManager;
import android.provider.Settings;
import android.text.format.Formatter;
import android.util.EventLog;
import android.util.Slog;
import android.util.TimeUtils;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
/**
* This class implements a service to monitor the amount of disk
* storage space on the device. If the free storage on device is less
@@ -66,17 +68,18 @@ import android.util.TimeUtils;
*/
public class DeviceStorageMonitorService extends Binder {
private static final String TAG = "DeviceStorageMonitorService";
+
private static final boolean DEBUG = false;
private static final boolean localLOGV = false;
+
private static final int DEVICE_MEMORY_WHAT = 1;
private static final int MONITOR_INTERVAL = 1; //in minutes
private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
- private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
- private static final int DEFAULT_THRESHOLD_MAX_BYTES = 500*1024*1024; // 500MB
+
private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
- private static final int DEFAULT_FULL_THRESHOLD_BYTES = 1024*1024; // 1MB
+
private long mFreeMem; // on /data
private long mFreeMemAfterLastCacheClear; // on /data
private long mLastReportedFreeMem;
@@ -84,14 +87,16 @@ public class DeviceStorageMonitorService extends Binder {
private boolean mLowMemFlag=false;
private boolean mMemFullFlag=false;
private Context mContext;
- private ContentResolver mContentResolver;
+ private ContentResolver mResolver;
private long mTotalMemory; // on /data
private StatFs mDataFileStats;
private StatFs mSystemFileStats;
private StatFs mCacheFileStats;
- private static final String DATA_PATH = "/data";
- private static final String SYSTEM_PATH = "/system";
- private static final String CACHE_PATH = "/cache";
+
+ private static final File DATA_PATH = Environment.getDataDirectory();
+ private static final File SYSTEM_PATH = Environment.getRootDirectory();
+ private static final File CACHE_PATH = Environment.getDownloadCacheDirectory();
+
private long mThreadStartTime = -1;
private boolean mClearSucceeded = false;
private boolean mClearingCache;
@@ -116,7 +121,7 @@ public class DeviceStorageMonitorService extends Binder {
// more files than absolutely needed, to reduce the frequency that
// flushing takes place.
private long mMemCacheTrimToThreshold;
- private int mMemFullThreshold;
+ private long mMemFullThreshold;
/**
* This string is used for ServiceManager access to this class.
@@ -151,7 +156,7 @@ public class DeviceStorageMonitorService extends Binder {
private final void restatDataDir() {
try {
- mDataFileStats.restat(DATA_PATH);
+ mDataFileStats.restat(DATA_PATH.getAbsolutePath());
mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
mDataFileStats.getBlockSize();
} catch (IllegalArgumentException e) {
@@ -163,7 +168,7 @@ public class DeviceStorageMonitorService extends Binder {
mFreeMem = Long.parseLong(debugFreeMem);
}
// Read the log interval from secure settings
- long freeMemLogInterval = Settings.Global.getLong(mContentResolver,
+ long freeMemLogInterval = Settings.Global.getLong(mResolver,
Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
//log the amount of free memory in event log
@@ -173,14 +178,14 @@ public class DeviceStorageMonitorService extends Binder {
mLastReportedFreeMemTime = currTime;
long mFreeSystem = -1, mFreeCache = -1;
try {
- mSystemFileStats.restat(SYSTEM_PATH);
+ mSystemFileStats.restat(SYSTEM_PATH.getAbsolutePath());
mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
mSystemFileStats.getBlockSize();
} catch (IllegalArgumentException e) {
// ignore; report -1
}
try {
- mCacheFileStats.restat(CACHE_PATH);
+ mCacheFileStats.restat(CACHE_PATH.getAbsolutePath());
mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
mCacheFileStats.getBlockSize();
} catch (IllegalArgumentException e) {
@@ -190,7 +195,7 @@ public class DeviceStorageMonitorService extends Binder {
mFreeMem, mFreeSystem, mFreeCache);
}
// Read the reporting threshold from secure settings
- long threshold = Settings.Global.getLong(mContentResolver,
+ long threshold = Settings.Global.getLong(mResolver,
Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
// If mFree changed significantly log the new value
@@ -303,40 +308,6 @@ public class DeviceStorageMonitorService extends Binder {
delay);
}
- /*
- * just query settings to retrieve the memory threshold.
- * Preferred this over using a ContentObserver since Settings.Secure caches the value
- * any way
- */
- private long getMemThreshold() {
- long value = Settings.Global.getInt(
- mContentResolver,
- Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
- DEFAULT_THRESHOLD_PERCENTAGE);
- if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value);
- value = (value*mTotalMemory)/100;
- long maxValue = Settings.Global.getInt(
- mContentResolver,
- Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
- DEFAULT_THRESHOLD_MAX_BYTES);
- //evaluate threshold value
- return value < maxValue ? value : maxValue;
- }
-
- /*
- * just query settings to retrieve the memory full threshold.
- * Preferred this over using a ContentObserver since Settings.Secure caches the value
- * any way
- */
- private int getMemFullThreshold() {
- int value = Settings.Global.getInt(
- mContentResolver,
- Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
- DEFAULT_FULL_THRESHOLD_BYTES);
- if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value);
- return value;
- }
-
/**
* Constructor to run service. initializes the disk space threshold value
* and posts an empty message to kickstart the process.
@@ -344,11 +315,11 @@ public class DeviceStorageMonitorService extends Binder {
public DeviceStorageMonitorService(Context context) {
mLastReportedFreeMemTime = 0;
mContext = context;
- mContentResolver = mContext.getContentResolver();
+ mResolver = mContext.getContentResolver();
//create StatFs object
- mDataFileStats = new StatFs(DATA_PATH);
- mSystemFileStats = new StatFs(SYSTEM_PATH);
- mCacheFileStats = new StatFs(CACHE_PATH);
+ mDataFileStats = new StatFs(DATA_PATH.getAbsolutePath());
+ mSystemFileStats = new StatFs(SYSTEM_PATH.getAbsolutePath());
+ mCacheFileStats = new StatFs(CACHE_PATH.getAbsolutePath());
//initialize total storage on device
mTotalMemory = (long)mDataFileStats.getBlockCount() *
mDataFileStats.getBlockSize();
@@ -360,9 +331,12 @@ public class DeviceStorageMonitorService extends Binder {
mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
// cache storage thresholds
- mMemLowThreshold = getMemThreshold();
- mMemFullThreshold = getMemFullThreshold();
+ final StorageManager sm = StorageManager.from(context);
+ mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);
+ mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);
+
mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;
mMemCacheTrimToThreshold = mMemLowThreshold
+ ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);
@@ -373,7 +347,6 @@ public class DeviceStorageMonitorService extends Binder {
mCacheFileDeletedObserver.startWatching();
}
-
/**
* This method sends a notification to NotificationManager to display
* an error dialog indicating low disk space and launch the Installer
diff --git a/services/java/com/android/server/EntropyMixer.java b/services/java/com/android/server/EntropyMixer.java
index 4632374..fbb66f9 100644
--- a/services/java/com/android/server/EntropyMixer.java
+++ b/services/java/com/android/server/EntropyMixer.java
@@ -23,6 +23,10 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
@@ -44,9 +48,6 @@ import android.util.Slog;
* <p>This class was modeled after the script in
* <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html">man
* 4 random</a>.
- *
- * <p>TODO: Investigate attempting to write entropy data at shutdown time
- * instead of periodically.
*/
public class EntropyMixer extends Binder {
private static final String TAG = "EntropyMixer";
@@ -73,12 +74,19 @@ public class EntropyMixer extends Binder {
}
};
- public EntropyMixer() {
- this(getSystemDir() + "/entropy.dat", "/dev/urandom");
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ writeEntropy();
+ }
+ };
+
+ public EntropyMixer(Context context) {
+ this(context, getSystemDir() + "/entropy.dat", "/dev/urandom");
}
/** Test only interface, not for public use */
- public EntropyMixer(String entropyFile, String randomDevice) {
+ public EntropyMixer(Context context, String entropyFile, String randomDevice) {
if (randomDevice == null) { throw new NullPointerException("randomDevice"); }
if (entropyFile == null) { throw new NullPointerException("entropyFile"); }
@@ -88,6 +96,10 @@ public class EntropyMixer extends Binder {
addDeviceSpecificEntropy();
writeEntropy();
scheduleEntropyWriter();
+ IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+ broadcastFilter.addAction(Intent.ACTION_POWER_CONNECTED);
+ broadcastFilter.addAction(Intent.ACTION_REBOOT);
+ context.registerReceiver(mBroadcastReceiver, broadcastFilter);
}
private void scheduleEntropyWriter() {
@@ -107,6 +119,7 @@ public class EntropyMixer extends Binder {
private void writeEntropy() {
try {
+ Slog.i(TAG, "Writing entropy...");
RandomBlock.fromFile(randomDevice).toFile(entropyFile, true);
} catch (IOException e) {
Slog.w(TAG, "Unable to write entropy", e);
@@ -142,6 +155,7 @@ public class EntropyMixer extends Binder {
out.println(SystemProperties.get("ro.bootloader"));
out.println(SystemProperties.get("ro.hardware"));
out.println(SystemProperties.get("ro.revision"));
+ out.println(SystemProperties.get("ro.build.fingerprint"));
out.println(new Object().hashCode());
out.println(System.currentTimeMillis());
out.println(System.nanoTime());
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index 8bc2da2..8eaa91d 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -157,3 +157,20 @@ option java_package com.android.server
# ConfigUpdateInstallReceiver.java
# ---------------------------
51300 config_install_failed (dir|3)
+
+# ---------------------------
+# IntentFirewall.java
+# ---------------------------
+51400 ifw_intent_matched (Intent Type|1|5),(Component Name|3),(Caller Uid|1|5),(Caller Pkg Count|1|1),(Caller Pkgs|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
+
+# ---------------------------
+# IdleMaintenanceService.java
+# ---------------------------
+2753 idle_maintenance_window_start (time|2|3), (lastUserActivity|2|3), (batteryLevel|1|6), (batteryCharging|1|5)
+2754 idle_maintenance_window_finish (time|2|3), (lastUserActivity|2|3), (batteryLevel|1|6), (batteryCharging|1|5)
+
+# ---------------------------
+# MountService.java
+# ---------------------------
+2755 fstrim_start (time|2|3)
+2756 fstrim_finish (time|2|3)
diff --git a/services/java/com/android/server/IdleMaintenanceService.java b/services/java/com/android/server/IdleMaintenanceService.java
new file mode 100644
index 0000000..584d4bc
--- /dev/null
+++ b/services/java/com/android/server/IdleMaintenanceService.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * This service observes the device state and when applicable sends
+ * broadcasts at the beginning and at the end of a period during which
+ * observers can perform idle maintenance tasks. Typical use of the
+ * idle maintenance is to perform somehow expensive tasks that can be
+ * postponed to a moment when they will not degrade user experience.
+ *
+ * The current implementation is very simple. The start of a maintenance
+ * window is announced if: the screen is off or showing a dream AND the
+ * battery level is more than twenty percent AND at least one hour passed
+ * activity).
+ *
+ * The end of a maintenance window is announced only if: a start was
+ * announced AND the screen turned on or a dream was stopped.
+ */
+public class IdleMaintenanceService extends BroadcastReceiver {
+
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = IdleMaintenanceService.class.getSimpleName();
+
+ private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1;
+
+ private static final long MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS = 24 * 60 * 60 * 1000; // 1 day
+
+ private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING = 30; // percent
+
+ private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING = 80; // percent
+
+ private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING = 20; // percent
+
+ private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 71 * 60 * 1000; // 71 min
+
+ private static final long MAX_IDLE_MAINTENANCE_DURATION = 71 * 60 * 1000; // 71 min
+
+ private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE =
+ "com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE";
+
+ private static final Intent sIdleMaintenanceStartIntent;
+ static {
+ sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);
+ sIdleMaintenanceStartIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ };
+
+ private static final Intent sIdleMaintenanceEndIntent;
+ static {
+ sIdleMaintenanceEndIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);
+ sIdleMaintenanceEndIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ }
+
+ private final AlarmManager mAlarmService;
+
+ private final BatteryService mBatteryService;
+
+ private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent;
+
+ private final Context mContext;
+
+ private final WakeLock mWakeLock;
+
+ private final Handler mHandler;
+
+ private long mLastIdleMaintenanceStartTimeMillis;
+
+ private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
+
+ private boolean mIdleMaintenanceStarted;
+
+ public IdleMaintenanceService(Context context, BatteryService batteryService) {
+ mContext = context;
+ mBatteryService = batteryService;
+
+ mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+
+ mHandler = new Handler(mContext.getMainLooper());
+
+ Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
+ intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mUpdateIdleMaintenanceStatePendingIntent = PendingIntent.getBroadcast(mContext, 0,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ register(mContext.getMainLooper());
+ }
+
+ public void register(Looper looper) {
+ IntentFilter intentFilter = new IntentFilter();
+
+ // Alarm actions.
+ intentFilter.addAction(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
+
+ // Battery actions.
+ intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+
+ // Screen actions.
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
+ // Dream actions.
+ intentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
+ intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED);
+
+ mContext.registerReceiverAsUser(this, UserHandle.ALL,
+ intentFilter, null, new Handler(looper));
+ }
+
+ private void scheduleUpdateIdleMaintenanceState(long delayMillis) {
+ final long triggetRealTimeMillis = SystemClock.elapsedRealtime() + delayMillis;
+ mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggetRealTimeMillis,
+ mUpdateIdleMaintenanceStatePendingIntent);
+ }
+
+ private void unscheduleUpdateIdleMaintenanceState() {
+ mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent);
+ }
+
+ private void updateIdleMaintenanceState() {
+ if (mIdleMaintenanceStarted) {
+ // Idle maintenance can be interrupted by user activity, or duration
+ // time out, or low battery.
+ if (!lastUserActivityPermitsIdleMaintenanceRunning()
+ || !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+ unscheduleUpdateIdleMaintenanceState();
+ mIdleMaintenanceStarted = false;
+ EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
+ mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
+ isBatteryCharging() ? 1 : 0);
+ sendIdleMaintenanceEndIntent();
+ // We stopped since we don't have enough battery or timed out but the
+ // user is not using the device, so we should be able to run maintenance
+ // in the next maintenance window since the battery may be charged
+ // without interaction and the min interval between maintenances passed.
+ if (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+ scheduleUpdateIdleMaintenanceState(
+ getNextIdleMaintenanceIntervalStartFromNow());
+ }
+ }
+ } else if (deviceStatePermitsIdleMaintenanceStart()
+ && lastUserActivityPermitsIdleMaintenanceStart()
+ && lastRunPermitsIdleMaintenanceStart()) {
+ // Now that we started idle maintenance, we should schedule another
+ // update for the moment when the idle maintenance times out.
+ scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION);
+ mIdleMaintenanceStarted = true;
+ EventLogTags.writeIdleMaintenanceWindowStart(SystemClock.elapsedRealtime(),
+ mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
+ isBatteryCharging() ? 1 : 0);
+ mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
+ sendIdleMaintenanceStartIntent();
+ } else if (lastUserActivityPermitsIdleMaintenanceStart()) {
+ if (lastRunPermitsIdleMaintenanceStart()) {
+ // The user does not use the device and we did not run maintenance in more
+ // than the min interval between runs, so schedule an update - maybe the
+ // battery will be charged latter.
+ scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
+ } else {
+ // The user does not use the device but we have run maintenance in the min
+ // interval between runs, so schedule an update after the min interval ends.
+ scheduleUpdateIdleMaintenanceState(
+ getNextIdleMaintenanceIntervalStartFromNow());
+ }
+ }
+ }
+
+ private long getNextIdleMaintenanceIntervalStartFromNow() {
+ return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
+ - SystemClock.elapsedRealtime();
+ }
+
+ private void sendIdleMaintenanceStartIntent() {
+ mWakeLock.acquire();
+ mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL,
+ null, this, mHandler, Activity.RESULT_OK, null, null);
+ }
+
+ private void sendIdleMaintenanceEndIntent() {
+ mWakeLock.acquire();
+ mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL,
+ null, this, mHandler, Activity.RESULT_OK, null, null);
+ }
+
+ private boolean deviceStatePermitsIdleMaintenanceStart() {
+ final int minBatteryLevel = isBatteryCharging()
+ ? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING
+ : MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING;
+ return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
+ && mBatteryService.getBatteryLevel() > minBatteryLevel);
+ }
+
+ private boolean lastUserActivityPermitsIdleMaintenanceStart() {
+ // The last time the user poked the device is above the threshold.
+ return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
+ && SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis
+ > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
+ }
+
+ private boolean lastRunPermitsIdleMaintenanceStart() {
+ // Enough time passed since the last maintenance run.
+ return SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis
+ > MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
+ }
+
+ private boolean lastUserActivityPermitsIdleMaintenanceRunning() {
+ // The user is not using the device.
+ return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID);
+ }
+
+ private boolean batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning() {
+ // Battery not too low and the maintenance duration did not timeout.
+ return (mBatteryService.getBatteryLevel() > MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING
+ && mLastIdleMaintenanceStartTimeMillis + MAX_IDLE_MAINTENANCE_DURATION
+ > SystemClock.elapsedRealtime());
+ }
+
+ private boolean isBatteryCharging() {
+ return mBatteryService.getPlugType() > 0
+ && mBatteryService.getInvalidCharger() == 0;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, intent.getAction());
+ }
+ String action = intent.getAction();
+ if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ // We care about battery only if maintenance is in progress so we can
+ // stop it if battery is too low. Note that here we assume that the
+ // maintenance clients are properly holding a wake lock. We will
+ // refactor the maintenance to use services instead of intents for the
+ // next release. The only client for this for now is internal an holds
+ // a wake lock correctly.
+ if (mIdleMaintenanceStarted) {
+ updateIdleMaintenanceState();
+ }
+ } else if (Intent.ACTION_SCREEN_ON.equals(action)
+ || Intent.ACTION_DREAMING_STOPPED.equals(action)) {
+ mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
+ // Unschedule any future updates since we already know that maintenance
+ // cannot be performed since the user is back.
+ unscheduleUpdateIdleMaintenanceState();
+ // If the screen went on/stopped dreaming, we know the user is using the
+ // device which means that idle maintenance should be stopped if running.
+ updateIdleMaintenanceState();
+ } else if (Intent.ACTION_SCREEN_OFF.equals(action)
+ || Intent.ACTION_DREAMING_STARTED.equals(action)) {
+ mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime();
+ // If screen went off/started dreaming, we may be able to start idle maintenance
+ // after the minimal user inactivity elapses. We schedule an alarm for when
+ // this timeout elapses since the device may go to sleep by then.
+ scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
+ } else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) {
+ updateIdleMaintenanceState();
+ } else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)
+ || Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
+ // We were holding a wake lock while broadcasting the idle maintenance
+ // intents but now that we finished the broadcast release the wake lock.
+ mWakeLock.release();
+ }
+ }
+}
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index a296d34..1dd5fc6 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -16,12 +16,14 @@
package com.android.server;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.inputmethod.InputMethodUtils;
+import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
-import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputSessionCallback;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
@@ -53,7 +55,6 @@ import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
@@ -76,8 +77,6 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.AtomicFile;
@@ -89,6 +88,7 @@ import android.util.Printer;
import android.util.Slog;
import android.util.Xml;
import android.view.IWindowManager;
+import android.view.InputChannel;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -154,15 +154,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
- private static final int NOT_A_SUBTYPE_ID = -1;
- private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
- private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
- private static final String SUBTYPE_MODE_VOICE = "voice";
+ private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
- private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
- "EnabledWhenDefaultIsNotAsciiCapable";
- private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
- private static final Locale ENGLISH_LOCALE = new Locale("en");
+
final Context mContext;
final Resources mRes;
@@ -171,12 +165,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final SettingsObserver mSettingsObserver;
final IWindowManager mIWindowManager;
final HandlerCaller mCaller;
+ final boolean mHasFeature;
private InputMethodFileManager mFileManager;
private InputMethodAndSubtypeListManager mImListManager;
private final HardKeyboardListener mHardKeyboardListener;
private final WindowManagerService mWindowManagerService;
- final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
+ final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1);
// All known input methods. mMethodMap also serves as the global
// lock for this class.
@@ -208,7 +203,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
class SessionState {
final ClientState client;
final IInputMethod method;
- final IInputMethodSession session;
+
+ IInputMethodSession session;
+ InputChannel channel;
@Override
public String toString() {
@@ -217,18 +214,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
System.identityHashCode(method))
+ " session " + Integer.toHexString(
System.identityHashCode(session))
+ + " channel " + channel
+ "}";
}
SessionState(ClientState _client, IInputMethod _method,
- IInputMethodSession _session) {
+ IInputMethodSession _session, InputChannel _channel) {
client = _client;
method = _method;
session = _session;
+ channel = _channel;
}
}
- class ClientState {
+ static final class ClientState {
final IInputMethodClient client;
final IInputContext inputContext;
final int uid;
@@ -389,6 +388,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private boolean mInputBoundToKeyguard;
class SettingsObserver extends ContentObserver {
+ String mLastEnabled = "";
+
SettingsObserver(Handler handler) {
super(handler);
ContentResolver resolver = mContext.getContentResolver();
@@ -402,7 +403,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
@Override public void onChange(boolean selfChange) {
synchronized (mMethodMap) {
- updateFromSettingsLocked();
+ boolean enabledChanged = false;
+ String newEnabled = mSettings.getEnabledInputMethodsStr();
+ if (!mLastEnabled.equals(newEnabled)) {
+ mLastEnabled = newEnabled;
+ enabledChanged = true;
+ }
+ updateFromSettingsLocked(enabledChanged);
}
}
}
@@ -510,7 +517,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- buildInputMethodListLocked(mMethodList, mMethodMap);
+ buildInputMethodListLocked(
+ mMethodList, mMethodMap, false /* resetDefaultEnabledIme */);
boolean changed = false;
@@ -546,28 +554,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (changed) {
- updateFromSettingsLocked();
+ updateFromSettingsLocked(false);
}
}
}
}
- private static class MethodCallback extends IInputMethodCallback.Stub {
- private final IInputMethod mMethod;
+ private static final class MethodCallback extends IInputSessionCallback.Stub {
private final InputMethodManagerService mParentIMMS;
+ private final IInputMethod mMethod;
+ private final InputChannel mChannel;
- MethodCallback(final IInputMethod method, final InputMethodManagerService imms) {
- mMethod = method;
+ MethodCallback(InputMethodManagerService imms, IInputMethod method,
+ InputChannel channel) {
mParentIMMS = imms;
+ mMethod = method;
+ mChannel = channel;
}
@Override
- public void finishedEvent(int seq, boolean handled) throws RemoteException {
- }
-
- @Override
- public void sessionCreated(IInputMethodSession session) throws RemoteException {
- mParentIMMS.onSessionCreated(mMethod, session);
+ public void sessionCreated(IInputMethodSession session) {
+ mParentIMMS.onSessionCreated(mMethod, session, mChannel);
}
}
@@ -610,6 +617,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}, true /*asyncHandler*/);
mWindowManagerService = windowManager;
mHardKeyboardListener = new HardKeyboardListener();
+ mHasFeature = context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS);
mImeSwitcherNotification = new Notification();
mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default;
@@ -670,9 +679,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// Just checking if defaultImiId is empty or not
final String defaultImiId = mSettings.getSelectedInputMethod();
+ if (DEBUG) {
+ Slog.d(TAG, "Initial default ime = " + defaultImiId);
+ }
mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- buildInputMethodListLocked(mMethodList, mMethodMap);
+ buildInputMethodListLocked(mMethodList, mMethodMap,
+ !mImeSelectedOnBoot /* resetDefaultEnabledIme */);
mSettings.enableAllIMEsIfThereIsNoEnabledIME();
if (!mImeSelectedOnBoot) {
@@ -681,7 +694,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
mSettingsObserver = new SettingsObserver(mHandler);
- updateFromSettingsLocked();
+ updateFromSettingsLocked(true);
// IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME
// according to the new system locale.
@@ -692,7 +705,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void onReceive(Context context, Intent intent) {
synchronized(mMethodMap) {
- checkCurrentLocaleChangedLocked();
+ resetStateIfCurrentLocaleChangedLocked();
}
}
}, filter);
@@ -700,21 +713,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private void resetDefaultImeLocked(Context context) {
// Do not reset the default (current) IME when it is a 3rd-party IME
- if (mCurMethodId != null && !isSystemIme(mMethodMap.get(mCurMethodId))) {
+ if (mCurMethodId != null
+ && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
return;
}
InputMethodInfo defIm = null;
for (InputMethodInfo imi : mMethodList) {
if (defIm == null) {
- if (isValidSystemDefaultIme(imi, context)) {
+ if (InputMethodUtils.isValidSystemDefaultIme(
+ mSystemReady, imi, context)) {
defIm = imi;
Slog.i(TAG, "Selected default: " + imi.getId());
}
}
}
if (defIm == null && mMethodList.size() > 0) {
- defIm = getMostApplicableDefaultIMELocked();
+ defIm = InputMethodUtils.getMostApplicableDefaultIME(
+ mSettings.getEnabledInputMethodListLocked());
Slog.i(TAG, "No default found, using " + defIm.getId());
}
if (defIm != null) {
@@ -722,7 +738,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- private void resetAllInternalStateLocked(boolean updateOnlyWhenLocaleChanged) {
+ private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged,
+ final boolean resetDefaultEnabledIme) {
if (!mSystemReady) {
// not system ready
return;
@@ -740,7 +757,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
// InputMethodAndSubtypeListManager should be reset when the locale is changed.
mImListManager = new InputMethodAndSubtypeListManager(mContext, this);
- buildInputMethodListLocked(mMethodList, mMethodMap);
+ buildInputMethodListLocked(mMethodList, mMethodMap, resetDefaultEnabledIme);
if (!updateOnlyWhenLocaleChanged) {
final String selectedImiId = mSettings.getSelectedInputMethod();
if (TextUtils.isEmpty(selectedImiId)) {
@@ -752,7 +769,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
}
- updateFromSettingsLocked();
+ updateFromSettingsLocked(true);
mLastSystemLocale = newLocale;
if (!updateOnlyWhenLocaleChanged) {
try {
@@ -764,58 +781,26 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- private void checkCurrentLocaleChangedLocked() {
- resetAllInternalStateLocked(true);
+ private void resetStateIfCurrentLocaleChangedLocked() {
+ resetAllInternalStateLocked(true /* updateOnlyWhenLocaleChanged */,
+ true /* resetDefaultImeLocked */);
}
private void switchUserLocked(int newUserId) {
mSettings.setCurrentUserId(newUserId);
// InputMethodFileManager should be reset when the user is changed
mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
- resetAllInternalStateLocked(false);
- }
-
- private boolean isValidSystemDefaultIme(InputMethodInfo imi, Context context) {
- if (!mSystemReady) {
- return false;
- }
- if (!isSystemIme(imi)) {
- return false;
- }
- if (imi.getIsDefaultResourceId() != 0) {
- try {
- Resources res = context.createPackageContext(
- imi.getPackageName(), 0).getResources();
- if (res.getBoolean(imi.getIsDefaultResourceId())
- && containsSubtypeOf(imi, context.getResources().getConfiguration().
- locale.getLanguage())) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException ex) {
- } catch (Resources.NotFoundException ex) {
- }
- }
- if (imi.getSubtypeCount() == 0) {
- Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName());
+ final String defaultImiId = mSettings.getSelectedInputMethod();
+ final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
+ if (DEBUG) {
+ Slog.d(TAG, "Switch user: " + newUserId + " current ime = " + defaultImiId);
}
- return false;
- }
-
- private static boolean isSystemImeThatHasEnglishSubtype(InputMethodInfo imi) {
- if (!isSystemIme(imi)) {
- return false;
+ resetAllInternalStateLocked(false /* updateOnlyWhenLocaleChanged */,
+ initialUserSwitch /* needsToResetDefaultIme */);
+ if (initialUserSwitch) {
+ InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mContext.getPackageManager(),
+ mSettings.getEnabledInputMethodListLocked());
}
- return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage());
- }
-
- private static boolean containsSubtypeOf(InputMethodInfo imi, String language) {
- final int N = imi.getSubtypeCount();
- for (int i = 0; i < N; ++i) {
- if (imi.getSubtypeAt(i).getLocale().startsWith(language)) {
- return true;
- }
- }
- return false;
}
@Override
@@ -853,10 +838,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mWindowManagerService.setOnHardKeyboardStatusChangeListener(
mHardKeyboardListener);
}
- buildInputMethodListLocked(mMethodList, mMethodMap);
+ buildInputMethodListLocked(mMethodList, mMethodMap,
+ !mImeSelectedOnBoot /* resetDefaultEnabledIme */);
if (!mImeSelectedOnBoot) {
Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
- checkCurrentLocaleChangedLocked();
+ resetStateIfCurrentLocaleChangedLocked();
+ InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
+ mContext.getPackageManager(),
+ mSettings.getEnabledInputMethodListLocked());
}
mLastSystemLocale = mRes.getConfiguration().locale;
try {
@@ -935,7 +924,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
return false;
}
- return mContext.bindService(service, conn, flags, mSettings.getCurrentUserId());
+ return mContext.bindServiceAsUser(service, conn, flags,
+ new UserHandle(mSettings.getCurrentUserId()));
}
@Override
@@ -966,24 +956,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) {
enabledInputMethodAndSubtypes.put(
- imi, getEnabledInputMethodSubtypeListLocked(imi, true));
+ imi, mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true));
}
return enabledInputMethodAndSubtypes;
}
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi,
- boolean allowsImplicitlySelectedSubtypes) {
- if (imi == null && mCurMethodId != null) {
- imi = mMethodMap.get(mCurMethodId);
- }
- List<InputMethodSubtype> enabledSubtypes =
- mSettings.getEnabledInputMethodSubtypeListLocked(imi);
- if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
- enabledSubtypes = getImplicitlyApplicableSubtypesLocked(mRes, imi);
- }
- return InputMethodSubtype.sort(mContext, 0, imi, enabledSubtypes);
- }
-
+ /**
+ * @param imi if null, returns enabled subtypes for the current imi
+ * @return enabled subtypes of the specified imi
+ */
@Override
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
boolean allowsImplicitlySelectedSubtypes) {
@@ -992,7 +973,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return Collections.emptyList();
}
synchronized (mMethodMap) {
- return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes);
+ if (imi == null && mCurMethodId != null) {
+ imi = mMethodMap.get(mCurMethodId);
+ }
+ return mSettings.getEnabledInputMethodSubtypeListLocked(
+ mContext, imi, allowsImplicitlySelectedSubtypes);
}
}
@@ -1014,7 +999,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return;
}
synchronized (mMethodMap) {
- mClients.remove(client.asBinder());
+ ClientState cs = mClients.remove(client.asBinder());
+ if (cs != null) {
+ clearClientSessionLocked(cs);
+ }
}
}
@@ -1089,7 +1077,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
showCurrentInputLocked(getAppShowFlags(), null);
}
- return new InputBindResult(session.session, mCurId, mCurSeq);
+ return new InputBindResult(session.session,
+ session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq);
}
InputBindResult startInputLocked(IInputMethodClient client,
@@ -1167,16 +1156,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (mHaveConnection) {
if (mCurMethod != null) {
- if (!cs.sessionRequested) {
- cs.sessionRequested = true;
- if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
- MSG_CREATE_SESSION, mCurMethod,
- new MethodCallback(mCurMethod, this)));
- }
// Return to client, and we will get back with it when
// we have had a session made for it.
- return new InputBindResult(null, mCurId, mCurSeq);
+ requestClientSessionLocked(cs);
+ return new InputBindResult(null, null, mCurId, mCurSeq);
} else if (SystemClock.uptimeMillis()
< (mLastBindTime+TIME_TO_RECONNECT)) {
// In this case we have connected to the service, but
@@ -1186,7 +1169,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// we can report back. If it has been too long, we want
// to fall through so we can try a disconnect/reconnect
// to see if we can get back in touch with the service.
- return new InputBindResult(null, mCurId, mCurSeq);
+ return new InputBindResult(null, null, mCurId, mCurSeq);
} else {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
@@ -1205,7 +1188,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (!mSystemReady) {
// If the system is not yet ready, we shouldn't be running third
// party code.
- return new InputBindResult(null, mCurMethodId, mCurSeq);
+ return new InputBindResult(null, null, mCurMethodId, mCurSeq);
}
InputMethodInfo info = mMethodMap.get(mCurMethodId);
@@ -1228,12 +1211,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mCurId = info.getId();
mCurToken = new Binder();
try {
- if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
+ if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
mIWindowManager.addWindowToken(mCurToken,
WindowManager.LayoutParams.TYPE_INPUT_METHOD);
} catch (RemoteException e) {
}
- return new InputBindResult(null, mCurId, mCurSeq);
+ return new InputBindResult(null, null, mCurId, mCurSeq);
} else {
mCurIntent = null;
Slog.w(TAG, "Failure connecting to input method service: "
@@ -1276,32 +1259,34 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
if (mCurClient != null) {
- if (DEBUG) Slog.v(TAG, "Creating first session while with client "
- + mCurClient);
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
- MSG_CREATE_SESSION, mCurMethod,
- new MethodCallback(mCurMethod, this)));
+ clearClientSessionLocked(mCurClient);
+ requestClientSessionLocked(mCurClient);
}
}
}
}
- void onSessionCreated(IInputMethod method, IInputMethodSession session) {
+ void onSessionCreated(IInputMethod method, IInputMethodSession session,
+ InputChannel channel) {
synchronized (mMethodMap) {
if (mCurMethod != null && method != null
&& mCurMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
+ clearClientSessionLocked(mCurClient);
mCurClient.curSession = new SessionState(mCurClient,
- method, session);
- mCurClient.sessionRequested = false;
+ method, session, channel);
InputBindResult res = attachNewInputLocked(true);
if (res.method != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
MSG_BIND_METHOD, mCurClient.client, res));
}
+ return;
}
}
}
+
+ // Session abandoned. Close its associated input channel.
+ channel.dispose();
}
void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) {
@@ -1336,14 +1321,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
}
}
-
- private void finishSession(SessionState sessionState) {
- if (sessionState != null && sessionState.session != null) {
- try {
- sessionState.session.finishSession();
- } catch (RemoteException e) {
- Slog.w(TAG, "Session failed to close due to remote exception", e);
- setImeWindowVisibilityStatusHiddenLocked();
+
+ void requestClientSessionLocked(ClientState cs) {
+ if (!cs.sessionRequested) {
+ if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
+ InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
+ cs.sessionRequested = true;
+ executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
+ MSG_CREATE_SESSION, mCurMethod, channels[1],
+ new MethodCallback(this, mCurMethod, channels[0])));
+ }
+ }
+
+ void clearClientSessionLocked(ClientState cs) {
+ finishSessionLocked(cs.curSession);
+ cs.curSession = null;
+ cs.sessionRequested = false;
+ }
+
+ private void finishSessionLocked(SessionState sessionState) {
+ if (sessionState != null) {
+ if (sessionState.session != null) {
+ try {
+ sessionState.session.finishSession();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Session failed to close due to remote exception", e);
+ setImeWindowVisibilityStatusHiddenLocked();
+ }
+ sessionState.session = null;
+ }
+ if (sessionState.channel != null) {
+ sessionState.channel.dispose();
+ sessionState.channel = null;
}
}
}
@@ -1351,12 +1360,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
void clearCurMethodLocked() {
if (mCurMethod != null) {
for (ClientState cs : mClients.values()) {
- cs.sessionRequested = false;
- finishSession(cs.curSession);
- cs.curSession = null;
+ clearClientSessionLocked(cs);
}
- finishSession(mEnabledSession);
+ finishSessionLocked(mEnabledSession);
mEnabledSession = null;
mCurMethod = null;
}
@@ -1441,8 +1448,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
InputMethodSubtype auxSubtype = null;
for(int i = 0; i < N; ++i) {
final InputMethodInfo imi = imis.get(i);
- final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeListLocked(
- imi, true);
+ final List<InputMethodSubtype> subtypes =
+ mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
final int subtypeCount = subtypes.size();
if (subtypeCount == 0) {
++nonAuxCount;
@@ -1547,7 +1554,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
SuggestionSpan ss = spans[i];
if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
mSecureSuggestionSpans.put(ss, currentImi);
- final InputMethodInfo targetImi = mSecureSuggestionSpans.get(ss);
}
}
}
@@ -1585,7 +1591,32 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return false;
}
- void updateFromSettingsLocked() {
+ void updateFromSettingsLocked(boolean enabledMayChange) {
+ if (enabledMayChange) {
+ List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
+ for (int i=0; i<enabled.size(); i++) {
+ // We allow the user to select "disabled until used" apps, so if they
+ // are enabling one of those here we now need to make it enabled.
+ InputMethodInfo imm = enabled.get(i);
+ try {
+ ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+ mSettings.getCurrentUserId());
+ if (ai != null && ai.enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ if (DEBUG) {
+ Slog.d(TAG, "Update state(" + imm.getId()
+ + "): DISABLED_UNTIL_USED -> DEFAULT");
+ }
+ mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
+ mContext.getBasePackageName());
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ }
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -1597,7 +1628,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (!TextUtils.isEmpty(id)) {
try {
- setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
+ setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
mCurMethodId = null;
@@ -1723,6 +1754,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
boolean res = false;
if (mCurMethod != null) {
+ if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
resultReceiver));
@@ -1794,13 +1826,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mShowExplicitlyRequested || mShowForced)) {
- if (DEBUG) Slog.v(TAG,
- "Not hiding: explicit show not cancelled by non-explicit hide");
+ if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
return false;
}
if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
- if (DEBUG) Slog.v(TAG,
- "Not hiding: forced show not cancelled by not-always hide");
+ if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
return false;
}
boolean res;
@@ -2008,7 +2038,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
synchronized (mMethodMap) {
if (subtype != null) {
- setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
+ setInputMethodWithSubtypeId(token, id, InputMethodUtils.getSubtypeIdFromHashCode(
mMethodMap.get(id), subtype.hashCode()));
} else {
setInputMethod(token, id);
@@ -2056,11 +2086,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// defined, there is no need to switch to the last IME.
if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
targetLastImiId = lastIme.first;
- subtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
+ subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
}
}
- if (TextUtils.isEmpty(targetLastImiId) && !canAddToLastInputMethod(mCurrentSubtype)) {
+ if (TextUtils.isEmpty(targetLastImiId)
+ && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
// This is a safety net. If the currentSubtype can't be added to the history
// and the framework couldn't find the last ime, we will make the last ime be
// the most applicable enabled keyboard subtype of the system imes.
@@ -2072,13 +2103,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
: mCurrentSubtype.getLocale();
for (int i = 0; i < N; ++i) {
final InputMethodInfo imi = enabled.get(i);
- if (imi.getSubtypeCount() > 0 && isSystemIme(imi)) {
+ if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
InputMethodSubtype keyboardSubtype =
- findLastResortApplicableSubtypeLocked(mRes, getSubtypes(imi),
- SUBTYPE_MODE_KEYBOARD, locale, true);
+ InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
+ InputMethodUtils.getSubtypes(imi),
+ InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
if (keyboardSubtype != null) {
targetLastImiId = imi.getId();
- subtypeId = getSubtypeIdFromHashCode(
+ subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
imi, keyboardSubtype.hashCode());
if(keyboardSubtype.getLocale().equals(locale)) {
break;
@@ -2132,7 +2164,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (lastImi == null) return null;
try {
final int lastSubtypeHash = Integer.valueOf(lastIme.second);
- final int lastSubtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
+ final int lastSubtypeId =
+ InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
return null;
}
@@ -2168,7 +2201,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mFileManager.addInputMethodSubtypes(imi, subtypes);
final long ident = Binder.clearCallingIdentity();
try {
- buildInputMethodListLocked(mMethodList, mMethodMap);
+ buildInputMethodListLocked(mMethodList, mMethodMap,
+ false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2307,8 +2341,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
case MSG_SHOW_SOFT_INPUT:
args = (SomeArgs)msg.obj;
try {
- ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
- (ResultReceiver)args.arg2);
+ if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+ + msg.arg1 + ", " + args.arg2 + ")");
+ ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
@@ -2316,8 +2351,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
case MSG_HIDE_SOFT_INPUT:
args = (SomeArgs)msg.obj;
try {
- ((IInputMethod)args.arg1).hideSoftInput(0,
- (ResultReceiver)args.arg2);
+ if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+ + args.arg2 + ")");
+ ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
@@ -2331,15 +2367,23 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
args.recycle();
return true;
- case MSG_CREATE_SESSION:
+ case MSG_CREATE_SESSION: {
args = (SomeArgs)msg.obj;
+ IInputMethod method = (IInputMethod)args.arg1;
+ InputChannel channel = (InputChannel)args.arg2;
try {
- ((IInputMethod)args.arg1).createSession(
- (IInputMethodCallback)args.arg2);
+ method.createSession(channel, (IInputSessionCallback)args.arg3);
} catch (RemoteException e) {
+ } finally {
+ // Dispose the channel if the input method is not local to this process
+ // because the remote proxy will get its own copy when unparceled.
+ if (channel != null && Binder.isProxy(method)) {
+ channel.dispose();
+ }
}
args.recycle();
return true;
+ }
// ---------------------------------------------------------
case MSG_START_INPUT:
@@ -2374,16 +2418,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// There is nothing interesting about the last client dying.
}
return true;
- case MSG_BIND_METHOD:
+ case MSG_BIND_METHOD: {
args = (SomeArgs)msg.obj;
+ IInputMethodClient client = (IInputMethodClient)args.arg1;
+ InputBindResult res = (InputBindResult)args.arg2;
try {
- ((IInputMethodClient)args.arg1).onBindMethod(
- (InputBindResult)args.arg2);
+ client.onBindMethod(res);
} catch (RemoteException e) {
Slog.w(TAG, "Client died receiving input method " + args.arg2);
+ } finally {
+ // Dispose the channel if the input method is not local to this process
+ // because the remote proxy will get its own copy when unparceled.
+ if (res.channel != null && Binder.isProxy(client)) {
+ res.channel.dispose();
+ }
}
args.recycle();
return true;
+ }
case MSG_SET_ACTIVE:
try {
((ClientState)msg.obj).client.setActive(msg.arg1 != 0);
@@ -2403,56 +2455,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return false;
}
- private static boolean isSystemIme(InputMethodInfo inputMethod) {
- return (inputMethod.getServiceInfo().applicationInfo.flags
- & ApplicationInfo.FLAG_SYSTEM) != 0;
- }
-
- private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
- ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
- final int subtypeCount = imi.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- subtypes.add(imi.getSubtypeAt(i));
- }
- return subtypes;
- }
-
- private static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
- InputMethodInfo imi, String mode) {
- ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
- final int subtypeCount = imi.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(i);
- if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
- subtypes.add(subtype);
- }
- }
- return subtypes;
- }
-
- private InputMethodInfo getMostApplicableDefaultIMELocked() {
- List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
- if (enabled != null && enabled.size() > 0) {
- // We'd prefer to fall back on a system IME, since that is safer.
- int i = enabled.size();
- int firstFoundSystemIme = -1;
- while (i > 0) {
- i--;
- final InputMethodInfo imi = enabled.get(i);
- if (isSystemImeThatHasEnglishSubtype(imi) && !imi.isAuxiliaryIme()) {
- return imi;
- }
- if (firstFoundSystemIme < 0 && isSystemIme(imi) && !imi.isAuxiliaryIme()) {
- firstFoundSystemIme = i;
- }
- }
- return enabled.get(Math.max(firstFoundSystemIme, 0));
- }
- return null;
- }
-
private boolean chooseNewDefaultIMELocked() {
- final InputMethodInfo imi = getMostApplicableDefaultIMELocked();
+ final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
+ mSettings.getEnabledInputMethodListLocked());
if (imi != null) {
if (DEBUG) {
Slog.d(TAG, "New default IME was selected: " + imi.getId());
@@ -2465,23 +2470,23 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
- HashMap<String, InputMethodInfo> map) {
+ HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) {
if (DEBUG) {
- Slog.d(TAG, "--- re-buildInputMethodList " + ", \n ------ \n" + getStackTrace());
+ Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ + " \n ------ \n" + getStackTrace());
}
list.clear();
map.clear();
// Use for queryIntentServicesAsUser
final PackageManager pm = mContext.getPackageManager();
- final Configuration config = mRes.getConfiguration();
- final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
String disabledSysImes = mSettings.getDisabledSystemInputMethods();
if (disabledSysImes == null) disabledSysImes = "";
final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
- PackageManager.GET_META_DATA, mSettings.getCurrentUserId());
+ PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+ mSettings.getCurrentUserId());
final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
mFileManager.getAllAdditionalInputMethodSubtypes();
@@ -2505,14 +2510,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final String id = p.getId();
map.put(id, p);
- // Valid system default IMEs and IMEs that have English subtypes are enabled
- // by default
- if ((isValidSystemDefaultIme(p, mContext) || isSystemImeThatHasEnglishSubtype(p))) {
- setInputMethodEnabledLocked(id, true);
- }
-
if (DEBUG) {
- Slog.d(TAG, "Found a third-party input method " + p);
+ Slog.d(TAG, "Found an input method " + p);
}
} catch (XmlPullParserException e) {
@@ -2522,12 +2521,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ if (resetDefaultEnabledIme) {
+ final ArrayList<InputMethodInfo> defaultEnabledIme =
+ InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list);
+ for (int i = 0; i < defaultEnabledIme.size(); ++i) {
+ final InputMethodInfo imi = defaultEnabledIme.get(i);
+ if (DEBUG) {
+ Slog.d(TAG, "--- enable ime = " + imi);
+ }
+ setInputMethodEnabledLocked(imi.getId(), true);
+ }
+ }
+
final String defaultImiId = mSettings.getSelectedInputMethod();
if (!TextUtils.isEmpty(defaultImiId)) {
if (!map.containsKey(defaultImiId)) {
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
if (chooseNewDefaultIMELocked()) {
- updateFromSettingsLocked();
+ updateFromSettingsLocked(true);
}
} else {
// Double check that the default IME is certainly enabled.
@@ -2576,7 +2587,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final boolean isScreenLocked = isScreenLocked();
final String lastInputMethodId = mSettings.getSelectedInputMethod();
- int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
+ int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
synchronized (mMethodMap) {
@@ -2596,8 +2607,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
if (currentSubtype != null) {
final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
- lastInputMethodSubtypeId =
- getSubtypeIdFromHashCode(currentImi, currentSubtype.hashCode());
+ lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
+ currentImi, currentSubtype.hashCode());
}
}
@@ -2652,6 +2663,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
public void onCheckedChanged(
CompoundButton buttonView, boolean isChecked) {
mWindowManagerService.setHardKeyboardEnabled(isChecked);
+ // Ensure that the input method dialog is dismissed when changing
+ // the hardware keyboard state.
+ hideInputMethodMenu();
}
});
@@ -2670,6 +2684,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
InputMethodInfo im = mIms[which];
int subtypeId = mSubtypeIds[which];
+ adapter.mCheckedItem = which;
+ adapter.notifyDataSetChanged();
hideInputMethodMenu();
if (im != null) {
if ((subtypeId < 0)
@@ -2767,7 +2783,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private final LayoutInflater mInflater;
private final int mTextViewResourceId;
private final List<ImeSubtypeListItem> mItemsList;
- private final int mCheckedItem;
+ public int mCheckedItem;
public ImeSubtypeListAdapter(Context context, int textViewResourceId,
List<ImeSubtypeListItem> itemsList, int checkedItem) {
super(context, textViewResourceId, itemsList);
@@ -2845,7 +2861,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
}
-
+
boolean setInputMethodEnabledLocked(String id, boolean enabled) {
// Make sure this is a valid input method.
InputMethodInfo imm = mMethodMap.get(id);
@@ -2887,25 +2903,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- private boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
- if (subtype == null) return true;
- return !subtype.isAuxiliary();
- }
-
- private void saveCurrentInputMethodAndSubtypeToHistory() {
- String subtypeId = NOT_A_SUBTYPE_ID_STR;
- if (mCurrentSubtype != null) {
- subtypeId = String.valueOf(mCurrentSubtype.hashCode());
- }
- if (canAddToLastInputMethod(mCurrentSubtype)) {
- mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
- }
- }
-
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
// Update the history of InputMethod and Subtype
- saveCurrentInputMethodAndSubtypeToHistory();
+ mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
// Set Subtype here
if (imi == null || subtypeId < 0) {
@@ -2946,7 +2947,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
if (subtypeHashCode != null) {
try {
- lastSubtypeId = getSubtypeIdFromHashCode(
+ lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
imi, Integer.valueOf(subtypeHashCode));
} catch (NumberFormatException e) {
Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
@@ -2956,159 +2957,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
}
- private int getSelectedInputMethodSubtypeId(String id) {
- InputMethodInfo imi = mMethodMap.get(id);
- if (imi == null) {
- return NOT_A_SUBTYPE_ID;
- }
- final int subtypeHashCode = mSettings.getSelectedInputMethodSubtypeHashCode();
- return getSubtypeIdFromHashCode(imi, subtypeHashCode);
- }
-
- private static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
- return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
- }
-
- private static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
- if (imi != null) {
- final int subtypeCount = imi.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- InputMethodSubtype ims = imi.getSubtypeAt(i);
- if (subtypeHashCode == ims.hashCode()) {
- return i;
- }
- }
- }
- return NOT_A_SUBTYPE_ID;
- }
-
- private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
- Resources res, InputMethodInfo imi) {
- final List<InputMethodSubtype> subtypes = getSubtypes(imi);
- final String systemLocale = res.getConfiguration().locale.toString();
- if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
- final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
- new HashMap<String, InputMethodSubtype>();
- final int N = subtypes.size();
- for (int i = 0; i < N; ++i) {
- // scan overriding implicitly enabled subtypes.
- InputMethodSubtype subtype = subtypes.get(i);
- if (subtype.overridesImplicitlyEnabledSubtype()) {
- final String mode = subtype.getMode();
- if (!applicableModeAndSubtypesMap.containsKey(mode)) {
- applicableModeAndSubtypesMap.put(mode, subtype);
- }
- }
- }
- if (applicableModeAndSubtypesMap.size() > 0) {
- return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
- }
- for (int i = 0; i < N; ++i) {
- final InputMethodSubtype subtype = subtypes.get(i);
- final String locale = subtype.getLocale();
- final String mode = subtype.getMode();
- // When system locale starts with subtype's locale, that subtype will be applicable
- // for system locale
- // For instance, it's clearly applicable for cases like system locale = en_US and
- // subtype = en, but it is not necessarily considered applicable for cases like system
- // locale = en and subtype = en_US.
- // We just call systemLocale.startsWith(locale) in this function because there is no
- // need to find applicable subtypes aggressively unlike
- // findLastResortApplicableSubtypeLocked.
- if (systemLocale.startsWith(locale)) {
- final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
- // If more applicable subtypes are contained, skip.
- if (applicableSubtype != null) {
- if (systemLocale.equals(applicableSubtype.getLocale())) continue;
- if (!systemLocale.equals(locale)) continue;
- }
- applicableModeAndSubtypesMap.put(mode, subtype);
- }
- }
- final InputMethodSubtype keyboardSubtype
- = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
- final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
- applicableModeAndSubtypesMap.values());
- if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
- for (int i = 0; i < N; ++i) {
- final InputMethodSubtype subtype = subtypes.get(i);
- final String mode = subtype.getMode();
- if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
- TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
- applicableSubtypes.add(subtype);
- }
- }
- }
- if (keyboardSubtype == null) {
- InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
- res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
- if (lastResortKeyboardSubtype != null) {
- applicableSubtypes.add(lastResortKeyboardSubtype);
- }
- }
- return applicableSubtypes;
- }
-
- /**
- * If there are no selected subtypes, tries finding the most applicable one according to the
- * given locale.
- * @param subtypes this function will search the most applicable subtype in subtypes
- * @param mode subtypes will be filtered by mode
- * @param locale subtypes will be filtered by locale
- * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
- * it will return the first subtype matched with mode
- * @return the most applicable subtypeId
- */
- private static InputMethodSubtype findLastResortApplicableSubtypeLocked(
- Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
- boolean canIgnoreLocaleAsLastResort) {
- if (subtypes == null || subtypes.size() == 0) {
- return null;
- }
- if (TextUtils.isEmpty(locale)) {
- locale = res.getConfiguration().locale.toString();
- }
- final String language = locale.substring(0, 2);
- boolean partialMatchFound = false;
- InputMethodSubtype applicableSubtype = null;
- InputMethodSubtype firstMatchedModeSubtype = null;
- final int N = subtypes.size();
- for (int i = 0; i < N; ++i) {
- InputMethodSubtype subtype = subtypes.get(i);
- final String subtypeLocale = subtype.getLocale();
- // An applicable subtype should match "mode". If mode is null, mode will be ignored,
- // and all subtypes with all modes can be candidates.
- if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
- if (firstMatchedModeSubtype == null) {
- firstMatchedModeSubtype = subtype;
- }
- if (locale.equals(subtypeLocale)) {
- // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
- applicableSubtype = subtype;
- break;
- } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
- // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
- applicableSubtype = subtype;
- partialMatchFound = true;
- }
- }
- }
-
- if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
- return firstMatchedModeSubtype;
- }
-
- // The first subtype applicable to the system locale will be defined as the most applicable
- // subtype.
- if (DEBUG) {
- if (applicableSubtype != null) {
- Slog.d(TAG, "Applicable InputMethodSubtype was found: "
- + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
- }
- }
- return applicableSubtype;
- }
-
// If there are no selected shortcuts, tries finding the most applicable ones.
private Pair<InputMethodInfo, InputMethodSubtype>
findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
@@ -3125,32 +2973,33 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
InputMethodSubtype subtype = null;
final List<InputMethodSubtype> enabledSubtypes =
- getEnabledInputMethodSubtypeListLocked(imi, true);
+ mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
// 1. Search by the current subtype's locale from enabledSubtypes.
if (mCurrentSubtype != null) {
- subtype = findLastResortApplicableSubtypeLocked(
+ subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
}
// 2. Search by the system locale from enabledSubtypes.
// 3. Search the first enabled subtype matched with mode from enabledSubtypes.
if (subtype == null) {
- subtype = findLastResortApplicableSubtypeLocked(
+ subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, enabledSubtypes, mode, null, true);
}
final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
- getOverridingImplicitlyEnabledSubtypes(imi, mode);
+ InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
final ArrayList<InputMethodSubtype> subtypesForSearch =
overridingImplicitlyEnabledSubtypes.isEmpty()
- ? getSubtypes(imi) : overridingImplicitlyEnabledSubtypes;
+ ? InputMethodUtils.getSubtypes(imi)
+ : overridingImplicitlyEnabledSubtypes;
// 4. Search by the current subtype's locale from all subtypes.
if (subtype == null && mCurrentSubtype != null) {
- subtype = findLastResortApplicableSubtypeLocked(
+ subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
}
// 5. Search by the system locale from all subtypes.
// 6. Search the first enabled subtype matched with mode from all subtypes.
if (subtype == null) {
- subtype = findLastResortApplicableSubtypeLocked(
+ subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, subtypesForSearch, mode, null, true);
}
if (subtype != null) {
@@ -3207,37 +3056,36 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (mCurMethodId == null) {
return null;
}
- final boolean subtypeIsSelected =
- mSettings.getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+ final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
if (imi == null || imi.getSubtypeCount() == 0) {
return null;
}
if (!subtypeIsSelected || mCurrentSubtype == null
- || !isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
- int subtypeId = getSelectedInputMethodSubtypeId(mCurMethodId);
+ || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
+ int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
if (subtypeId == NOT_A_SUBTYPE_ID) {
// If there are no selected subtypes, the framework will try to find
// the most applicable subtype from explicitly or implicitly enabled
// subtypes.
List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- getEnabledInputMethodSubtypeListLocked(imi, true);
+ mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
// If there is only one explicitly or implicitly enabled subtype,
// just returns it.
if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
} else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
- mCurrentSubtype = findLastResortApplicableSubtypeLocked(
+ mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, explicitlyOrImplicitlyEnabledSubtypes,
- SUBTYPE_MODE_KEYBOARD, null, true);
+ InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
if (mCurrentSubtype == null) {
- mCurrentSubtype = findLastResortApplicableSubtypeLocked(
+ mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
true);
}
}
} else {
- mCurrentSubtype = getSubtypes(imi).get(subtypeId);
+ mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
}
}
return mCurrentSubtype;
@@ -3266,7 +3114,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
Pair<InputMethodInfo, InputMethodSubtype> info =
findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
- SUBTYPE_MODE_VOICE);
+ InputMethodUtils.SUBTYPE_MODE_VOICE);
if (info != null) {
ret.add(info.first);
ret.add(info.second);
@@ -3292,7 +3140,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (mMethodMap) {
if (subtype != null && mCurMethodId != null) {
InputMethodInfo imi = mMethodMap.get(mCurMethodId);
- int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
+ int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
if (subtypeId != NOT_A_SUBTYPE_ID) {
setInputMethodLocked(mCurMethodId, subtypeId);
return true;
@@ -3343,7 +3191,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
final int N = imList.size();
final int currentSubtypeId = subtype != null
- ? getSubtypeIdFromHashCode(imi, subtype.hashCode())
+ ? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
: NOT_A_SUBTYPE_ID;
for (int i = 0; i < N; ++i) {
final ImeSubtypeListItem isli = imList.get(i);
@@ -3384,7 +3232,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
}
- ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi);
final CharSequence imeLabel = imi.loadLabel(mPm);
if (showSubtypes && enabledSubtypeSet.size() > 0) {
final int subtypeCount = imi.getSubtypeCount();
@@ -3420,451 +3267,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- /**
- * Utility class for putting and getting settings for InputMethod
- * TODO: Move all putters and getters of settings to this class.
- */
- private static class InputMethodSettings {
- // The string for enabled input method is saved as follows:
- // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
- private static final char INPUT_METHOD_SEPARATER = ':';
- private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
- private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
-
- private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
-
- private final Resources mRes;
- private final ContentResolver mResolver;
- private final HashMap<String, InputMethodInfo> mMethodMap;
- private final ArrayList<InputMethodInfo> mMethodList;
-
- private String mEnabledInputMethodsStrCache;
- private int mCurrentUserId;
-
- private static void buildEnabledInputMethodsSettingString(
- StringBuilder builder, Pair<String, ArrayList<String>> pair) {
- String id = pair.first;
- ArrayList<String> subtypes = pair.second;
- builder.append(id);
- // Inputmethod and subtypes are saved in the settings as follows:
- // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
- for (String subtypeId: subtypes) {
- builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
- }
- }
-
- public InputMethodSettings(
- Resources res, ContentResolver resolver,
- HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
- int userId) {
- setCurrentUserId(userId);
- mRes = res;
- mResolver = resolver;
- mMethodMap = methodMap;
- mMethodList = methodList;
- }
-
- public void setCurrentUserId(int userId) {
- if (DEBUG) {
- Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
- + userId + ", new ime = " + getSelectedInputMethod());
- }
- // IMMS settings are kept per user, so keep track of current user
- mCurrentUserId = userId;
- }
-
- public List<InputMethodInfo> getEnabledInputMethodListLocked() {
- return createEnabledInputMethodListLocked(
- getEnabledInputMethodsAndSubtypeListLocked());
- }
-
- public List<Pair<InputMethodInfo, ArrayList<String>>>
- getEnabledInputMethodAndSubtypeHashCodeListLocked() {
- return createEnabledInputMethodAndSubtypeHashCodeListLocked(
- getEnabledInputMethodsAndSubtypeListLocked());
- }
-
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
- InputMethodInfo imi) {
- List<Pair<String, ArrayList<String>>> imsList =
- getEnabledInputMethodsAndSubtypeListLocked();
- ArrayList<InputMethodSubtype> enabledSubtypes =
- new ArrayList<InputMethodSubtype>();
- if (imi != null) {
- for (Pair<String, ArrayList<String>> imsPair : imsList) {
- InputMethodInfo info = mMethodMap.get(imsPair.first);
- if (info != null && info.getId().equals(imi.getId())) {
- final int subtypeCount = info.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- InputMethodSubtype ims = info.getSubtypeAt(i);
- for (String s: imsPair.second) {
- if (String.valueOf(ims.hashCode()).equals(s)) {
- enabledSubtypes.add(ims);
- }
- }
- }
- break;
- }
- }
- }
- return enabledSubtypes;
- }
-
- // At the initial boot, the settings for input methods are not set,
- // so we need to enable IME in that case.
- public void enableAllIMEsIfThereIsNoEnabledIME() {
- if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
- StringBuilder sb = new StringBuilder();
- final int N = mMethodList.size();
- for (int i = 0; i < N; i++) {
- InputMethodInfo imi = mMethodList.get(i);
- Slog.i(TAG, "Adding: " + imi.getId());
- if (i > 0) sb.append(':');
- sb.append(imi.getId());
- }
- putEnabledInputMethodsStr(sb.toString());
- }
- }
-
- private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
- ArrayList<Pair<String, ArrayList<String>>> imsList
- = new ArrayList<Pair<String, ArrayList<String>>>();
- final String enabledInputMethodsStr = getEnabledInputMethodsStr();
- if (TextUtils.isEmpty(enabledInputMethodsStr)) {
- return imsList;
- }
- mInputMethodSplitter.setString(enabledInputMethodsStr);
- while (mInputMethodSplitter.hasNext()) {
- String nextImsStr = mInputMethodSplitter.next();
- mSubtypeSplitter.setString(nextImsStr);
- if (mSubtypeSplitter.hasNext()) {
- ArrayList<String> subtypeHashes = new ArrayList<String>();
- // The first element is ime id.
- String imeId = mSubtypeSplitter.next();
- while (mSubtypeSplitter.hasNext()) {
- subtypeHashes.add(mSubtypeSplitter.next());
- }
- imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
- }
- }
- return imsList;
- }
-
- public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
- if (reloadInputMethodStr) {
- getEnabledInputMethodsStr();
- }
- if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
- // Add in the newly enabled input method.
- putEnabledInputMethodsStr(id);
- } else {
- putEnabledInputMethodsStr(
- mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
- }
- }
-
- /**
- * Build and put a string of EnabledInputMethods with removing specified Id.
- * @return the specified id was removed or not.
- */
- public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
- StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
- boolean isRemoved = false;
- boolean needsAppendSeparator = false;
- for (Pair<String, ArrayList<String>> ims: imsList) {
- String curId = ims.first;
- if (curId.equals(id)) {
- // We are disabling this input method, and it is
- // currently enabled. Skip it to remove from the
- // new list.
- isRemoved = true;
- } else {
- if (needsAppendSeparator) {
- builder.append(INPUT_METHOD_SEPARATER);
- } else {
- needsAppendSeparator = true;
- }
- buildEnabledInputMethodsSettingString(builder, ims);
- }
- }
- if (isRemoved) {
- // Update the setting with the new list of input methods.
- putEnabledInputMethodsStr(builder.toString());
- }
- return isRemoved;
- }
-
- private List<InputMethodInfo> createEnabledInputMethodListLocked(
- List<Pair<String, ArrayList<String>>> imsList) {
- final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
- for (Pair<String, ArrayList<String>> ims: imsList) {
- InputMethodInfo info = mMethodMap.get(ims.first);
- if (info != null) {
- res.add(info);
- }
- }
- return res;
- }
-
- private List<Pair<InputMethodInfo, ArrayList<String>>>
- createEnabledInputMethodAndSubtypeHashCodeListLocked(
- List<Pair<String, ArrayList<String>>> imsList) {
- final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
- = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
- for (Pair<String, ArrayList<String>> ims : imsList) {
- InputMethodInfo info = mMethodMap.get(ims.first);
- if (info != null) {
- res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
- }
- }
- return res;
- }
-
- private void putEnabledInputMethodsStr(String str) {
- Settings.Secure.putStringForUser(
- mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
- mEnabledInputMethodsStrCache = str;
- if (DEBUG) {
- Slog.d(TAG, "putEnabledInputMethodStr: " + str);
- }
- }
-
- private String getEnabledInputMethodsStr() {
- mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
- mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
- if (DEBUG) {
- Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
- + ", " + mCurrentUserId);
- }
- return mEnabledInputMethodsStrCache;
- }
-
- private void saveSubtypeHistory(
- List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
- StringBuilder builder = new StringBuilder();
- boolean isImeAdded = false;
- if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
- builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
- newSubtypeId);
- isImeAdded = true;
- }
- for (Pair<String, String> ime: savedImes) {
- String imeId = ime.first;
- String subtypeId = ime.second;
- if (TextUtils.isEmpty(subtypeId)) {
- subtypeId = NOT_A_SUBTYPE_ID_STR;
- }
- if (isImeAdded) {
- builder.append(INPUT_METHOD_SEPARATER);
- } else {
- isImeAdded = true;
- }
- builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
- subtypeId);
- }
- // Remove the last INPUT_METHOD_SEPARATER
- putSubtypeHistoryStr(builder.toString());
- }
-
- public void addSubtypeToHistory(String imeId, String subtypeId) {
- List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
- for (Pair<String, String> ime: subtypeHistory) {
- if (ime.first.equals(imeId)) {
- if (DEBUG) {
- Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
- + ime.second);
- }
- // We should break here
- subtypeHistory.remove(ime);
- break;
- }
- }
- if (DEBUG) {
- Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
- }
- saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
- }
-
- private void putSubtypeHistoryStr(String str) {
- if (DEBUG) {
- Slog.d(TAG, "putSubtypeHistoryStr: " + str);
- }
- Settings.Secure.putStringForUser(
- mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
- }
-
- public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
- // Gets the first one from the history
- return getLastSubtypeForInputMethodLockedInternal(null);
- }
-
- public String getLastSubtypeForInputMethodLocked(String imeId) {
- Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
- if (ime != null) {
- return ime.second;
- } else {
- return null;
- }
- }
-
- private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
- List<Pair<String, ArrayList<String>>> enabledImes =
- getEnabledInputMethodsAndSubtypeListLocked();
- List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
- for (Pair<String, String> imeAndSubtype : subtypeHistory) {
- final String imeInTheHistory = imeAndSubtype.first;
- // If imeId is empty, returns the first IME and subtype in the history
- if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
- final String subtypeInTheHistory = imeAndSubtype.second;
- final String subtypeHashCode =
- getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
- enabledImes, imeInTheHistory, subtypeInTheHistory);
- if (!TextUtils.isEmpty(subtypeHashCode)) {
- if (DEBUG) {
- Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
- }
- return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
- }
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "No enabled IME found in the history");
- }
- return null;
- }
-
- private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
- ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
- for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
- if (enabledIme.first.equals(imeId)) {
- final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
- final InputMethodInfo imi = mMethodMap.get(imeId);
- if (explicitlyEnabledSubtypes.size() == 0) {
- // If there are no explicitly enabled subtypes, applicable subtypes are
- // enabled implicitly.
- // If IME is enabled and no subtypes are enabled, applicable subtypes
- // are enabled implicitly, so needs to treat them to be enabled.
- if (imi != null && imi.getSubtypeCount() > 0) {
- List<InputMethodSubtype> implicitlySelectedSubtypes =
- getImplicitlyApplicableSubtypesLocked(mRes, imi);
- if (implicitlySelectedSubtypes != null) {
- final int N = implicitlySelectedSubtypes.size();
- for (int i = 0; i < N; ++i) {
- final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
- if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
- return subtypeHashCode;
- }
- }
- }
- }
- } else {
- for (String s: explicitlyEnabledSubtypes) {
- if (s.equals(subtypeHashCode)) {
- // If both imeId and subtypeId are enabled, return subtypeId.
- try {
- final int hashCode = Integer.valueOf(subtypeHashCode);
- // Check whether the subtype id is valid or not
- if (isValidSubtypeId(imi, hashCode)) {
- return s;
- } else {
- return NOT_A_SUBTYPE_ID_STR;
- }
- } catch (NumberFormatException e) {
- return NOT_A_SUBTYPE_ID_STR;
- }
- }
- }
- }
- // If imeId was enabled but subtypeId was disabled.
- return NOT_A_SUBTYPE_ID_STR;
- }
- }
- // If both imeId and subtypeId are disabled, return null
- return null;
- }
-
- private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
- ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
- final String subtypeHistoryStr = getSubtypeHistoryStr();
- if (TextUtils.isEmpty(subtypeHistoryStr)) {
- return imsList;
- }
- mInputMethodSplitter.setString(subtypeHistoryStr);
- while (mInputMethodSplitter.hasNext()) {
- String nextImsStr = mInputMethodSplitter.next();
- mSubtypeSplitter.setString(nextImsStr);
- if (mSubtypeSplitter.hasNext()) {
- String subtypeId = NOT_A_SUBTYPE_ID_STR;
- // The first element is ime id.
- String imeId = mSubtypeSplitter.next();
- while (mSubtypeSplitter.hasNext()) {
- subtypeId = mSubtypeSplitter.next();
- break;
- }
- imsList.add(new Pair<String, String>(imeId, subtypeId));
- }
- }
- return imsList;
- }
-
- private String getSubtypeHistoryStr() {
- if (DEBUG) {
- Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
- mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
- }
- return Settings.Secure.getStringForUser(
- mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
- }
-
- public void putSelectedInputMethod(String imeId) {
- if (DEBUG) {
- Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
- + mCurrentUserId);
- }
- Settings.Secure.putStringForUser(
- mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
- }
-
- public void putSelectedSubtype(int subtypeId) {
- if (DEBUG) {
- Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
- + mCurrentUserId);
- }
- Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
- subtypeId, mCurrentUserId);
- }
-
- public String getDisabledSystemInputMethods() {
- return Settings.Secure.getStringForUser(
- mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
- }
-
- public String getSelectedInputMethod() {
- if (DEBUG) {
- Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
- mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
- + ", " + mCurrentUserId);
- }
- return Settings.Secure.getStringForUser(
- mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
- }
-
- public int getSelectedInputMethodSubtypeHashCode() {
- try {
- return Settings.Secure.getIntForUser(
- mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
- } catch (SettingNotFoundException e) {
- return NOT_A_SUBTYPE_ID;
- }
- }
-
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
- }
-
// TODO: Cache the state for each user and reset when the cached user is removed.
private static class InputMethodFileManager {
private static final String SYSTEM_PATH = "system";
diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java
index 9b19008..35345f5 100644
--- a/services/java/com/android/server/IntentResolver.java
+++ b/services/java/com/android/server/IntentResolver.java
@@ -117,7 +117,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
boolean printedHeader = false;
F filter;
for (int i=0; i<N && (filter=a[i]) != null; i++) {
- if (packageName != null && !packageName.equals(packageForFilter(filter))) {
+ if (packageName != null && !isPackageForFilter(packageName, filter)) {
continue;
}
if (title != null) {
@@ -357,11 +357,11 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
}
/**
- * Return the package that owns this filter. This must be implemented to
- * provide correct filtering of Intents that have specified a package name
- * they are to be delivered to.
+ * Returns whether this filter is owned by this package. This must be
+ * implemented to provide correct filtering of Intents that have
+ * specified a package name they are to be delivered to.
*/
- protected abstract String packageForFilter(F filter);
+ protected abstract boolean isPackageForFilter(String packageName, F filter);
protected abstract F[] newArray(int size);
@@ -556,7 +556,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
}
// Is delivery being limited to filters owned by a particular package?
- if (packageName != null && !packageName.equals(packageForFilter(filter))) {
+ if (packageName != null && !isPackageForFilter(packageName, filter)) {
if (debug) {
Slog.v(TAG, " Filter is not from package " + packageName + "; skipping");
}
@@ -710,8 +710,8 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
}
private final IntentResolverOld<F, R> mOldResolver = new IntentResolverOld<F, R>() {
- @Override protected String packageForFilter(F filter) {
- return IntentResolver.this.packageForFilter(filter);
+ @Override protected boolean isPackageForFilter(String packageName, F filter) {
+ return IntentResolver.this.isPackageForFilter(packageName, filter);
}
@Override protected boolean allowFilterResult(F filter, List<R> dest) {
return IntentResolver.this.allowFilterResult(filter, dest);
diff --git a/services/java/com/android/server/IntentResolverOld.java b/services/java/com/android/server/IntentResolverOld.java
index 4dd77ce..94a2379 100644
--- a/services/java/com/android/server/IntentResolverOld.java
+++ b/services/java/com/android/server/IntentResolverOld.java
@@ -106,7 +106,7 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object
boolean printedHeader = false;
for (int i=0; i<N; i++) {
F filter = a.get(i);
- if (packageName != null && !packageName.equals(packageForFilter(filter))) {
+ if (packageName != null && !isPackageForFilter(packageName, filter)) {
continue;
}
if (title != null) {
@@ -336,11 +336,11 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object
}
/**
- * Return the package that owns this filter. This must be implemented to
- * provide correct filtering of Intents that have specified a package name
- * they are to be delivered to.
+ * Returns whether this filter is owned by this package. This must be
+ * implemented to provide correct filtering of Intents that have
+ * specified a package name they are to be delivered to.
*/
- protected abstract String packageForFilter(F filter);
+ protected abstract boolean isPackageForFilter(String packageName, F filter);
@SuppressWarnings("unchecked")
protected R newResult(F filter, int match, int userId) {
@@ -529,7 +529,7 @@ public abstract class IntentResolverOld<F extends IntentFilter, R extends Object
}
// Is delivery being limited to filters owned by a particular package?
- if (packageName != null && !packageName.equals(packageForFilter(filter))) {
+ if (packageName != null && !isPackageForFilter(packageName, filter)) {
if (debug) {
Slog.v(TAG, " Filter is not from package " + packageName + "; skipping");
}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 0f08c56..f784030 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -46,6 +47,7 @@ import android.location.LocationRequest;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -58,11 +60,11 @@ import android.os.WorkSource;
import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
-
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.server.location.GeocoderProxy;
+import com.android.server.location.GeofenceProxy;
import com.android.server.location.GeofenceManager;
import com.android.server.location.GpsLocationProvider;
import com.android.server.location.LocationBlacklist;
@@ -86,9 +88,9 @@ import java.util.Set;
* The service class that manages LocationProviders and issues location
* updates and alerts.
*/
-public class LocationManagerService extends ILocationManager.Stub implements Runnable {
+public class LocationManagerService extends ILocationManager.Stub {
private static final String TAG = "LocationManagerService";
- public static final boolean D = false;
+ public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
private static final String WAKELOCK_KEY = TAG;
private static final String THREAD_NAME = TAG;
@@ -114,6 +116,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
private static final int MSG_LOCATION_CHANGED = 1;
+ private static final long NANOS_PER_MILLI = 1000000L;
+
// Location Providers may sometimes deliver location updates
// slightly faster that requested - provide grace period so
// we don't unnecessarily filter events that are otherwise on
@@ -123,24 +127,23 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest();
private final Context mContext;
+ private final AppOpsManager mAppOps;
// used internally for synchronization
private final Object mLock = new Object();
- // --- fields below are final after init() ---
+ // --- fields below are final after systemReady() ---
private LocationFudger mLocationFudger;
private GeofenceManager mGeofenceManager;
- private PowerManager.WakeLock mWakeLock;
private PackageManager mPackageManager;
+ private PowerManager mPowerManager;
private GeocoderProxy mGeocodeProvider;
private IGpsStatusProvider mGpsStatusProvider;
private INetInitiatedListener mNetInitiatedListener;
private LocationWorkerHandler mLocationHandler;
private PassiveProvider mPassiveProvider; // track passive provider for special cases
private LocationBlacklist mBlacklist;
-
- // --- fields below are protected by mWakeLock ---
- private int mPendingBroadcasts;
+ private HandlerThread mHandlerThread;
// --- fields below are protected by mLock ---
// Set of providers that are explicitly enabled
@@ -175,6 +178,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
// mapping from provider name to last known location
private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>();
+ // same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
+ // locations stored here are not fudged for coarse permissions.
+ private final HashMap<String, Location> mLastLocationCoarseInterval =
+ new HashMap<String, Location>();
+
// all providers that operate over proxy, for authorizing incoming location
private final ArrayList<LocationProviderProxy> mProxyProviders =
new ArrayList<LocationProviderProxy>();
@@ -185,6 +193,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
public LocationManagerService(Context context) {
super();
mContext = context;
+ mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
if (D) Log.d(TAG, "Constructed");
@@ -192,48 +201,53 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
public void systemReady() {
- Thread thread = new Thread(null, this, THREAD_NAME);
- thread.start();
- }
-
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- Looper.prepare();
- mLocationHandler = new LocationWorkerHandler();
- init();
- Looper.loop();
- }
-
- private void init() {
- if (D) Log.d(TAG, "init()");
-
- PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
- mPackageManager = mContext.getPackageManager();
-
- mBlacklist = new LocationBlacklist(mContext, mLocationHandler);
- mBlacklist.init();
- mLocationFudger = new LocationFudger(mContext, mLocationHandler);
-
synchronized (mLock) {
+ if (D) Log.d(TAG, "systemReady()");
+
+ // fetch package manager
+ mPackageManager = mContext.getPackageManager();
+
+ // fetch power manager
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+
+ // prepare worker thread
+ mHandlerThread = new HandlerThread(THREAD_NAME, Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ mLocationHandler = new LocationWorkerHandler(mHandlerThread.getLooper());
+
+ // prepare mLocationHandler's dependents
+ mLocationFudger = new LocationFudger(mContext, mLocationHandler);
+ mBlacklist = new LocationBlacklist(mContext, mLocationHandler);
+ mBlacklist.init();
+ mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
+
+ // Monitor for app ops mode changes.
+ AppOpsManager.Callback callback = new AppOpsManager.Callback() {
+ public void opChanged(int op, String packageName) {
+ synchronized (mLock) {
+ applyAllProviderRequirementsLocked();
+ }
+ }
+ };
+ mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null, callback);
+
+ // prepare providers
loadProvidersLocked();
+ updateProvidersLocked();
}
- mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
-
// listen for settings changes
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
new ContentObserver(mLocationHandler) {
- @Override
- public void onChange(boolean selfChange) {
- synchronized (mLock) {
- updateProvidersLocked();
- }
- }
- }, UserHandle.USER_ALL);
- mPackageMonitor.register(mContext, Looper.myLooper(), true);
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ updateProvidersLocked();
+ }
+ }
+ }, UserHandle.USER_ALL);
+ mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
// listen for user change
IntentFilter intentFilter = new IntentFilter();
@@ -247,9 +261,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
}
}
- }, UserHandle.ALL, intentFilter, null, null);
-
- updateProvidersLocked();
+ }, UserHandle.ALL, intentFilter, null, mLocationHandler);
}
private void ensureFallbackFusedProviderPresentLocked(ArrayList<String> pkgs) {
@@ -326,10 +338,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
addProviderLocked(passiveProvider);
mEnabledProviders.add(passiveProvider.getName());
mPassiveProvider = passiveProvider;
+ // Create a gps location provider
+ GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this,
+ mLocationHandler.getLooper());
if (GpsLocationProvider.isSupported()) {
- // Create a gps location provider
- GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this);
mGpsStatusProvider = gpsProvider.getGpsStatusProvider();
mNetInitiatedListener = gpsProvider.getNetInitiatedListener();
addProviderLocked(gpsProvider);
@@ -362,7 +375,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
mContext,
LocationManager.NETWORK_PROVIDER,
NETWORK_LOCATION_SERVICE_ACTION,
- providerPackageNames, mLocationHandler, mCurrentUserId);
+ com.android.internal.R.bool.config_enableNetworkLocationOverlay,
+ com.android.internal.R.string.config_networkLocationProviderPackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames,
+ mLocationHandler);
if (networkProvider != null) {
mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProvider);
mProxyProviders.add(networkProvider);
@@ -376,7 +392,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
mContext,
LocationManager.FUSED_PROVIDER,
FUSED_LOCATION_SERVICE_ACTION,
- providerPackageNames, mLocationHandler, mCurrentUserId);
+ com.android.internal.R.bool.config_enableFusedLocationOverlay,
+ com.android.internal.R.string.config_fusedLocationProviderPackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames,
+ mLocationHandler);
if (fusedLocationProvider != null) {
addProviderLocked(fusedLocationProvider);
mProxyProviders.add(fusedLocationProvider);
@@ -388,11 +407,26 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
// bind to geocoder provider
- mGeocodeProvider = GeocoderProxy.createAndBind(mContext, providerPackageNames,
- mCurrentUserId);
+ mGeocodeProvider = GeocoderProxy.createAndBind(mContext,
+ com.android.internal.R.bool.config_enableGeocoderOverlay,
+ com.android.internal.R.string.config_geocoderProviderPackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames,
+ mLocationHandler);
if (mGeocodeProvider == null) {
Slog.e(TAG, "no geocoder provider found");
}
+
+ // bind to geofence provider
+ GeofenceProxy provider = GeofenceProxy.createAndBind(mContext,
+ com.android.internal.R.bool.config_enableGeofenceOverlay,
+ com.android.internal.R.string.config_geofenceProviderPackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames,
+ mLocationHandler,
+ gpsProvider.getGpsGeofenceProxy());
+ if (provider == null) {
+ Slog.e(TAG, "no geofence provider found");
+ }
+
}
/**
@@ -401,11 +435,12 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
*/
private void switchUser(int userId) {
mBlacklist.switchUser(userId);
+ mLocationHandler.removeMessages(MSG_LOCATION_CHANGED);
synchronized (mLock) {
mLastLocation.clear();
+ mLastLocationCoarseInterval.clear();
for (LocationProviderInterface p : mProviders) {
updateProviderListenersLocked(p.getName(), false, mCurrentUserId);
- p.switchUser(userId);
}
mCurrentUserId = userId;
updateProvidersLocked();
@@ -429,6 +464,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>();
int mPendingBroadcasts;
+ PowerManager.WakeLock mWakeLock;
Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
String packageName) {
@@ -443,6 +479,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
mUid = uid;
mPid = pid;
mPackageName = packageName;
+
+ // construct/configure wakelock
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
+ mWakeLock.setWorkSource(new WorkSource(mUid, mPackageName));
}
@Override
@@ -605,10 +645,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
removeUpdatesLocked(this);
}
synchronized (this) {
- if (mPendingBroadcasts > 0) {
- LocationManagerService.this.decrementPendingBroadcasts();
- mPendingBroadcasts = 0;
- }
+ clearPendingBroadcastsLocked();
}
}
@@ -624,32 +661,45 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
// containing the sending of the broadcaset
private void incrementPendingBroadcastsLocked() {
if (mPendingBroadcasts++ == 0) {
- LocationManagerService.this.incrementPendingBroadcasts();
+ mWakeLock.acquire();
}
}
private void decrementPendingBroadcastsLocked() {
if (--mPendingBroadcasts == 0) {
- LocationManagerService.this.decrementPendingBroadcasts();
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+ }
+
+ public void clearPendingBroadcastsLocked() {
+ if (mPendingBroadcasts > 0) {
+ mPendingBroadcasts = 0;
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
}
}
}
@Override
public void locationCallbackFinished(ILocationListener listener) {
- //Do not use getReceiver here as that will add the ILocationListener to
+ //Do not use getReceiverLocked here as that will add the ILocationListener to
//the receiver list if it is not found. If it is not found then the
//LocationListener was removed when it had a pending broadcast and should
//not be added back.
- IBinder binder = listener.asBinder();
- Receiver receiver = mReceivers.get(binder);
- if (receiver != null) {
- synchronized (receiver) {
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- receiver.decrementPendingBroadcastsLocked();
- Binder.restoreCallingIdentity(identity);
- }
+ synchronized (mLock) {
+ IBinder binder = listener.asBinder();
+ Receiver receiver = mReceivers.get(binder);
+ if (receiver != null) {
+ synchronized (receiver) {
+ // so wakelock calls will succeed
+ long identity = Binder.clearCallingIdentity();
+ receiver.decrementPendingBroadcastsLocked();
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
}
@@ -664,11 +714,15 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
mProvidersByName.remove(provider.getName());
}
-
- private boolean isAllowedBySettingsLocked(String provider, int userId) {
- if (userId != mCurrentUserId) {
- return false;
- }
+ /**
+ * Returns "true" if access to the specified location provider is allowed by the current
+ * user's settings. Access to all location providers is forbidden to non-location-provider
+ * processes belonging to background users.
+ *
+ * @param provider the name of the location provider
+ * @return
+ */
+ private boolean isAllowedByCurrentUserSettingsLocked(String provider) {
if (mEnabledProviders.contains(provider)) {
return true;
}
@@ -682,6 +736,22 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
/**
+ * Returns "true" if access to the specified location provider is allowed by the specified
+ * user's settings. Access to all location providers is forbidden to non-location-provider
+ * processes belonging to background users.
+ *
+ * @param provider the name of the location provider
+ * @param uid the requestor's UID
+ * @return
+ */
+ private boolean isAllowedByUserSettingsLocked(String provider, int uid) {
+ if (UserHandle.getUserId(uid) != mCurrentUserId && !isUidALocationProvider(uid)) {
+ return false;
+ }
+ return isAllowedByCurrentUserSettingsLocked(provider);
+ }
+
+ /**
* Returns the permission string associated with the specified resolution level.
*
* @param resolutionLevel the resolution level
@@ -796,6 +866,37 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
}
+ public static int resolutionLevelToOp(int allowedResolutionLevel) {
+ if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
+ if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
+ return AppOpsManager.OP_COARSE_LOCATION;
+ } else {
+ return AppOpsManager.OP_FINE_LOCATION;
+ }
+ }
+ return -1;
+ }
+
+ boolean reportLocationAccessNoThrow(int uid, String packageName, int allowedResolutionLevel) {
+ int op = resolutionLevelToOp(allowedResolutionLevel);
+ if (op >= 0) {
+ if (mAppOps.noteOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean checkLocationAccess(int uid, String packageName, int allowedResolutionLevel) {
+ int op = resolutionLevelToOp(allowedResolutionLevel);
+ if (op >= 0) {
+ if (mAppOps.checkOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Returns all providers by name, including passive, but excluding
* fused, also including ones that are not permitted to
@@ -828,7 +929,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
ArrayList<String> out;
- int callingUserId = UserHandle.getCallingUserId();
+ int uid = Binder.getCallingUid();;
long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -839,7 +940,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
continue;
}
if (allowedResolutionLevel >= getMinimumResolutionLevelForProviderUse(name)) {
- if (enabledOnly && !isAllowedBySettingsLocked(name, callingUserId)) {
+ if (enabledOnly && !isAllowedByUserSettingsLocked(name, uid)) {
continue;
}
if (criteria != null && !LocationProvider.propertiesMeetCriteria(
@@ -915,7 +1016,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
LocationProviderInterface p = mProviders.get(i);
boolean isEnabled = p.isEnabled();
String name = p.getName();
- boolean shouldBeEnabled = isAllowedBySettingsLocked(name, mCurrentUserId);
+ boolean shouldBeEnabled = isAllowedByCurrentUserSettingsLocked(name);
if (isEnabled && !shouldBeEnabled) {
updateProviderListenersLocked(name, false, mCurrentUserId);
changesMade = true;
@@ -983,11 +1084,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
if (records != null) {
for (UpdateRecord record : records) {
if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) {
- LocationRequest locationRequest = record.mRequest;
- providerRequest.locationRequests.add(locationRequest);
- if (locationRequest.getInterval() < providerRequest.interval) {
- providerRequest.reportLocation = true;
- providerRequest.interval = locationRequest.getInterval();
+ if (checkLocationAccess(record.mReceiver.mUid, record.mReceiver.mPackageName,
+ record.mReceiver.mAllowedResolutionLevel)) {
+ LocationRequest locationRequest = record.mRequest;
+ providerRequest.locationRequests.add(locationRequest);
+ if (locationRequest.getInterval() < providerRequest.interval) {
+ providerRequest.reportLocation = true;
+ providerRequest.interval = locationRequest.getInterval();
+ }
}
}
}
@@ -1003,7 +1107,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) {
LocationRequest locationRequest = record.mRequest;
if (locationRequest.getInterval() <= thresholdInterval) {
- worksource.add(record.mReceiver.mUid);
+ worksource.add(record.mReceiver.mUid, record.mReceiver.mPackageName);
}
}
}
@@ -1077,7 +1181,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
}
- private Receiver getReceiver(ILocationListener listener, int pid, int uid, String packageName) {
+ private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
+ String packageName) {
IBinder binder = listener.asBinder();
Receiver receiver = mReceivers.get(binder);
if (receiver == null) {
@@ -1094,7 +1199,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
return receiver;
}
- private Receiver getReceiver(PendingIntent intent, int pid, int uid, String packageName) {
+ private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName) {
Receiver receiver = mReceivers.get(intent);
if (receiver == null) {
receiver = new Receiver(null, intent, pid, uid, packageName);
@@ -1108,9 +1213,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
* and consistency requirements.
*
* @param request the LocationRequest from which to create a sanitized version
- * @param shouldBeCoarse whether the sanitized version should be held to coarse resolution
- * constraints
- * @param fastestCoarseIntervalMS minimum interval allowed for coarse resolution
* @return a version of request that meets the given resolution and consistency requirements
* @hide
*/
@@ -1161,17 +1263,17 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
}
- private Receiver checkListenerOrIntent(ILocationListener listener, PendingIntent intent,
+ private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent,
int pid, int uid, String packageName) {
if (intent == null && listener == null) {
- throw new IllegalArgumentException("need eiter listener or intent");
+ throw new IllegalArgumentException("need either listener or intent");
} else if (intent != null && listener != null) {
throw new IllegalArgumentException("cannot register both listener and intent");
} else if (intent != null) {
checkPendingIntent(intent);
- return getReceiver(intent, pid, uid, packageName);
+ return getReceiverLocked(intent, pid, uid, packageName);
} else {
- return getReceiver(listener, pid, uid, packageName);
+ return getReceiverLocked(listener, pid, uid, packageName);
}
}
@@ -1187,12 +1289,16 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- Receiver recevier = checkListenerOrIntent(listener, intent, pid, uid, packageName);
-
// providers may use public location API's, need to clear identity
long identity = Binder.clearCallingIdentity();
try {
+ // We don't check for MODE_IGNORED here; we will do that when we go to deliver
+ // a location.
+ checkLocationAccess(uid, packageName, allowedResolutionLevel);
+
synchronized (mLock) {
+ Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid,
+ packageName);
requestLocationUpdatesLocked(sanitizedRequest, recevier, pid, uid, packageName);
}
} finally {
@@ -1209,21 +1315,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
if (name == null) {
throw new IllegalArgumentException("provider name must not be null");
}
+
+ if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
+ + " " + name + " " + request + " from " + packageName + "(" + uid + ")");
LocationProviderInterface provider = mProvidersByName.get(name);
if (provider == null) {
throw new IllegalArgumentException("provider doesn't exisit: " + provider);
}
- if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
- + " " + name + " " + request + " from " + packageName + "(" + uid + ")");
-
UpdateRecord record = new UpdateRecord(name, request, receiver);
UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
if (oldRecord != null) {
oldRecord.disposeLocked(false);
}
- boolean isProviderEnabled = isAllowedBySettingsLocked(name, UserHandle.getUserId(uid));
+ boolean isProviderEnabled = isAllowedByUserSettingsLocked(name, uid);
if (isProviderEnabled) {
applyRequirementsLocked(name);
} else {
@@ -1239,16 +1345,17 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- Receiver receiver = checkListenerOrIntent(listener, intent, pid, uid, packageName);
- // providers may use public location API's, need to clear identity
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
+ synchronized (mLock) {
+ Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, packageName);
+
+ // providers may use public location API's, need to clear identity
+ long identity = Binder.clearCallingIdentity();
+ try {
removeUpdatesLocked(receiver);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
}
}
@@ -1258,10 +1365,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) {
receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
synchronized (receiver) {
- if (receiver.mPendingBroadcasts > 0) {
- decrementPendingBroadcasts();
- receiver.mPendingBroadcasts = 0;
- }
+ receiver.clearPendingBroadcastsLocked();
}
}
@@ -1280,7 +1384,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
// update provider
for (String provider : providers) {
// If provider is already disabled, don't need to do anything
- if (!isAllowedBySettingsLocked(provider, mCurrentUserId)) {
+ if (!isAllowedByCurrentUserSettingsLocked(provider)) {
continue;
}
@@ -1288,6 +1392,17 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
}
+ private void applyAllProviderRequirementsLocked() {
+ for (LocationProviderInterface p : mProviders) {
+ // If provider is already disabled, don't need to do anything
+ if (!isAllowedByCurrentUserSettingsLocked(p.getName())) {
+ continue;
+ }
+
+ applyRequirementsLocked(p.getName());
+ }
+ }
+
@Override
public Location getLastLocation(LocationRequest request, String packageName) {
if (D) Log.d(TAG, "getLastLocation: " + request);
@@ -1298,7 +1413,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
request.getProvider());
// no need to sanitize this request, as only the provider name is used
- long identity = Binder.clearCallingIdentity();
+ final int uid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
try {
if (mBlacklist.isBlacklisted(packageName)) {
if (D) Log.d(TAG, "not returning last loc for blacklisted app: " +
@@ -1306,6 +1422,12 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
return null;
}
+ if (!reportLocationAccessNoThrow(uid, packageName, allowedResolutionLevel)) {
+ if (D) Log.d(TAG, "not returning last loc for no op app: " +
+ packageName);
+ return null;
+ }
+
synchronized (mLock) {
// Figure out the provider. Either its explicitly request (deprecated API's),
// or use the fused provider
@@ -1314,9 +1436,16 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
LocationProviderInterface provider = mProvidersByName.get(name);
if (provider == null) return null;
- if (!isAllowedBySettingsLocked(name, mCurrentUserId)) return null;
+ if (!isAllowedByUserSettingsLocked(name, uid)) return null;
- Location location = mLastLocation.get(name);
+ Location location;
+ if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
+ // Make sure that an app with coarse permissions can't get frequent location
+ // updates by calling LocationManager.getLastKnownLocation repeatedly.
+ location = mLastLocationCoarseInterval.get(name);
+ } else {
+ location = mLastLocation.get(name);
+ }
if (location == null) {
return null;
}
@@ -1358,7 +1487,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
long identity = Binder.clearCallingIdentity();
try {
- mGeofenceManager.addFence(sanitizedRequest, geofence, intent, uid, packageName);
+ mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
+ uid, packageName);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1383,13 +1513,24 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
@Override
- public boolean addGpsStatusListener(IGpsStatusListener listener) {
+ public boolean addGpsStatusListener(IGpsStatusListener listener, String packageName) {
if (mGpsStatusProvider == null) {
return false;
}
- checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
+ int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
LocationManager.GPS_PROVIDER);
+ final int uid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (!checkLocationAccess(uid, packageName, allowedResolutionLevel)) {
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
try {
mGpsStatusProvider.addGpsStatusListener(listener);
} catch (RemoteException e) {
@@ -1476,19 +1617,39 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
provider);
if (LocationManager.FUSED_PROVIDER.equals(provider)) return false;
+ int uid = Binder.getCallingUid();
long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
LocationProviderInterface p = mProvidersByName.get(provider);
if (p == null) return false;
- return isAllowedBySettingsLocked(provider, mCurrentUserId);
+ return isAllowedByUserSettingsLocked(provider, uid);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
+ /**
+ * Returns "true" if the UID belongs to a bound location provider.
+ *
+ * @param uid the uid
+ * @return true if uid belongs to a bound location provider
+ */
+ private boolean isUidALocationProvider(int uid) {
+ if (uid == Process.SYSTEM_UID) {
+ return true;
+ }
+ if (mGeocodeProvider != null) {
+ if (doesPackageHaveUid(uid, mGeocodeProvider.getConnectedPackageName())) return true;
+ }
+ for (LocationProviderProxy proxy : mProxyProviders) {
+ if (doesPackageHaveUid(uid, proxy.getConnectedPackageName())) return true;
+ }
+ return false;
+ }
+
private void checkCallerIsProvider() {
if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER)
== PackageManager.PERMISSION_GRANTED) {
@@ -1502,14 +1663,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
// also allow providers with a UID matching the
// currently bound package name
- int uid = Binder.getCallingUid();
-
- if (mGeocodeProvider != null) {
- if (doesPackageHaveUid(uid, mGeocodeProvider.getConnectedPackageName())) return;
- }
- for (LocationProviderProxy proxy : mProxyProviders) {
- if (doesPackageHaveUid(uid, proxy.getConnectedPackageName())) return;
+ if (isUidALocationProvider(Binder.getCallingUid())) {
+ return;
}
+
throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " +
"or UID of a currently bound location provider");
}
@@ -1554,7 +1711,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
// Check whether sufficient time has passed
long minTime = record.mRequest.getFastestInterval();
- long delta = (loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos()) / 1000000L;
+ long delta = (loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos())
+ / NANOS_PER_MILLI;
if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) {
return false;
}
@@ -1607,13 +1765,30 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
lastLocation.set(location);
+ // Update last known coarse interval location if enough time has passed.
+ Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider);
+ if (lastLocationCoarseInterval == null) {
+ lastLocationCoarseInterval = new Location(location);
+ mLastLocationCoarseInterval.put(provider, lastLocationCoarseInterval);
+ }
+ long timeDiffNanos = location.getElapsedRealtimeNanos()
+ - lastLocationCoarseInterval.getElapsedRealtimeNanos();
+ if (timeDiffNanos > LocationFudger.FASTEST_INTERVAL_MS * NANOS_PER_MILLI) {
+ lastLocationCoarseInterval.set(location);
+ }
+ // Don't ever return a coarse location that is more recent than the allowed update
+ // interval (i.e. don't allow an app to keep registering and unregistering for
+ // location updates to overcome the minimum interval).
+ noGPSLocation =
+ lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+
// Skip if there are no UpdateRecords for this provider.
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
if (records == null || records.size() == 0) return;
// Fetch coarse location
Location coarseLocation = null;
- if (noGPSLocation != null && !noGPSLocation.equals(lastNoGPSLocation)) {
+ if (noGPSLocation != null) {
coarseLocation = mLocationFudger.getOrCreate(noGPSLocation);
}
@@ -1633,7 +1808,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
boolean receiverDead = false;
int receiverUserId = UserHandle.getUserId(receiver.mUid);
- if (receiverUserId != mCurrentUserId) {
+ if (receiverUserId != mCurrentUserId && !isUidALocationProvider(receiver.mUid)) {
if (D) {
Log.d(TAG, "skipping loc update for background user " + receiverUserId +
" (current user: " + mCurrentUserId + ", app: " +
@@ -1648,6 +1823,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
continue;
}
+ if (!reportLocationAccessNoThrow(receiver.mUid, receiver.mPackageName,
+ receiver.mAllowedResolutionLevel)) {
+ if (D) Log.d(TAG, "skipping loc update for no op app: " +
+ receiver.mPackageName);
+ continue;
+ }
+
Location notifyLocation = null;
if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
notifyLocation = coarseLocation; // use coarse location
@@ -1715,6 +1897,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
private class LocationWorkerHandler extends Handler {
+ public LocationWorkerHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -1725,17 +1911,32 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
}
+ private boolean isMockProvider(String provider) {
+ synchronized (mLock) {
+ return mMockProviders.containsKey(provider);
+ }
+ }
+
private void handleLocationChanged(Location location, boolean passive) {
- String provider = location.getProvider();
+ // create a working copy of the incoming Location so that the service can modify it without
+ // disturbing the caller's copy
+ Location myLocation = new Location(location);
+ String provider = myLocation.getProvider();
- if (!passive) {
- // notify passive provider of the new location
- mPassiveProvider.updateLocation(location);
+ // set "isFromMockProvider" bit if location came from a mock provider. we do not clear this
+ // bit if location did not come from a mock provider because passive/fused providers can
+ // forward locations from mock providers, and should not grant them legitimacy in doing so.
+ if (!myLocation.isFromMockProvider() && isMockProvider(provider)) {
+ myLocation.setIsFromMockProvider(true);
}
synchronized (mLock) {
- if (isAllowedBySettingsLocked(provider, mCurrentUserId)) {
- handleLocationChangedLocked(location, passive);
+ if (isAllowedByCurrentUserSettingsLocked(provider)) {
+ if (!passive) {
+ // notify passive provider of the new location
+ mPassiveProvider.updateLocation(myLocation);
+ }
+ handleLocationChangedLocked(myLocation, passive);
}
}
}
@@ -1766,43 +1967,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
};
- // Wake locks
-
- private void incrementPendingBroadcasts() {
- synchronized (mWakeLock) {
- if (mPendingBroadcasts++ == 0) {
- try {
- mWakeLock.acquire();
- log("Acquired wakelock");
- } catch (Exception e) {
- // This is to catch a runtime exception thrown when we try to release an
- // already released lock.
- Slog.e(TAG, "exception in acquireWakeLock()", e);
- }
- }
- }
- }
-
- private void decrementPendingBroadcasts() {
- synchronized (mWakeLock) {
- if (--mPendingBroadcasts == 0) {
- try {
- // Release wake lock
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- log("Released wakelock");
- } else {
- log("Can't release wakelock again!");
- }
- } catch (Exception e) {
- // This is to catch a runtime exception thrown when we try to release an
- // already released lock.
- Slog.e(TAG, "exception in releaseWakeLock()", e);
- }
- }
- }
- }
-
// Geocoder
@Override
@@ -1876,6 +2040,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
addProviderLocked(provider);
mMockProviders.put(name, provider);
mLastLocation.put(name, null);
+ mLastLocationCoarseInterval.put(name, null);
updateProvidersLocked();
}
Binder.restoreCallingIdentity(identity);
@@ -1898,6 +2063,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
addProviderLocked(realProvider);
}
mLastLocation.put(provider, null);
+ mLastLocationCoarseInterval.put(provider, null);
updateProvidersLocked();
Binder.restoreCallingIdentity(identity);
}
@@ -2029,6 +2195,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
pw.println(" " + provider + ": " + location);
}
+ pw.println(" Last Known Locations Coarse Intervals:");
+ for (Map.Entry<String, Location> entry : mLastLocationCoarseInterval.entrySet()) {
+ String provider = entry.getKey();
+ Location location = entry.getValue();
+ pw.println(" " + provider + ": " + location);
+ }
+
mGeofenceManager.dump(pw);
if (mEnabledProviders.size() > 0) {
diff --git a/services/java/com/android/server/LockSettingsService.java b/services/java/com/android/server/LockSettingsService.java
new file mode 100644
index 0000000..d52a8e2
--- /dev/null
+++ b/services/java/com/android/server/LockSettingsService.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+
+import static android.content.Context.USER_SERVICE;
+import static android.Manifest.permission.READ_PROFILE;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Keeps the lock pattern/password data and related settings for each user.
+ * Used by LockPatternUtils. Needs to be a service because Settings app also needs
+ * to be able to save lockscreen information for secondary users.
+ * @hide
+ */
+public class LockSettingsService extends ILockSettings.Stub {
+
+ private final DatabaseHelper mOpenHelper;
+ private static final String TAG = "LockSettingsService";
+
+ private static final String TABLE = "locksettings";
+ private static final String COLUMN_KEY = "name";
+ private static final String COLUMN_USERID = "user";
+ private static final String COLUMN_VALUE = "value";
+
+ private static final String[] COLUMNS_FOR_QUERY = {
+ COLUMN_VALUE
+ };
+
+ private static final String SYSTEM_DIRECTORY = "/system/";
+ private static final String LOCK_PATTERN_FILE = "gesture.key";
+ private static final String LOCK_PASSWORD_FILE = "password.key";
+
+ private final Context mContext;
+
+ public LockSettingsService(Context context) {
+ mContext = context;
+ // Open the database
+ mOpenHelper = new DatabaseHelper(mContext);
+ }
+
+ public void systemReady() {
+ migrateOldData();
+ }
+
+ private void migrateOldData() {
+ try {
+ // These Settings moved before multi-user was enabled, so we only have to do it for the
+ // root user.
+ if (getString("migrated", null, 0) == null) {
+ final ContentResolver cr = mContext.getContentResolver();
+ for (String validSetting : VALID_SETTINGS) {
+ String value = Settings.Secure.getString(cr, validSetting);
+ if (value != null) {
+ setString(validSetting, value, 0);
+ }
+ }
+ // No need to move the password / pattern files. They're already in the right place.
+ setString("migrated", "true", 0);
+ Slog.i(TAG, "Migrated lock settings to new location");
+ }
+
+ // These Settings changed after multi-user was enabled, hence need to be moved per user.
+ if (getString("migrated_user_specific", null, 0) == null) {
+ final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+ final ContentResolver cr = mContext.getContentResolver();
+ List<UserInfo> users = um.getUsers();
+ for (int user = 0; user < users.size(); user++) {
+ // Migrate owner info
+ final int userId = users.get(user).id;
+ final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
+ String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
+ if (ownerInfo != null) {
+ setString(OWNER_INFO, ownerInfo, userId);
+ Settings.Secure.putStringForUser(cr, ownerInfo, "", userId);
+ }
+
+ // Migrate owner info enabled. Note there was a bug where older platforms only
+ // stored this value if the checkbox was toggled at least once. The code detects
+ // this case by handling the exception.
+ final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+ boolean enabled;
+ try {
+ int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
+ enabled = ivalue != 0;
+ setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
+ } catch (SettingNotFoundException e) {
+ // Setting was never stored. Store it if the string is not empty.
+ if (!TextUtils.isEmpty(ownerInfo)) {
+ setLong(OWNER_INFO_ENABLED, 1, userId);
+ }
+ }
+ Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
+ }
+ // No need to move the password / pattern files. They're already in the right place.
+ setString("migrated_user_specific", "true", 0);
+ Slog.i(TAG, "Migrated per-user lock settings to new location");
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to migrate old data", re);
+ }
+ }
+
+ private static final void checkWritePermission(int userId) {
+ final int callingUid = Binder.getCallingUid();
+ if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
+ throw new SecurityException("uid=" + callingUid
+ + " not authorized to write lock settings");
+ }
+ }
+
+ private static final void checkPasswordReadPermission(int userId) {
+ final int callingUid = Binder.getCallingUid();
+ if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
+ throw new SecurityException("uid=" + callingUid
+ + " not authorized to read lock password");
+ }
+ }
+
+ private final void checkReadPermission(String requestedKey, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ for (int i = 0; i < READ_PROFILE_PROTECTED_SETTINGS.length; i++) {
+ String key = READ_PROFILE_PROTECTED_SETTINGS[i];
+ if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_PROFILE)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("uid=" + callingUid
+ + " needs permission " + READ_PROFILE + " to read "
+ + requestedKey + " for user " + userId);
+ }
+ }
+ }
+
+ @Override
+ public void setBoolean(String key, boolean value, int userId) throws RemoteException {
+ checkWritePermission(userId);
+
+ writeToDb(key, value ? "1" : "0", userId);
+ }
+
+ @Override
+ public void setLong(String key, long value, int userId) throws RemoteException {
+ checkWritePermission(userId);
+
+ writeToDb(key, Long.toString(value), userId);
+ }
+
+ @Override
+ public void setString(String key, String value, int userId) throws RemoteException {
+ checkWritePermission(userId);
+
+ writeToDb(key, value, userId);
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
+ checkReadPermission(key, userId);
+
+ String value = readFromDb(key, null, userId);
+ return TextUtils.isEmpty(value) ?
+ defaultValue : (value.equals("1") || value.equals("true"));
+ }
+
+ @Override
+ public long getLong(String key, long defaultValue, int userId) throws RemoteException {
+ checkReadPermission(key, userId);
+
+ String value = readFromDb(key, null, userId);
+ return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
+ }
+
+ @Override
+ public String getString(String key, String defaultValue, int userId) throws RemoteException {
+ checkReadPermission(key, userId);
+
+ return readFromDb(key, defaultValue, userId);
+ }
+
+ private String getLockPatternFilename(int userId) {
+ String dataSystemDirectory =
+ android.os.Environment.getDataDirectory().getAbsolutePath() +
+ SYSTEM_DIRECTORY;
+ if (userId == 0) {
+ // Leave it in the same place for user 0
+ return dataSystemDirectory + LOCK_PATTERN_FILE;
+ } else {
+ return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
+ .getAbsolutePath();
+ }
+ }
+
+ private String getLockPasswordFilename(int userId) {
+ String dataSystemDirectory =
+ android.os.Environment.getDataDirectory().getAbsolutePath() +
+ SYSTEM_DIRECTORY;
+ if (userId == 0) {
+ // Leave it in the same place for user 0
+ return dataSystemDirectory + LOCK_PASSWORD_FILE;
+ } else {
+ return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
+ .getAbsolutePath();
+ }
+ }
+
+ @Override
+ public boolean havePassword(int userId) throws RemoteException {
+ // Do we need a permissions check here?
+
+ return new File(getLockPasswordFilename(userId)).length() > 0;
+ }
+
+ @Override
+ public boolean havePattern(int userId) throws RemoteException {
+ // Do we need a permissions check here?
+
+ return new File(getLockPatternFilename(userId)).length() > 0;
+ }
+
+ @Override
+ public void setLockPattern(byte[] hash, int userId) throws RemoteException {
+ checkWritePermission(userId);
+
+ writeFile(getLockPatternFilename(userId), hash);
+ }
+
+ @Override
+ public boolean checkPattern(byte[] hash, int userId) throws RemoteException {
+ checkPasswordReadPermission(userId);
+ try {
+ // Read all the bytes from the file
+ RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r");
+ final byte[] stored = new byte[(int) raf.length()];
+ int got = raf.read(stored, 0, stored.length);
+ raf.close();
+ if (got <= 0) {
+ return true;
+ }
+ // Compare the hash from the file with the entered pattern's hash
+ return Arrays.equals(stored, hash);
+ } catch (FileNotFoundException fnfe) {
+ Slog.e(TAG, "Cannot read file " + fnfe);
+ return true;
+ } catch (IOException ioe) {
+ Slog.e(TAG, "Cannot read file " + ioe);
+ return true;
+ }
+ }
+
+ @Override
+ public void setLockPassword(byte[] hash, int userId) throws RemoteException {
+ checkWritePermission(userId);
+
+ writeFile(getLockPasswordFilename(userId), hash);
+ }
+
+ @Override
+ public boolean checkPassword(byte[] hash, int userId) throws RemoteException {
+ checkPasswordReadPermission(userId);
+
+ try {
+ // Read all the bytes from the file
+ RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
+ final byte[] stored = new byte[(int) raf.length()];
+ int got = raf.read(stored, 0, stored.length);
+ raf.close();
+ if (got <= 0) {
+ return true;
+ }
+ // Compare the hash from the file with the entered password's hash
+ return Arrays.equals(stored, hash);
+ } catch (FileNotFoundException fnfe) {
+ Slog.e(TAG, "Cannot read file " + fnfe);
+ return true;
+ } catch (IOException ioe) {
+ Slog.e(TAG, "Cannot read file " + ioe);
+ return true;
+ }
+ }
+
+ @Override
+ public void removeUser(int userId) {
+ checkWritePermission(userId);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ try {
+ File file = new File(getLockPasswordFilename(userId));
+ if (file.exists()) {
+ file.delete();
+ }
+ file = new File(getLockPatternFilename(userId));
+ if (file.exists()) {
+ file.delete();
+ }
+
+ db.beginTransaction();
+ db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ private void writeFile(String name, byte[] hash) {
+ try {
+ // Write the hash to file
+ RandomAccessFile raf = new RandomAccessFile(name, "rw");
+ // Truncate the file if pattern is null, to clear the lock
+ if (hash == null || hash.length == 0) {
+ raf.setLength(0);
+ } else {
+ raf.write(hash, 0, hash.length);
+ }
+ raf.close();
+ } catch (IOException ioe) {
+ Slog.e(TAG, "Error writing to file " + ioe);
+ }
+ }
+
+ private void writeToDb(String key, String value, int userId) {
+ writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
+ }
+
+ private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
+ ContentValues cv = new ContentValues();
+ cv.put(COLUMN_KEY, key);
+ cv.put(COLUMN_USERID, userId);
+ cv.put(COLUMN_VALUE, value);
+
+ db.beginTransaction();
+ try {
+ db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
+ new String[] {key, Integer.toString(userId)});
+ db.insert(TABLE, null, cv);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ private String readFromDb(String key, String defaultValue, int userId) {
+ Cursor cursor;
+ String result = defaultValue;
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
+ COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
+ new String[] { Integer.toString(userId), key },
+ null, null, null)) != null) {
+ if (cursor.moveToFirst()) {
+ result = cursor.getString(0);
+ }
+ cursor.close();
+ }
+ return result;
+ }
+
+ class DatabaseHelper extends SQLiteOpenHelper {
+ private static final String TAG = "LockSettingsDB";
+ private static final String DATABASE_NAME = "locksettings.db";
+
+ private static final int DATABASE_VERSION = 1;
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ setWriteAheadLoggingEnabled(true);
+ }
+
+ private void createTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE + " (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ COLUMN_KEY + " TEXT," +
+ COLUMN_USERID + " INTEGER," +
+ COLUMN_VALUE + " TEXT" +
+ ");");
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ createTable(db);
+ initializeDefaults(db);
+ }
+
+ private void initializeDefaults(SQLiteDatabase db) {
+ // Get the lockscreen default from a system property, if available
+ boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default",
+ false);
+ if (lockScreenDisable) {
+ writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+ // Nothing yet
+ }
+ }
+
+ private static final String[] VALID_SETTINGS = new String[] {
+ LockPatternUtils.LOCKOUT_PERMANENT_KEY,
+ LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
+ LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
+ LockPatternUtils.PASSWORD_TYPE_KEY,
+ LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
+ LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
+ LockPatternUtils.LOCKSCREEN_OPTIONS,
+ LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
+ LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
+ LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
+ LockPatternUtils.PASSWORD_HISTORY_KEY,
+ Secure.LOCK_PATTERN_ENABLED,
+ Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
+ Secure.LOCK_PATTERN_VISIBLE,
+ Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
+ };
+
+ // These are protected with a read permission
+ private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] {
+ Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
+ Secure.LOCK_SCREEN_OWNER_INFO
+ };
+}
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 2e0c977..f402f4b 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -43,6 +43,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.storage.IMountService;
@@ -63,6 +64,7 @@ import com.android.internal.app.IMediaContainerService;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.NativeDaemonConnector.Command;
+import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.am.ActivityManagerService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerService;
@@ -173,6 +175,11 @@ class MountService extends IMountService.Stub
public static final int VolumeDiskInserted = 630;
public static final int VolumeDiskRemoved = 631;
public static final int VolumeBadRemoval = 632;
+
+ /*
+ * 700 series - fstrim
+ */
+ public static final int FstrimCompleted = 700;
}
private Context mContext;
@@ -596,6 +603,27 @@ class MountService extends IMountService.Stub
}
};
+ private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ waitForReady();
+ String action = intent.getAction();
+ // Since fstrim will be run on a daily basis we do not expect
+ // fstrim to be too long, so it is not interruptible. We will
+ // implement interruption only in case we see issues.
+ if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
+ try {
+ // This method runs on the handler thread,
+ // so it is safe to directly call into vold.
+ mConnector.execute("fstrim", "dotrim");
+ EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
+ } catch (NativeDaemonConnectorException ndce) {
+ Slog.e(TAG, "Failed to run fstrim!");
+ }
+ }
+ }
+ };
+
private final class MountServiceBinderListener implements IBinder.DeathRecipient {
final IMountServiceListener mListener;
@@ -837,6 +865,8 @@ class MountService extends IMountService.Stub
if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL);
action = Intent.ACTION_MEDIA_BAD_REMOVAL;
+ } else if (code == VoldResponseCode.FstrimCompleted) {
+ EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
} else {
Slog.e(TAG, String.format("Unknown code {%d}", code));
}
@@ -1301,6 +1331,12 @@ class MountService extends IMountService.Stub
mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
}
+ // Watch for idle maintenance changes
+ IntentFilter idleMaintenanceFilter = new IntentFilter();
+ idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
+ mContext.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL,
+ idleMaintenanceFilter, null, mHandler);
+
// Add OBB Action Handler to MountService thread.
mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
@@ -1577,7 +1613,7 @@ class MountService extends IMountService.Stub
boolean mounted = false;
try {
mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()));
- } catch (IllegalStateException e) {
+ } catch (IllegalArgumentException e) {
}
if (!mounted) {
@@ -1607,8 +1643,8 @@ class MountService extends IMountService.Stub
int rc = StorageResultCode.OperationSucceeded;
try {
- mConnector.execute("asec", "create", id, sizeMb, fstype, key, ownerUid,
- external ? "1" : "0");
+ mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key),
+ ownerUid, external ? "1" : "0");
} catch (NativeDaemonConnectorException e) {
rc = StorageResultCode.OperationFailedInternalError;
}
@@ -1708,7 +1744,7 @@ class MountService extends IMountService.Stub
int rc = StorageResultCode.OperationSucceeded;
try {
- mConnector.execute("asec", "mount", id, key, ownerUid);
+ mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid);
} catch (NativeDaemonConnectorException e) {
int code = e.getCode();
if (code != VoldResponseCode.OpFailedStorageBusy) {
@@ -1984,7 +2020,7 @@ class MountService extends IMountService.Stub
final NativeDaemonEvent event;
try {
- event = mConnector.execute("cryptfs", "checkpw", password);
+ event = mConnector.execute("cryptfs", "checkpw", new SensitiveArg(password));
final int code = Integer.parseInt(event.getMessage());
if (code == 0) {
@@ -2023,7 +2059,7 @@ class MountService extends IMountService.Stub
}
try {
- mConnector.execute("cryptfs", "enablecrypto", "inplace", password);
+ mConnector.execute("cryptfs", "enablecrypto", "inplace", new SensitiveArg(password));
} catch (NativeDaemonConnectorException e) {
// Encryption failed
return e.getCode();
@@ -2048,7 +2084,7 @@ class MountService extends IMountService.Stub
final NativeDaemonEvent event;
try {
- event = mConnector.execute("cryptfs", "changepw", password);
+ event = mConnector.execute("cryptfs", "changepw", new SensitiveArg(password));
return Integer.parseInt(event.getMessage());
} catch (NativeDaemonConnectorException e) {
// Encryption failed
@@ -2081,7 +2117,7 @@ class MountService extends IMountService.Stub
final NativeDaemonEvent event;
try {
- event = mConnector.execute("cryptfs", "verifypw", password);
+ event = mConnector.execute("cryptfs", "verifypw", new SensitiveArg(password));
Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
return Integer.parseInt(event.getMessage());
} catch (NativeDaemonConnectorException e) {
@@ -2447,8 +2483,8 @@ class MountService extends IMountService.Stub
int rc = StorageResultCode.OperationSucceeded;
try {
- mConnector.execute(
- "obb", "mount", mObbState.voldPath, hashedKey, mObbState.ownerGid);
+ mConnector.execute("obb", "mount", mObbState.voldPath, new SensitiveArg(hashedKey),
+ mObbState.ownerGid);
} catch (NativeDaemonConnectorException e) {
int code = e.getCode();
if (code != VoldResponseCode.OpFailedStorageBusy) {
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index 5e94a9f..47840e0 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -204,25 +204,55 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
}
/**
+ * Wrapper around argument that indicates it's sensitive and shouldn't be
+ * logged.
+ */
+ public static class SensitiveArg {
+ private final Object mArg;
+
+ public SensitiveArg(Object arg) {
+ mArg = arg;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(mArg);
+ }
+ }
+
+ /**
* Make command for daemon, escaping arguments as needed.
*/
- private void makeCommand(StringBuilder builder, String cmd, Object... args)
- throws NativeDaemonConnectorException {
- // TODO: eventually enforce that cmd doesn't contain arguments
+ @VisibleForTesting
+ static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
+ String cmd, Object... args) {
if (cmd.indexOf('\0') >= 0) {
- throw new IllegalArgumentException("unexpected command: " + cmd);
+ throw new IllegalArgumentException("Unexpected command: " + cmd);
+ }
+ if (cmd.indexOf(' ') >= 0) {
+ throw new IllegalArgumentException("Arguments must be separate from command");
}
- builder.append(cmd);
+ rawBuilder.append(sequenceNumber).append(' ').append(cmd);
+ logBuilder.append(sequenceNumber).append(' ').append(cmd);
for (Object arg : args) {
final String argString = String.valueOf(arg);
if (argString.indexOf('\0') >= 0) {
- throw new IllegalArgumentException("unexpected argument: " + arg);
+ throw new IllegalArgumentException("Unexpected argument: " + arg);
}
- builder.append(' ');
- appendEscaped(builder, argString);
+ rawBuilder.append(' ');
+ logBuilder.append(' ');
+
+ appendEscaped(rawBuilder, argString);
+ if (arg instanceof SensitiveArg) {
+ logBuilder.append("[scrubbed]");
+ } else {
+ appendEscaped(logBuilder, argString);
+ }
}
+
+ rawBuilder.append('\0');
}
/**
@@ -240,7 +270,8 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
/**
* Issue the given command to the native daemon and return a single expected
- * response.
+ * response. Any arguments must be separated from base command so they can
+ * be properly escaped.
*
* @throws NativeDaemonConnectorException when problem communicating with
* native daemon, or if the response matches
@@ -274,7 +305,8 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
/**
* Issue the given command to the native daemon and return any
* {@link NativeDaemonEvent#isClassContinue()} responses, including the
- * final terminal response.
+ * final terminal response. Any arguments must be separated from base
+ * command so they can be properly escaped.
*
* @throws NativeDaemonConnectorException when problem communicating with
* native daemon, or if the response matches
@@ -287,10 +319,11 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
}
/**
- * Issue the given command to the native daemon and return any
- * {@linke NativeDaemonEvent@isClassContinue()} responses, including the
- * final terminal response. Note that the timeout does not count time in
- * deep sleep.
+ * Issue the given command to the native daemon and return any {@linke
+ * NativeDaemonEvent@isClassContinue()} responses, including the final
+ * terminal response. Note that the timeout does not count time in deep
+ * sleep. Any arguments must be separated from base command so they can be
+ * properly escaped.
*
* @throws NativeDaemonConnectorException when problem communicating with
* native daemon, or if the response matches
@@ -299,27 +332,27 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
*/
public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
throws NativeDaemonConnectorException {
+ final long startTime = SystemClock.elapsedRealtime();
+
final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
+ final StringBuilder rawBuilder = new StringBuilder();
+ final StringBuilder logBuilder = new StringBuilder();
final int sequenceNumber = mSequenceNumber.incrementAndGet();
- final StringBuilder cmdBuilder =
- new StringBuilder(Integer.toString(sequenceNumber)).append(' ');
- final long startTime = SystemClock.elapsedRealtime();
- makeCommand(cmdBuilder, cmd, args);
+ makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
- final String logCmd = cmdBuilder.toString(); /* includes cmdNum, cmd, args */
- log("SND -> {" + logCmd + "}");
+ final String rawCmd = rawBuilder.toString();
+ final String logCmd = logBuilder.toString();
- cmdBuilder.append('\0');
- final String sentCmd = cmdBuilder.toString(); /* logCmd + \0 */
+ log("SND -> {" + logCmd + "}");
synchronized (mDaemonLock) {
if (mOutputStream == null) {
throw new NativeDaemonConnectorException("missing output stream");
} else {
try {
- mOutputStream.write(sentCmd.getBytes(Charsets.UTF_8));
+ mOutputStream.write(rawCmd.getBytes(Charsets.UTF_8));
} catch (IOException e) {
throw new NativeDaemonConnectorException("problem sending command", e);
}
@@ -328,7 +361,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
NativeDaemonEvent event = null;
do {
- event = mResponseQueue.remove(sequenceNumber, timeout, sentCmd);
+ event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
if (event == null) {
loge("timed-out waiting for response to " + logCmd);
throw new NativeDaemonFailureException(logCmd, event);
@@ -353,51 +386,6 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
}
/**
- * Issue a command to the native daemon and return the raw responses.
- *
- * @deprecated callers should move to {@link #execute(String, Object...)}
- * which returns parsed {@link NativeDaemonEvent}.
- */
- @Deprecated
- public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
- final ArrayList<String> rawEvents = Lists.newArrayList();
- final NativeDaemonEvent[] events = executeForList(cmd);
- for (NativeDaemonEvent event : events) {
- rawEvents.add(event.getRawEvent());
- }
- return rawEvents;
- }
-
- /**
- * Issues a list command and returns the cooked list of all
- * {@link NativeDaemonEvent#getMessage()} which match requested code.
- */
- @Deprecated
- public String[] doListCommand(String cmd, int expectedCode)
- throws NativeDaemonConnectorException {
- final ArrayList<String> list = Lists.newArrayList();
-
- final NativeDaemonEvent[] events = executeForList(cmd);
- for (int i = 0; i < events.length - 1; i++) {
- final NativeDaemonEvent event = events[i];
- final int code = event.getCode();
- if (code == expectedCode) {
- list.add(event.getMessage());
- } else {
- throw new NativeDaemonConnectorException(
- "unexpected list response " + code + " instead of " + expectedCode);
- }
- }
-
- final NativeDaemonEvent finalEvent = events[events.length - 1];
- if (!finalEvent.isClassOk()) {
- throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent);
- }
-
- return list.toArray(new String[list.size()]);
- }
-
- /**
* Append the given argument to {@link StringBuilder}, escaping as needed,
* and surrounding with quotes when it contains spaces.
*/
@@ -444,7 +432,8 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
}
/**
- * Command builder that handles argument list building.
+ * Command builder that handles argument list building. Any arguments must
+ * be separated from base command so they can be properly escaped.
*/
public static class Command {
private String mCmd;
@@ -487,10 +476,11 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
private static class ResponseQueue {
private static class PendingCmd {
- public int cmdNum;
+ public final int cmdNum;
+ public final String logCmd;
+
public BlockingQueue<NativeDaemonEvent> responses =
new ArrayBlockingQueue<NativeDaemonEvent>(10);
- public String request;
// The availableResponseCount member is used to track when we can remove this
// instance from the ResponseQueue.
@@ -508,7 +498,11 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
// hold references to this instance already so it can be removed from
// mPendingCmds queue.
public int availableResponseCount;
- public PendingCmd(int c, String r) {cmdNum = c; request = r;}
+
+ public PendingCmd(int cmdNum, String logCmd) {
+ this.cmdNum = cmdNum;
+ this.logCmd = logCmd;
+ }
}
private final LinkedList<PendingCmd> mPendingCmds;
@@ -537,7 +531,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
// let any waiter timeout waiting for this
PendingCmd pendingCmd = mPendingCmds.remove();
Slog.e("NativeDaemonConnector.ResponseQueue",
- "Removing request: " + pendingCmd.request + " (" +
+ "Removing request: " + pendingCmd.logCmd + " (" +
pendingCmd.cmdNum + ")");
}
found = new PendingCmd(cmdNum, null);
@@ -555,7 +549,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
// note that the timeout does not count time in deep sleep. If you don't want
// the device to sleep, hold a wakelock
- public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String origCmd) {
+ public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
PendingCmd found = null;
synchronized (mPendingCmds) {
for (PendingCmd pendingCmd : mPendingCmds) {
@@ -565,7 +559,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
}
}
if (found == null) {
- found = new PendingCmd(cmdNum, origCmd);
+ found = new PendingCmd(cmdNum, logCmd);
mPendingCmds.add(found);
}
found.availableResponseCount--;
@@ -587,7 +581,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
pw.println("Pending requests:");
synchronized (mPendingCmds) {
for (PendingCmd pendingCmd : mPendingCmds) {
- pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.request);
+ pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
}
}
}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 3ddae3e..3b84c732 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -23,10 +23,9 @@ import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_TETHERING;
+import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceRxThrottleResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceTxThrottleResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.TetherDnsFwdTgtListResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.TetherInterfaceListResult;
@@ -35,7 +34,6 @@ import static com.android.server.NetworkManagementService.NetdResponseCode.Tethe
import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult;
import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
-import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.Context;
import android.net.INetworkManagementEventObserver;
import android.net.InterfaceConfiguration;
@@ -60,6 +58,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.util.Preconditions;
import com.android.server.NativeDaemonConnector.Command;
+import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.net.LockdownVpnTracker;
import com.google.android.collect.Maps;
@@ -121,11 +120,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub
public static final int SoftapStatusResult = 214;
public static final int InterfaceRxCounterResult = 216;
public static final int InterfaceTxCounterResult = 217;
- public static final int InterfaceRxThrottleResult = 218;
- public static final int InterfaceTxThrottleResult = 219;
public static final int QuotaCounterResult = 220;
public static final int TetheringStatsResult = 221;
public static final int DnsProxyQueryResult = 222;
+ public static final int ClatdStatusResult = 223;
public static final int InterfaceChange = 600;
public static final int BandwidthControl = 601;
@@ -235,6 +233,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
try {
mObservers.getBroadcastItem(i).interfaceStatusChanged(iface, up);
} catch (RemoteException e) {
+ } catch (RuntimeException e) {
}
}
mObservers.finishBroadcast();
@@ -250,6 +249,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
try {
mObservers.getBroadcastItem(i).interfaceLinkStateChanged(iface, up);
} catch (RemoteException e) {
+ } catch (RuntimeException e) {
}
}
mObservers.finishBroadcast();
@@ -264,6 +264,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
try {
mObservers.getBroadcastItem(i).interfaceAdded(iface);
} catch (RemoteException e) {
+ } catch (RuntimeException e) {
}
}
mObservers.finishBroadcast();
@@ -283,6 +284,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
try {
mObservers.getBroadcastItem(i).interfaceRemoved(iface);
} catch (RemoteException e) {
+ } catch (RuntimeException e) {
}
}
mObservers.finishBroadcast();
@@ -297,6 +299,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
try {
mObservers.getBroadcastItem(i).limitReached(limitName, iface);
} catch (RemoteException e) {
+ } catch (RuntimeException e) {
}
}
mObservers.finishBroadcast();
@@ -311,6 +314,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
try {
mObservers.getBroadcastItem(i).interfaceClassDataActivityChanged(label, active);
} catch (RemoteException e) {
+ } catch (RuntimeException e) {
}
}
mObservers.finishBroadcast();
@@ -835,36 +839,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub
return event.getMessage().endsWith("started");
}
- // TODO(BT) Remove
- public void startReverseTethering(String iface)
- throws IllegalStateException {
- if (DBG) Slog.d(TAG, "startReverseTethering in");
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- // cmd is "tether start first_start first_stop second_start second_stop ..."
- // an odd number of addrs will fail
- String cmd = "tether start-reverse";
- cmd += " " + iface;
- if (DBG) Slog.d(TAG, "startReverseTethering cmd: " + cmd);
- try {
- mConnector.doCommand(cmd);
- } catch (NativeDaemonConnectorException e) {
- throw new IllegalStateException("Unable to communicate to native daemon");
- }
- BluetoothTetheringDataTracker.getInstance().startReverseTether(iface);
-
- }
-
- // TODO(BT) Remove
- public void stopReverseTethering() throws IllegalStateException {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.doCommand("tether stop-reverse");
- } catch (NativeDaemonConnectorException e) {
- throw new IllegalStateException("Unable to communicate to native daemon to stop tether");
- }
- BluetoothTetheringDataTracker.getInstance().stopReverseTether();
- }
-
@Override
public void tetherInterface(String iface) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
@@ -1016,7 +990,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
mConnector.execute("softap", "set", wlanIface);
} else {
mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
- getSecurityType(wifiConfig), wifiConfig.preSharedKey);
+ getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey));
}
mConnector.execute("softap", "startap");
} catch (NativeDaemonConnectorException e) {
@@ -1065,7 +1039,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
mConnector.execute("softap", "set", wlanIface);
} else {
mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
- getSecurityType(wifiConfig), wifiConfig.preSharedKey);
+ getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey));
}
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
@@ -1120,19 +1094,31 @@ public class NetworkManagementService extends INetworkManagementService.Stub
@Override
public NetworkStats getNetworkStatsSummaryDev() {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- return mStatsFactory.readNetworkStatsSummaryDev();
+ try {
+ return mStatsFactory.readNetworkStatsSummaryDev();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
}
@Override
public NetworkStats getNetworkStatsSummaryXt() {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- return mStatsFactory.readNetworkStatsSummaryXt();
+ try {
+ return mStatsFactory.readNetworkStatsSummaryXt();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
}
@Override
public NetworkStats getNetworkStatsDetail() {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- return mStatsFactory.readNetworkStatsDetail(UID_ALL);
+ try {
+ return mStatsFactory.readNetworkStatsDetail(UID_ALL);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
}
@Override
@@ -1289,7 +1275,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub
@Override
public NetworkStats getNetworkStatsUidDetail(int uid) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- return mStatsFactory.readNetworkStatsDetail(uid);
+ try {
+ return mStatsFactory.readNetworkStatsDetail(uid);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
}
@Override
@@ -1345,49 +1335,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute("interface", "setthrottle", iface, rxKbps, txKbps);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- private int getInterfaceThrottle(String iface, boolean rx) {
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("interface", "getthrottle", iface, rx ? "rx" : "tx");
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
-
- if (rx) {
- event.checkCode(InterfaceRxThrottleResult);
- } else {
- event.checkCode(InterfaceTxThrottleResult);
- }
-
- try {
- return Integer.parseInt(event.getMessage());
- } catch (NumberFormatException e) {
- throw new IllegalStateException("unexpected response:" + event);
- }
- }
-
- @Override
- public int getInterfaceRxThrottle(String iface) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- return getInterfaceThrottle(iface, true);
- }
-
- @Override
- public int getInterfaceTxThrottle(String iface) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- return getInterfaceThrottle(iface, false);
- }
-
- @Override
public void setDefaultInterfaceForDns(String iface) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
@@ -1398,10 +1345,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void setDnsServersForInterface(String iface, String[] servers) {
+ public void setDnsServersForInterface(String iface, String[] servers, String domains) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- final Command cmd = new Command("resolver", "setifdns", iface);
+ final Command cmd = new Command("resolver", "setifdns", iface,
+ (domains == null ? "" : domains));
+
for (String s : servers) {
InetAddress a = NetworkUtils.numericToInetAddress(s);
if (a.isAnyLocalAddress() == false) {
@@ -1509,6 +1458,66 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
+ public void setDnsInterfaceForPid(String iface, int pid) throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+ try {
+ mConnector.execute("resolver", "setifaceforpid", iface, pid);
+ } catch (NativeDaemonConnectorException e) {
+ throw new IllegalStateException(
+ "Error communicating with native deamon to set interface for pid" + iface, e);
+ }
+ }
+
+ @Override
+ public void clearDnsInterfaceForPid(int pid) throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+ try {
+ mConnector.execute("resolver", "clearifaceforpid", pid);
+ } catch (NativeDaemonConnectorException e) {
+ throw new IllegalStateException(
+ "Error communicating with native deamon to clear interface for pid " + pid, e);
+ }
+ }
+
+ @Override
+ public void startClatd(String interfaceName) throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("clatd", "start", interfaceName);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void stopClatd() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("clatd", "stop");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public boolean isClatdStarted() {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ final NativeDaemonEvent event;
+ try {
+ event = mConnector.execute("clatd", "status");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+
+ event.checkCode(ClatdStatusResult);
+ return event.getMessage().endsWith("started");
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void monitor() {
if (mConnector != null) {
mConnector.monitor();
diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java
index 790be55..3bfd190 100644
--- a/services/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/java/com/android/server/NetworkTimeUpdateService.java
@@ -57,15 +57,6 @@ public class NetworkTimeUpdateService {
private static final int EVENT_POLL_NETWORK_TIME = 2;
private static final int EVENT_NETWORK_CONNECTED = 3;
- /** Normal polling frequency */
- private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs
- /** Try-again polling interval, in case the network request failed */
- private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds
- /** Number of times to try again */
- private static final int TRY_AGAIN_TIMES_MAX = 3;
- /** If the time difference is greater than this threshold, then update the time. */
- private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000;
-
private static final String ACTION_POLL =
"com.android.server.NetworkTimeUpdateService.action.POLL";
private static int POLL_REQUEST = 0;
@@ -86,6 +77,15 @@ public class NetworkTimeUpdateService {
private SettingsObserver mSettingsObserver;
// The last time that we successfully fetched the NTP time.
private long mLastNtpFetchTime = NOT_SET;
+
+ // Normal polling frequency
+ private final long mPollingIntervalMs;
+ // Try-again polling interval, in case the network request failed
+ private final long mPollingIntervalShorterMs;
+ // Number of times to try again
+ private final int mTryAgainTimesMax;
+ // If the time difference is greater than this threshold, then update the time.
+ private final int mTimeErrorThresholdMs;
// Keeps track of how many quick attempts were made to fetch NTP time.
// During bootup, the network may not have been up yet, or it's taking time for the
// connection to happen.
@@ -97,6 +97,15 @@ public class NetworkTimeUpdateService {
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
Intent pollIntent = new Intent(ACTION_POLL, null);
mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
+
+ mPollingIntervalMs = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_ntpPollingInterval);
+ mPollingIntervalShorterMs = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_ntpPollingIntervalShorter);
+ mTryAgainTimesMax = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_ntpRetry);
+ mTimeErrorThresholdMs = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_ntpThreshold);
}
/** Initialize the receivers and initiate the first NTP request */
@@ -143,35 +152,35 @@ public class NetworkTimeUpdateService {
if (!isAutomaticTimeRequested()) return;
final long refTime = SystemClock.elapsedRealtime();
- // If NITZ time was received less than POLLING_INTERVAL_MS time ago,
+ // If NITZ time was received less than mPollingIntervalMs time ago,
// no need to sync to NTP.
- if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) {
- resetAlarm(POLLING_INTERVAL_MS);
+ if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
+ resetAlarm(mPollingIntervalMs);
return;
}
final long currentTime = System.currentTimeMillis();
if (DBG) Log.d(TAG, "System time = " + currentTime);
// Get the NTP time
- if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS
+ if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
|| event == EVENT_AUTO_TIME_CHANGED) {
if (DBG) Log.d(TAG, "Before Ntp fetch");
// force refresh NTP cache when outdated
- if (mTime.getCacheAge() >= POLLING_INTERVAL_MS) {
+ if (mTime.getCacheAge() >= mPollingIntervalMs) {
mTime.forceRefresh();
}
// only update when NTP time is fresh
- if (mTime.getCacheAge() < POLLING_INTERVAL_MS) {
+ if (mTime.getCacheAge() < mPollingIntervalMs) {
final long ntp = mTime.currentTimeMillis();
mTryAgainCounter = 0;
// If the clock is more than N seconds off or this is the first time it's been
// fetched since boot, set the current time.
- if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS
+ if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
|| mLastNtpFetchTime == NOT_SET) {
// Set the system time
if (DBG && mLastNtpFetchTime == NOT_SET
- && Math.abs(ntp - currentTime) <= TIME_ERROR_THRESHOLD_MS) {
+ && Math.abs(ntp - currentTime) <= mTimeErrorThresholdMs) {
Log.d(TAG, "For initial setup, rtc = " + currentTime);
}
if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
@@ -186,17 +195,17 @@ public class NetworkTimeUpdateService {
} else {
// Try again shortly
mTryAgainCounter++;
- if (mTryAgainCounter <= TRY_AGAIN_TIMES_MAX) {
- resetAlarm(POLLING_INTERVAL_SHORTER_MS);
+ if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
+ resetAlarm(mPollingIntervalShorterMs);
} else {
// Try much later
mTryAgainCounter = 0;
- resetAlarm(POLLING_INTERVAL_MS);
+ resetAlarm(mPollingIntervalMs);
}
return;
}
}
- resetAlarm(POLLING_INTERVAL_MS);
+ resetAlarm(mPollingIntervalMs);
}
/**
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 37d7ce7..29aaeaf 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -23,6 +23,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.ITransientNotification;
@@ -30,15 +31,21 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.IAudioService;
import android.media.IRingtonePlayer;
@@ -51,8 +58,12 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
+import android.service.notification.INotificationListener;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AtomicFile;
@@ -62,26 +73,26 @@ import android.util.Slog;
import android.util.Xml;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.widget.RemoteViews;
import android.widget.Toast;
-import com.android.internal.statusbar.StatusBarNotification;
-import com.android.internal.util.FastXmlSerializer;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
import libcore.io.IoUtils;
@@ -118,8 +129,11 @@ public class NotificationManagerService extends INotificationManager.Stub
private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
private static final boolean ENABLE_BLOCKED_TOASTS = true;
+ private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
+
final Context mContext;
final IActivityManager mAm;
+ final UserManager mUserManager;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -148,6 +162,7 @@ public class NotificationManagerService extends INotificationManager.Stub
private boolean mInCall = false;
private boolean mNotificationPulseEnabled;
+ // used as a mutex for access to all active notifications & listeners
private final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
@@ -156,6 +171,21 @@ public class NotificationManagerService extends INotificationManager.Stub
private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
private NotificationRecord mLedNotification;
+ private final AppOpsManager mAppOps;
+
+ // contains connections to all connected listeners, including app services
+ // and system listeners
+ private ArrayList<NotificationListenerInfo> mListeners
+ = new ArrayList<NotificationListenerInfo>();
+ // things that will be put into mListeners as soon as they're ready
+ private ArrayList<String> mServicesBinding = new ArrayList<String>();
+ // lists the component names of all enabled (and therefore connected) listener
+ // app services for the current user only
+ private HashSet<ComponentName> mEnabledListenersForCurrentUser
+ = new HashSet<ComponentName>();
+ // Just the packages from mEnabledListenersForCurrentUser
+ private HashSet<String> mEnabledListenerPackageNames = new HashSet<String>();
+
// Notification control database. For now just contains disabled packages.
private AtomicFile mPolicyFile;
private HashSet<String> mBlockedPackages = new HashSet<String>();
@@ -169,6 +199,181 @@ public class NotificationManagerService extends INotificationManager.Stub
private static final String TAG_PACKAGE = "package";
private static final String ATTR_NAME = "name";
+ private class NotificationListenerInfo implements DeathRecipient {
+ INotificationListener listener;
+ ComponentName component;
+ int userid;
+ boolean isSystem;
+ ServiceConnection connection;
+
+ public NotificationListenerInfo(INotificationListener listener, ComponentName component,
+ int userid, boolean isSystem) {
+ this.listener = listener;
+ this.component = component;
+ this.userid = userid;
+ this.isSystem = isSystem;
+ this.connection = null;
+ }
+
+ public NotificationListenerInfo(INotificationListener listener, ComponentName component,
+ int userid, ServiceConnection connection) {
+ this.listener = listener;
+ this.component = component;
+ this.userid = userid;
+ this.isSystem = false;
+ this.connection = connection;
+ }
+
+ boolean enabledAndUserMatches(StatusBarNotification sbn) {
+ final int nid = sbn.getUserId();
+ if (!isEnabledForCurrentUser()) {
+ return false;
+ }
+ if (this.userid == UserHandle.USER_ALL) return true;
+ return (nid == UserHandle.USER_ALL || nid == this.userid);
+ }
+
+ public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
+ if (!enabledAndUserMatches(sbn)) {
+ return;
+ }
+ try {
+ listener.onNotificationPosted(sbn);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
+ }
+ }
+
+ public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
+ if (!enabledAndUserMatches(sbn)) return;
+ try {
+ listener.onNotificationRemoved(sbn);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ if (connection == null) {
+ // This is not a service; it won't be recreated. We can give up this connection.
+ unregisterListener(this.listener, this.userid);
+ }
+ }
+
+ /** convenience method for looking in mEnabledListenersForCurrentUser */
+ public boolean isEnabledForCurrentUser() {
+ if (this.isSystem) return true;
+ if (this.connection == null) return false;
+ return mEnabledListenersForCurrentUser.contains(this.component);
+ }
+ }
+
+ private static class Archive {
+ static final int BUFFER_SIZE = 250;
+ ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE);
+
+ public Archive() {
+ }
+
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ final int N = mBuffer.size();
+ sb.append("Archive (");
+ sb.append(N);
+ sb.append(" notification");
+ sb.append((N==1)?")":"s)");
+ return sb.toString();
+ }
+
+ public void record(StatusBarNotification nr) {
+ if (mBuffer.size() == BUFFER_SIZE) {
+ mBuffer.removeFirst();
+ }
+
+ // We don't want to store the heavy bits of the notification in the archive,
+ // but other clients in the system process might be using the object, so we
+ // store a (lightened) copy.
+ mBuffer.addLast(nr.cloneLight());
+ }
+
+
+ public void clear() {
+ mBuffer.clear();
+ }
+
+ public Iterator<StatusBarNotification> descendingIterator() {
+ return mBuffer.descendingIterator();
+ }
+ public Iterator<StatusBarNotification> ascendingIterator() {
+ return mBuffer.iterator();
+ }
+ public Iterator<StatusBarNotification> filter(
+ final Iterator<StatusBarNotification> iter, final String pkg, final int userId) {
+ return new Iterator<StatusBarNotification>() {
+ StatusBarNotification mNext = findNext();
+
+ private StatusBarNotification findNext() {
+ while (iter.hasNext()) {
+ StatusBarNotification nr = iter.next();
+ if ((pkg == null || nr.getPackageName() == pkg)
+ && (userId == UserHandle.USER_ALL || nr.getUserId() == userId)) {
+ return nr;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return mNext == null;
+ }
+
+ @Override
+ public StatusBarNotification next() {
+ StatusBarNotification next = mNext;
+ if (next == null) {
+ throw new NoSuchElementException();
+ }
+ mNext = findNext();
+ return next;
+ }
+
+ @Override
+ public void remove() {
+ iter.remove();
+ }
+ };
+ }
+
+ public StatusBarNotification[] getArray(int count) {
+ if (count == 0) count = Archive.BUFFER_SIZE;
+ final StatusBarNotification[] a
+ = new StatusBarNotification[Math.min(count, mBuffer.size())];
+ Iterator<StatusBarNotification> iter = descendingIterator();
+ int i=0;
+ while (iter.hasNext() && i < count) {
+ a[i++] = iter.next();
+ }
+ return a;
+ }
+
+ public StatusBarNotification[] getArray(int count, String pkg, int userId) {
+ if (count == 0) count = Archive.BUFFER_SIZE;
+ final StatusBarNotification[] a
+ = new StatusBarNotification[Math.min(count, mBuffer.size())];
+ Iterator<StatusBarNotification> iter = filter(descendingIterator(), pkg, userId);
+ int i=0;
+ while (iter.hasNext() && i < count) {
+ a[i++] = iter.next();
+ }
+ return a;
+ }
+
+ }
+
+ Archive mArchive = new Archive();
+
private void loadBlockDb() {
synchronized(mBlockedPackages) {
if (mPolicyFile == null) {
@@ -218,79 +423,40 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- private void writeBlockDb() {
- synchronized(mBlockedPackages) {
- FileOutputStream outfile = null;
- try {
- outfile = mPolicyFile.startWrite();
-
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(outfile, "utf-8");
-
- out.startDocument(null, true);
-
- out.startTag(null, TAG_BODY); {
- out.attribute(null, ATTR_VERSION, String.valueOf(DB_VERSION));
- out.startTag(null, TAG_BLOCKED_PKGS); {
- // write all known network policies
- for (String pkg : mBlockedPackages) {
- out.startTag(null, TAG_PACKAGE); {
- out.attribute(null, ATTR_NAME, pkg);
- } out.endTag(null, TAG_PACKAGE);
- }
- } out.endTag(null, TAG_BLOCKED_PKGS);
- } out.endTag(null, TAG_BODY);
-
- out.endDocument();
-
- mPolicyFile.finishWrite(outfile);
- } catch (IOException e) {
- if (outfile != null) {
- mPolicyFile.failWrite(outfile);
- }
- }
- }
- }
-
- public boolean areNotificationsEnabledForPackage(String pkg) {
+ /**
+ * Use this when you just want to know if notifications are OK for this package.
+ */
+ public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
checkCallerIsSystem();
- return areNotificationsEnabledForPackageInt(pkg);
+ return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
+ == AppOpsManager.MODE_ALLOWED);
}
- // Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
- private boolean areNotificationsEnabledForPackageInt(String pkg) {
- final boolean enabled = !mBlockedPackages.contains(pkg);
- if (DBG) {
- Slog.v(TAG, "notifications are " + (enabled?"en":"dis") + "abled for " + pkg);
+ /** Use this when you actually want to post a notification or toast.
+ *
+ * Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
+ */
+ private boolean noteNotificationOp(String pkg, int uid) {
+ if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
+ != AppOpsManager.MODE_ALLOWED) {
+ Slog.v(TAG, "notifications are disabled by AppOps for " + pkg);
+ return false;
}
- return enabled;
+ return true;
}
- public void setNotificationsEnabledForPackage(String pkg, boolean enabled) {
+ public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
checkCallerIsSystem();
- if (DBG) {
- Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
- }
- if (enabled) {
- mBlockedPackages.remove(pkg);
- } else {
- mBlockedPackages.add(pkg);
-
- // Now, cancel any outstanding notifications that are part of a just-disabled app
- if (ENABLE_BLOCKED_NOTIFICATIONS) {
- synchronized (mNotificationList) {
- final int N = mNotificationList.size();
- for (int i=0; i<N; i++) {
- final NotificationRecord r = mNotificationList.get(i);
- if (r.pkg.equals(pkg)) {
- cancelNotificationLocked(r, false);
- }
- }
- }
- }
- // Don't bother canceling toasts, they'll go away soon enough.
+
+ Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
+
+ mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
+ enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
+
+ // Now, cancel any outstanding notifications that are part of a just-disabled app
+ if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
+ cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
}
- writeBlockDb();
}
@@ -319,61 +485,510 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- private static final class NotificationRecord
+ /**
+ * System-only API for getting a list of current (i.e. not cleared) notifications.
+ *
+ * Requires ACCESS_NOTIFICATIONS which is signature|system.
+ */
+ @Override
+ public StatusBarNotification[] getActiveNotifications(String callingPkg) {
+ // enforce() will ensure the calling uid has the correct permission
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
+ "NotificationManagerService.getActiveNotifications");
+
+ StatusBarNotification[] tmp = null;
+ int uid = Binder.getCallingUid();
+
+ // noteOp will check to make sure the callingPkg matches the uid
+ if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
+ == AppOpsManager.MODE_ALLOWED) {
+ synchronized (mNotificationList) {
+ tmp = new StatusBarNotification[mNotificationList.size()];
+ final int N = mNotificationList.size();
+ for (int i=0; i<N; i++) {
+ tmp[i] = mNotificationList.get(i).sbn;
+ }
+ }
+ }
+ return tmp;
+ }
+
+ /**
+ * System-only API for getting a list of recent (cleared, no longer shown) notifications.
+ *
+ * Requires ACCESS_NOTIFICATIONS which is signature|system.
+ */
+ @Override
+ public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
+ // enforce() will ensure the calling uid has the correct permission
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
+ "NotificationManagerService.getHistoricalNotifications");
+
+ StatusBarNotification[] tmp = null;
+ int uid = Binder.getCallingUid();
+
+ // noteOp will check to make sure the callingPkg matches the uid
+ if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
+ == AppOpsManager.MODE_ALLOWED) {
+ synchronized (mArchive) {
+ tmp = mArchive.getArray(count);
+ }
+ }
+ return tmp;
+ }
+
+ /**
+ * Remove notification access for any services that no longer exist.
+ */
+ void disableNonexistentListeners() {
+ int currentUser = ActivityManager.getCurrentUser();
+ String flatIn = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+ currentUser);
+ if (!TextUtils.isEmpty(flatIn)) {
+ if (DBG) Slog.v(TAG, "flat before: " + flatIn);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
+ new Intent(NotificationListenerService.SERVICE_INTERFACE),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ currentUser);
+
+ Set<ComponentName> installed = new HashSet<ComponentName>();
+ for (int i = 0, count = installedServices.size(); i < count; i++) {
+ ResolveInfo resolveInfo = installedServices.get(i);
+ ServiceInfo info = resolveInfo.serviceInfo;
+
+ if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals(
+ info.permission)) {
+ Slog.w(TAG, "Skipping notification listener service "
+ + info.packageName + "/" + info.name
+ + ": it does not require the permission "
+ + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
+ continue;
+ }
+ installed.add(new ComponentName(info.packageName, info.name));
+ }
+
+ String flatOut = "";
+ if (!installed.isEmpty()) {
+ String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
+ ArrayList<String> remaining = new ArrayList<String>(enabled.length);
+ for (int i = 0; i < enabled.length; i++) {
+ ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
+ if (installed.contains(enabledComponent)) {
+ remaining.add(enabled[i]);
+ }
+ }
+ flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining);
+ }
+ if (DBG) Slog.v(TAG, "flat after: " + flatOut);
+ if (!flatIn.equals(flatOut)) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+ flatOut, currentUser);
+ }
+ }
+ }
+
+ /**
+ * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS
+ * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
+ */
+ void rebindListenerServices() {
+ final int currentUser = ActivityManager.getCurrentUser();
+ String flat = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+ currentUser);
+
+ NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
+ final ArrayList<ComponentName> toAdd;
+
+ synchronized (mNotificationList) {
+ // unbind and remove all existing listeners
+ toRemove = mListeners.toArray(toRemove);
+
+ toAdd = new ArrayList<ComponentName>();
+ final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>();
+ final HashSet<String> newPackages = new HashSet<String>();
+
+ // decode the list of components
+ if (flat != null) {
+ String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
+ for (int i=0; i<components.length; i++) {
+ final ComponentName component
+ = ComponentName.unflattenFromString(components[i]);
+ if (component != null) {
+ newEnabled.add(component);
+ toAdd.add(component);
+ newPackages.add(component.getPackageName());
+ }
+ }
+
+ mEnabledListenersForCurrentUser = newEnabled;
+ mEnabledListenerPackageNames = newPackages;
+ }
+ }
+
+ for (NotificationListenerInfo info : toRemove) {
+ final ComponentName component = info.component;
+ final int oldUser = info.userid;
+ Slog.v(TAG, "disabling notification listener for user " + oldUser + ": " + component);
+ unregisterListenerService(component, info.userid);
+ }
+
+ final int N = toAdd.size();
+ for (int i=0; i<N; i++) {
+ final ComponentName component = toAdd.get(i);
+ Slog.v(TAG, "enabling notification listener for user " + currentUser + ": "
+ + component);
+ registerListenerService(component, currentUser);
+ }
+ }
+
+ /**
+ * Register a listener binder directly with the notification manager.
+ *
+ * Only works with system callers. Apps should extend
+ * {@link android.service.notification.NotificationListenerService}.
+ */
+ @Override
+ public void registerListener(final INotificationListener listener,
+ final ComponentName component, final int userid) {
+ checkCallerIsSystem();
+
+ synchronized (mNotificationList) {
+ try {
+ NotificationListenerInfo info
+ = new NotificationListenerInfo(listener, component, userid, true);
+ listener.asBinder().linkToDeath(info, 0);
+ mListeners.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ }
+
+ /**
+ * Version of registerListener that takes the name of a
+ * {@link android.service.notification.NotificationListenerService} to bind to.
+ *
+ * This is the mechanism by which third parties may subscribe to notifications.
+ */
+ private void registerListenerService(final ComponentName name, final int userid) {
+ checkCallerIsSystem();
+
+ if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid);
+
+ synchronized (mNotificationList) {
+ final String servicesBindingTag = name.toString() + "/" + userid;
+ if (mServicesBinding.contains(servicesBindingTag)) {
+ // stop registering this thing already! we're working on it
+ return;
+ }
+ mServicesBinding.add(servicesBindingTag);
+
+ final int N = mListeners.size();
+ for (int i=N-1; i>=0; i--) {
+ final NotificationListenerInfo info = mListeners.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ // cut old connections
+ if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener);
+ mListeners.remove(i);
+ if (info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+ }
+
+ Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
+ intent.setComponent(name);
+
+ intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
+ com.android.internal.R.string.notification_listener_binding_label);
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
+ mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));
+
+ try {
+ if (DBG) Slog.v(TAG, "binding: " + intent);
+ if (!mContext.bindServiceAsUser(intent,
+ new ServiceConnection() {
+ INotificationListener mListener;
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mNotificationList) {
+ mServicesBinding.remove(servicesBindingTag);
+ try {
+ mListener = INotificationListener.Stub.asInterface(service);
+ NotificationListenerInfo info = new NotificationListenerInfo(
+ mListener, name, userid, this);
+ service.linkToDeath(info, 0);
+ mListeners.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Slog.v(TAG, "notification listener connection lost: " + name);
+ }
+ },
+ Context.BIND_AUTO_CREATE,
+ new UserHandle(userid)))
+ {
+ mServicesBinding.remove(servicesBindingTag);
+ Slog.w(TAG, "Unable to bind listener service: " + intent);
+ return;
+ }
+ } catch (SecurityException ex) {
+ Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Remove a listener binder directly
+ */
+ @Override
+ public void unregisterListener(INotificationListener listener, int userid) {
+ // no need to check permissions; if your listener binder is in the list,
+ // that's proof that you had permission to add it in the first place
+
+ synchronized (mNotificationList) {
+ final int N = mListeners.size();
+ for (int i=N-1; i>=0; i--) {
+ final NotificationListenerInfo info = mListeners.get(i);
+ if (info.listener.asBinder() == listener.asBinder()
+ && info.userid == userid) {
+ mListeners.remove(i);
+ if (info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove a listener service for the given user by ComponentName
+ */
+ private void unregisterListenerService(ComponentName name, int userid) {
+ checkCallerIsSystem();
+
+ synchronized (mNotificationList) {
+ final int N = mListeners.size();
+ for (int i=N-1; i>=0; i--) {
+ final NotificationListenerInfo info = mListeners.get(i);
+ if (name.equals(info.component)
+ && info.userid == userid) {
+ mListeners.remove(i);
+ if (info.connection != null) {
+ try {
+ mContext.unbindService(info.connection);
+ } catch (IllegalArgumentException ex) {
+ // something happened to the service: we think we have a connection
+ // but it's bogus.
+ Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * asynchronously notify all listeners about a new notification
+ */
+ private void notifyPostedLocked(NotificationRecord n) {
+ // make a copy in case changes are made to the underlying Notification object
+ final StatusBarNotification sbn = n.sbn.clone();
+ for (final NotificationListenerInfo info : mListeners) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ info.notifyPostedIfUserMatch(sbn);
+ }});
+ }
+ }
+
+ /**
+ * asynchronously notify all listeners about a removed notification
+ */
+ private void notifyRemovedLocked(NotificationRecord n) {
+ // make a copy in case changes are made to the underlying Notification object
+ // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification
+ final StatusBarNotification sbn_light = n.sbn.cloneLight();
+
+ for (final NotificationListenerInfo info : mListeners) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ info.notifyRemovedIfUserMatch(sbn_light);
+ }});
+ }
+ }
+
+ // -- APIs to support listeners clicking/clearing notifications --
+
+ private NotificationListenerInfo checkListenerToken(INotificationListener listener) {
+ final IBinder token = listener.asBinder();
+ final int N = mListeners.size();
+ for (int i=0; i<N; i++) {
+ final NotificationListenerInfo info = mListeners.get(i);
+ if (info.listener.asBinder() == token) return info;
+ }
+ throw new SecurityException("Disallowed call from unknown listener: " + listener);
+ }
+
+ /**
+ * Allow an INotificationListener to simulate a "clear all" operation.
+ *
+ * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onClearAllNotifications}
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ */
+ public void cancelAllNotificationsFromListener(INotificationListener token) {
+ NotificationListenerInfo info = checkListenerToken(token);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ cancelAll(info.userid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
+ *
+ * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ */
+ public void cancelNotificationFromListener(INotificationListener token, String pkg, String tag, int id) {
+ NotificationListenerInfo info = checkListenerToken(token);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ cancelNotification(pkg, tag, id, 0,
+ Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
+ true,
+ info.userid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Allow an INotificationListener to request the list of outstanding notifications seen by
+ * the current user. Useful when starting up, after which point the listener callbacks should
+ * be used.
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ */
+ public StatusBarNotification[] getActiveNotificationsFromListener(INotificationListener token) {
+ NotificationListenerInfo info = checkListenerToken(token);
+
+ StatusBarNotification[] result = new StatusBarNotification[0];
+ ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>();
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ for (int i=0; i<N; i++) {
+ StatusBarNotification sbn = mNotificationList.get(i).sbn;
+ if (info.enabledAndUserMatches(sbn)) {
+ list.add(sbn);
+ }
+ }
+ }
+ return list.toArray(result);
+ }
+
+ // -- end of listener APIs --
+
+ public static final class NotificationRecord
{
- final String pkg;
- final String tag;
- final int id;
- final int uid;
- final int initialPid;
- final int userId;
- final Notification notification;
- final int score;
+ final StatusBarNotification sbn;
IBinder statusBarKey;
- NotificationRecord(String pkg, String tag, int id, int uid, int initialPid,
- int userId, int score, Notification notification)
+ NotificationRecord(StatusBarNotification sbn)
{
- this.pkg = pkg;
- this.tag = tag;
- this.id = id;
- this.uid = uid;
- this.initialPid = initialPid;
- this.userId = userId;
- this.score = score;
- this.notification = notification;
+ this.sbn = sbn;
}
+ public Notification getNotification() { return sbn.getNotification(); }
+ public int getFlags() { return sbn.getNotification().flags; }
+ public int getUserId() { return sbn.getUserId(); }
+
void dump(PrintWriter pw, String prefix, Context baseContext) {
+ final Notification notification = sbn.getNotification();
pw.println(prefix + this);
+ pw.println(prefix + " uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon)
- + " / " + idDebugString(baseContext, this.pkg, notification.icon));
- pw.println(prefix + " pri=" + notification.priority);
- pw.println(prefix + " score=" + this.score);
+ + " / " + idDebugString(baseContext, sbn.getPackageName(), notification.icon));
+ pw.println(prefix + " pri=" + notification.priority + " score=" + sbn.getScore());
pw.println(prefix + " contentIntent=" + notification.contentIntent);
pw.println(prefix + " deleteIntent=" + notification.deleteIntent);
pw.println(prefix + " tickerText=" + notification.tickerText);
pw.println(prefix + " contentView=" + notification.contentView);
- pw.println(prefix + " uid=" + uid + " userId=" + userId);
- pw.println(prefix + " defaults=0x" + Integer.toHexString(notification.defaults));
- pw.println(prefix + " flags=0x" + Integer.toHexString(notification.flags));
+ pw.println(prefix + String.format(" defaults=0x%08x flags=0x%08x",
+ notification.defaults, notification.flags));
pw.println(prefix + " sound=" + notification.sound);
pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
- pw.println(prefix + " ledARGB=0x" + Integer.toHexString(notification.ledARGB)
- + " ledOnMS=" + notification.ledOnMS
- + " ledOffMS=" + notification.ledOffMS);
+ pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d",
+ notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
+ if (notification.actions != null && notification.actions.length > 0) {
+ pw.println(prefix + " actions={");
+ final int N = notification.actions.length;
+ for (int i=0; i<N; i++) {
+ final Notification.Action action = notification.actions[i];
+ pw.println(String.format("%s [%d] \"%s\" -> %s",
+ prefix,
+ i,
+ action.title,
+ action.actionIntent.toString()
+ ));
+ }
+ pw.println(prefix + " }");
+ }
+ if (notification.extras != null && notification.extras.size() > 0) {
+ pw.println(prefix + " extras={");
+ for (String key : notification.extras.keySet()) {
+ pw.print(prefix + " " + key + "=");
+ Object val = notification.extras.get(key);
+ if (val == null) {
+ pw.println("null");
+ } else {
+ pw.print(val.toString());
+ if (val instanceof Bitmap) {
+ pw.print(String.format(" (%dx%d)",
+ ((Bitmap) val).getWidth(),
+ ((Bitmap) val).getHeight()));
+ } else if (val.getClass().isArray()) {
+ pw.println(" {");
+ final int N = Array.getLength(val);
+ for (int i=0; i<N; i++) {
+ if (i > 0) pw.println(",");
+ pw.print(prefix + " " + Array.get(val, i));
+ }
+ pw.print("\n" + prefix + " }");
+ }
+ pw.println();
+ }
+ }
+ pw.println(prefix + " }");
+ }
}
@Override
- public final String toString()
- {
- return "NotificationRecord{"
- + Integer.toHexString(System.identityHashCode(this))
- + " pkg=" + pkg
- + " id=" + Integer.toHexString(id)
- + " tag=" + tag
- + " score=" + score
- + "}";
+ public final String toString() {
+ return String.format(
+ "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d: %s)",
+ System.identityHashCode(this),
+ this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), this.sbn.getTag(),
+ this.sbn.getScore(), this.sbn.getNotification());
}
}
@@ -518,14 +1133,20 @@ public class NotificationManagerService extends INotificationManager.Stub
String action = intent.getAction();
boolean queryRestart = false;
+ boolean queryRemove = false;
boolean packageChanged = false;
+ boolean cancelNotifications = true;
- if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
+ if (action.equals(Intent.ACTION_PACKAGE_ADDED)
+ || (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
|| (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
|| (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
String pkgList[] = null;
+ boolean queryReplace = queryRemove &&
+ intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ if (DBG) Slog.i(TAG, "queryReplace=" + queryReplace);
if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
} else if (queryRestart) {
@@ -545,17 +1166,34 @@ public class NotificationManagerService extends INotificationManager.Stub
.getApplicationEnabledSetting(pkgName);
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
- return;
+ cancelNotifications = false;
}
}
pkgList = new String[]{pkgName};
}
+
+ boolean anyListenersInvolved = false;
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
- cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart,
- UserHandle.USER_ALL);
+ if (cancelNotifications) {
+ cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart,
+ UserHandle.USER_ALL);
+ }
+ if (mEnabledListenerPackageNames.contains(pkgName)) {
+ anyListenersInvolved = true;
+ }
}
}
+
+ if (anyListenersInvolved) {
+ // if we're not replacing a package, clean up orphaned bits
+ if (!queryReplace) {
+ disableNonexistentListeners();
+ }
+ // make sure we're still bound to any of our
+ // listeners who may have just upgraded
+ rebindListenerServices();
+ }
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
@@ -574,37 +1212,55 @@ public class NotificationManagerService extends INotificationManager.Stub
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
// turn off LED when user passes through lock screen
mNotificationLight.turnOff();
+ } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
+ // reload per-user settings
+ mSettingsObserver.update(null);
}
}
};
class SettingsObserver extends ContentObserver {
+ private final Uri NOTIFICATION_LIGHT_PULSE_URI
+ = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
+
+ private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
+ = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
SettingsObserver(Handler handler) {
super(handler);
}
void observe() {
ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.NOTIFICATION_LIGHT_PULSE), false, this);
- update();
+ resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
+ false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
+ false, this, UserHandle.USER_ALL);
+ update(null);
}
- @Override public void onChange(boolean selfChange) {
- update();
+ @Override public void onChange(boolean selfChange, Uri uri) {
+ update(uri);
}
- public void update() {
+ public void update(Uri uri) {
ContentResolver resolver = mContext.getContentResolver();
- boolean pulseEnabled = Settings.System.getInt(resolver,
- Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
- if (mNotificationPulseEnabled != pulseEnabled) {
- mNotificationPulseEnabled = pulseEnabled;
- updateNotificationPulse();
+ if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
+ boolean pulseEnabled = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
+ if (mNotificationPulseEnabled != pulseEnabled) {
+ mNotificationPulseEnabled = pulseEnabled;
+ updateNotificationPulse();
+ }
+ }
+ if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
+ rebindListenerServices();
}
}
}
+ private SettingsObserver mSettingsObserver;
+
static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
int[] ar = r.getIntArray(resid);
if (ar == null) {
@@ -625,10 +1281,13 @@ public class NotificationManagerService extends INotificationManager.Stub
mContext = context;
mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
mAm = ActivityManagerNative.getDefault();
+ mUserManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
mToastQueue = new ArrayList<ToastRecord>();
mHandler = new WorkerHandler();
- loadBlockDb();
+ mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+
+ importOldBlockDb();
mStatusBar = statusBar;
statusBar.setNotificationCallbacks(mNotificationCallbacks);
@@ -670,8 +1329,10 @@ public class NotificationManagerService extends INotificationManager.Stub
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
filter.addAction(Intent.ACTION_USER_PRESENT);
filter.addAction(Intent.ACTION_USER_STOPPED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
mContext.registerReceiver(mIntentReceiver, filter);
IntentFilter pkgFilter = new IntentFilter();
+ pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
@@ -681,8 +1342,30 @@ public class NotificationManagerService extends INotificationManager.Stub
IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mIntentReceiver, sdFilter);
- SettingsObserver observer = new SettingsObserver(mHandler);
- observer.observe();
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mSettingsObserver.observe();
+ }
+
+ /**
+ * Read the old XML-based app block database and import those blockages into the AppOps system.
+ */
+ private void importOldBlockDb() {
+ loadBlockDb();
+
+ PackageManager pm = mContext.getPackageManager();
+ for (String pkg : mBlockedPackages) {
+ PackageInfo info = null;
+ try {
+ info = pm.getPackageInfo(pkg, 0);
+ setNotificationsEnabledForPackage(pkg, info.applicationInfo.uid, false);
+ } catch (NameNotFoundException e) {
+ // forget you
+ }
+ }
+ mBlockedPackages.clear();
+ if (mPolicyFile != null) {
+ mPolicyFile.delete();
+ }
}
void systemReady() {
@@ -691,6 +1374,9 @@ public class NotificationManagerService extends INotificationManager.Stub
// no beeping until we're basically done booting
mSystemReady = true;
+
+ // make sure our listener services are properly bound
+ rebindListenerServices();
}
// Toasts
@@ -704,11 +1390,13 @@ public class NotificationManagerService extends INotificationManager.Stub
return ;
}
- final boolean isSystemToast = ("android".equals(pkg));
+ final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
- if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
- Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
- return;
+ if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
+ if (!isSystemToast) {
+ Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
+ return;
+ }
}
synchronized (mToastQueue) {
@@ -898,10 +1586,10 @@ public class NotificationManagerService extends INotificationManager.Stub
// Notifications
// ============================================================================
- public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,
- int[] idOut, int userId)
+ public void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id,
+ Notification notification, int[] idOut, int userId)
{
- enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
+ enqueueNotificationInternal(pkg, basePkg, Binder.getCallingUid(), Binder.getCallingPid(),
tag, id, notification, idOut, userId);
}
@@ -911,14 +1599,14 @@ public class NotificationManagerService extends INotificationManager.Stub
// Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
// uid/pid of another application)
- public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
- String tag, int id, Notification notification, int[] idOut, int userId)
+ public void enqueueNotificationInternal(String pkg, String basePkg, int callingUid,
+ int callingPid, String tag, int id, Notification notification, int[] idOut, int userId)
{
if (DBG) {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
}
checkCallerIsSystemOrSameApp(pkg);
- final boolean isSystemNotification = ("android".equals(pkg));
+ final boolean isSystemNotification = isCallerSystem() || ("android".equals(pkg));
userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, userId, true, false, "enqueueNotification", pkg);
@@ -932,7 +1620,7 @@ public class NotificationManagerService extends INotificationManager.Stub
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
final NotificationRecord r = mNotificationList.get(i);
- if (r.pkg.equals(pkg) && r.userId == userId) {
+ if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
@@ -982,9 +1670,11 @@ public class NotificationManagerService extends INotificationManager.Stub
// 3. Apply local rules
// blocked apps
- if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) {
- score = JUNK_SCORE;
- Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request.");
+ if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
+ if (!isSystemNotification) {
+ score = JUNK_SCORE;
+ Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request.");
+ }
}
if (DBG) {
@@ -1000,10 +1690,9 @@ public class NotificationManagerService extends INotificationManager.Stub
final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD);
synchronized (mNotificationList) {
- NotificationRecord r = new NotificationRecord(pkg, tag, id,
- callingUid, callingPid, userId,
- score,
- notification);
+ final StatusBarNotification n = new StatusBarNotification(
+ pkg, id, tag, callingUid, callingPid, score, notification, user);
+ NotificationRecord r = new NotificationRecord(n);
NotificationRecord old = null;
int index = indexOfNotificationLocked(pkg, tag, id, userId);
@@ -1015,7 +1704,7 @@ public class NotificationManagerService extends INotificationManager.Stub
// Make sure we don't lose the foreground service state.
if (old != null) {
notification.flags |=
- old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
+ old.getNotification().flags&Notification.FLAG_FOREGROUND_SERVICE;
}
}
@@ -1035,8 +1724,6 @@ public class NotificationManagerService extends INotificationManager.Stub
}
if (notification.icon != 0) {
- final StatusBarNotification n = new StatusBarNotification(
- pkg, id, tag, r.uid, r.initialPid, score, notification, user);
if (old != null && old.statusBarKey != null) {
r.statusBarKey = old.statusBarKey;
long identity = Binder.clearCallingIdentity();
@@ -1050,7 +1737,7 @@ public class NotificationManagerService extends INotificationManager.Stub
long identity = Binder.clearCallingIdentity();
try {
r.statusBarKey = mStatusBar.addNotification(n);
- if ((n.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
+ if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0
&& canInterrupt) {
mAttentionLight.pulse();
}
@@ -1063,8 +1750,10 @@ public class NotificationManagerService extends INotificationManager.Stub
if (currentUser == userId) {
sendAccessibilityEvent(notification, pkg);
}
+
+ notifyPostedLocked(r);
} else {
- Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
+ Slog.e(TAG, "Not posting notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
long identity = Binder.clearCallingIdentity();
try {
@@ -1073,15 +1762,20 @@ public class NotificationManagerService extends INotificationManager.Stub
finally {
Binder.restoreCallingIdentity(identity);
}
+
+ notifyRemovedLocked(r);
}
+ // ATTENTION: in a future release we will bail out here
+ // so that we do not play sounds, show lights, etc. for invalid notifications
+ Slog.e(TAG, "WARNING: In a future release this will crash the app: " + n.getPackageName());
}
// If we're not supposed to beep, vibrate, etc. then don't.
if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
&& (!(old != null
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
- && (r.userId == UserHandle.USER_ALL ||
- (r.userId == userId && r.userId == currentUser))
+ && (r.getUserId() == UserHandle.USER_ALL ||
+ (r.getUserId() == userId && r.getUserId() == currentUser))
&& canInterrupt
&& mSystemReady) {
@@ -1089,8 +1783,12 @@ public class NotificationManagerService extends INotificationManager.Stub
.getSystemService(Context.AUDIO_SERVICE);
// sound
+
+ // should we use the default notification sound? (indicated either by DEFAULT_SOUND
+ // or because notification.sound is pointing at Settings.System.NOTIFICATION_SOUND)
final boolean useDefaultSound =
- (notification.defaults & Notification.DEFAULT_SOUND) != 0;
+ (notification.defaults & Notification.DEFAULT_SOUND) != 0
+ || Settings.System.DEFAULT_NOTIFICATION_URI.equals(notification.sound);
Uri soundUri = null;
boolean hasValidSound = false;
@@ -1157,22 +1855,21 @@ public class NotificationManagerService extends INotificationManager.Stub
// does not have the VIBRATE permission.
long identity = Binder.clearCallingIdentity();
try {
- mVibrator.vibrate(useDefaultVibrate ? mDefaultVibrationPattern
- : mFallbackVibrationPattern,
+ mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
+ useDefaultVibrate ? mDefaultVibrationPattern
+ : mFallbackVibrationPattern,
((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
} finally {
Binder.restoreCallingIdentity(identity);
}
} else if (notification.vibrate.length > 1) {
// If you want your own vibration pattern, you need the VIBRATE permission
- mVibrator.vibrate(notification.vibrate,
+ mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), notification.vibrate,
((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
}
}
}
- // this option doesn't shut off the lights
-
// light
// the most recent thing gets the light
mLights.remove(old);
@@ -1187,7 +1884,7 @@ public class NotificationManagerService extends INotificationManager.Stub
updateLightsLocked();
} else {
if (old != null
- && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
+ && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) {
updateLightsLocked();
}
}
@@ -1218,19 +1915,19 @@ public class NotificationManagerService extends INotificationManager.Stub
private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) {
// tell the app
if (sendDelete) {
- if (r.notification.deleteIntent != null) {
+ if (r.getNotification().deleteIntent != null) {
try {
- r.notification.deleteIntent.send();
+ r.getNotification().deleteIntent.send();
} catch (PendingIntent.CanceledException ex) {
// do nothing - there's no relevant way to recover, and
// no reason to let this propagate
- Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
+ Slog.w(TAG, "canceled PendingIntent for " + r.sbn.getPackageName(), ex);
}
}
}
// status bar
- if (r.notification.icon != 0) {
+ if (r.getNotification().icon != 0) {
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(r.statusBarKey);
@@ -1239,6 +1936,7 @@ public class NotificationManagerService extends INotificationManager.Stub
Binder.restoreCallingIdentity(identity);
}
r.statusBarKey = null;
+ notifyRemovedLocked(r);
}
// sound
@@ -1273,6 +1971,9 @@ public class NotificationManagerService extends INotificationManager.Stub
if (mLedNotification == r) {
mLedNotification = null;
}
+
+ // Save it for users of getHistoricalNotifications()
+ mArchive.record(r.sbn);
}
/**
@@ -1289,10 +1990,10 @@ public class NotificationManagerService extends INotificationManager.Stub
if (index >= 0) {
NotificationRecord r = mNotificationList.get(index);
- if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
+ if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) {
return;
}
- if ((r.notification.flags & mustNotHaveFlags) != 0) {
+ if ((r.getNotification().flags & mustNotHaveFlags) != 0) {
return;
}
@@ -1313,9 +2014,9 @@ public class NotificationManagerService extends INotificationManager.Stub
// looking for USER_ALL notifications? match everything
userId == UserHandle.USER_ALL
// a notification sent to USER_ALL matches any query
- || r.userId == UserHandle.USER_ALL
+ || r.getUserId() == UserHandle.USER_ALL
// an exact user match
- || r.userId == userId;
+ || r.getUserId() == userId;
}
/**
@@ -1336,16 +2037,16 @@ public class NotificationManagerService extends INotificationManager.Stub
continue;
}
// Don't remove notifications to all, if there's no package name specified
- if (r.userId == UserHandle.USER_ALL && pkg == null) {
+ if (r.getUserId() == UserHandle.USER_ALL && pkg == null) {
continue;
}
- if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) {
+ if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) {
continue;
}
- if ((r.notification.flags & mustNotHaveFlags) != 0) {
+ if ((r.getFlags() & mustNotHaveFlags) != 0) {
continue;
}
- if (pkg != null && !r.pkg.equals(pkg)) {
+ if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
continue;
}
canceledSomething = true;
@@ -1383,19 +2084,26 @@ public class NotificationManagerService extends INotificationManager.Stub
cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId);
}
+ // Return true if the caller is a system or phone UID and therefore should not have
+ // any notifications or toasts blocked.
+ boolean isCallerSystem() {
+ final int uid = Binder.getCallingUid();
+ final int appid = UserHandle.getAppId(uid);
+ return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
+ }
+
void checkCallerIsSystem() {
- int uid = Binder.getCallingUid();
- if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) {
+ if (isCallerSystem()) {
return;
}
- throw new SecurityException("Disallowed call for uid " + uid);
+ throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
}
void checkCallerIsSystemOrSameApp(String pkg) {
- int uid = Binder.getCallingUid();
- if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) {
+ if (isCallerSystem()) {
return;
}
+ final int uid = Binder.getCallingUid();
try {
ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
pkg, 0, UserHandle.getCallingUserId());
@@ -1418,7 +2126,7 @@ public class NotificationManagerService extends INotificationManager.Stub
continue;
}
- if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
+ if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR)) == 0) {
mNotificationList.remove(i);
cancelNotificationLocked(r, true);
@@ -1445,10 +2153,11 @@ public class NotificationManagerService extends INotificationManager.Stub
if (mLedNotification == null || mInCall || mScreenOn) {
mNotificationLight.turnOff();
} else {
- int ledARGB = mLedNotification.notification.ledARGB;
- int ledOnMS = mLedNotification.notification.ledOnMS;
- int ledOffMS = mLedNotification.notification.ledOffMS;
- if ((mLedNotification.notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
+ final Notification ledno = mLedNotification.sbn.getNotification();
+ int ledARGB = ledno.ledARGB;
+ int ledOnMS = ledno.ledOnMS;
+ int ledOffMS = ledno.ledOffMS;
+ if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) {
ledARGB = mDefaultNotificationColor;
ledOnMS = mDefaultNotificationLedOn;
ledOffMS = mDefaultNotificationLedOff;
@@ -1468,19 +2177,19 @@ public class NotificationManagerService extends INotificationManager.Stub
final int len = list.size();
for (int i=0; i<len; i++) {
NotificationRecord r = list.get(i);
- if (!notificationMatchesUserId(r, userId) || r.id != id) {
+ if (!notificationMatchesUserId(r, userId) || r.sbn.getId() != id) {
continue;
}
if (tag == null) {
- if (r.tag != null) {
+ if (r.sbn.getTag() != null) {
continue;
}
} else {
- if (!tag.equals(r.tag)) {
+ if (!tag.equals(r.sbn.getTag())) {
continue;
}
}
- if (r.pkg.equals(pkg)) {
+ if (r.sbn.getPackageName().equals(pkg)) {
return i;
}
}
@@ -1506,6 +2215,19 @@ public class NotificationManagerService extends INotificationManager.Stub
pw.println("Current Notification Manager state:");
+ pw.println(" Listeners (" + mEnabledListenersForCurrentUser.size()
+ + ") enabled for current user:");
+ for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
+ pw.println(" " + cmpt);
+ }
+
+ pw.println(" Live listeners (" + mListeners.size() + "):");
+ for (NotificationListenerInfo info : mListeners) {
+ pw.println(" " + info.component
+ + " (user " + info.userid + "): " + info.listener
+ + (info.isSystem?" SYSTEM":""));
+ }
+
int N;
synchronized (mToastQueue) {
@@ -1534,7 +2256,7 @@ public class NotificationManagerService extends INotificationManager.Stub
if (N > 0) {
pw.println(" Lights List:");
for (int i=0; i<N; i++) {
- mLights.get(i).dump(pw, " ", mContext);
+ pw.println(" " + mLights.get(i));
}
pw.println(" ");
}
@@ -1543,6 +2265,17 @@ public class NotificationManagerService extends INotificationManager.Stub
pw.println(" mVibrateNotification=" + mVibrateNotification);
pw.println(" mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
pw.println(" mSystemReady=" + mSystemReady);
+ pw.println(" mArchive=" + mArchive.toString());
+ Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
+ int i=0;
+ while (iter.hasNext()) {
+ pw.println(" " + iter.next());
+ if (++i >= 5) {
+ if (iter.hasNext()) pw.println(" ...");
+ break;
+ }
+ }
+
}
}
}
diff --git a/services/java/com/android/server/PreferredComponent.java b/services/java/com/android/server/PreferredComponent.java
index 718b05d..bb22545 100644
--- a/services/java/com/android/server/PreferredComponent.java
+++ b/services/java/com/android/server/PreferredComponent.java
@@ -164,17 +164,19 @@ public class PreferredComponent {
return mParseError;
}
- public void writeToXml(XmlSerializer serializer) throws IOException {
+ public void writeToXml(XmlSerializer serializer, boolean full) throws IOException {
final int NS = mSetClasses != null ? mSetClasses.length : 0;
serializer.attribute(null, "name", mShortComponent);
- if (mMatch != 0) {
- serializer.attribute(null, "match", Integer.toHexString(mMatch));
- }
- serializer.attribute(null, "set", Integer.toString(NS));
- for (int s=0; s<NS; s++) {
- serializer.startTag(null, "set");
- serializer.attribute(null, "name", mSetComponents[s]);
- serializer.endTag(null, "set");
+ if (full) {
+ if (mMatch != 0) {
+ serializer.attribute(null, "match", Integer.toHexString(mMatch));
+ }
+ serializer.attribute(null, "set", Integer.toString(NS));
+ for (int s=0; s<NS; s++) {
+ serializer.startTag(null, "set");
+ serializer.attribute(null, "name", mSetComponents[s]);
+ serializer.endTag(null, "set");
+ }
}
}
diff --git a/services/java/com/android/server/SerialService.java b/services/java/com/android/server/SerialService.java
index 5d2b2a0..1abe458 100644
--- a/services/java/com/android/server/SerialService.java
+++ b/services/java/com/android/server/SerialService.java
@@ -51,7 +51,12 @@ public class SerialService extends ISerialManager.Stub {
public ParcelFileDescriptor openSerialPort(String path) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
- return native_open(path);
+ for (int i = 0; i < mSerialPorts.length; i++) {
+ if (mSerialPorts[i].equals(path)) {
+ return native_open(path);
+ }
+ }
+ throw new IllegalArgumentException("Invalid serial port " + path);
}
private native ParcelFileDescriptor native_open(String path);
diff --git a/services/java/com/android/server/ServiceWatcher.java b/services/java/com/android/server/ServiceWatcher.java
index 2e7c6d1..5c7bfab 100644
--- a/services/java/com/android/server/ServiceWatcher.java
+++ b/services/java/com/android/server/ServiceWatcher.java
@@ -16,15 +16,18 @@
package com.android.server;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
+import android.content.res.Resources;
import android.os.Handler;
import android.os.IBinder;
import android.os.UserHandle;
@@ -34,6 +37,7 @@ import com.android.internal.content.PackageMonitor;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -44,12 +48,20 @@ import java.util.List;
public class ServiceWatcher implements ServiceConnection {
private static final boolean D = false;
public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
+ public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
private final String mTag;
private final Context mContext;
private final PackageManager mPm;
private final List<HashSet<Signature>> mSignatureSets;
private final String mAction;
+
+ /**
+ * If mServicePackageName is not null, only this package will be searched for the service that
+ * implements mAction. When null, all packages in the system that matches one of the signature
+ * in mSignatureSets are searched.
+ */
+ private final String mServicePackageName;
private final Runnable mNewServiceWork;
private final Handler mHandler;
@@ -59,7 +71,11 @@ public class ServiceWatcher implements ServiceConnection {
private IBinder mBinder; // connected service
private String mPackageName; // current best package
private int mVersion = Integer.MIN_VALUE; // current best version
- private int mCurrentUserId;
+ /**
+ * Whether the currently-connected service is multiuser-aware. This can change at run-time
+ * when switching from one version of a service to another.
+ */
+ private boolean mIsMultiuser = false;
public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
List<String> initialPackageNames) {
@@ -80,24 +96,60 @@ public class ServiceWatcher implements ServiceConnection {
}
public ServiceWatcher(Context context, String logTag, String action,
- List<String> initialPackageNames, Runnable newServiceWork, Handler handler, int userId) {
+ int overlaySwitchResId, int defaultServicePackageNameResId,
+ int initialPackageNamesResId, Runnable newServiceWork,
+ Handler handler) {
mContext = context;
mTag = logTag;
mAction = action;
mPm = mContext.getPackageManager();
mNewServiceWork = newServiceWork;
mHandler = handler;
- mCurrentUserId = userId;
-
+ Resources resources = context.getResources();
+
+ // Whether to enable service overlay.
+ boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
+ ArrayList<String> initialPackageNames = new ArrayList<String>();
+ if (enableOverlay) {
+ // A list of package names used to create the signatures.
+ String[] pkgs = resources.getStringArray(initialPackageNamesResId);
+ if (pkgs != null) initialPackageNames.addAll(Arrays.asList(pkgs));
+ mServicePackageName = null;
+ if (D) Log.d(mTag, "Overlay enabled, packages=" + Arrays.toString(pkgs));
+ } else {
+ // The default package name that is searched for service implementation when overlay is
+ // disabled.
+ String servicePackageName = resources.getString(defaultServicePackageNameResId);
+ if (servicePackageName != null) initialPackageNames.add(servicePackageName);
+ mServicePackageName = servicePackageName;
+ if (D) Log.d(mTag, "Overlay disabled, default package=" + servicePackageName);
+ }
mSignatureSets = getSignatureSets(context, initialPackageNames);
}
public boolean start() {
synchronized (mLock) {
- if (!bindBestPackageLocked(null)) return false;
+ if (!bindBestPackageLocked(mServicePackageName)) return false;
+ }
+
+ // listen for user change
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ switchUser();
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, mHandler);
+
+ // listen for relevant package changes if service overlay is enabled.
+ if (mServicePackageName == null) {
+ mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
}
- mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
return true;
}
@@ -113,46 +165,57 @@ public class ServiceWatcher implements ServiceConnection {
if (justCheckThisPackage != null) {
intent.setPackage(justCheckThisPackage);
}
- List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(new Intent(mAction),
- PackageManager.GET_META_DATA, mCurrentUserId);
+ List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
+ PackageManager.GET_META_DATA, UserHandle.USER_OWNER);
int bestVersion = Integer.MIN_VALUE;
String bestPackage = null;
- for (ResolveInfo rInfo : rInfos) {
- String packageName = rInfo.serviceInfo.packageName;
-
- // check signature
- try {
- PackageInfo pInfo;
- pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
- if (!isSignatureMatch(pInfo.signatures)) {
- Log.w(mTag, packageName + " resolves service " + mAction +
- ", but has wrong signature, ignoring");
+ boolean bestIsMultiuser = false;
+ if (rInfos != null) {
+ for (ResolveInfo rInfo : rInfos) {
+ String packageName = rInfo.serviceInfo.packageName;
+
+ // check signature
+ try {
+ PackageInfo pInfo;
+ pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ if (!isSignatureMatch(pInfo.signatures)) {
+ Log.w(mTag, packageName + " resolves service " + mAction
+ + ", but has wrong signature, ignoring");
+ continue;
+ }
+ } catch (NameNotFoundException e) {
+ Log.wtf(mTag, e);
continue;
}
- } catch (NameNotFoundException e) {
- Log.wtf(mTag, e);
- continue;
- }
- // check version
- int version = 0;
- if (rInfo.serviceInfo.metaData != null) {
- version = rInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, 0);
+ // check metadata
+ int version = Integer.MIN_VALUE;
+ boolean isMultiuser = false;
+ if (rInfo.serviceInfo.metaData != null) {
+ version = rInfo.serviceInfo.metaData.getInt(
+ EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
+ isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
+ }
+
+ if (version > mVersion) {
+ bestVersion = version;
+ bestPackage = packageName;
+ bestIsMultiuser = isMultiuser;
+ }
}
- if (version > mVersion) {
- bestVersion = version;
- bestPackage = packageName;
+ if (D) {
+ Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
+ (justCheckThisPackage == null ? ""
+ : "(" + justCheckThisPackage + ") "), rInfos.size(),
+ (bestPackage == null ? "no new best package"
+ : "new best package: " + bestPackage)));
}
+ } else {
+ if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
}
-
- if (D) Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
- (justCheckThisPackage == null ? "" : "(" + justCheckThisPackage + ") "),
- rInfos.size(),
- (bestPackage == null ? "no new best package" : "new best packge: " + bestPackage)));
-
if (bestPackage != null) {
- bindToPackageLocked(bestPackage, bestVersion);
+ bindToPackageLocked(bestPackage, bestVersion, bestIsMultiuser);
return true;
}
return false;
@@ -163,21 +226,24 @@ public class ServiceWatcher implements ServiceConnection {
pkg = mPackageName;
mPackageName = null;
mVersion = Integer.MIN_VALUE;
+ mIsMultiuser = false;
if (pkg != null) {
if (D) Log.d(mTag, "unbinding " + pkg);
mContext.unbindService(this);
}
}
- private void bindToPackageLocked(String packageName, int version) {
+ private void bindToPackageLocked(String packageName, int version, boolean isMultiuser) {
unbindLocked();
Intent intent = new Intent(mAction);
intent.setPackage(packageName);
mPackageName = packageName;
mVersion = version;
- if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ")");
- mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
- | Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_NOT_VISIBLE, mCurrentUserId);
+ mIsMultiuser = isMultiuser;
+ if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ") ("
+ + (isMultiuser ? "multi" : "single") + "-user)");
+ mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_NOT_VISIBLE, mIsMultiuser ? UserHandle.OWNER : UserHandle.CURRENT);
}
public static boolean isSignatureMatch(Signature[] signatures,
@@ -214,8 +280,9 @@ public class ServiceWatcher implements ServiceConnection {
// package updated, make sure to rebind
unbindLocked();
}
- // check the updated package in case it is better
- bindBestPackageLocked(packageName);
+ // Need to check all packages because this method is also called when a
+ // system app is uninstalled and the stock version in reinstalled.
+ bindBestPackageLocked(null);
}
}
@@ -227,7 +294,7 @@ public class ServiceWatcher implements ServiceConnection {
unbindLocked();
}
// check the new package is case it is better
- bindBestPackageLocked(packageName);
+ bindBestPackageLocked(null);
}
}
@@ -242,6 +309,20 @@ public class ServiceWatcher implements ServiceConnection {
}
}
}
+
+ @Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ synchronized (mLock) {
+ if (packageName.equals(mPackageName)) {
+ // service enabled or disabled, make sure to rebind
+ unbindLocked();
+ }
+ // the service might be disabled, need to search for a new
+ // package
+ bindBestPackageLocked(null);
+ }
+ return super.onPackageChanged(packageName, uid, components);
+ }
};
@Override
@@ -290,11 +371,12 @@ public class ServiceWatcher implements ServiceConnection {
}
}
- public void switchUser(int userId) {
+ public void switchUser() {
synchronized (mLock) {
- unbindLocked();
- mCurrentUserId = userId;
- bindBestPackageLocked(null);
+ if (!mIsMultiuser) {
+ unbindLocked();
+ bindBestPackageLocked(mServicePackageName);
+ }
}
}
}
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java
index 1fe98af..c21d8c6 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/StatusBarManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.app.StatusBarManager;
+import android.service.notification.StatusBarNotification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -33,7 +34,6 @@ import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
-import com.android.internal.statusbar.StatusBarNotification;
import com.android.server.wm.WindowManagerService;
import java.io.FileDescriptor;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 55885e6..9455017 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,29 +16,27 @@
package com.android.server;
-import android.accounts.AccountManagerService;
import android.app.ActivityManagerNative;
import android.bluetooth.BluetoothAdapter;
import android.content.ComponentName;
import android.content.ContentResolver;
-import android.content.ContentService;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioService;
import android.net.wifi.p2p.WifiP2pService;
+import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.RemoteException;
-import android.os.SchedulingPolicyService;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.server.search.SearchManagerService;
import android.service.dreams.DreamService;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -48,21 +46,25 @@ import android.view.WindowManager;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.SamplingProfilerIntegration;
-import com.android.internal.widget.LockSettingsService;
import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accounts.AccountManagerService;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.BatteryStatsService;
+import com.android.server.content.ContentService;
import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
+import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.Installer;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerService;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
+import com.android.server.search.SearchManagerService;
import com.android.server.usb.UsbService;
+import com.android.server.wifi.WifiService;
import com.android.server.wm.WindowManagerService;
import dalvik.system.VMRuntime;
@@ -147,7 +149,6 @@ class ServerThread extends Thread {
TwilightService twilight = null;
UiModeManagerService uiMode = null;
RecognitionManagerService recognition = null;
- ThrottleService throttle = null;
NetworkTimeUpdateService networkTimeUpdater = null;
CommonTimeManagementService commonTimeMgmtService = null;
InputManagerService inputManager = null;
@@ -207,9 +208,6 @@ class ServerThread extends Thread {
installer = new Installer();
installer.ping();
- Slog.i(TAG, "Entropy Mixer");
- ServiceManager.addService("entropy", new EntropyMixer());
-
Slog.i(TAG, "Power Manager");
power = new PowerManagerService();
ServiceManager.addService(Context.POWER_SERVICE, power);
@@ -257,12 +255,14 @@ class ServerThread extends Thread {
}
ActivityManagerService.setSystemProcess();
-
+
+ Slog.i(TAG, "Entropy Mixer");
+ ServiceManager.addService("entropy", new EntropyMixer(context));
+
Slog.i(TAG, "User Service");
ServiceManager.addService(Context.USER_SERVICE,
UserManagerService.getInstance());
-
mContentResolver = context.getContentResolver();
// The AccountManager must come before the ContentService
@@ -331,6 +331,9 @@ class ServerThread extends Thread {
Slog.i(TAG, "No Bluetooh Service (emulator)");
} else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {
Slog.i(TAG, "No Bluetooth Service (factory test)");
+ } else if (!context.getPackageManager().hasSystemFeature
+ (PackageManager.FEATURE_BLUETOOTH)) {
+ Slog.i(TAG, "No Bluetooth Service (Bluetooth Hardware Not Present)");
} else {
Slog.i(TAG, "Bluetooth Manager Service");
bluetooth = new BluetoothManagerService(context);
@@ -513,15 +516,6 @@ class ServerThread extends Thread {
}
try {
- Slog.i(TAG, "Throttle Service");
- throttle = new ThrottleService(context);
- ServiceManager.addService(
- Context.THROTTLE_SERVICE, throttle);
- } catch (Throwable e) {
- reportWtf("starting ThrottleService", e);
- }
-
- try {
Slog.i(TAG, "UpdateLock Service");
ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
new UpdateLockService(context));
@@ -748,6 +742,13 @@ class ServerThread extends Thread {
reportWtf("starting DreamManagerService", e);
}
}
+
+ try {
+ Slog.i(TAG, "IdleMaintenanceService");
+ new IdleMaintenanceService(context, battery);
+ } catch (Throwable e) {
+ reportWtf("starting IdleMaintenanceService", e);
+ }
}
// Before things start rolling, be sure we have decided whether
@@ -841,7 +842,6 @@ class ServerThread extends Thread {
final ConnectivityService connectivityF = connectivity;
final DockObserver dockF = dock;
final UsbService usbF = usb;
- final ThrottleService throttleF = throttle;
final TwilightService twilightF = twilight;
final UiModeManagerService uiModeF = uiMode;
final AppWidgetService appWidgetF = appWidget;
@@ -867,6 +867,11 @@ class ServerThread extends Thread {
public void run() {
Slog.i(TAG, "Making services ready");
+ try {
+ ActivityManagerService.self().startObservingNativeCrashes();
+ } catch (Throwable e) {
+ reportWtf("observing native crashes", e);
+ }
if (!headless) startSystemUi(contextF);
try {
if (mountServiceF != null) mountServiceF.systemReady();
@@ -954,11 +959,6 @@ class ServerThread extends Thread {
reportWtf("making Country Detector Service ready", e);
}
try {
- if (throttleF != null) throttleF.systemReady();
- } catch (Throwable e) {
- reportWtf("making Throttle Service ready", e);
- }
- try {
if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady();
} catch (Throwable e) {
reportWtf("making Network Time Service ready", e);
@@ -1060,6 +1060,8 @@ public class SystemServer {
// as efficient as possible with its memory usage.
VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
+ Environment.setUserRequired(true);
+
System.loadLibrary("android_servers");
init1(args);
}
diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java
index d0d8428..7dd9988 100644
--- a/services/java/com/android/server/TextServicesManagerService.java
+++ b/services/java/com/android/server/TextServicesManagerService.java
@@ -248,7 +248,8 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
return false;
}
- return mContext.bindService(service, conn, flags, mSettings.getCurrentUserId());
+ return mContext.bindServiceAsUser(service, conn, flags,
+ new UserHandle(mSettings.getCurrentUserId()));
}
private void unbindServiceLocked() {
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
deleted file mode 100644
index 75eb3c4..0000000
--- a/services/java/com/android/server/ThrottleService.java
+++ /dev/null
@@ -1,1160 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-
-import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.net.INetworkManagementEventObserver;
-import android.net.IThrottleManager;
-import android.net.NetworkStats;
-import android.net.ThrottleManager;
-import android.os.Binder;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.NtpTrustedTime;
-import android.util.Slog;
-import android.util.TrustedTime;
-
-import com.android.internal.R;
-import com.android.internal.telephony.TelephonyProperties;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.Random;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-// TODO - add comments - reference the ThrottleManager for public API
-public class ThrottleService extends IThrottleManager.Stub {
-
- private static final String TESTING_ENABLED_PROPERTY = "persist.throttle.testing";
-
- private static final String TAG = "ThrottleService";
- private static final boolean DBG = true;
- private static final boolean VDBG = false;
- private Handler mHandler;
- private HandlerThread mThread;
-
- private Context mContext;
-
- private static final int INITIAL_POLL_DELAY_SEC = 90;
- private static final int TESTING_POLLING_PERIOD_SEC = 60 * 1;
- private static final int TESTING_RESET_PERIOD_SEC = 60 * 10;
- private static final long TESTING_THRESHOLD = 1 * 1024 * 1024;
-
- private static final long MAX_NTP_CACHE_AGE = 24 * 60 * 60 * 1000;
-
- private long mMaxNtpCacheAge = MAX_NTP_CACHE_AGE;
-
- private int mPolicyPollPeriodSec;
- private AtomicLong mPolicyThreshold;
- private AtomicInteger mPolicyThrottleValue;
- private int mPolicyResetDay; // 1-28
- private int mPolicyNotificationsAllowedMask;
-
- private long mLastRead; // read byte count from last poll
- private long mLastWrite; // write byte count from last poll
-
- private static final String ACTION_POLL = "com.android.server.ThrottleManager.action.POLL";
- private static int POLL_REQUEST = 0;
- private PendingIntent mPendingPollIntent;
- private static final String ACTION_RESET = "com.android.server.ThorottleManager.action.RESET";
- private static int RESET_REQUEST = 1;
- private PendingIntent mPendingResetIntent;
-
- private INetworkManagementService mNMService;
- private AlarmManager mAlarmManager;
- private NotificationManager mNotificationManager;
-
- private DataRecorder mRecorder;
-
- private String mIface;
-
- private static final int NOTIFICATION_WARNING = 2;
-
- private Notification mThrottlingNotification;
- private boolean mWarningNotificationSent = false;
-
- private InterfaceObserver mInterfaceObserver;
- private SettingsObserver mSettingsObserver;
-
- private AtomicInteger mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc
- private static final int THROTTLE_INDEX_UNINITIALIZED = -1;
- private static final int THROTTLE_INDEX_UNTHROTTLED = 0;
-
- private Intent mPollStickyBroadcast;
-
- private TrustedTime mTime;
-
- private static INetworkManagementService getNetworkManagementService() {
- final IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- return INetworkManagementService.Stub.asInterface(b);
- }
-
- public ThrottleService(Context context) {
- this(context, getNetworkManagementService(), NtpTrustedTime.getInstance(context),
- context.getResources().getString(R.string.config_datause_iface));
- }
-
- public ThrottleService(Context context, INetworkManagementService nmService, TrustedTime time,
- String iface) {
- if (VDBG) Slog.v(TAG, "Starting ThrottleService");
- mContext = context;
-
- mPolicyThreshold = new AtomicLong();
- mPolicyThrottleValue = new AtomicInteger();
- mThrottleIndex = new AtomicInteger();
-
- mIface = iface;
- mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
- Intent pollIntent = new Intent(ACTION_POLL, null);
- mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
- Intent resetIntent = new Intent(ACTION_RESET, null);
- mPendingResetIntent = PendingIntent.getBroadcast(mContext, RESET_REQUEST, resetIntent, 0);
-
- mNMService = nmService;
- mTime = time;
-
- mNotificationManager = (NotificationManager)mContext.getSystemService(
- Context.NOTIFICATION_SERVICE);
- }
-
- private static class InterfaceObserver extends INetworkManagementEventObserver.Stub {
- private int mMsg;
- private Handler mHandler;
- private String mIface;
-
- InterfaceObserver(Handler handler, int msg, String iface) {
- super();
- mHandler = handler;
- mMsg = msg;
- mIface = iface;
- }
-
- public void interfaceStatusChanged(String iface, boolean up) {
- if (up) {
- if (TextUtils.equals(iface, mIface)) {
- mHandler.obtainMessage(mMsg).sendToTarget();
- }
- }
- }
-
- public void interfaceLinkStateChanged(String iface, boolean up) {
- }
-
- public void interfaceAdded(String iface) {
- // TODO - an interface added in the UP state should also trigger a StatusChanged
- // notification..
- if (TextUtils.equals(iface, mIface)) {
- mHandler.obtainMessage(mMsg).sendToTarget();
- }
- }
-
- public void interfaceRemoved(String iface) {}
- public void limitReached(String limitName, String iface) {}
- public void interfaceClassDataActivityChanged(String label, boolean active) {}
- }
-
-
- private static class SettingsObserver extends ContentObserver {
- private int mMsg;
- private Handler mHandler;
- SettingsObserver(Handler handler, int msg) {
- super(handler);
- mHandler = handler;
- mMsg = msg;
- }
-
- void register(Context context) {
- ContentResolver resolver = context.getContentResolver();
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.THROTTLE_POLLING_SEC), false, this);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.THROTTLE_THRESHOLD_BYTES), false, this);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.THROTTLE_VALUE_KBITSPS), false, this);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.THROTTLE_RESET_DAY), false, this);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.THROTTLE_NOTIFICATION_TYPE), false, this);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.THROTTLE_HELP_URI), false, this);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.THROTTLE_MAX_NTP_CACHE_AGE_SEC), false, this);
- }
-
- void unregister(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- resolver.unregisterContentObserver(this);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- mHandler.obtainMessage(mMsg).sendToTarget();
- }
- }
-
- private void enforceAccessPermission() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.ACCESS_NETWORK_STATE,
- "ThrottleService");
- }
-
- private long ntpToWallTime(long ntpTime) {
- // get time quickly without worrying about trusted state
- long bestNow = mTime.hasCache() ? mTime.currentTimeMillis()
- : System.currentTimeMillis();
- long localNow = System.currentTimeMillis();
- return localNow + (ntpTime - bestNow);
- }
-
- // TODO - fetch for the iface
- // return time in the local, system wall time, correcting for the use of ntp
-
- public long getResetTime(String iface) {
- enforceAccessPermission();
- long resetTime = 0;
- if (mRecorder != null) {
- resetTime = mRecorder.getPeriodEnd();
- }
- resetTime = ntpToWallTime(resetTime);
- return resetTime;
- }
-
- // TODO - fetch for the iface
- // return time in the local, system wall time, correcting for the use of ntp
- public long getPeriodStartTime(String iface) {
- long startTime = 0;
- enforceAccessPermission();
- if (mRecorder != null) {
- startTime = mRecorder.getPeriodStart();
- }
- startTime = ntpToWallTime(startTime);
- return startTime;
- }
- //TODO - a better name? getCliffByteCountThreshold?
- // TODO - fetch for the iface
- public long getCliffThreshold(String iface, int cliff) {
- enforceAccessPermission();
- if (cliff == 1) {
- return mPolicyThreshold.get();
- }
- return 0;
- }
- // TODO - a better name? getThrottleRate?
- // TODO - fetch for the iface
- public int getCliffLevel(String iface, int cliff) {
- enforceAccessPermission();
- if (cliff == 1) {
- return mPolicyThrottleValue.get();
- }
- return 0;
- }
-
- public String getHelpUri() {
- enforceAccessPermission();
- return Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.THROTTLE_HELP_URI);
- }
-
- // TODO - fetch for the iface
- public long getByteCount(String iface, int dir, int period, int ago) {
- enforceAccessPermission();
- if ((period == ThrottleManager.PERIOD_CYCLE) && (mRecorder != null)) {
- if (dir == ThrottleManager.DIRECTION_TX) return mRecorder.getPeriodTx(ago);
- if (dir == ThrottleManager.DIRECTION_RX) return mRecorder.getPeriodRx(ago);
- }
- return 0;
- }
-
- // TODO - a better name - getCurrentThrottleRate?
- // TODO - fetch for the iface
- public int getThrottle(String iface) {
- enforceAccessPermission();
- if (mThrottleIndex.get() == 1) {
- return mPolicyThrottleValue.get();
- }
- return 0;
- }
-
- void systemReady() {
- if (VDBG) Slog.v(TAG, "systemReady");
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- dispatchPoll();
- }
- }, new IntentFilter(ACTION_POLL));
-
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- dispatchReset();
- }
- }, new IntentFilter(ACTION_RESET));
-
- // use a new thread as we don't want to stall the system for file writes
- mThread = new HandlerThread(TAG);
- mThread.start();
- mHandler = new MyHandler(mThread.getLooper());
- mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget();
-
- mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface);
- try {
- mNMService.registerObserver(mInterfaceObserver);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not register InterfaceObserver " + e);
- }
-
- mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED);
- mSettingsObserver.register(mContext);
- }
-
- void shutdown() {
- // TODO: eventually connect with ShutdownThread to persist stats during
- // graceful shutdown.
-
- if (mThread != null) {
- mThread.quit();
- }
-
- if (mSettingsObserver != null) {
- mSettingsObserver.unregister(mContext);
- }
-
- if (mPollStickyBroadcast != null) {
- mContext.removeStickyBroadcastAsUser(mPollStickyBroadcast, UserHandle.ALL);
- }
- }
-
- void dispatchPoll() {
- mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget();
- }
-
- void dispatchReset() {
- mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget();
- }
-
- private static final int EVENT_REBOOT_RECOVERY = 0;
- private static final int EVENT_POLICY_CHANGED = 1;
- private static final int EVENT_POLL_ALARM = 2;
- private static final int EVENT_RESET_ALARM = 3;
- private static final int EVENT_IFACE_UP = 4;
- private class MyHandler extends Handler {
- public MyHandler(Looper l) {
- super(l);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case EVENT_REBOOT_RECOVERY:
- onRebootRecovery();
- break;
- case EVENT_POLICY_CHANGED:
- onPolicyChanged();
- break;
- case EVENT_POLL_ALARM:
- onPollAlarm();
- break;
- case EVENT_RESET_ALARM:
- onResetAlarm();
- break;
- case EVENT_IFACE_UP:
- onIfaceUp();
- }
- }
-
- private void onRebootRecovery() {
- if (VDBG) Slog.v(TAG, "onRebootRecovery");
- // check for sim change TODO
- // reregister for notification of policy change
-
- mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED);
-
- mRecorder = new DataRecorder(mContext, ThrottleService.this);
-
- // get policy
- mHandler.obtainMessage(EVENT_POLICY_CHANGED).sendToTarget();
-
- // if we poll now we won't have network connectivity or even imsi access
- // queue up a poll to happen in a little while - after ntp and imsi are avail
- // TODO - make this callback based (ie, listen for notificaitons)
- mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_POLL_ALARM),
- INITIAL_POLL_DELAY_SEC * 1000);
- }
-
- // check for new policy info (threshold limit/value/etc)
- private void onPolicyChanged() {
- boolean testing = SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true");
-
- int pollingPeriod = mContext.getResources().getInteger(
- R.integer.config_datause_polling_period_sec);
- mPolicyPollPeriodSec = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.THROTTLE_POLLING_SEC, pollingPeriod);
-
- // TODO - remove testing stuff?
- long defaultThreshold = mContext.getResources().getInteger(
- R.integer.config_datause_threshold_bytes);
- int defaultValue = mContext.getResources().getInteger(
- R.integer.config_datause_throttle_kbitsps);
- long threshold = Settings.Global.getLong(mContext.getContentResolver(),
- Settings.Global.THROTTLE_THRESHOLD_BYTES, defaultThreshold);
- int value = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.THROTTLE_VALUE_KBITSPS, defaultValue);
-
- mPolicyThreshold.set(threshold);
- mPolicyThrottleValue.set(value);
- if (testing) {
- mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC;
- mPolicyThreshold.set(TESTING_THRESHOLD);
- }
-
- mPolicyResetDay = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.THROTTLE_RESET_DAY, -1);
- if (mPolicyResetDay == -1 ||
- ((mPolicyResetDay < 1) || (mPolicyResetDay > 28))) {
- Random g = new Random();
- mPolicyResetDay = 1 + g.nextInt(28); // 1-28
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.THROTTLE_RESET_DAY, mPolicyResetDay);
- }
- if (mIface == null) {
- mPolicyThreshold.set(0);
- }
-
- int defaultNotificationType = mContext.getResources().getInteger(
- R.integer.config_datause_notification_type);
- mPolicyNotificationsAllowedMask = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.THROTTLE_NOTIFICATION_TYPE, defaultNotificationType);
-
- final int maxNtpCacheAgeSec = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.THROTTLE_MAX_NTP_CACHE_AGE_SEC,
- (int) (MAX_NTP_CACHE_AGE / 1000));
- mMaxNtpCacheAge = maxNtpCacheAgeSec * 1000;
-
- if (VDBG || (mPolicyThreshold.get() != 0)) {
- Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" +
- mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold.get() +
- ", value=" + mPolicyThrottleValue.get() + ", resetDay=" + mPolicyResetDay +
- ", noteType=" + mPolicyNotificationsAllowedMask + ", mMaxNtpCacheAge=" +
- mMaxNtpCacheAge);
- }
-
- // force updates
- mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED);
-
- onResetAlarm();
-
- onPollAlarm();
-
- Intent broadcast = new Intent(ThrottleManager.POLICY_CHANGED_ACTION);
- mContext.sendBroadcastAsUser(broadcast, UserHandle.ALL);
- }
-
- private void onPollAlarm() {
- long now = SystemClock.elapsedRealtime();
- long next = now + mPolicyPollPeriodSec * 1000;
-
- // when trusted cache outdated, try refreshing
- if (mTime.getCacheAge() > mMaxNtpCacheAge) {
- if (mTime.forceRefresh()) {
- if (VDBG) Slog.d(TAG, "updated trusted time, reseting alarm");
- dispatchReset();
- }
- }
-
- long incRead = 0;
- long incWrite = 0;
- try {
- final NetworkStats stats = mNMService.getNetworkStatsSummaryDev();
- final int index = stats.findIndex(mIface, NetworkStats.UID_ALL,
- NetworkStats.SET_DEFAULT, NetworkStats.TAG_NONE);
-
- if (index != -1) {
- final NetworkStats.Entry entry = stats.getValues(index, null);
- incRead = entry.rxBytes - mLastRead;
- incWrite = entry.txBytes - mLastWrite;
- } else {
- // missing iface, assume stats are 0
- Slog.w(TAG, "unable to find stats for iface " + mIface);
- }
-
- // handle iface resets - on some device the 3g iface comes and goes and gets
- // totals reset to 0. Deal with it
- if ((incRead < 0) || (incWrite < 0)) {
- incRead += mLastRead;
- incWrite += mLastWrite;
- mLastRead = 0;
- mLastWrite = 0;
- }
- } catch (IllegalStateException e) {
- Slog.e(TAG, "problem during onPollAlarm: " + e);
- } catch (RemoteException e) {
- Slog.e(TAG, "problem during onPollAlarm: " + e);
- }
-
- // don't count this data if we're roaming.
- boolean roaming = "true".equals(
- SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING));
- if (!roaming) {
- mRecorder.addData(incRead, incWrite);
- }
-
- long periodRx = mRecorder.getPeriodRx(0);
- long periodTx = mRecorder.getPeriodTx(0);
- long total = periodRx + periodTx;
- if (VDBG || (mPolicyThreshold.get() != 0)) {
- Slog.d(TAG, "onPollAlarm - roaming =" + roaming +
- ", read =" + incRead + ", written =" + incWrite + ", new total =" + total);
- }
- mLastRead += incRead;
- mLastWrite += incWrite;
-
- checkThrottleAndPostNotification(total);
-
- Intent broadcast = new Intent(ThrottleManager.THROTTLE_POLL_ACTION);
- broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_READ, periodRx);
- broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_WRITE, periodTx);
- broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, getPeriodStartTime(mIface));
- broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, getResetTime(mIface));
- mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL);
- mPollStickyBroadcast = broadcast;
-
- mAlarmManager.cancel(mPendingPollIntent);
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
- }
-
- private void onIfaceUp() {
- // if we were throttled before, be sure and set it again - the iface went down
- // (and may have disappeared all together) and these settings were lost
- if (mThrottleIndex.get() == 1) {
- try {
- mNMService.setInterfaceThrottle(mIface, -1, -1);
- mNMService.setInterfaceThrottle(mIface,
- mPolicyThrottleValue.get(), mPolicyThrottleValue.get());
- } catch (Exception e) {
- Slog.e(TAG, "error setting Throttle: " + e);
- }
- }
- }
-
- private void checkThrottleAndPostNotification(long currentTotal) {
- // is throttling enabled?
- long threshold = mPolicyThreshold.get();
- if (threshold == 0) {
- clearThrottleAndNotification();
- return;
- }
-
- // have we spoken with an ntp server yet?
- // this is controversial, but we'd rather err towards not throttling
- if (!mTime.hasCache()) {
- Slog.w(TAG, "missing trusted time, skipping throttle check");
- return;
- }
-
- // check if we need to throttle
- if (currentTotal > threshold) {
- if (mThrottleIndex.get() != 1) {
- mThrottleIndex.set(1);
- if (DBG) Slog.d(TAG, "Threshold " + threshold + " exceeded!");
- try {
- mNMService.setInterfaceThrottle(mIface,
- mPolicyThrottleValue.get(), mPolicyThrottleValue.get());
- } catch (Exception e) {
- Slog.e(TAG, "error setting Throttle: " + e);
- }
-
- mNotificationManager.cancel(R.drawable.stat_sys_throttled);
-
- postNotification(R.string.throttled_notification_title,
- R.string.throttled_notification_message,
- R.drawable.stat_sys_throttled,
- Notification.FLAG_ONGOING_EVENT);
-
- Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION);
- broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL,
- mPolicyThrottleValue.get());
- mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL);
-
- } // else already up!
- } else {
- clearThrottleAndNotification();
- if ((mPolicyNotificationsAllowedMask & NOTIFICATION_WARNING) != 0) {
- // check if we should warn about throttle
- // pretend we only have 1/2 the time remaining that we actually do
- // if our burn rate in the period so far would have us exceed the limit
- // in that 1/2 window, warn the user.
- // this gets more generous in the early to middle period and converges back
- // to the limit as we move toward the period end.
-
- // adding another factor - it must be greater than the total cap/4
- // else we may get false alarms very early in the period.. in the first
- // tenth of a percent of the period if we used more than a tenth of a percent
- // of the cap we'd get a warning and that's not desired.
- long start = mRecorder.getPeriodStart();
- long end = mRecorder.getPeriodEnd();
- long periodLength = end - start;
- long now = System.currentTimeMillis();
- long timeUsed = now - start;
- long warningThreshold = 2*threshold*timeUsed/(timeUsed+periodLength);
- if ((currentTotal > warningThreshold) && (currentTotal > threshold/4)) {
- if (mWarningNotificationSent == false) {
- mWarningNotificationSent = true;
- mNotificationManager.cancel(R.drawable.stat_sys_throttled);
- postNotification(R.string.throttle_warning_notification_title,
- R.string.throttle_warning_notification_message,
- R.drawable.stat_sys_throttled,
- 0);
- }
- } else {
- if (mWarningNotificationSent == true) {
- mNotificationManager.cancel(R.drawable.stat_sys_throttled);
- mWarningNotificationSent =false;
- }
- }
- }
- }
- }
-
- private void postNotification(int titleInt, int messageInt, int icon, int flags) {
- Intent intent = new Intent();
- // TODO - fix up intent
- intent.setClassName("com.android.phone", "com.android.phone.DataUsage");
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-
- PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0,
- null, UserHandle.CURRENT);
-
- Resources r = Resources.getSystem();
- CharSequence title = r.getText(titleInt);
- CharSequence message = r.getText(messageInt);
- if (mThrottlingNotification == null) {
- mThrottlingNotification = new Notification();
- mThrottlingNotification.when = 0;
- // TODO - fixup icon
- mThrottlingNotification.icon = icon;
- mThrottlingNotification.defaults &= ~Notification.DEFAULT_SOUND;
- }
- mThrottlingNotification.flags = flags;
- mThrottlingNotification.tickerText = title;
- mThrottlingNotification.setLatestEventInfo(mContext, title, message, pi);
-
- mNotificationManager.notifyAsUser(null, mThrottlingNotification.icon,
- mThrottlingNotification, UserHandle.ALL);
- }
-
-
- private void clearThrottleAndNotification() {
- if (mThrottleIndex.get() != THROTTLE_INDEX_UNTHROTTLED) {
- mThrottleIndex.set(THROTTLE_INDEX_UNTHROTTLED);
- try {
- mNMService.setInterfaceThrottle(mIface, -1, -1);
- } catch (Exception e) {
- Slog.e(TAG, "error clearing Throttle: " + e);
- }
- Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION);
- broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, -1);
- mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL);
- mNotificationManager.cancelAsUser(null, R.drawable.stat_sys_throttled,
- UserHandle.ALL);
- mWarningNotificationSent = false;
- }
- }
-
- private Calendar calculatePeriodEnd(long now) {
- Calendar end = GregorianCalendar.getInstance();
- end.setTimeInMillis(now);
- int day = end.get(Calendar.DAY_OF_MONTH);
- end.set(Calendar.DAY_OF_MONTH, mPolicyResetDay);
- end.set(Calendar.HOUR_OF_DAY, 0);
- end.set(Calendar.MINUTE, 0);
- end.set(Calendar.SECOND, 0);
- end.set(Calendar.MILLISECOND, 0);
- if (day >= mPolicyResetDay) {
- int month = end.get(Calendar.MONTH);
- if (month == Calendar.DECEMBER) {
- end.set(Calendar.YEAR, end.get(Calendar.YEAR) + 1);
- month = Calendar.JANUARY - 1;
- }
- end.set(Calendar.MONTH, month + 1);
- }
-
- // TODO - remove!
- if (SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true")) {
- end = GregorianCalendar.getInstance();
- end.setTimeInMillis(now);
- end.add(Calendar.SECOND, TESTING_RESET_PERIOD_SEC);
- }
- return end;
- }
- private Calendar calculatePeriodStart(Calendar end) {
- Calendar start = (Calendar)end.clone();
- int month = end.get(Calendar.MONTH);
- if (end.get(Calendar.MONTH) == Calendar.JANUARY) {
- month = Calendar.DECEMBER + 1;
- start.set(Calendar.YEAR, start.get(Calendar.YEAR) - 1);
- }
- start.set(Calendar.MONTH, month - 1);
-
- // TODO - remove!!
- if (SystemProperties.get(TESTING_ENABLED_PROPERTY).equals("true")) {
- start = (Calendar)end.clone();
- start.add(Calendar.SECOND, -TESTING_RESET_PERIOD_SEC);
- }
- return start;
- }
-
- private void onResetAlarm() {
- if (VDBG || (mPolicyThreshold.get() != 0)) {
- Slog.d(TAG, "onResetAlarm - last period had " + mRecorder.getPeriodRx(0) +
- " bytes read and " + mRecorder.getPeriodTx(0) + " written");
- }
-
- // when trusted cache outdated, try refreshing
- if (mTime.getCacheAge() > mMaxNtpCacheAge) {
- mTime.forceRefresh();
- }
-
- // as long as we have a trusted time cache, we always reset alarms,
- // even if the refresh above failed.
- if (mTime.hasCache()) {
- final long now = mTime.currentTimeMillis();
- Calendar end = calculatePeriodEnd(now);
- Calendar start = calculatePeriodStart(end);
-
- if (mRecorder.setNextPeriod(start, end)) {
- onPollAlarm();
- }
-
- mAlarmManager.cancel(mPendingResetIntent);
- long offset = end.getTimeInMillis() - now;
- // use Elapsed realtime so clock changes don't fool us.
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME,
- SystemClock.elapsedRealtime() + offset,
- mPendingResetIntent);
- } else {
- if (VDBG) Slog.d(TAG, "no trusted time, not resetting period");
- }
- }
- }
-
- // records bytecount data for a given time and accumulates it into larger time windows
- // for logging and other purposes
- //
- // since time can be changed (user or network action) we will have to track the time of the
- // last recording and deal with it.
- private static class DataRecorder {
- long[] mPeriodRxData;
- long[] mPeriodTxData;
- int mCurrentPeriod;
- int mPeriodCount;
-
- Calendar mPeriodStart;
- Calendar mPeriodEnd;
-
- ThrottleService mParent;
- Context mContext;
- String mImsi = null;
-
- TelephonyManager mTelephonyManager;
-
- DataRecorder(Context context, ThrottleService parent) {
- mContext = context;
- mParent = parent;
-
- mTelephonyManager = (TelephonyManager)mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
-
- synchronized (mParent) {
- mPeriodCount = 6;
- mPeriodRxData = new long[mPeriodCount];
- mPeriodTxData = new long[mPeriodCount];
-
- mPeriodStart = Calendar.getInstance();
- mPeriodEnd = Calendar.getInstance();
-
- retrieve();
- }
- }
-
- boolean setNextPeriod(Calendar start, Calendar end) {
- // TODO - how would we deal with a dual-IMSI device?
- checkForSubscriberId();
- boolean startNewPeriod = true;
-
- if (start.equals(mPeriodStart) && end.equals(mPeriodEnd)) {
- // same endpoints - keep collecting
- if (VDBG) {
- Slog.d(TAG, "same period (" + start.getTimeInMillis() + "," +
- end.getTimeInMillis() +") - ammending data");
- }
- startNewPeriod = false;
- } else {
- if (VDBG) {
- if(start.equals(mPeriodEnd) || start.after(mPeriodEnd)) {
- Slog.d(TAG, "next period (" + start.getTimeInMillis() + "," +
- end.getTimeInMillis() + ") - old end was " +
- mPeriodEnd.getTimeInMillis() + ", following");
- } else {
- Slog.d(TAG, "new period (" + start.getTimeInMillis() + "," +
- end.getTimeInMillis() + ") replacing old (" +
- mPeriodStart.getTimeInMillis() + "," +
- mPeriodEnd.getTimeInMillis() + ")");
- }
- }
- synchronized (mParent) {
- ++mCurrentPeriod;
- if (mCurrentPeriod >= mPeriodCount) mCurrentPeriod = 0;
- mPeriodRxData[mCurrentPeriod] = 0;
- mPeriodTxData[mCurrentPeriod] = 0;
- }
- }
- setPeriodStart(start);
- setPeriodEnd(end);
- record();
- return startNewPeriod;
- }
-
- public long getPeriodEnd() {
- synchronized (mParent) {
- return mPeriodEnd.getTimeInMillis();
- }
- }
-
- private void setPeriodEnd(Calendar end) {
- synchronized (mParent) {
- mPeriodEnd = end;
- }
- }
-
- public long getPeriodStart() {
- synchronized (mParent) {
- return mPeriodStart.getTimeInMillis();
- }
- }
-
- private void setPeriodStart(Calendar start) {
- synchronized (mParent) {
- mPeriodStart = start;
- }
- }
-
- public int getPeriodCount() {
- synchronized (mParent) {
- return mPeriodCount;
- }
- }
-
- private void zeroData(int field) {
- synchronized (mParent) {
- for(int period = 0; period<mPeriodCount; period++) {
- mPeriodRxData[period] = 0;
- mPeriodTxData[period] = 0;
- }
- mCurrentPeriod = 0;
- }
-
- }
-
- // if time moves backward accumulate all read/write that's lost into the now
- // otherwise time moved forward.
- void addData(long bytesRead, long bytesWritten) {
- checkForSubscriberId();
-
- synchronized (mParent) {
- mPeriodRxData[mCurrentPeriod] += bytesRead;
- mPeriodTxData[mCurrentPeriod] += bytesWritten;
- }
- record();
- }
-
- private File getDataFile() {
- File dataDir = Environment.getDataDirectory();
- File throttleDir = new File(dataDir, "system/throttle");
- throttleDir.mkdirs();
- String mImsi = mTelephonyManager.getSubscriberId();
- File dataFile;
- if (mImsi == null) {
- dataFile = useMRUFile(throttleDir);
- if (VDBG) Slog.v(TAG, "imsi not available yet, using " + dataFile);
- } else {
- String imsiHash = Integer.toString(mImsi.hashCode());
- dataFile = new File(throttleDir, imsiHash);
- }
- // touch the file so it's not LRU
- dataFile.setLastModified(System.currentTimeMillis());
- checkAndDeleteLRUDataFile(throttleDir);
- return dataFile;
- }
-
- // TODO - get broadcast (TelephonyIntents.ACTION_SIM_STATE_CHANGED) instead of polling
- private void checkForSubscriberId() {
- if (mImsi != null) return;
-
- mImsi = mTelephonyManager.getSubscriberId();
- if (mImsi == null) return;
-
- if (VDBG) Slog.d(TAG, "finally have imsi - retreiving data");
- retrieve();
- }
-
- private final static int MAX_SIMS_SUPPORTED = 3;
-
- private void checkAndDeleteLRUDataFile(File dir) {
- File[] files = dir.listFiles();
-
- if (files == null || files.length <= MAX_SIMS_SUPPORTED) return;
- if (DBG) Slog.d(TAG, "Too many data files");
- do {
- File oldest = null;
- for (File f : files) {
- if ((oldest == null) || (oldest.lastModified() > f.lastModified())) {
- oldest = f;
- }
- }
- if (oldest == null) return;
- if (DBG) Slog.d(TAG, " deleting " + oldest);
- oldest.delete();
- files = dir.listFiles();
- } while (files.length > MAX_SIMS_SUPPORTED);
- }
-
- private File useMRUFile(File dir) {
- File newest = null;
- File[] files = dir.listFiles();
-
- if (files != null) {
- for (File f : files) {
- if ((newest == null) || (newest.lastModified() < f.lastModified())) {
- newest = f;
- }
- }
- }
- if (newest == null) {
- newest = new File(dir, "temp");
- }
- return newest;
- }
-
-
- private static final int DATA_FILE_VERSION = 1;
-
- private void record() {
- // 1 int version
- // 1 int mPeriodCount
- // 13*6 long[PERIOD_COUNT] mPeriodRxData
- // 13*6 long[PERIOD_COUNT] mPeriodTxData
- // 1 int mCurrentPeriod
- // 13 long periodStartMS
- // 13 long periodEndMS
- // 200 chars max
- StringBuilder builder = new StringBuilder();
- builder.append(DATA_FILE_VERSION);
- builder.append(":");
- builder.append(mPeriodCount);
- builder.append(":");
- for(int i = 0; i < mPeriodCount; i++) {
- builder.append(mPeriodRxData[i]);
- builder.append(":");
- }
- for(int i = 0; i < mPeriodCount; i++) {
- builder.append(mPeriodTxData[i]);
- builder.append(":");
- }
- builder.append(mCurrentPeriod);
- builder.append(":");
- builder.append(mPeriodStart.getTimeInMillis());
- builder.append(":");
- builder.append(mPeriodEnd.getTimeInMillis());
-
- BufferedWriter out = null;
- try {
- out = new BufferedWriter(new FileWriter(getDataFile()), 256);
- out.write(builder.toString());
- } catch (IOException e) {
- Slog.e(TAG, "Error writing data file");
- return;
- } finally {
- if (out != null) {
- try {
- out.close();
- } catch (Exception e) {}
- }
- }
- }
-
- private void retrieve() {
- // clean out any old data first. If we fail to read we don't want old stuff
- zeroData(0);
-
- File f = getDataFile();
- byte[] buffer;
- FileInputStream s = null;
- try {
- buffer = new byte[(int)f.length()];
- s = new FileInputStream(f);
- s.read(buffer);
- } catch (IOException e) {
- Slog.e(TAG, "Error reading data file");
- return;
- } finally {
- if (s != null) {
- try {
- s.close();
- } catch (Exception e) {}
- }
- }
- String data = new String(buffer);
- if (data == null || data.length() == 0) {
- if (DBG) Slog.d(TAG, "data file empty");
- return;
- }
- String[] parsed = data.split(":");
- int parsedUsed = 0;
- if (parsed.length < 6) {
- Slog.e(TAG, "reading data file with insufficient length - ignoring");
- return;
- }
-
- int periodCount;
- long[] periodRxData;
- long[] periodTxData;
- int currentPeriod;
- Calendar periodStart;
- Calendar periodEnd;
- try {
- if (Integer.parseInt(parsed[parsedUsed++]) != DATA_FILE_VERSION) {
- Slog.e(TAG, "reading data file with bad version - ignoring");
- return;
- }
-
- periodCount = Integer.parseInt(parsed[parsedUsed++]);
- if (parsed.length != 5 + (2 * periodCount)) {
- Slog.e(TAG, "reading data file with bad length (" + parsed.length +
- " != " + (5 + (2 * periodCount)) + ") - ignoring");
- return;
- }
- periodRxData = new long[periodCount];
- for (int i = 0; i < periodCount; i++) {
- periodRxData[i] = Long.parseLong(parsed[parsedUsed++]);
- }
- periodTxData = new long[periodCount];
- for (int i = 0; i < periodCount; i++) {
- periodTxData[i] = Long.parseLong(parsed[parsedUsed++]);
- }
-
- currentPeriod = Integer.parseInt(parsed[parsedUsed++]);
-
- periodStart = new GregorianCalendar();
- periodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
- periodEnd = new GregorianCalendar();
- periodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
- } catch (Exception e) {
- Slog.e(TAG, "Error parsing data file - ignoring");
- return;
- }
- synchronized (mParent) {
- mPeriodCount = periodCount;
- mPeriodRxData = periodRxData;
- mPeriodTxData = periodTxData;
- mCurrentPeriod = currentPeriod;
- mPeriodStart = periodStart;
- mPeriodEnd = periodEnd;
- }
- }
-
- long getPeriodRx(int which) {
- synchronized (mParent) {
- if (which > mPeriodCount) return 0;
- which = mCurrentPeriod - which;
- if (which < 0) which += mPeriodCount;
- return mPeriodRxData[which];
- }
- }
- long getPeriodTx(int which) {
- synchronized (mParent) {
- if (which > mPeriodCount) return 0;
- which = mCurrentPeriod - which;
- if (which < 0) which += mPeriodCount;
- return mPeriodTxData[which];
- }
- }
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.DUMP)
- != PackageManager.PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump ThrottleService " +
- "from from pid=" + Binder.getCallingPid() + ", uid=" +
- Binder.getCallingUid());
- return;
- }
- pw.println();
-
- pw.println("The threshold is " + mPolicyThreshold.get() +
- ", after which you experince throttling to " +
- mPolicyThrottleValue.get() + "kbps");
- pw.println("Current period is " +
- (mRecorder.getPeriodEnd() - mRecorder.getPeriodStart())/1000 + " seconds long " +
- "and ends in " + (getResetTime(mIface) - System.currentTimeMillis()) / 1000 +
- " seconds.");
- pw.println("Polling every " + mPolicyPollPeriodSec + " seconds");
- pw.println("Current Throttle Index is " + mThrottleIndex.get());
- pw.println("mMaxNtpCacheAge=" + mMaxNtpCacheAge);
-
- for (int i = 0; i < mRecorder.getPeriodCount(); i++) {
- pw.println(" Period[" + i + "] - read:" + mRecorder.getPeriodRx(i) + ", written:" +
- mRecorder.getPeriodTx(i));
- }
- }
-}
diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java
index 0e456f1..062be01 100644
--- a/services/java/com/android/server/UiModeManagerService.java
+++ b/services/java/com/android/server/UiModeManagerService.java
@@ -494,7 +494,7 @@ final class UiModeManagerService extends IUiModeManager.Stub {
if (Sandman.shouldStartDockApp(mContext, homeIntent)) {
try {
int result = ActivityManagerNative.getDefault().startActivityWithConfig(
- null, homeIntent, null, null, null, 0, 0,
+ null, null, homeIntent, null, null, null, 0, 0,
mConfiguration, null, UserHandle.USER_CURRENT);
if (result >= ActivityManager.START_SUCCESS) {
dockAppStarted = true;
diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java
index df91dec..21d3111 100644
--- a/services/java/com/android/server/VibratorService.java
+++ b/services/java/com/android/server/VibratorService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -30,6 +31,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Binder;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.Vibrator;
@@ -39,6 +41,9 @@ import android.provider.Settings.SettingNotFoundException;
import android.util.Slog;
import android.view.InputDevice;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;
@@ -54,6 +59,8 @@ public class VibratorService extends IVibratorService.Stub
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
+ private final IAppOpsService mAppOpsService;
+ private final IBatteryStats mBatteryStatsService;
private InputManager mIm;
volatile VibrateThread mThread;
@@ -64,6 +71,8 @@ public class VibratorService extends IVibratorService.Stub
private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+ private int mCurVibUid = -1;
+
native static boolean vibratorExists();
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
@@ -75,23 +84,25 @@ public class VibratorService extends IVibratorService.Stub
private final long[] mPattern;
private final int mRepeat;
private final int mUid;
+ private final String mPackageName;
- Vibration(IBinder token, long millis, int uid) {
- this(token, millis, null, 0, uid);
+ Vibration(IBinder token, long millis, int uid, String packageName) {
+ this(token, millis, null, 0, uid, packageName);
}
- Vibration(IBinder token, long[] pattern, int repeat, int uid) {
- this(token, 0, pattern, repeat, uid);
+ Vibration(IBinder token, long[] pattern, int repeat, int uid, String packageName) {
+ this(token, 0, pattern, repeat, uid, packageName);
}
private Vibration(IBinder token, long millis, long[] pattern,
- int repeat, int uid) {
+ int repeat, int uid, String packageName) {
mToken = token;
mTimeout = millis;
mStartTime = SystemClock.uptimeMillis();
mPattern = pattern;
mRepeat = repeat;
mUid = uid;
+ mPackageName = packageName;
}
public void binderDied() {
@@ -131,6 +142,9 @@ public class VibratorService extends IVibratorService.Stub
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
+ mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+
mVibrations = new LinkedList<Vibration>();
IntentFilter filter = new IntentFilter();
@@ -164,12 +178,23 @@ public class VibratorService extends IVibratorService.Stub
return doVibratorExists();
}
- public void vibrate(long milliseconds, IBinder token) {
+ private void verifyIncomingUid(int uid) {
+ if (uid == Binder.getCallingUid()) {
+ return;
+ }
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+
+ public void vibrate(int uid, String packageName, long milliseconds, IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires VIBRATE permission");
}
- int uid = Binder.getCallingUid();
+ verifyIncomingUid(uid);
// We're running in the system server so we cannot crash. Check for a
// timeout of 0 or negative. This will ensure that a vibration has
// either a timeout of > 0 or a non-null pattern.
@@ -180,12 +205,18 @@ public class VibratorService extends IVibratorService.Stub
return;
}
- Vibration vib = new Vibration(token, milliseconds, uid);
- synchronized (mVibrations) {
- removeVibrationLocked(token);
- doCancelVibrateLocked();
- mCurrentVibration = vib;
- startVibrationLocked(vib);
+ Vibration vib = new Vibration(token, milliseconds, uid, packageName);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mVibrations) {
+ removeVibrationLocked(token);
+ doCancelVibrateLocked();
+ mCurrentVibration = vib;
+ startVibrationLocked(vib);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -199,12 +230,13 @@ public class VibratorService extends IVibratorService.Stub
return true;
}
- public void vibratePattern(long[] pattern, int repeat, IBinder token) {
+ public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
+ IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires VIBRATE permission");
}
- int uid = Binder.getCallingUid();
+ verifyIncomingUid(uid);
// so wakelock calls will succeed
long identity = Binder.clearCallingIdentity();
try {
@@ -224,7 +256,7 @@ public class VibratorService extends IVibratorService.Stub
return;
}
- Vibration vib = new Vibration(token, pattern, repeat, uid);
+ Vibration vib = new Vibration(token, pattern, repeat, uid, packageName);
try {
token.linkToDeath(vib, 0);
} catch (RemoteException e) {
@@ -291,11 +323,13 @@ public class VibratorService extends IVibratorService.Stub
}
doVibratorOff();
mH.removeCallbacks(mVibrationRunnable);
+ reportFinishVibrationLocked();
}
// Lock held on mVibrations
private void startNextVibrationLocked() {
if (mVibrations.size() <= 0) {
+ reportFinishVibrationLocked();
mCurrentVibration = null;
return;
}
@@ -305,8 +339,19 @@ public class VibratorService extends IVibratorService.Stub
// Lock held on mVibrations
private void startVibrationLocked(final Vibration vib) {
+ try {
+ int mode = mAppOpsService.startOperation(AppOpsManager.OP_VIBRATE, vib.mUid, vib.mPackageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
+ }
+ mH.post(mVibrationRunnable);
+ return;
+ }
+ } catch (RemoteException e) {
+ }
if (vib.mTimeout != 0) {
- doVibratorOn(vib.mTimeout);
+ doVibratorOn(vib.mTimeout, vib.mUid);
mH.postDelayed(mVibrationRunnable, vib.mTimeout);
} else {
// mThread better be null here. doCancelVibrate should always be
@@ -316,6 +361,17 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ private void reportFinishVibrationLocked() {
+ if (mCurrentVibration != null) {
+ try {
+ mAppOpsService.finishOperation(AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
+ mCurrentVibration.mPackageName);
+ } catch (RemoteException e) {
+ }
+ mCurrentVibration = null;
+ }
+ }
+
// Lock held on mVibrations
private Vibration removeVibrationLocked(IBinder token) {
ListIterator<Vibration> iter = mVibrations.listIterator(0);
@@ -413,8 +469,13 @@ public class VibratorService extends IVibratorService.Stub
return vibratorExists();
}
- private void doVibratorOn(long millis) {
+ private void doVibratorOn(long millis, int uid) {
synchronized (mInputDeviceVibrators) {
+ try {
+ mBatteryStatsService.noteVibratorOn(uid, millis);
+ mCurVibUid = uid;
+ } catch (RemoteException e) {
+ }
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
@@ -428,6 +489,13 @@ public class VibratorService extends IVibratorService.Stub
private void doVibratorOff() {
synchronized (mInputDeviceVibrators) {
+ if (mCurVibUid >= 0) {
+ try {
+ mBatteryStatsService.noteVibratorOff(mCurVibUid);
+ } catch (RemoteException e) {
+ }
+ mCurVibUid = -1;
+ }
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
@@ -470,10 +538,11 @@ public class VibratorService extends IVibratorService.Stub
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
synchronized (this) {
+ final long[] pattern = mVibration.mPattern;
+ final int len = pattern.length;
+ final int repeat = mVibration.mRepeat;
+ final int uid = mVibration.mUid;
int index = 0;
- long[] pattern = mVibration.mPattern;
- int len = pattern.length;
- int repeat = mVibration.mRepeat;
long duration = 0;
while (!mDone) {
@@ -493,7 +562,7 @@ public class VibratorService extends IVibratorService.Stub
// duration is saved for delay() at top of loop
duration = pattern[index++];
if (duration > 0) {
- VibratorService.this.doVibratorOn(duration);
+ VibratorService.this.doVibratorOn(duration, uid);
}
} else {
if (repeat < 0) {
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index 21a1956..6823f13 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -885,7 +885,8 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
0, null, new UserHandle(serviceUserId)));
- if (!mContext.bindService(intent, newConn, Context.BIND_AUTO_CREATE, serviceUserId)) {
+ if (!mContext.bindServiceAsUser(intent, newConn, Context.BIND_AUTO_CREATE,
+ new UserHandle(serviceUserId))) {
String msg = "Unable to bind service: "
+ componentName;
if (fromUser) {
diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java
index 1663106..3aec4ea 100644
--- a/services/java/com/android/server/Watchdog.java
+++ b/services/java/com/android/server/Watchdog.java
@@ -16,6 +16,9 @@
package com.android.server;
+import android.app.IActivityController;
+import android.os.Binder;
+import android.os.RemoteException;
import com.android.server.am.ActivityManagerService;
import com.android.server.power.PowerManagerService;
@@ -88,10 +91,11 @@ public class Watchdog extends Thread {
AlarmManagerService mAlarm;
ActivityManagerService mActivity;
boolean mCompleted;
- boolean mForceKillSystem;
Monitor mCurrentMonitor;
int mPhonePid;
+ IActivityController mController;
+ boolean mAllowRestart = true;
final Calendar mCalendar = Calendar.getInstance();
int mMinScreenOff = MEMCHECK_DEFAULT_MIN_SCREEN_OFF;
@@ -135,7 +139,9 @@ public class Watchdog extends Thread {
final int size = mMonitors.size();
for (int i = 0 ; i < size ; i++) {
- mCurrentMonitor = mMonitors.get(i);
+ synchronized (Watchdog.this) {
+ mCurrentMonitor = mMonitors.get(i);
+ }
mCurrentMonitor.monitor();
}
@@ -222,6 +228,18 @@ public class Watchdog extends Thread {
}
}
+ public void setActivityController(IActivityController controller) {
+ synchronized (this) {
+ mController = controller;
+ }
+ }
+
+ public void setAllowRestart(boolean allowRestart) {
+ synchronized (this) {
+ mAllowRestart = allowRestart;
+ }
+ }
+
public void addMonitor(Monitor monitor) {
synchronized (this) {
if (isAlive()) {
@@ -388,6 +406,9 @@ public class Watchdog extends Thread {
mCompleted = false;
mHandler.sendEmptyMessage(MONITOR);
+
+ final String name;
+ final boolean allowRestart;
synchronized (this) {
long timeout = TIME_TO_WAIT;
@@ -396,16 +417,16 @@ public class Watchdog extends Thread {
// to timeout on is asleep as well and won't have a chance to run, causing a false
// positive on when to kill things.
long start = SystemClock.uptimeMillis();
- while (timeout > 0 && !mForceKillSystem) {
+ while (timeout > 0) {
try {
- wait(timeout); // notifyAll() is called when mForceKillSystem is set
+ wait(timeout);
} catch (InterruptedException e) {
Log.wtf(TAG, e);
}
timeout = TIME_TO_WAIT - (SystemClock.uptimeMillis() - start);
}
- if (mCompleted && !mForceKillSystem) {
+ if (mCompleted) {
// The monitors have returned.
waitedHalf = false;
continue;
@@ -421,14 +442,15 @@ public class Watchdog extends Thread {
waitedHalf = true;
continue;
}
+
+ name = (mCurrentMonitor != null) ?
+ mCurrentMonitor.getClass().getName() : "null";
+ allowRestart = mAllowRestart;
}
// If we got here, that means that the system is most likely hung.
// First collect stack traces from all threads of the system process.
// Then kill this process so that the system will restart.
-
- final String name = (mCurrentMonitor != null) ?
- mCurrentMonitor.getClass().getName() : "null";
EventLog.writeEvent(EventLogTags.WATCHDOG, name);
ArrayList<Integer> pids = new ArrayList<Integer>();
@@ -473,13 +495,34 @@ public class Watchdog extends Thread {
dropboxThread.join(2000); // wait up to 2 seconds for it to return.
} catch (InterruptedException ignored) {}
+ IActivityController controller;
+ synchronized (this) {
+ controller = mController;
+ }
+ if (controller != null) {
+ Slog.i(TAG, "Reporting stuck state to activity controller");
+ try {
+ Binder.setDumpDisabled("Service dumps disabled due to hung system process.");
+ // 1 = keep waiting, -1 = kill system
+ int res = controller.systemNotResponding(name);
+ if (res >= 0) {
+ Slog.i(TAG, "Activity controller requested to coninue to wait");
+ waitedHalf = false;
+ continue;
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
// Only kill the process if the debugger is not attached.
- if (!Debug.isDebuggerConnected()) {
+ if (Debug.isDebuggerConnected()) {
+ Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
+ } else if (!allowRestart) {
+ Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
+ } else {
Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + name);
Process.killProcess(Process.myPid());
System.exit(10);
- } else {
- Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
}
waitedHalf = false;
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index eb414fa..9e893da 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -18,22 +18,30 @@ package com.android.server.accessibility;
import android.content.Context;
import android.os.PowerManager;
+import android.util.Pools.SimplePool;
import android.util.Slog;
+import android.view.Choreographer;
+import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputFilter;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
+/**
+ * This class is an input filter for implementing accessibility features such
+ * as display magnification and explore by touch.
+ *
+ * NOTE: This class has to be created and poked only from the main thread.
+ */
class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation {
private static final String TAG = AccessibilityInputFilter.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final int UNDEFINED_DEVICE_ID = -1;
-
/**
* Flag for enabling the screen magnification feature.
*
@@ -48,27 +56,66 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
static final int FLAG_FEATURE_TOUCH_EXPLORATION = 0x00000002;
+ /**
+ * Flag for enabling the filtering key events feature.
+ *
+ * @see #setEnabledFeatures(int)
+ */
+ static final int FLAG_FEATURE_FILTER_KEY_EVENTS = 0x00000004;
+
+ private final Runnable mProcessBatchedEventsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ final long frameTimeNanos = mChoreographer.getFrameTimeNanos();
+ if (DEBUG) {
+ Slog.i(TAG, "Begin batch processing for frame: " + frameTimeNanos);
+ }
+ processBatchedEvents(frameTimeNanos);
+ if (DEBUG) {
+ Slog.i(TAG, "End batch processing.");
+ }
+ if (mEventQueue != null) {
+ scheduleProcessBatchedEvents();
+ }
+ }
+ };
+
private final Context mContext;
private final PowerManager mPm;
private final AccessibilityManagerService mAms;
- private int mCurrentDeviceId;
+ private final Choreographer mChoreographer;
+
+ private int mCurrentTouchDeviceId;
private boolean mInstalled;
private int mEnabledFeatures;
private TouchExplorer mTouchExplorer;
+
private ScreenMagnifier mScreenMagnifier;
+
private EventStreamTransformation mEventHandler;
+ private MotionEventHolder mEventQueue;
+
+ private boolean mMotionEventSequenceStarted;
+
+ private boolean mHoverEventSequenceStarted;
+
+ private boolean mKeyEventSequenceStarted;
+
+ private boolean mFilterKeyEvents;
+
AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
super(context.getMainLooper());
mContext = context;
mAms = service;
mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mChoreographer = Choreographer.getInstance();
}
@Override
@@ -98,31 +145,144 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
+ Integer.toHexString(policyFlags));
}
- if (mEventHandler == null) {
+ if (event instanceof MotionEvent
+ && event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ onMotionEvent(motionEvent, policyFlags);
+ } else if (event instanceof KeyEvent
+ && event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
+ KeyEvent keyEvent = (KeyEvent) event;
+ onKeyEvent(keyEvent, policyFlags);
+ } else {
super.onInputEvent(event, policyFlags);
- return;
}
- if (event.getSource() != InputDevice.SOURCE_TOUCHSCREEN) {
+ }
+
+ private void onMotionEvent(MotionEvent event, int policyFlags) {
+ if (mEventHandler == null) {
super.onInputEvent(event, policyFlags);
return;
}
if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
+ mMotionEventSequenceStarted = false;
+ mHoverEventSequenceStarted = false;
mEventHandler.clear();
super.onInputEvent(event, policyFlags);
return;
}
final int deviceId = event.getDeviceId();
- if (mCurrentDeviceId != deviceId) {
- if (mCurrentDeviceId != UNDEFINED_DEVICE_ID) {
- mEventHandler.clear();
+ if (mCurrentTouchDeviceId != deviceId) {
+ mCurrentTouchDeviceId = deviceId;
+ mMotionEventSequenceStarted = false;
+ mHoverEventSequenceStarted = false;
+ mEventHandler.clear();
+ }
+ if (mCurrentTouchDeviceId < 0) {
+ super.onInputEvent(event, policyFlags);
+ return;
+ }
+ // We do not handle scroll events.
+ if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
+ super.onInputEvent(event, policyFlags);
+ return;
+ }
+ // Wait for a down touch event to start processing.
+ if (event.isTouchEvent()) {
+ if (!mMotionEventSequenceStarted) {
+ if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ return;
+ }
+ mMotionEventSequenceStarted = true;
+ }
+ } else {
+ // Wait for an enter hover event to start processing.
+ if (!mHoverEventSequenceStarted) {
+ if (event.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) {
+ return;
+ }
+ mHoverEventSequenceStarted = true;
}
- mCurrentDeviceId = deviceId;
}
- mPm.userActivity(event.getEventTime(), false);
- MotionEvent rawEvent = (MotionEvent) event;
- MotionEvent transformedEvent = MotionEvent.obtain(rawEvent);
- mEventHandler.onMotionEvent(transformedEvent, rawEvent, policyFlags);
- transformedEvent.recycle();
+ batchMotionEvent((MotionEvent) event, policyFlags);
+ }
+
+ private void onKeyEvent(KeyEvent event, int policyFlags) {
+ if (!mFilterKeyEvents) {
+ super.onInputEvent(event, policyFlags);
+ return;
+ }
+ if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
+ mKeyEventSequenceStarted = false;
+ super.onInputEvent(event, policyFlags);
+ return;
+ }
+ // Wait for a down key event to start processing.
+ if (!mKeyEventSequenceStarted) {
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return;
+ }
+ mKeyEventSequenceStarted = true;
+ }
+ mAms.notifyKeyEvent(event, policyFlags);
+ }
+
+ private void scheduleProcessBatchedEvents() {
+ mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
+ mProcessBatchedEventsRunnable, null);
+ }
+
+ private void batchMotionEvent(MotionEvent event, int policyFlags) {
+ if (DEBUG) {
+ Slog.i(TAG, "Batching event: " + event + ", policyFlags: " + policyFlags);
+ }
+ if (mEventQueue == null) {
+ mEventQueue = MotionEventHolder.obtain(event, policyFlags);
+ scheduleProcessBatchedEvents();
+ return;
+ }
+ if (mEventQueue.event.addBatch(event)) {
+ return;
+ }
+ MotionEventHolder holder = MotionEventHolder.obtain(event, policyFlags);
+ holder.next = mEventQueue;
+ mEventQueue.previous = holder;
+ mEventQueue = holder;
+ }
+
+ private void processBatchedEvents(long frameNanos) {
+ MotionEventHolder current = mEventQueue;
+ while (current.next != null) {
+ current = current.next;
+ }
+ while (true) {
+ if (current == null) {
+ mEventQueue = null;
+ break;
+ }
+ if (current.event.getEventTimeNano() >= frameNanos) {
+ // Finished with this choreographer frame. Do the rest on the next one.
+ current.next = null;
+ break;
+ }
+ handleMotionEvent(current.event, current.policyFlags);
+ MotionEventHolder prior = current;
+ current = current.previous;
+ prior.recycle();
+ }
+ }
+
+ private void handleMotionEvent(MotionEvent event, int policyFlags) {
+ if (DEBUG) {
+ Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags);
+ }
+ // Since we do batch processing it is possible that by the time the
+ // next batch is processed the event handle had been set to null.
+ if (mEventHandler != null) {
+ mPm.userActivity(event.getEventTime(), false);
+ MotionEvent transformedEvent = MotionEvent.obtain(event);
+ mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
+ transformedEvent.recycle();
+ }
}
@Override
@@ -168,8 +328,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
private void enableFeatures() {
+ mMotionEventSequenceStarted = false;
+ mHoverEventSequenceStarted = false;
if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) {
- mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext);
+ mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext,
+ Display.DEFAULT_DISPLAY, mAms);
mEventHandler.setNext(this);
}
if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
@@ -181,9 +344,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mEventHandler = mTouchExplorer;
}
}
+ if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
+ mFilterKeyEvents = true;
+ }
}
- private void disableFeatures() {
+ void disableFeatures() {
if (mTouchExplorer != null) {
mTouchExplorer.clear();
mTouchExplorer.onDestroy();
@@ -195,10 +361,44 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mScreenMagnifier = null;
}
mEventHandler = null;
+ mKeyEventSequenceStarted = false;
+ mMotionEventSequenceStarted = false;
+ mHoverEventSequenceStarted = false;
+ mFilterKeyEvents = false;
}
@Override
public void onDestroy() {
/* ignore */
}
+
+ private static class MotionEventHolder {
+ private static final int MAX_POOL_SIZE = 32;
+ private static final SimplePool<MotionEventHolder> sPool =
+ new SimplePool<MotionEventHolder>(MAX_POOL_SIZE);
+
+ public int policyFlags;
+ public MotionEvent event;
+ public MotionEventHolder next;
+ public MotionEventHolder previous;
+
+ public static MotionEventHolder obtain(MotionEvent event, int policyFlags) {
+ MotionEventHolder holder = sPool.acquire();
+ if (holder == null) {
+ holder = new MotionEventHolder();
+ }
+ holder.event = MotionEvent.obtain(event);
+ holder.policyFlags = policyFlags;
+ return holder;
+ }
+
+ public void recycle() {
+ event.recycle();
+ event = null;
+ policyFlags = 0;
+ next = null;
+ previous = null;
+ sPool.release(this);
+ }
+ }
}
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index a34d44c..f1e4b0c 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.accessibility;
import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
-import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -62,16 +61,20 @@ import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindow;
import android.view.IWindowManager;
import android.view.InputDevice;
+import android.view.InputEventConsistencyVerifier;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.WindowInfo;
+import android.view.MagnificationSpec;
import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
@@ -87,9 +90,12 @@ import com.android.internal.statusbar.IStatusBarService;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -122,10 +128,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED =
"temporaryEnableAccessibilityStateUntilKeyguardRemoved";
+ private static final ComponentName sFakeAccessibilityServiceComponentName =
+ new ComponentName("foo.bar", "FakeService");
+
+ private static final String FUNCTION_DUMP = "dump";
+
private static final char COMPONENT_NAME_SEPARATOR = ':';
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
+ private static final int MAX_POOL_SIZE = 10;
+
private static int sIdCounter = 0;
private static int sNextWindowId;
@@ -134,6 +147,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private final Object mLock = new Object();
+ private final Pool<PendingEvent> mPendingEventPool =
+ new SimplePool<PendingEvent>(MAX_POOL_SIZE);
+
private final SimpleStringSplitter mStringColonSplitter =
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
@@ -154,8 +170,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private final MainHandler mMainHandler;
- private Service mUiAutomationService;
-
private Service mQueryBridge;
private AlertDialog mEnableTouchExplorationDialog;
@@ -164,6 +178,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private boolean mHasInputFilter;
+ private final Set<ComponentName> mTempComponentNameSet = new HashSet<ComponentName>();
+
+ private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
+ new ArrayList<AccessibilityServiceInfo>();
+
private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
new RemoteCallbackList<IAccessibilityManagerClient>();
@@ -174,11 +193,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
- private final TempUserStateChangeMemento mTempStateChangeForCurrentUserMemento =
- new TempUserStateChangeMemento();
-
private int mCurrentUserId = UserHandle.USER_OWNER;
+ //TODO: Remove this hack
+ private boolean mInitialized;
+
private UserState getCurrentUserStateLocked() {
return getUserStateLocked(mCurrentUserId);
}
@@ -221,10 +240,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return;
}
// We will update when the automation service dies.
- if (mUiAutomationService == null) {
- UserState userState = getCurrentUserStateLocked();
- populateInstalledAccessibilityServiceLocked(userState);
- manageServicesLocked(userState);
+ UserState userState = getCurrentUserStateLocked();
+ // We have to reload the installed services since some services may
+ // have different attributes, resolve info (does not support equals),
+ // etc. Remove them then to force reload. Do it even if automation is
+ // running since when it goes away, we will have to reload as well.
+ userState.mInstalledServices.clear();
+ if (userState.mUiAutomationService == null) {
+ if (readConfigurationForUserStateLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
@@ -236,8 +261,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (userId != mCurrentUserId) {
return;
}
- UserState state = getUserStateLocked(userId);
- Iterator<ComponentName> it = state.mEnabledServices.iterator();
+ UserState userState = getUserStateLocked(userId);
+ Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
ComponentName comp = it.next();
String compPkg = comp.getPackageName();
@@ -246,13 +271,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// Update the enabled services setting.
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- state.mEnabledServices, userId);
+ userState.mEnabledServices, userId);
// Update the touch exploration granted services setting.
- state.mTouchExplorationGrantedServices.remove(comp);
+ userState.mTouchExplorationGrantedServices.remove(comp);
persistComponentNamesToSettingLocked(
Settings.Secure.
- TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
- state.mEnabledServices, userId);
+ TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ userState.mTouchExplorationGrantedServices, userId);
+ // We will update when the automation service dies.
+ if (userState.mUiAutomationService == null) {
+ onUserStateChangedLocked(userState);
+ }
return;
}
}
@@ -267,8 +296,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (userId != mCurrentUserId) {
return false;
}
- UserState state = getUserStateLocked(userId);
- Iterator<ComponentName> it = state.mEnabledServices.iterator();
+ UserState userState = getUserStateLocked(userId);
+ Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
ComponentName comp = it.next();
String compPkg = comp.getPackageName();
@@ -280,7 +309,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
it.remove();
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- state.mEnabledServices, userId);
+ userState.mEnabledServices, userId);
+ // We will update when the automation service dies.
+ if (userState.mUiAutomationService == null) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
@@ -307,7 +340,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- restoreStateFromMementoIfNeeded();
+ // We will update when the automation service dies.
+ UserState userState = getCurrentUserStateLocked();
+ if (userState.mUiAutomationService == null) {
+ if (readConfigurationForUserStateLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
+ }
}
}
}, UserHandle.ALL, intentFilter, null, null);
@@ -326,7 +365,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (DEBUG) {
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
- return getClientState(userState);
+ return userState.getClientState();
} else {
userState.mClients.register(client);
// If this client is not for the current user we do not
@@ -336,7 +375,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
Slog.i(LOG_TAG, "Added user client for pid:" + Binder.getCallingPid()
+ " and userId:" + mCurrentUserId);
}
- return (resolvedUserId == mCurrentUserId) ? getClientState(userState) : 0;
+ return (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0;
}
}
}
@@ -370,7 +409,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
synchronized (mLock) {
final int resolvedUserId = mSecurityPolicy
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
- return getUserStateLocked(resolvedUserId).mInstalledServices;
+ // The automation service is a fake one and should not be reported
+ // to clients as being installed - it really is not.
+ UserState userState = getUserStateLocked(resolvedUserId);
+ if (userState.mUiAutomationService != null) {
+ List<AccessibilityServiceInfo> installedServices =
+ new ArrayList<AccessibilityServiceInfo>();
+ installedServices.addAll(userState.mInstalledServices);
+ installedServices.remove(userState.mUiAutomationService);
+ return installedServices;
+ }
+ return userState.mInstalledServices;
}
}
@@ -380,9 +429,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
synchronized (mLock) {
final int resolvedUserId = mSecurityPolicy
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
+
+ // The automation service is a fake one and should not be reported
+ // to clients as being enabled. The automation service is always the
+ // only active one, if it exists.
+ UserState userState = getUserStateLocked(resolvedUserId);
+ if (userState.mUiAutomationService != null) {
+ return Collections.emptyList();
+ }
+
result = mEnabledServicesForFeedbackTempList;
result.clear();
- List<Service> services = getUserStateLocked(resolvedUserId).mServices;
+ List<Service> services = userState.mBoundServices;
while (feedbackType != 0) {
final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType));
feedbackType &= ~feedbackTypeBit;
@@ -407,7 +465,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (resolvedUserId != mCurrentUserId) {
return;
}
- services = getUserStateLocked(resolvedUserId).mServices;
+ services = getUserStateLocked(resolvedUserId).mBoundServices;
}
for (int i = 0, count = services.size(); i < count; i++) {
Service service = services.get(i);
@@ -507,33 +565,63 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return -1;
}
- public void registerUiTestAutomationService(IAccessibilityServiceClient serviceClient,
+ public void registerUiTestAutomationService(IBinder owner,
+ IAccessibilityServiceClient serviceClient,
AccessibilityServiceInfo accessibilityServiceInfo) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
- ComponentName componentName = new ComponentName("foo.bar",
- "AutomationAccessibilityService");
+
+ accessibilityServiceInfo.setComponentName(sFakeAccessibilityServiceComponentName);
+
synchronized (mLock) {
- // If an automation services is connected to the system all services are stopped
- // so the automation one is the only one running. Settings are not changed so when
- // the automation service goes away the state is restored from the settings.
UserState userState = getCurrentUserStateLocked();
- unbindAllServicesLocked(userState);
- // If necessary enable accessibility and announce that.
- if (!userState.mIsAccessibilityEnabled) {
- userState.mIsAccessibilityEnabled = true;
+ if (userState.mUiAutomationService != null) {
+ throw new IllegalStateException("UiAutomationService " + serviceClient
+ + "already registered!");
+ }
+
+ try {
+ owner.linkToDeath(userState.mUiAutomationSerivceOnwerDeathRecipient, 0);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Couldn't register for the death of a"
+ + " UiTestAutomationService!", re);
+ return;
}
- // No touch exploration.
+
+ userState.mUiAutomationServiceOwner = owner;
+ userState.mUiAutomationServiceClient = serviceClient;
+
+ // Set the temporary state.
+ userState.mIsAccessibilityEnabled = true;
userState.mIsTouchExplorationEnabled = false;
+ userState.mIsEnhancedWebAccessibilityEnabled = false;
+ userState.mIsDisplayMagnificationEnabled = false;
+ userState.mInstalledServices.add(accessibilityServiceInfo);
+ userState.mEnabledServices.clear();
+ userState.mEnabledServices.add(sFakeAccessibilityServiceComponentName);
+ userState.mTouchExplorationGrantedServices.add(sFakeAccessibilityServiceComponentName);
- // Hook the automation service up.
- mUiAutomationService = new Service(mCurrentUserId, componentName,
- accessibilityServiceInfo, true);
- mUiAutomationService.onServiceConnected(componentName, serviceClient.asBinder());
+ // Use the new state instead of settings.
+ onUserStateChangedLocked(userState);
+ }
+ }
- updateInputFilterLocked(userState);
- scheduleSendStateToClientsLocked(userState);
+ public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
+ synchronized (mLock) {
+ UserState userState = getCurrentUserStateLocked();
+ // Automation service is not bound, so pretend it died to perform clean up.
+ if (userState.mUiAutomationService != null
+ && serviceClient != null
+ && userState.mUiAutomationService != null
+ && userState.mUiAutomationService.mServiceInterface != null
+ && userState.mUiAutomationService.mServiceInterface.asBinder()
+ == serviceClient.asBinder()) {
+ userState.mUiAutomationService.binderDied();
+ } else {
+ throw new IllegalStateException("UiAutomationService " + serviceClient
+ + " not registered!");
+ }
}
}
@@ -550,40 +638,45 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return;
}
synchronized (mLock) {
- UserState userState = getCurrentUserStateLocked();
- // Stash the old state so we can restore it when the keyguard is gone.
- mTempStateChangeForCurrentUserMemento.initialize(mCurrentUserId, getCurrentUserStateLocked());
// Set the temporary state.
+ UserState userState = getCurrentUserStateLocked();
+
+ // This is a nop if UI automation is enabled.
+ if (userState.mUiAutomationService != null) {
+ return;
+ }
+
userState.mIsAccessibilityEnabled = true;
- userState.mIsTouchExplorationEnabled= touchExplorationEnabled;
+ userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
+ userState.mIsEnhancedWebAccessibilityEnabled = false;
userState.mIsDisplayMagnificationEnabled = false;
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
+ userState.mBindingServices.clear();
userState.mTouchExplorationGrantedServices.clear();
userState.mTouchExplorationGrantedServices.add(service);
- // Update the internal state.
- performServiceManagementLocked(userState);
- updateInputFilterLocked(userState);
- scheduleSendStateToClientsLocked(userState);
+
+ // User the current state instead settings.
+ onUserStateChangedLocked(userState);
}
}
- public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
+ boolean onGesture(int gestureId) {
synchronized (mLock) {
- // Automation service is not bound, so pretend it died to perform clean up.
- if (mUiAutomationService != null && mUiAutomationService.mServiceInterface != null
- && serviceClient != null && mUiAutomationService.mServiceInterface
- .asBinder() == serviceClient.asBinder()) {
- mUiAutomationService.binderDied();
+ boolean handled = notifyGestureLocked(gestureId, false);
+ if (!handled) {
+ handled = notifyGestureLocked(gestureId, true);
}
+ return handled;
}
}
- boolean onGesture(int gestureId) {
+ boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
synchronized (mLock) {
- boolean handled = notifyGestureLocked(gestureId, false);
+ KeyEvent localClone = KeyEvent.obtain(event);
+ boolean handled = notifyKeyEventLocked(localClone, policyFlags, false);
if (!handled) {
- handled = notifyGestureLocked(gestureId, true);
+ handled = notifyKeyEventLocked(localClone, policyFlags, true);
}
return handled;
}
@@ -617,6 +710,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return false;
}
focus.getBoundsInScreen(outBounds);
+
+ MagnificationSpec spec = service.getCompatibleMagnificationSpec(focus.getWindowId());
+ if (spec != null && !spec.isNop()) {
+ outBounds.offset((int) -spec.offsetX, (int) -spec.offsetY);
+ outBounds.scale(1 / spec.scale);
+ }
+
// Clip to the window rectangle.
Rect windowBounds = mTempRect;
getActiveWindowBounds(windowBounds);
@@ -624,6 +724,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// Clip to the screen rectangle.
mDefaultDisplay.getRealSize(mTempPoint);
outBounds.intersect(0, 0, mTempPoint.x, mTempPoint.y);
+
return true;
} finally {
client.removeConnection(connectionId);
@@ -644,19 +745,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
token = getCurrentUserStateLocked().mWindowTokens.get(windowId);
}
}
- WindowInfo info = null;
try {
- info = mWindowManagerService.getWindowInfo(token);
- if (info != null) {
- outBounds.set(info.frame);
+ mWindowManagerService.getWindowFrame(token, outBounds);
+ if (!outBounds.isEmpty()) {
return true;
}
} catch (RemoteException re) {
/* ignore */
- } finally {
- if (info != null) {
- info.recycle();
- }
}
return false;
}
@@ -673,15 +768,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mSecurityPolicy.onTouchInteractionEnd();
}
+ void onMagnificationStateChanged() {
+ notifyClearAccessibilityNodeInfoCacheLocked();
+ }
+
private void switchUser(int userId) {
synchronized (mLock) {
- // The user switched so we do not need to restore the current user
- // state since we will fully rebuild it when he becomes current again.
- mTempStateChangeForCurrentUserMemento.clear();
+ if (mCurrentUserId == userId && mInitialized) {
+ return;
+ }
// Disconnect from services for the old user.
UserState oldUserState = getUserStateLocked(mCurrentUserId);
- unbindAllServicesLocked(oldUserState);
+ oldUserState.onSwitchToAnotherUser();
// Disable the local managers for the old user.
if (oldUserState.mClients.getRegisteredCallbackCount() > 0) {
@@ -696,9 +795,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// The user changed.
mCurrentUserId = userId;
- // Recreate the internal state for the new user.
- mMainHandler.obtainMessage(MainHandler.MSG_SEND_RECREATE_INTERNAL_STATE,
- mCurrentUserId, 0).sendToTarget();
+ UserState userState = getCurrentUserStateLocked();
+ if (userState.mUiAutomationService != null) {
+ // Switching users disables the UI automation service.
+ userState.mUiAutomationService.binderDied();
+ }
+
+ readConfigurationForUserStateLocked(userState);
+ // Even if reading did not yield change, we have to update
+ // the state since the context in which the current user
+ // state was used has changed since it was inactive.
+ onUserStateChangedLocked(userState);
if (announceNewUser) {
// Schedule announcement of the current user if needed.
@@ -714,25 +821,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- private void restoreStateFromMementoIfNeeded() {
- synchronized (mLock) {
- if (mTempStateChangeForCurrentUserMemento.mUserId != UserHandle.USER_NULL) {
- UserState userState = getCurrentUserStateLocked();
- // Restore the state from the memento.
- mTempStateChangeForCurrentUserMemento.applyTo(userState);
- mTempStateChangeForCurrentUserMemento.clear();
- // Update the internal state.
- performServiceManagementLocked(userState);
- updateInputFilterLocked(userState);
- scheduleSendStateToClientsLocked(userState);
- }
- }
- }
-
private Service getQueryBridge() {
if (mQueryBridge == null) {
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
- mQueryBridge = new Service(UserHandle.USER_NULL, null, info, true);
+ info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
+ mQueryBridge = new Service(UserHandle.USER_NULL,
+ sFakeAccessibilityServiceComponentName, info);
}
return mQueryBridge;
}
@@ -748,8 +842,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// behavior is observed from different combinations of
// enabled accessibility services.
UserState state = getCurrentUserStateLocked();
- for (int i = state.mServices.size() - 1; i >= 0; i--) {
- Service service = state.mServices.get(i);
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ Service service = state.mBoundServices.get(i);
if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) {
service.notifyGesture(gestureId);
return true;
@@ -758,6 +852,39 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return false;
}
+ private boolean notifyKeyEventLocked(KeyEvent event, int policyFlags, boolean isDefault) {
+ // TODO: Now we are giving the key events to the last enabled
+ // service that can handle them Ideally, the user should
+ // make the call which service handles key events. However,
+ // only one service should handle key events to avoid user
+ // frustration when different behavior is observed from
+ // different combinations of enabled accessibility services.
+ UserState state = getCurrentUserStateLocked();
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ Service service = state.mBoundServices.get(i);
+ // Key events are handled only by services that declared
+ // this capability and requested to filter key events.
+ if (!service.mRequestFilterKeyEvents ||
+ (service.mAccessibilityServiceInfo.getCapabilities() & AccessibilityServiceInfo
+ .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
+ continue;
+ }
+ if (service.mIsDefault == isDefault) {
+ service.notifyKeyEvent(event, policyFlags);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void notifyClearAccessibilityNodeInfoCacheLocked() {
+ UserState state = getCurrentUserStateLocked();
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ Service service = state.mBoundServices.get(i);
+ service.notifyClearAccessibilityNodeInfoCache();
+ }
+ }
+
/**
* Removes an AccessibilityInteractionConnection.
*
@@ -779,8 +906,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- private void populateInstalledAccessibilityServiceLocked(UserState userState) {
- userState.mInstalledServices.clear();
+ private boolean readInstalledAccessibilityServiceLocked(UserState userState) {
+ mTempAccessibilityServiceInfoList.clear();
List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
new Intent(AccessibilityService.SERVICE_INTERFACE),
@@ -801,28 +928,53 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
AccessibilityServiceInfo accessibilityServiceInfo;
try {
accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
- userState.mInstalledServices.add(accessibilityServiceInfo);
+ mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);
} catch (XmlPullParserException xppe) {
Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
} catch (IOException ioe) {
Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", ioe);
}
}
+
+ if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) {
+ userState.mInstalledServices.clear();
+ userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList);
+ mTempAccessibilityServiceInfoList.clear();
+ return true;
+ }
+
+ mTempAccessibilityServiceInfoList.clear();
+ return false;
}
- private void populateEnabledAccessibilityServicesLocked(UserState userState) {
- populateComponentNamesFromSettingLocked(
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
- userState.mUserId,
- userState.mEnabledServices);
+ private boolean readEnabledAccessibilityServicesLocked(UserState userState) {
+ mTempComponentNameSet.clear();
+ readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mUserId, mTempComponentNameSet);
+ if (!mTempComponentNameSet.equals(userState.mEnabledServices)) {
+ userState.mEnabledServices.clear();
+ userState.mEnabledServices.addAll(mTempComponentNameSet);
+ mTempComponentNameSet.clear();
+ return true;
+ }
+ mTempComponentNameSet.clear();
+ return false;
}
- private void populateTouchExplorationGrantedAccessibilityServicesLocked(
+ private boolean readTouchExplorationGrantedAccessibilityServicesLocked(
UserState userState) {
- populateComponentNamesFromSettingLocked(
+ mTempComponentNameSet.clear();
+ readComponentNamesFromSettingLocked(
Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
- userState.mUserId,
- userState.mTouchExplorationGrantedServices);
+ userState.mUserId, mTempComponentNameSet);
+ if (!mTempComponentNameSet.equals(userState.mTouchExplorationGrantedServices)) {
+ userState.mTouchExplorationGrantedServices.clear();
+ userState.mTouchExplorationGrantedServices.addAll(mTempComponentNameSet);
+ mTempComponentNameSet.clear();
+ return true;
+ }
+ mTempComponentNameSet.clear();
+ return false;
}
/**
@@ -836,8 +988,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
boolean isDefault) {
try {
UserState state = getCurrentUserStateLocked();
- for (int i = 0, count = state.mServices.size(); i < count; i++) {
- Service service = state.mServices.get(i);
+ for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
+ Service service = state.mBoundServices.get(i);
if (service.mIsDefault == isDefault) {
if (canDispathEventLocked(service, event, state.mHandledFeedbackTypes)) {
@@ -854,24 +1006,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- /**
- * Adds a service for a user.
- *
- * @param service The service to add.
- * @param userId The user id.
- */
- private void tryAddServiceLocked(Service service, int userId) {
+ private void addServiceLocked(Service service, UserState userState) {
try {
- UserState userState = getUserStateLocked(userId);
- if (userState.mServices.contains(service)) {
- return;
- }
- service.linkToOwnDeath();
- userState.mServices.add(service);
+ service.linkToOwnDeathLocked();
+ userState.mBoundServices.add(service);
userState.mComponentNameToServiceMap.put(service.mComponentName, service);
- updateInputFilterLocked(userState);
- tryEnableTouchExplorationLocked(service);
- } catch (RemoteException e) {
+ } catch (RemoteException re) {
/* do nothing */
}
}
@@ -882,18 +1022,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
* @param service The service.
* @return True if the service was removed, false otherwise.
*/
- private boolean tryRemoveServiceLocked(Service service) {
- UserState userState = getUserStateLocked(service.mUserId);
- final boolean removed = userState.mServices.remove(service);
- if (!removed) {
- return false;
- }
+ private void removeServiceLocked(Service service, UserState userState) {
+ userState.mBoundServices.remove(service);
userState.mComponentNameToServiceMap.remove(service.mComponentName);
- service.unlinkToOwnDeath();
- service.dispose();
- updateInputFilterLocked(userState);
- tryDisableTouchExplorationLocked(service);
- return removed;
+ service.unlinkToOwnDeathLocked();
}
/**
@@ -912,12 +1044,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private boolean canDispathEventLocked(Service service, AccessibilityEvent event,
int handledFeedbackTypes) {
- if (!service.canReceiveEvents()) {
+ if (!service.canReceiveEventsLocked()) {
return false;
}
if (!event.isImportantForAccessibility()
- && !service.mIncludeNotImportantViews) {
+ && (service.mFetchFlags
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
return false;
}
@@ -940,29 +1073,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return false;
}
- /**
- * Manages services by starting enabled ones and stopping disabled ones.
- */
- private void manageServicesLocked(UserState userState) {
- final int enabledInstalledServicesCount = updateServicesStateLocked(userState);
- // No enabled installed services => disable accessibility to avoid
- // sending accessibility events with no recipient across processes.
- if (userState.mIsAccessibilityEnabled && enabledInstalledServicesCount == 0) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId);
- }
- }
-
- /**
- * Unbinds all bound services for a user.
- *
- * @param userState The user state.
- */
private void unbindAllServicesLocked(UserState userState) {
- List<Service> services = userState.mServices;
+ List<Service> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
Service service = services.get(i);
- if (service.unbind()) {
+ if (service.unbindLocked()) {
i--;
count--;
}
@@ -977,7 +1092,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
* @param userId The user id.
* @param outComponentNames The output component names.
*/
- private void populateComponentNamesFromSettingLocked(String settingName, int userId,
+ private void readComponentNamesFromSettingLocked(String settingName, int userId,
Set<ComponentName> outComponentNames) {
String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
settingName, userId);
@@ -1018,19 +1133,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
settingName, builder.toString(), userId);
}
- /**
- * Updates the state of each service by starting (or keeping running) enabled ones and
- * stopping the rest.
- *
- * @param userState The user state for which to do that.
- * @return The number of enabled installed services.
- */
- private int updateServicesStateLocked(UserState userState) {
+ private void manageServicesLocked(UserState userState) {
Map<ComponentName, Service> componentNameToServiceMap =
userState.mComponentNameToServiceMap;
boolean isEnabled = userState.mIsAccessibilityEnabled;
- int enabledInstalledServices = 0;
for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
ComponentName componentName = ComponentName.unflattenFromString(
@@ -1038,43 +1145,71 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
Service service = componentNameToServiceMap.get(componentName);
if (isEnabled) {
+ // Wait for the binding if it is in process.
+ if (userState.mBindingServices.contains(componentName)) {
+ continue;
+ }
if (userState.mEnabledServices.contains(componentName)) {
if (service == null) {
- service = new Service(userState.mUserId, componentName,
- installedService, false);
+ service = new Service(userState.mUserId, componentName, installedService);
+ } else if (userState.mBoundServices.contains(service)) {
+ continue;
}
- service.bind();
- enabledInstalledServices++;
+ service.bindLocked();
} else {
if (service != null) {
- service.unbind();
+ service.unbindLocked();
}
}
} else {
if (service != null) {
- service.unbind();
+ service.unbindLocked();
+ } else {
+ userState.mBindingServices.remove(componentName);
}
}
}
- return enabledInstalledServices;
+ // No enabled installed services => disable accessibility to avoid
+ // sending accessibility events with no recipient across processes.
+ if (isEnabled && userState.mEnabledServices.isEmpty()) {
+ userState.mIsAccessibilityEnabled = false;
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId);
+ }
}
- private void scheduleSendStateToClientsLocked(UserState userState) {
- if (mGlobalClients.getRegisteredCallbackCount() > 0
- || userState.mClients.getRegisteredCallbackCount() > 0) {
- final int clientState = getClientState(userState);
+ private void scheduleUpdateClientsIfNeededLocked(UserState userState) {
+ final int clientState = userState.getClientState();
+ if (userState.mLastSentClientState != clientState
+ && (mGlobalClients.getRegisteredCallbackCount() > 0
+ || userState.mClients.getRegisteredCallbackCount() > 0)) {
+ userState.mLastSentClientState = clientState;
mMainHandler.obtainMessage(MainHandler.MSG_SEND_STATE_TO_CLIENTS,
clientState, userState.mUserId) .sendToTarget();
}
}
- private void updateInputFilterLocked(UserState userState) {
+ private void scheduleUpdateInputFilter(UserState userState) {
+ mMainHandler.obtainMessage(MainHandler.MSG_UPDATE_INPUT_FILTER, userState).sendToTarget();
+ }
+
+ private void updateInputFilter(UserState userState) {
boolean setInputFilter = false;
AccessibilityInputFilter inputFilter = null;
synchronized (mLock) {
- if ((userState.mIsAccessibilityEnabled && userState.mIsTouchExplorationEnabled)
- || userState.mIsDisplayMagnificationEnabled) {
+ int flags = 0;
+ if (userState.mIsDisplayMagnificationEnabled) {
+ flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
+ }
+ // Touch exploration without accessibility makes no sense.
+ if (userState.mIsAccessibilityEnabled && userState.mIsTouchExplorationEnabled) {
+ flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
+ }
+ if (userState.mIsFilterKeyEventsEnabled) {
+ flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
+ }
+ if (flags != 0) {
if (!mHasInputFilter) {
mHasInputFilter = true;
if (mInputFilter == null) {
@@ -1084,18 +1219,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
inputFilter = mInputFilter;
setInputFilter = true;
}
- int flags = 0;
- if (userState.mIsDisplayMagnificationEnabled) {
- flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
- }
- if (userState.mIsTouchExplorationEnabled) {
- flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
- }
mInputFilter.setEnabledFeatures(flags);
} else {
if (mHasInputFilter) {
mHasInputFilter = false;
- mInputFilter.setEnabledFeatures(0);
+ mInputFilter.disableFeatures();
inputFilter = null;
setInputFilter = true;
}
@@ -1111,9 +1239,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
private void showEnableTouchExplorationDialog(final Service service) {
- String label = service.mResolveInfo.loadLabel(
- mContext.getPackageManager()).toString();
synchronized (mLock) {
+ String label = service.mResolveInfo.loadLabel(
+ mContext.getPackageManager()).toString();
+
final UserState state = getCurrentUserStateLocked();
if (state.mIsTouchExplorationEnabled) {
return;
@@ -1125,136 +1254,273 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mEnableTouchExplorationDialog = new AlertDialog.Builder(mContext)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // The user allowed the service to toggle touch exploration.
- state.mTouchExplorationGrantedServices.add(service.mComponentName);
- persistComponentNamesToSettingLocked(
- Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
- state.mTouchExplorationGrantedServices, state.mUserId);
- // Enable touch exploration.
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
- service.mUserId);
- }
- })
- .setNegativeButton(android.R.string.cancel, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- })
- .setTitle(R.string.enable_explore_by_touch_warning_title)
- .setMessage(mContext.getString(
- R.string.enable_explore_by_touch_warning_message, label))
- .create();
- mEnableTouchExplorationDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
- mEnableTouchExplorationDialog.getWindow().getAttributes().privateFlags
- |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
- mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true);
- mEnableTouchExplorationDialog.show();
- }
- }
-
- private int getClientState(UserState userState) {
- int clientState = 0;
- if (userState.mIsAccessibilityEnabled) {
- clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // The user allowed the service to toggle touch exploration.
+ state.mTouchExplorationGrantedServices.add(service.mComponentName);
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ state.mTouchExplorationGrantedServices, state.mUserId);
+ // Enable touch exploration.
+ UserState userState = getUserStateLocked(service.mUserId);
+ userState.mIsTouchExplorationEnabled = true;
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
+ service.mUserId);
+ onUserStateChangedLocked(userState);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .setTitle(R.string.enable_explore_by_touch_warning_title)
+ .setMessage(mContext.getString(
+ R.string.enable_explore_by_touch_warning_message, label))
+ .create();
+ mEnableTouchExplorationDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ mEnableTouchExplorationDialog.getWindow().getAttributes().privateFlags
+ |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true);
+ mEnableTouchExplorationDialog.show();
}
- // Touch exploration relies on enabled accessibility.
- if (userState.mIsAccessibilityEnabled && userState.mIsTouchExplorationEnabled) {
- clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
- }
- return clientState;
}
- private void recreateInternalStateLocked(UserState userState) {
- populateInstalledAccessibilityServiceLocked(userState);
- populateEnabledAccessibilityServicesLocked(userState);
- populateTouchExplorationGrantedAccessibilityServicesLocked(userState);
-
- handleTouchExplorationEnabledSettingChangedLocked(userState);
- handleDisplayMagnificationEnabledSettingChangedLocked(userState);
- handleAccessibilityEnabledSettingChangedLocked(userState);
+ private void onUserStateChangedLocked(UserState userState) {
+ // TODO: Remove this hack
+ mInitialized = true;
+ updateLegacyCapabilities(userState);
+ updateServicesLocked(userState);
+ updateFilterKeyEventsLocked(userState);
+ updateTouchExplorationLocked(userState);
+ updateEnhancedWebAccessibilityLocked(userState);
+ scheduleUpdateInputFilter(userState);
+ scheduleUpdateClientsIfNeededLocked(userState);
+ }
- performServiceManagementLocked(userState);
- updateInputFilterLocked(userState);
- scheduleSendStateToClientsLocked(userState);
+ private void updateLegacyCapabilities(UserState userState) {
+ // Up to JB-MR1 we had a white list with services that can enable touch
+ // exploration. When a service is first started we show a dialog to the
+ // use to get a permission to white list the service.
+ final int installedServiceCount = userState.mInstalledServices.size();
+ for (int i = 0; i < installedServiceCount; i++) {
+ AccessibilityServiceInfo serviceInfo = userState.mInstalledServices.get(i);
+ ResolveInfo resolveInfo = serviceInfo.getResolveInfo();
+ if ((serviceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION) == 0
+ && resolveInfo.serviceInfo.applicationInfo.targetSdkVersion
+ <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ ComponentName componentName = new ComponentName(
+ resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+ if (userState.mTouchExplorationGrantedServices.contains(componentName)) {
+ serviceInfo.setCapabilities(serviceInfo.getCapabilities()
+ | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION);
+ }
+ }
+ }
}
- private void handleAccessibilityEnabledSettingChangedLocked(UserState userState) {
- userState.mIsAccessibilityEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId) == 1;
+ private void updateFilterKeyEventsLocked(UserState userState) {
+ final int serviceCount = userState.mBoundServices.size();
+ for (int i = 0; i < serviceCount; i++) {
+ Service service = userState.mBoundServices.get(i);
+ if (service.mRequestFilterKeyEvents
+ && (service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo
+ .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) != 0) {
+ userState.mIsFilterKeyEventsEnabled = true;
+ return;
+ }
+ }
+ userState.mIsFilterKeyEventsEnabled = false;
}
- private void performServiceManagementLocked(UserState userState) {
- if (userState.mIsAccessibilityEnabled ) {
+ private void updateServicesLocked(UserState userState) {
+ if (userState.mIsAccessibilityEnabled) {
manageServicesLocked(userState);
} else {
unbindAllServicesLocked(userState);
}
}
- private void handleTouchExplorationEnabledSettingChangedLocked(UserState userState) {
- userState.mIsTouchExplorationEnabled = Settings.Secure.getIntForUser(
+ private boolean readConfigurationForUserStateLocked(UserState userState) {
+ boolean somthingChanged = false;
+ somthingChanged |= readAccessibilityEnabledSettingLocked(userState);
+ somthingChanged |= readInstalledAccessibilityServiceLocked(userState);
+ somthingChanged |= readEnabledAccessibilityServicesLocked(userState);
+ somthingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);
+ somthingChanged |= readTouchExplorationEnabledSettingLocked(userState);
+ somthingChanged |= readEnhancedWebAccessibilityEnabledChangedLocked(userState);
+ somthingChanged |= readDisplayMagnificationEnabledSettingLocked(userState);
+ return somthingChanged;
+ }
+
+ private boolean readAccessibilityEnabledSettingLocked(UserState userState) {
+ final boolean accessibilityEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId) == 1;
+ if (accessibilityEnabled != userState.mIsAccessibilityEnabled) {
+ userState.mIsAccessibilityEnabled = accessibilityEnabled;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean readTouchExplorationEnabledSettingLocked(UserState userState) {
+ final boolean touchExplorationEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1;
+ if (touchExplorationEnabled != userState.mIsTouchExplorationEnabled) {
+ userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
+ return true;
+ }
+ return false;
}
- private void handleDisplayMagnificationEnabledSettingChangedLocked(UserState userState) {
- userState.mIsDisplayMagnificationEnabled = Settings.Secure.getIntForUser(
+ private boolean readDisplayMagnificationEnabledSettingLocked(UserState userState) {
+ final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
0, userState.mUserId) == 1;
+ if (displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled) {
+ userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled;
+ return true;
+ }
+ return false;
}
- private void handleTouchExplorationGrantedAccessibilityServicesChangedLocked(
- UserState userState) {
- final int serviceCount = userState.mServices.size();
+ private boolean readEnhancedWebAccessibilityEnabledChangedLocked(UserState userState) {
+ final boolean enhancedWeAccessibilityEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+ 0, userState.mUserId) == 1;
+ if (enhancedWeAccessibilityEnabled != userState.mIsEnhancedWebAccessibilityEnabled) {
+ userState.mIsEnhancedWebAccessibilityEnabled = enhancedWeAccessibilityEnabled;
+ return true;
+ }
+ return false;
+ }
+
+ private void updateTouchExplorationLocked(UserState userState) {
+ boolean enabled = false;
+ final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
- Service service = userState.mServices.get(i);
- if (service.mRequestTouchExplorationMode
- && userState.mTouchExplorationGrantedServices.contains(
- service.mComponentName)) {
- tryEnableTouchExplorationLocked(service);
- return;
+ Service service = userState.mBoundServices.get(i);
+ if (canRequestAndRequestsTouchExplorationLocked(service)) {
+ enabled = true;
+ break;
}
}
- if (userState.mIsTouchExplorationEnabled) {
+ if (enabled != userState.mIsTouchExplorationEnabled) {
+ userState.mIsTouchExplorationEnabled = enabled;
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId);
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0,
+ userState.mUserId);
}
}
- private void tryEnableTouchExplorationLocked(final Service service) {
- UserState userState = getUserStateLocked(service.mUserId);
- if (!userState.mIsTouchExplorationEnabled && service.mRequestTouchExplorationMode
- && service.canReceiveEvents()) {
- final boolean canToggleTouchExploration =
- userState.mTouchExplorationGrantedServices.contains(service.mComponentName);
- if (!service.mIsAutomation && !canToggleTouchExploration) {
- showEnableTouchExplorationDialog(service);
- } else {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1, userState.mUserId);
+ private boolean canRequestAndRequestsTouchExplorationLocked(Service service) {
+ // Service not ready or cannot request the feature - well nothing to do.
+ if (!service.canReceiveEventsLocked() || !service.mRequestTouchExplorationMode) {
+ return false;
+ }
+ // UI test automation service can always enable it.
+ if (service.mIsAutomation) {
+ return true;
+ }
+ if (service.mResolveInfo.serviceInfo.applicationInfo.targetSdkVersion
+ <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // Up to JB-MR1 we had a white list with services that can enable touch
+ // exploration. When a service is first started we show a dialog to the
+ // use to get a permission to white list the service.
+ UserState userState = getUserStateLocked(service.mUserId);
+ if (userState.mTouchExplorationGrantedServices.contains(service.mComponentName)) {
+ return true;
+ } else if (mEnableTouchExplorationDialog == null
+ || !mEnableTouchExplorationDialog.isShowing()) {
+ mMainHandler.obtainMessage(
+ MainHandler.MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG,
+ service).sendToTarget();
+ }
+ } else {
+ // Starting in JB-MR2 we request an accessibility service to declare
+ // certain capabilities in its meta-data to allow it to enable the
+ // corresponding features.
+ if ((service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION) != 0) {
+ return true;
}
}
+ return false;
}
- private void tryDisableTouchExplorationLocked(Service service) {
- UserState userState = getUserStateLocked(service.mUserId);
- if (userState.mIsTouchExplorationEnabled) {
- final int serviceCount = userState.mServices.size();
- for (int i = 0; i < serviceCount; i++) {
- Service other = userState.mServices.get(i);
- if (other != service && other.mRequestTouchExplorationMode) {
- return;
- }
+ private void updateEnhancedWebAccessibilityLocked(UserState userState) {
+ boolean enabled = false;
+ final int serviceCount = userState.mBoundServices.size();
+ for (int i = 0; i < serviceCount; i++) {
+ Service service = userState.mBoundServices.get(i);
+ if (canRequestAndRequestsEnhancedWebAccessibilityLocked(service)) {
+ enabled = true;
+ break;
}
+ }
+ if (enabled != userState.mIsEnhancedWebAccessibilityEnabled) {
+ userState.mIsEnhancedWebAccessibilityEnabled = enabled;
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId);
+ Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, enabled ? 1 : 0,
+ userState.mUserId);
+ }
+ }
+
+ private boolean canRequestAndRequestsEnhancedWebAccessibilityLocked(Service service) {
+ if (!service.canReceiveEventsLocked() || !service.mRequestEnhancedWebAccessibility ) {
+ return false;
+ }
+ if (service.mIsAutomation || (service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
+ synchronized (mLock) {
+ pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
+ pw.println();
+ final int userCount = mUserStates.size();
+ for (int i = 0; i < userCount; i++) {
+ UserState userState = mUserStates.valueAt(i);
+ pw.append("User state[attributes:{id=" + userState.mUserId);
+ pw.append(", currentUser=" + (userState.mUserId == mCurrentUserId));
+ pw.append(", accessibilityEnabled=" + userState.mIsAccessibilityEnabled);
+ pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled);
+ pw.append(", displayMagnificationEnabled="
+ + userState.mIsDisplayMagnificationEnabled);
+ if (userState.mUiAutomationService != null) {
+ pw.append(", ");
+ userState.mUiAutomationService.dump(fd, pw, args);
+ pw.println();
+ }
+ pw.append("}");
+ pw.println();
+ pw.append(" services:{");
+ final int serviceCount = userState.mBoundServices.size();
+ for (int j = 0; j < serviceCount; j++) {
+ if (j > 0) {
+ pw.append(", ");
+ pw.println();
+ pw.append(" ");
+ }
+ Service service = userState.mBoundServices.get(j);
+ service.dump(fd, pw, args);
+ }
+ pw.println("}]");
+ pw.println();
+ }
}
}
@@ -1291,9 +1557,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
public static final int MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER = 1;
public static final int MSG_SEND_STATE_TO_CLIENTS = 2;
public static final int MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER = 3;
- public static final int MSG_SEND_RECREATE_INTERNAL_STATE = 4;
- public static final int MSG_UPDATE_ACTIVE_WINDOW = 5;
- public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 6;
+ public static final int MSG_UPDATE_ACTIVE_WINDOW = 4;
+ public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 5;
+ public static final int MSG_UPDATE_INPUT_FILTER = 6;
+ public static final int MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG = 7;
+ public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8;
public MainHandler(Looper looper) {
super(looper);
@@ -1312,6 +1580,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
event.recycle();
} break;
+ case MSG_SEND_KEY_EVENT_TO_INPUT_FILTER: {
+ KeyEvent event = (KeyEvent) msg.obj;
+ final int policyFlags = msg.arg1;
+ synchronized (mLock) {
+ if (mHasInputFilter && mInputFilter != null) {
+ mInputFilter.sendInputEvent(event, policyFlags);
+ }
+ }
+ event.recycle();
+ } break;
case MSG_SEND_STATE_TO_CLIENTS: {
final int clientState = msg.arg1;
final int userId = msg.arg2;
@@ -1322,13 +1600,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
final int userId = msg.arg1;
sendStateToClientsForUser(0, userId);
} break;
- case MSG_SEND_RECREATE_INTERNAL_STATE: {
- final int userId = msg.arg1;
- synchronized (mLock) {
- UserState userState = getUserStateLocked(userId);
- recreateInternalStateLocked(userState);
- }
- } break;
case MSG_UPDATE_ACTIVE_WINDOW: {
final int windowId = msg.arg1;
final int eventType = msg.arg2;
@@ -1337,6 +1608,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
case MSG_ANNOUNCE_NEW_USER_IF_NEEDED: {
announceNewUserIfNeeded();
} break;
+ case MSG_UPDATE_INPUT_FILTER: {
+ UserState userState = (UserState) msg.obj;
+ updateInputFilter(userState);
+ } break;
+ case MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG: {
+ Service service = (Service) msg.obj;
+ showEnableTouchExplorationDialog(service);
+ } break;
}
}
@@ -1383,6 +1662,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ private PendingEvent obtainPendingEventLocked(KeyEvent event, int policyFlags, int sequence) {
+ PendingEvent pendingEvent = mPendingEventPool.acquire();
+ if (pendingEvent == null) {
+ pendingEvent = new PendingEvent();
+ }
+ pendingEvent.event = event;
+ pendingEvent.policyFlags = policyFlags;
+ pendingEvent.sequence = sequence;
+ return pendingEvent;
+ }
+
+ private void recyclePendingEventLocked(PendingEvent pendingEvent) {
+ pendingEvent.clear();
+ mPendingEventPool.release(pendingEvent);
+ }
+
/**
* This class represents an accessibility service. It stores all per service
* data required for the service management, provides API for starting/stopping the
@@ -1392,11 +1687,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
* connection for the service.
*/
class Service extends IAccessibilityServiceConnection.Stub
- implements ServiceConnection, DeathRecipient {
-
- // We pick the MSB to avoid collision since accessibility event types are
- // used as message types allowing us to remove messages per event type.
- private static final int MSG_ON_GESTURE = 0x80000000;
+ implements ServiceConnection, DeathRecipient {;
final int mUserId;
@@ -1418,7 +1709,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
boolean mRequestTouchExplorationMode;
- boolean mIncludeNotImportantViews;
+ boolean mRequestEnhancedWebAccessibility;
+
+ boolean mRequestFilterKeyEvents;
+
+ int mFetchFlags;
long mNotificationTimeout;
@@ -1426,8 +1721,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
Intent mIntent;
- boolean mCanRetrieveScreenContent;
-
boolean mIsAutomation;
final Rect mTempBounds = new Rect();
@@ -1438,46 +1731,38 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
final SparseArray<AccessibilityEvent> mPendingEvents =
new SparseArray<AccessibilityEvent>();
- /**
- * Handler for delayed event dispatch.
- */
- public Handler mHandler = new Handler(mMainHandler.getLooper()) {
+ final KeyEventDispatcher mKeyEventDispatcher = new KeyEventDispatcher();
+
+ boolean mWasConnectedAndDied;
+
+ // Handler only for dispatching accessibility events since we use event
+ // types as message types allowing us to remove messages per event type.
+ public Handler mEventDispatchHandler = new Handler(mMainHandler.getLooper()) {
@Override
public void handleMessage(Message message) {
- final int type = message.what;
- switch (type) {
- case MSG_ON_GESTURE: {
- final int gestureId = message.arg1;
- notifyGestureInternal(gestureId);
- } break;
- default: {
- final int eventType = type;
- notifyAccessibilityEventInternal(eventType);
- } break;
- }
+ final int eventType = message.what;
+ notifyAccessibilityEventInternal(eventType);
}
};
+ // Handler for scheduling method invocations on the main thread.
+ public InvocationHandler mInvocationHandler = new InvocationHandler(
+ mMainHandler.getLooper());
+
public Service(int userId, ComponentName componentName,
- AccessibilityServiceInfo accessibilityServiceInfo, boolean isAutomation) {
+ AccessibilityServiceInfo accessibilityServiceInfo) {
mUserId = userId;
mResolveInfo = accessibilityServiceInfo.getResolveInfo();
mId = sIdCounter++;
mComponentName = componentName;
mAccessibilityServiceInfo = accessibilityServiceInfo;
- mIsAutomation = isAutomation;
- if (!isAutomation) {
- mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent();
- mRequestTouchExplorationMode =
- (accessibilityServiceInfo.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+ mIsAutomation = (sFakeAccessibilityServiceComponentName.equals(componentName));
+ if (!mIsAutomation) {
mIntent = new Intent().setComponent(mComponentName);
mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.accessibility_binding_label);
mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
- } else {
- mCanRetrieveScreenContent = true;
}
setDynamicallyConfigurableProperties(accessibilityServiceInfo);
}
@@ -1494,24 +1779,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (mIsAutomation || info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.JELLY_BEAN) {
- mIncludeNotImportantViews =
- (info.flags & FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ } else {
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ }
+ }
+
+ if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
+ } else {
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
}
mRequestTouchExplorationMode = (info.flags
& AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
-
- // If this service is up and running we may have to enable touch
- // exploration, otherwise this will happen when the service connects.
- synchronized (mLock) {
- if (canReceiveEvents()) {
- if (mRequestTouchExplorationMode) {
- tryEnableTouchExplorationLocked(this);
- } else {
- tryDisableTouchExplorationLocked(this);
- }
- }
- }
+ mRequestEnhancedWebAccessibility = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0;
+ mRequestFilterKeyEvents = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
}
/**
@@ -1519,9 +1805,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
*
* @return True if binding is successful.
*/
- public boolean bind() {
- if (!mIsAutomation && mService == null) {
- return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE, mUserId);
+ public boolean bindLocked() {
+ UserState userState = getUserStateLocked(mUserId);
+ if (!mIsAutomation) {
+ if (mService == null && mContext.bindServiceAsUser(
+ mIntent, this, Context.BIND_AUTO_CREATE, new UserHandle(mUserId))) {
+ userState.mBindingServices.add(mComponentName);
+ }
+ } else {
+ userState.mBindingServices.add(mComponentName);
+ mService = userState.mUiAutomationServiceClient.asBinder();
+ onServiceConnected(mComponentName, mService);
+ userState.mUiAutomationService = this;
}
return false;
}
@@ -1532,24 +1827,32 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
*
* @return True if unbinding is successful.
*/
- public boolean unbind() {
- if (mService != null) {
- synchronized (mLock) {
- tryRemoveServiceLocked(this);
- }
- if (!mIsAutomation) {
- mContext.unbindService(this);
- }
- return true;
+ public boolean unbindLocked() {
+ if (mService == null) {
+ return false;
}
- return false;
+ UserState userState = getUserStateLocked(mUserId);
+ mKeyEventDispatcher.flush();
+ if (!mIsAutomation) {
+ mContext.unbindService(this);
+ } else {
+ userState.destroyUiAutomationService();
+ }
+ removeServiceLocked(this, userState);
+ resetLocked();
+ return true;
}
- public boolean canReceiveEvents() {
+ public boolean canReceiveEventsLocked() {
return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
}
@Override
+ public void setOnKeyEventResult(boolean handled, int sequence) {
+ mKeyEventDispatcher.setOnKeyEventResult(handled, sequence);
+ }
+
+ @Override
public AccessibilityServiceInfo getServiceInfo() {
synchronized (mLock) {
return mAccessibilityServiceInfo;
@@ -1571,6 +1874,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} else {
setDynamicallyConfigurableProperties(info);
}
+ UserState userState = getUserStateLocked(mUserId);
+ onUserStateChangedLocked(userState);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1579,21 +1884,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
- mService = service;
- mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
- try {
- mServiceInterface.setConnection(this, mId);
- synchronized (mLock) {
- tryAddServiceLocked(this, mUserId);
+ synchronized (mLock) {
+ mService = service;
+ mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
+ UserState userState = getUserStateLocked(mUserId);
+ addServiceLocked(this, userState);
+ if (userState.mBindingServices.contains(mComponentName) || mWasConnectedAndDied) {
+ userState.mBindingServices.remove(mComponentName);
+ mWasConnectedAndDied = false;
+ try {
+ mServiceInterface.setConnection(this, mId);
+ onUserStateChangedLocked(userState);
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Error while setting connection for service: "
+ + service, re);
+ binderDied();
+ }
+ } else {
+ binderDied();
}
- } catch (RemoteException re) {
- Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re);
}
}
@Override
- public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
- long accessibilityNodeId, int viewId, int interactionId,
+ public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewIdResName, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId;
@@ -1603,28 +1918,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
.resolveCallingUserIdEnforcingPermissionsLocked(
UserHandle.getCallingUserId());
if (resolvedUserId != mCurrentUserId) {
- return -1;
+ return false;
}
mSecurityPolicy.enforceCanRetrieveWindowContent(this);
final boolean permissionGranted = mSecurityPolicy.canRetrieveWindowContent(this);
if (!permissionGranted) {
- return 0;
+ return false;
} else {
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
connection = getConnectionLocked(resolvedWindowId);
if (connection == null) {
- return 0;
+ return false;
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
+ MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
- connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
- return getCompatibilityScale(resolvedWindowId);
+ connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId,
+ viewIdResName, interactionId, callback, mFetchFlags, interrogatingPid,
+ interrogatingTid, spec);
+ return true;
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId().");
@@ -1632,11 +1947,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} finally {
Binder.restoreCallingIdentity(identityToken);
}
- return 0;
+ return false;
}
@Override
- public float findAccessibilityNodeInfosByText(int accessibilityWindowId,
+ public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId,
long accessibilityNodeId, String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
@@ -1647,30 +1962,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
.resolveCallingUserIdEnforcingPermissionsLocked(
UserHandle.getCallingUserId());
if (resolvedUserId != mCurrentUserId) {
- return -1;
+ return false;
}
mSecurityPolicy.enforceCanRetrieveWindowContent(this);
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
final boolean permissionGranted =
mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
if (!permissionGranted) {
- return 0;
+ return false;
} else {
connection = getConnectionLocked(resolvedWindowId);
if (connection == null) {
- return 0;
+ return false;
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
+ MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,
- interactionId, callback, flags, interrogatingPid,
- interrogatingTid);
- return getCompatibilityScale(resolvedWindowId);
+ interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid,
+ spec);
+ return true;
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
@@ -1678,12 +1992,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} finally {
Binder.restoreCallingIdentity(identityToken);
}
- return 0;
+ return false;
}
@Override
- public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
- long accessibilityNodeId, int interactionId,
+ public boolean findAccessibilityNodeInfoByAccessibilityId(
+ int accessibilityWindowId, long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
long interrogatingTid) throws RemoteException {
final int resolvedWindowId;
@@ -1693,29 +2007,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
.resolveCallingUserIdEnforcingPermissionsLocked(
UserHandle.getCallingUserId());
if (resolvedUserId != mCurrentUserId) {
- return -1;
+ return false;
}
mSecurityPolicy.enforceCanRetrieveWindowContent(this);
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
final boolean permissionGranted =
mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
if (!permissionGranted) {
- return 0;
+ return false;
} else {
connection = getConnectionLocked(resolvedWindowId);
if (connection == null) {
- return 0;
+ return false;
}
}
}
- final int allFlags = flags | ((mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0);
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
+ MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
- interactionId, callback, allFlags, interrogatingPid, interrogatingTid);
- return getCompatibilityScale(resolvedWindowId);
+ interactionId, callback, mFetchFlags | flags, interrogatingPid,
+ interrogatingTid, spec);
+ return true;
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
@@ -1723,11 +2037,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} finally {
Binder.restoreCallingIdentity(identityToken);
}
- return 0;
+ return false;
}
@Override
- public float findFocus(int accessibilityWindowId, long accessibilityNodeId,
+ public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId,
int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
@@ -1738,29 +2052,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
.resolveCallingUserIdEnforcingPermissionsLocked(
UserHandle.getCallingUserId());
if (resolvedUserId != mCurrentUserId) {
- return -1;
+ return false;
}
mSecurityPolicy.enforceCanRetrieveWindowContent(this);
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
final boolean permissionGranted =
mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
if (!permissionGranted) {
- return 0;
+ return false;
} else {
connection = getConnectionLocked(resolvedWindowId);
if (connection == null) {
- return 0;
+ return false;
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
+ MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.findFocus(accessibilityNodeId, focusType, interactionId, callback,
- flags, interrogatingPid, interrogatingTid);
- return getCompatibilityScale(resolvedWindowId);
+ mFetchFlags, interrogatingPid, interrogatingTid, spec);
+ return true;
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling findAccessibilityFocus()");
@@ -1768,11 +2081,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} finally {
Binder.restoreCallingIdentity(identityToken);
}
- return 0;
+ return false;
}
@Override
- public float focusSearch(int accessibilityWindowId, long accessibilityNodeId,
+ public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId,
int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
@@ -1783,29 +2096,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
.resolveCallingUserIdEnforcingPermissionsLocked(
UserHandle.getCallingUserId());
if (resolvedUserId != mCurrentUserId) {
- return -1;
+ return false;
}
mSecurityPolicy.enforceCanRetrieveWindowContent(this);
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
final boolean permissionGranted =
mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
if (!permissionGranted) {
- return 0;
+ return false;
} else {
connection = getConnectionLocked(resolvedWindowId);
if (connection == null) {
- return 0;
+ return false;
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
+ MagnificationSpec spec = getCompatibleMagnificationSpec(resolvedWindowId);
try {
connection.focusSearch(accessibilityNodeId, direction, interactionId, callback,
- flags, interrogatingPid, interrogatingTid);
- return getCompatibilityScale(resolvedWindowId);
+ mFetchFlags, interrogatingPid, interrogatingTid, spec);
+ return true;
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()");
@@ -1813,7 +2125,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} finally {
Binder.restoreCallingIdentity(identityToken);
}
- return 0;
+ return false;
}
@Override
@@ -1843,13 +2155,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
}
- final int flags = (mIncludeNotImportantViews) ?
- AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.performAccessibilityAction(accessibilityNodeId, action, arguments,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling performAccessibilityAction()");
@@ -1894,19 +2204,36 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ @Override
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
+ synchronized (mLock) {
+ pw.append("Service[label=" + mAccessibilityServiceInfo.getResolveInfo()
+ .loadLabel(mContext.getPackageManager()));
+ pw.append(", feedbackType"
+ + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
+ pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
+ pw.append(", eventTypes="
+ + AccessibilityEvent.eventTypeToString(mEventTypes));
+ pw.append(", notificationTimeout=" + mNotificationTimeout);
+ pw.append("]");
+ }
+ }
+
+ @Override
public void onServiceDisconnected(ComponentName componentName) {
/* do nothing - #binderDied takes care */
}
- public void linkToOwnDeath() throws RemoteException {
+ public void linkToOwnDeathLocked() throws RemoteException {
mService.linkToDeath(this, 0);
}
- public void unlinkToOwnDeath() {
+ public void unlinkToOwnDeathLocked() {
mService.unlinkToDeath(this, 0);
}
- public void dispose() {
+ public void resetLocked() {
try {
// Clear the proxy in the other process so this
// IAccessibilityServiceConnection can be garbage collected.
@@ -1918,15 +2245,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mServiceInterface = null;
}
+ public boolean isConnectedLocked() {
+ return (mService != null);
+ }
+
public void binderDied() {
synchronized (mLock) {
- // The death recipient is unregistered in tryRemoveServiceLocked
- tryRemoveServiceLocked(this);
- // We no longer have an automation service, so restore
- // the state based on values in the settings database.
+ // It is possible that this service's package was force stopped during
+ // whose handling the death recipient is unlinked and still get a call
+ // on binderDied since the call was made before we unlink but was
+ // waiting on the lock we held during the force stop handling.
+ if (!isConnectedLocked()) {
+ return;
+ }
+ mWasConnectedAndDied = true;
+ mKeyEventDispatcher.flush();
+ UserState userState = getUserStateLocked(mUserId);
+ // The death recipient is unregistered in removeServiceLocked
+ removeServiceLocked(this, userState);
+ resetLocked();
if (mIsAutomation) {
- mUiAutomationService = null;
- recreateInternalStateLocked(getUserStateLocked(mUserId));
+ // We no longer have an automation service, so restore
+ // the state based on values in the settings database.
+ userState.mInstalledServices.remove(mAccessibilityServiceInfo);
+ userState.mEnabledServices.remove(mComponentName);
+ userState.destroyUiAutomationService();
}
}
}
@@ -1948,12 +2291,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
final int what = eventType;
if (oldEvent != null) {
- mHandler.removeMessages(what);
+ mEventDispatchHandler.removeMessages(what);
oldEvent.recycle();
}
- Message message = mHandler.obtainMessage(what);
- mHandler.sendMessageDelayed(message, mNotificationTimeout);
+ Message message = mEventDispatchHandler.obtainMessage(what);
+ mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
}
}
@@ -2018,7 +2361,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
public void notifyGesture(int gestureId) {
- mHandler.obtainMessage(MSG_ON_GESTURE, gestureId, 0).sendToTarget();
+ mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
+ gestureId, 0).sendToTarget();
+ }
+
+ public void notifyKeyEvent(KeyEvent event, int policyFlags) {
+ mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_KEY_EVENT,
+ policyFlags, 0, event).sendToTarget();
+ }
+
+ public void notifyClearAccessibilityNodeInfoCache() {
+ mInvocationHandler.sendEmptyMessage(
+ InvocationHandler.MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE);
}
private void notifyGestureInternal(int gestureId) {
@@ -2033,6 +2387,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ private void notifyKeyEventInternal(KeyEvent event, int policyFlags) {
+ mKeyEventDispatcher.notifyKeyEvent(event, policyFlags);
+ }
+
+ private void notifyClearAccessibilityNodeInfoCacheInternal() {
+ IAccessibilityServiceClient listener = mServiceInterface;
+ if (listener != null) {
+ try {
+ listener.clearAccessibilityNodeInfoCache();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
+ + " to be cleared.", re);
+ }
+ }
+ }
+
private void sendDownAndUpKeyEvents(int keyCode) {
final long token = Binder.clearCallingIdentity();
@@ -2115,20 +2485,193 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
return accessibilityWindowId;
}
- private float getCompatibilityScale(int windowId) {
+ private MagnificationSpec getCompatibleMagnificationSpec(int windowId) {
try {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
+ if (windowToken == null) {
+ windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId);
+ }
if (windowToken != null) {
- return mWindowManagerService.getWindowCompatibilityScale(windowToken);
- }
- windowToken = getCurrentUserStateLocked().mWindowTokens.get(windowId);
- if (windowToken != null) {
- return mWindowManagerService.getWindowCompatibilityScale(windowToken);
+ return mWindowManagerService.getCompatibleMagnificationSpecForWindow(
+ windowToken);
}
} catch (RemoteException re) {
/* ignore */
}
- return 1.0f;
+ return null;
+ }
+
+ private final class InvocationHandler extends Handler {
+
+ public static final int MSG_ON_GESTURE = 1;
+ public static final int MSG_ON_KEY_EVENT = 2;
+ public static final int MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 3;
+ public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4;
+
+ public InvocationHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_ON_GESTURE: {
+ final int gestureId = message.arg1;
+ notifyGestureInternal(gestureId);
+ } break;
+ case MSG_ON_KEY_EVENT: {
+ KeyEvent event = (KeyEvent) message.obj;
+ final int policyFlags = message.arg1;
+ notifyKeyEventInternal(event, policyFlags);
+ } break;
+ case MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: {
+ notifyClearAccessibilityNodeInfoCacheInternal();
+ } break;
+ case MSG_ON_KEY_EVENT_TIMEOUT: {
+ PendingEvent eventState = (PendingEvent) message.obj;
+ setOnKeyEventResult(false, eventState.sequence);
+ } break;
+ default: {
+ throw new IllegalArgumentException("Unknown message: " + type);
+ }
+ }
+ }
+ }
+
+ private final class KeyEventDispatcher {
+
+ private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
+
+ private PendingEvent mPendingEvents;
+
+ private final InputEventConsistencyVerifier mSentEventsVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled()
+ ? new InputEventConsistencyVerifier(
+ this, 0, KeyEventDispatcher.class.getSimpleName()) : null;
+
+ public void notifyKeyEvent(KeyEvent event, int policyFlags) {
+ final PendingEvent pendingEvent;
+
+ synchronized (mLock) {
+ pendingEvent = addPendingEventLocked(event, policyFlags);
+ }
+
+ Message message = mInvocationHandler.obtainMessage(
+ InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
+ mInvocationHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
+
+ try {
+ // Accessibility services are exclusively not in the system
+ // process, therefore no need to clone the motion event to
+ // prevent tampering. It will be cloned in the IPC call.
+ mServiceInterface.onKeyEvent(pendingEvent.event, pendingEvent.sequence);
+ } catch (RemoteException re) {
+ setOnKeyEventResult(false, pendingEvent.sequence);
+ }
+ }
+
+ public void setOnKeyEventResult(boolean handled, int sequence) {
+ synchronized (mLock) {
+ PendingEvent pendingEvent = removePendingEventLocked(sequence);
+ if (pendingEvent != null) {
+ mInvocationHandler.removeMessages(
+ InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
+ pendingEvent);
+ pendingEvent.handled = handled;
+ finishPendingEventLocked(pendingEvent);
+ }
+ }
+ }
+
+ public void flush() {
+ synchronized (mLock) {
+ cancelAllPendingEventsLocked();
+ if (mSentEventsVerifier != null) {
+ mSentEventsVerifier.reset();
+ }
+ }
+ }
+
+ private PendingEvent addPendingEventLocked(KeyEvent event, int policyFlags) {
+ final int sequence = event.getSequenceNumber();
+ PendingEvent pendingEvent = obtainPendingEventLocked(event, policyFlags, sequence);
+ pendingEvent.next = mPendingEvents;
+ mPendingEvents = pendingEvent;
+ return pendingEvent;
+ }
+
+ private PendingEvent removePendingEventLocked(int sequence) {
+ PendingEvent previous = null;
+ PendingEvent current = mPendingEvents;
+
+ while (current != null) {
+ if (current.sequence == sequence) {
+ if (previous != null) {
+ previous.next = current.next;
+ } else {
+ mPendingEvents = current.next;
+ }
+ current.next = null;
+ return current;
+ }
+ previous = current;
+ current = current.next;
+ }
+ return null;
+ }
+
+ private void finishPendingEventLocked(PendingEvent pendingEvent) {
+ if (!pendingEvent.handled) {
+ sendKeyEventToInputFilter(pendingEvent.event, pendingEvent.policyFlags);
+ }
+ // Nullify the event since we do not want it to be
+ // recycled yet. It will be sent to the input filter.
+ pendingEvent.event = null;
+ recyclePendingEventLocked(pendingEvent);
+ }
+
+ private void sendKeyEventToInputFilter(KeyEvent event, int policyFlags) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Injecting event: " + event);
+ }
+ if (mSentEventsVerifier != null) {
+ mSentEventsVerifier.onKeyEvent(event, 0);
+ }
+ policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
+ mMainHandler.obtainMessage(MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER,
+ policyFlags, 0, event).sendToTarget();
+ }
+
+ private void cancelAllPendingEventsLocked() {
+ while (mPendingEvents != null) {
+ PendingEvent pendingEvent = removePendingEventLocked(mPendingEvents.sequence);
+ pendingEvent.handled = false;
+ mInvocationHandler.removeMessages(InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
+ pendingEvent);
+ finishPendingEventLocked(pendingEvent);
+ }
+ }
+ }
+ }
+
+ private static final class PendingEvent {
+ PendingEvent next;
+
+ KeyEvent event;
+ int policyFlags;
+ int sequence;
+ boolean handled;
+
+ public void clear() {
+ if (event != null) {
+ event.recycle();
+ event = null;
+ }
+ next = null;
+ policyFlags = 0;
+ sequence = 0;
+ handled = false;
}
}
@@ -2147,7 +2690,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
| AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT
| AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT
| AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
- | AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
+ | AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+ | AccessibilityNodeInfo.ACTION_COPY
+ | AccessibilityNodeInfo.ACTION_PASTE
+ | AccessibilityNodeInfo.ACTION_CUT
+ | AccessibilityNodeInfo.ACTION_SET_SELECTION;
private static final int RETRIEVAL_ALLOWING_EVENT_TYPES =
AccessibilityEvent.TYPE_VIEW_CLICKED
@@ -2255,7 +2802,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
public boolean canRetrieveWindowContent(Service service) {
- return service.mCanRetrieveScreenContent;
+ return (service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
}
public void enforceCanRetrieveWindowContent(Service service) throws RemoteException {
@@ -2313,7 +2861,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
if (!hasPermission(permission)) {
throw new SecurityException("You do not have " + permission
- + " required to call " + function);
+ + " required to call " + function + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
}
}
@@ -2357,76 +2906,111 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private class UserState {
public final int mUserId;
- public final CopyOnWriteArrayList<Service> mServices = new CopyOnWriteArrayList<Service>();
+ // Non-transient state.
public final RemoteCallbackList<IAccessibilityManagerClient> mClients =
new RemoteCallbackList<IAccessibilityManagerClient>();
+ public final SparseArray<AccessibilityConnectionWrapper> mInteractionConnections =
+ new SparseArray<AccessibilityConnectionWrapper>();
+
+ public final SparseArray<IBinder> mWindowTokens = new SparseArray<IBinder>();
+
+ // Transient state.
+
+ public final CopyOnWriteArrayList<Service> mBoundServices =
+ new CopyOnWriteArrayList<Service>();
+
public final Map<ComponentName, Service> mComponentNameToServiceMap =
new HashMap<ComponentName, Service>();
public final List<AccessibilityServiceInfo> mInstalledServices =
new ArrayList<AccessibilityServiceInfo>();
+ public final Set<ComponentName> mBindingServices = new HashSet<ComponentName>();
+
public final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
public final Set<ComponentName> mTouchExplorationGrantedServices =
new HashSet<ComponentName>();
- public final SparseArray<AccessibilityConnectionWrapper>
- mInteractionConnections =
- new SparseArray<AccessibilityConnectionWrapper>();
-
- public final SparseArray<IBinder> mWindowTokens = new SparseArray<IBinder>();
-
public int mHandledFeedbackTypes = 0;
+ public int mLastSentClientState = -1;
+
public boolean mIsAccessibilityEnabled;
public boolean mIsTouchExplorationEnabled;
+ public boolean mIsEnhancedWebAccessibilityEnabled;
public boolean mIsDisplayMagnificationEnabled;
+ public boolean mIsFilterKeyEventsEnabled;
- public UserState(int userId) {
- mUserId = userId;
- }
- }
+ private Service mUiAutomationService;
+ private IAccessibilityServiceClient mUiAutomationServiceClient;
- private class TempUserStateChangeMemento {
- public int mUserId = UserHandle.USER_NULL;
- public boolean mIsAccessibilityEnabled;
- public boolean mIsTouchExplorationEnabled;
- public boolean mIsDisplayMagnificationEnabled;
- public final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
- public final Set<ComponentName> mTouchExplorationGrantedServices =
- new HashSet<ComponentName>();
+ private IBinder mUiAutomationServiceOwner;
+ private final DeathRecipient mUiAutomationSerivceOnwerDeathRecipient =
+ new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ mUiAutomationServiceOwner.unlinkToDeath(
+ mUiAutomationSerivceOnwerDeathRecipient, 0);
+ mUiAutomationServiceOwner = null;
+ if (mUiAutomationService != null) {
+ mUiAutomationService.binderDied();
+ }
+ }
+ };
- public void initialize(int userId, UserState userState) {
+ public UserState(int userId) {
mUserId = userId;
- mIsAccessibilityEnabled = userState.mIsAccessibilityEnabled;
- mIsTouchExplorationEnabled = userState.mIsTouchExplorationEnabled;
- mIsDisplayMagnificationEnabled = userState.mIsDisplayMagnificationEnabled;
- mEnabledServices.clear();
- mEnabledServices.addAll(userState.mEnabledServices);
- mTouchExplorationGrantedServices.clear();
- mTouchExplorationGrantedServices.addAll(userState.mTouchExplorationGrantedServices);
}
- public void applyTo(UserState userState) {
- userState.mIsAccessibilityEnabled = mIsAccessibilityEnabled;
- userState.mIsTouchExplorationEnabled = mIsTouchExplorationEnabled;
- userState.mIsDisplayMagnificationEnabled = mIsDisplayMagnificationEnabled;
- userState.mEnabledServices.clear();
- userState.mEnabledServices.addAll(mEnabledServices);
- userState.mTouchExplorationGrantedServices.clear();
- userState.mTouchExplorationGrantedServices.addAll(mTouchExplorationGrantedServices);
+ public int getClientState() {
+ int clientState = 0;
+ if (mIsAccessibilityEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+ }
+ // Touch exploration relies on enabled accessibility.
+ if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+ }
+ return clientState;
}
- public void clear() {
- mUserId = UserHandle.USER_NULL;
+ public void onSwitchToAnotherUser() {
+ // Clear UI test automation state.
+ if (mUiAutomationService != null) {
+ mUiAutomationService.binderDied();
+ }
+
+ // Unbind all services.
+ unbindAllServicesLocked(this);
+
+ // Clear service management state.
+ mBoundServices.clear();
+ mBindingServices.clear();
+
+ // Clear event management state.
+ mHandledFeedbackTypes = 0;
+ mLastSentClientState = -1;
+
+ // Clear state persisted in settings.
+ mEnabledServices.clear();
+ mTouchExplorationGrantedServices.clear();
mIsAccessibilityEnabled = false;
mIsTouchExplorationEnabled = false;
+ mIsEnhancedWebAccessibilityEnabled = false;
mIsDisplayMagnificationEnabled = false;
- mEnabledServices.clear();
- mTouchExplorationGrantedServices.clear();
+ }
+
+ public void destroyUiAutomationService() {
+ mUiAutomationService = null;
+ mUiAutomationServiceClient = null;
+ if (mUiAutomationServiceOwner != null) {
+ mUiAutomationServiceOwner.unlinkToDeath(
+ mUiAutomationSerivceOnwerDeathRecipient, 0);
+ mUiAutomationServiceOwner = null;
+ }
}
}
@@ -2447,6 +3031,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private final Uri mTouchExplorationGrantedAccessibilityServicesUri = Settings.Secure
.getUriFor(Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES);
+ private final Uri mEnhancedWebAccessibilityUri = Settings.Secure
+ .getUriFor(Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -2463,6 +3050,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
contentResolver.registerContentObserver(
mTouchExplorationGrantedAccessibilityServicesUri,
false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(mEnhancedWebAccessibilityUri,
+ false, this, UserHandle.USER_ALL);
}
@Override
@@ -2470,50 +3059,61 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (mAccessibilityEnabledUri.equals(uri)) {
synchronized (mLock) {
// We will update when the automation service dies.
- if (mUiAutomationService == null) {
- UserState userState = getCurrentUserStateLocked();
- handleAccessibilityEnabledSettingChangedLocked(userState);
- performServiceManagementLocked(userState);
- updateInputFilterLocked(userState);
- scheduleSendStateToClientsLocked(userState);
+ UserState userState = getCurrentUserStateLocked();
+ if (userState.mUiAutomationService == null) {
+ if (readAccessibilityEnabledSettingLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
} else if (mTouchExplorationEnabledUri.equals(uri)) {
synchronized (mLock) {
// We will update when the automation service dies.
- if (mUiAutomationService == null) {
- UserState userState = getCurrentUserStateLocked();
- handleTouchExplorationEnabledSettingChangedLocked(userState);
- updateInputFilterLocked(userState);
- scheduleSendStateToClientsLocked(userState);
+ UserState userState = getCurrentUserStateLocked();
+ if (userState.mUiAutomationService == null) {
+ if (readTouchExplorationEnabledSettingLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
} else if (mDisplayMagnificationEnabledUri.equals(uri)) {
synchronized (mLock) {
// We will update when the automation service dies.
- if (mUiAutomationService == null) {
- UserState userState = getCurrentUserStateLocked();
- handleDisplayMagnificationEnabledSettingChangedLocked(userState);
- updateInputFilterLocked(userState);
- scheduleSendStateToClientsLocked(userState);
+ UserState userState = getCurrentUserStateLocked();
+ if (userState.mUiAutomationService == null) {
+ if (readDisplayMagnificationEnabledSettingLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
} else if (mEnabledAccessibilityServicesUri.equals(uri)) {
synchronized (mLock) {
// We will update when the automation service dies.
- if (mUiAutomationService == null) {
- UserState userState = getCurrentUserStateLocked();
- populateEnabledAccessibilityServicesLocked(userState);
- manageServicesLocked(userState);
+ UserState userState = getCurrentUserStateLocked();
+ if (userState.mUiAutomationService == null) {
+ if (readEnabledAccessibilityServicesLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
} else if (mTouchExplorationGrantedAccessibilityServicesUri.equals(uri)) {
synchronized (mLock) {
// We will update when the automation service dies.
- if (mUiAutomationService == null) {
- UserState userState = getCurrentUserStateLocked();
- populateTouchExplorationGrantedAccessibilityServicesLocked(userState);
- handleTouchExplorationGrantedAccessibilityServicesChangedLocked(userState);
+ UserState userState = getCurrentUserStateLocked();
+ if (userState.mUiAutomationService == null) {
+ if (readTouchExplorationGrantedAccessibilityServicesLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
+ }
+ }
+ } else if (mEnhancedWebAccessibilityUri.equals(uri)) {
+ synchronized (mLock) {
+ // We will update when the automation service dies.
+ UserState userState = getCurrentUserStateLocked();
+ if (userState.mUiAutomationService == null) {
+ if (readEnhancedWebAccessibilityEnabledChangedLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
diff --git a/services/java/com/android/server/accessibility/EventStreamTransformation.java b/services/java/com/android/server/accessibility/EventStreamTransformation.java
index 3289a15..8c93e7b 100644
--- a/services/java/com/android/server/accessibility/EventStreamTransformation.java
+++ b/services/java/com/android/server/accessibility/EventStreamTransformation.java
@@ -57,7 +57,7 @@ import android.view.accessibility.AccessibilityEvent;
interface EventStreamTransformation {
/**
- * Receives motion event. Passed are the event transformed by previous
+ * Receives a motion event. Passed are the event transformed by previous
* transformations and the raw event to which no transformations have
* been applied.
*
diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java
index 482bff5..1bf2c42 100644
--- a/services/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -16,8 +16,6 @@
package com.android.server.accessibility;
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
@@ -25,15 +23,10 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
+import android.graphics.Region;
import android.os.AsyncTask;
+import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
@@ -43,35 +36,23 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Property;
import android.util.Slog;
-import android.view.Display;
-import android.view.DisplayInfo;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.Gravity;
-import android.view.IDisplayContentChangeListener;
+import android.view.IMagnificationCallbacks;
import android.view.IWindowManager;
+import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
-import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowInfo;
-import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import com.android.internal.R;
import com.android.internal.os.SomeArgs;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.Locale;
/**
@@ -91,7 +72,7 @@ import java.util.Locale;
* moving finger will be magnified to fit the screen. For example, if the
* screen was not magnified and the user triple taps and holds the screen
* would magnify and the viewport will follow the user's finger. When the
- * finger goes up the screen will clear zoom out. If the same user interaction
+ * finger goes up the screen will zoom out. If the same user interaction
* is performed when the screen is magnified, the viewport movement will
* be the same but when the finger goes up the screen will stay magnified.
* In other words, the initial magnified state is sticky.
@@ -106,54 +87,55 @@ import java.util.Locale;
* to pan the viewport. Note that in this mode the content is panned as
* opposed to the viewport dragging mode in which the viewport is moved.
*
- * 5. When in a permanent magnified state the user can use three or more
+ * 5. When in a permanent magnified state the user can use two or more
* fingers to change the magnification scale which will become the current
* default magnification scale. The next time the user magnifies the same
* magnification scale would be used.
*
* 6. The magnification scale will be persisted in settings and in the cloud.
*/
-public final class ScreenMagnifier implements EventStreamTransformation {
+public final class ScreenMagnifier extends IMagnificationCallbacks.Stub
+ implements EventStreamTransformation {
+
+ private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
private static final boolean DEBUG_STATE_TRANSITIONS = false;
private static final boolean DEBUG_DETECTING = false;
- private static final boolean DEBUG_TRANSFORMATION = false;
+ private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
private static final boolean DEBUG_PANNING = false;
private static final boolean DEBUG_SCALING = false;
- private static final boolean DEBUG_VIEWPORT_WINDOW = false;
- private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
- private static final boolean DEBUG_ROTATION = false;
private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
- private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
-
private static final int STATE_DELEGATING = 1;
private static final int STATE_DETECTING = 2;
private static final int STATE_VIEWPORT_DRAGGING = 3;
private static final int STATE_MAGNIFIED_INTERACTION = 4;
private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
- private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
- private static final float DEFAULT_WINDOW_ANIMATION_SCALE = 1.0f;
-
private static final int MULTI_TAP_TIME_SLOP_ADJUSTMENT = 50;
- private final IWindowManager mWindowManagerService = IWindowManager.Stub.asInterface(
- ServiceManager.getService("window"));
- private final WindowManager mWindowManager;
- private final DisplayProvider mDisplayProvider;
+ private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
+ private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+ private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
+ private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
- private final DetectingStateHandler mDetectingStateHandler = new DetectingStateHandler();
- private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
- private final StateViewportDraggingHandler mStateViewportDraggingHandler =
- new StateViewportDraggingHandler();
+ private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
+
+ private static final int MY_PID = android.os.Process.myPid();
- private final Interpolator mInterpolator = new DecelerateInterpolator(2.5f);
+ private final Rect mTempRect = new Rect();
+ private final Rect mTempRect1 = new Rect();
+ private final Context mContext;
+ private final IWindowManager mWindowManager;
private final MagnificationController mMagnificationController;
- private final DisplayContentObserver mDisplayContentObserver;
private final ScreenStateObserver mScreenStateObserver;
- private final Viewport mViewport;
+
+ private final DetectingStateHandler mDetectingStateHandler;
+ private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
+ private final StateViewportDraggingHandler mStateViewportDraggingHandler;
+
+ private final AccessibilityManagerService mAms;
private final int mTapTimeSlop = ViewConfiguration.getTapTimeout();
private final int mMultiTapTimeSlop =
@@ -161,11 +143,9 @@ public final class ScreenMagnifier implements EventStreamTransformation {
private final int mTapDistanceSlop;
private final int mMultiTapDistanceSlop;
- private final int mShortAnimationDuration;
- private final int mLongAnimationDuration;
- private final float mWindowAnimationScale;
+ private final long mLongAnimationDuration;
- private final Context mContext;
+ private final Region mMagnifiedBounds = new Region();
private EventStreamTransformation mNext;
@@ -178,36 +158,176 @@ public final class ScreenMagnifier implements EventStreamTransformation {
private long mDelegatingStateDownTime;
- public ScreenMagnifier(Context context) {
+ private boolean mUpdateMagnificationSpecOnNextBoundsChange;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
+ Region bounds = (Region) message.obj;
+ handleOnMagnifiedBoundsChanged(bounds);
+ bounds.recycle();
+ } break;
+ case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ final int left = args.argi1;
+ final int top = args.argi2;
+ final int right = args.argi3;
+ final int bottom = args.argi4;
+ handleOnRectangleOnScreenRequested(left, top, right, bottom);
+ args.recycle();
+ } break;
+ case MESSAGE_ON_USER_CONTEXT_CHANGED: {
+ handleOnUserContextChanged();
+ } break;
+ case MESSAGE_ON_ROTATION_CHANGED: {
+ final int rotation = message.arg1;
+ handleOnRotationChanged(rotation);
+ } break;
+ }
+ }
+ };
+
+ public ScreenMagnifier(Context context, int displayId, AccessibilityManagerService service) {
mContext = context;
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService("window"));
+ mAms = service;
- mShortAnimationDuration = context.getResources().getInteger(
- com.android.internal.R.integer.config_shortAnimTime);
mLongAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_longAnimTime);
mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
- mWindowAnimationScale = Settings.Global.getFloat(context.getContentResolver(),
- Settings.Global.WINDOW_ANIMATION_SCALE, DEFAULT_WINDOW_ANIMATION_SCALE);
-
- mMagnificationController = new MagnificationController(mShortAnimationDuration);
- mDisplayProvider = new DisplayProvider(context, mWindowManager);
- mViewport = new Viewport(mContext, mWindowManager, mWindowManagerService,
- mDisplayProvider, mInterpolator, mShortAnimationDuration);
- mDisplayContentObserver = new DisplayContentObserver(mContext, mViewport,
- mMagnificationController, mWindowManagerService, mDisplayProvider,
- mLongAnimationDuration, mWindowAnimationScale);
- mScreenStateObserver = new ScreenStateObserver(mContext, mViewport,
- mMagnificationController);
+ mDetectingStateHandler = new DetectingStateHandler();
+ mStateViewportDraggingHandler = new StateViewportDraggingHandler();
mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
context);
+ mMagnificationController = new MagnificationController(mLongAnimationDuration);
+ mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController);
+
+ try {
+ mWindowManager.setMagnificationCallbacks(this);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+
transitionToState(STATE_DETECTING);
}
@Override
+ public void onMagnifedBoundsChanged(Region bounds) {
+ Region newBounds = Region.obtain(bounds);
+ mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, newBounds).sendToTarget();
+ if (MY_PID != Binder.getCallingPid()) {
+ bounds.recycle();
+ }
+ }
+
+ private void handleOnMagnifiedBoundsChanged(Region bounds) {
+ // If there was a rotation we have to update the center of the magnified
+ // region since the old offset X/Y may be out of its acceptable range for
+ // the new display width and height.
+ if (mUpdateMagnificationSpecOnNextBoundsChange) {
+ mUpdateMagnificationSpecOnNextBoundsChange = false;
+ MagnificationSpec spec = mMagnificationController.getMagnificationSpec();
+ Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
+ final float scale = spec.scale;
+ final float centerX = (-spec.offsetX + magnifiedFrame.width() / 2) / scale;
+ final float centerY = (-spec.offsetY + magnifiedFrame.height() / 2) / scale;
+ mMagnificationController.setScaleAndMagnifiedRegionCenter(scale, centerX,
+ centerY, false);
+ }
+ mMagnifiedBounds.set(bounds);
+ mAms.onMagnificationStateChanged();
+ }
+
+ @Override
+ public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = left;
+ args.argi2 = top;
+ args.argi3 = right;
+ args.argi4 = bottom;
+ mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
+ }
+
+ private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+ Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
+ if (!magnifiedFrame.intersects(left, top, right, bottom)) {
+ return;
+ }
+ Rect magnifFrameInScreenCoords = mTempRect1;
+ getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords);
+ final float scrollX;
+ final float scrollY;
+ if (right - left > magnifFrameInScreenCoords.width()) {
+ final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
+ if (direction == View.LAYOUT_DIRECTION_LTR) {
+ scrollX = left - magnifFrameInScreenCoords.left;
+ } else {
+ scrollX = right - magnifFrameInScreenCoords.right;
+ }
+ } else if (left < magnifFrameInScreenCoords.left) {
+ scrollX = left - magnifFrameInScreenCoords.left;
+ } else if (right > magnifFrameInScreenCoords.right) {
+ scrollX = right - magnifFrameInScreenCoords.right;
+ } else {
+ scrollX = 0;
+ }
+ if (bottom - top > magnifFrameInScreenCoords.height()) {
+ scrollY = top - magnifFrameInScreenCoords.top;
+ } else if (top < magnifFrameInScreenCoords.top) {
+ scrollY = top - magnifFrameInScreenCoords.top;
+ } else if (bottom > magnifFrameInScreenCoords.bottom) {
+ scrollY = bottom - magnifFrameInScreenCoords.bottom;
+ } else {
+ scrollY = 0;
+ }
+ final float scale = mMagnificationController.getScale();
+ mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
+ }
+
+ @Override
+ public void onRotationChanged(int rotation) {
+ mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
+ }
+
+ private void handleOnRotationChanged(int rotation) {
+ resetMagnificationIfNeeded();
+ if (mMagnificationController.isMagnifying()) {
+ mUpdateMagnificationSpecOnNextBoundsChange = true;
+ }
+ }
+
+ @Override
+ public void onUserContextChanged() {
+ mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
+ }
+
+ private void handleOnUserContextChanged() {
+ resetMagnificationIfNeeded();
+ }
+
+ private void getMagnifiedFrameInContentCoords(Rect rect) {
+ MagnificationSpec spec = mMagnificationController.getMagnificationSpec();
+ mMagnifiedBounds.getBounds(rect);
+ rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
+ rect.scale(1.0f / spec.scale);
+ }
+
+ private void resetMagnificationIfNeeded() {
+ if (mMagnificationController.isMagnifying()
+ && isScreenMagnificationAutoUpdateEnabled(mContext)) {
+ mMagnificationController.reset(true);
+ }
+ }
+
+ @Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
mMagnifiedContentInteractonStateHandler.onMotionEvent(event);
@@ -257,12 +377,12 @@ public final class ScreenMagnifier implements EventStreamTransformation {
@Override
public void onDestroy() {
- mMagnificationController.setScaleAndMagnifiedRegionCenter(1.0f,
- 0, 0, true);
- mViewport.setFrameShown(false, true);
- mDisplayProvider.destroy();
- mDisplayContentObserver.destroy();
mScreenStateObserver.destroy();
+ try {
+ mWindowManager.setMagnificationCallbacks(null);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
}
private void handleMotionEventStateDelegating(MotionEvent event,
@@ -284,10 +404,10 @@ public final class ScreenMagnifier implements EventStreamTransformation {
final float eventX = event.getX();
final float eventY = event.getY();
if (mMagnificationController.isMagnifying()
- && mViewport.getBounds().contains((int) eventX, (int) eventY)) {
+ && mMagnifiedBounds.contains((int) eventX, (int) eventY)) {
final float scale = mMagnificationController.getScale();
- final float scaledOffsetX = mMagnificationController.getScaledOffsetX();
- final float scaledOffsetY = mMagnificationController.getScaledOffsetY();
+ final float scaledOffsetX = mMagnificationController.getOffsetX();
+ final float scaledOffsetY = mMagnificationController.getOffsetY();
final int pointerCount = event.getPointerCount();
PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
@@ -412,16 +532,11 @@ public final class ScreenMagnifier implements EventStreamTransformation {
if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
return true;
}
- final float scale = mMagnificationController.getScale();
- final float scrollX = distanceX / scale;
- final float scrollY = distanceY / scale;
- final float centerX = mMagnificationController.getMagnifiedRegionCenterX() + scrollX;
- final float centerY = mMagnificationController.getMagnifiedRegionCenterY() + scrollY;
if (DEBUG_PANNING) {
- Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX
- + " scrollY: " + scrollY);
+ Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
+ + " scrollY: " + distanceY);
}
- mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, false);
+ mMagnificationController.offsetMagnifiedRegionCenter(distanceX, distanceY);
return true;
}
@@ -485,7 +600,7 @@ public final class ScreenMagnifier implements EventStreamTransformation {
}
final float eventX = event.getX();
final float eventY = event.getY();
- if (mViewport.getBounds().contains((int) eventX, (int) eventY)) {
+ if (mMagnifiedBounds.contains((int) eventX, (int) eventY)) {
if (mLastMoveOutsideMagnifiedRegion) {
mLastMoveOutsideMagnifiedRegion = false;
mMagnificationController.setMagnifiedRegionCenter(eventX,
@@ -501,7 +616,6 @@ public final class ScreenMagnifier implements EventStreamTransformation {
case MotionEvent.ACTION_UP: {
if (!mTranslationEnabledBeforePan) {
mMagnificationController.reset(true);
- mViewport.setFrameShown(false, true);
}
clear();
transitionToState(STATE_DETECTING);
@@ -559,7 +673,7 @@ public final class ScreenMagnifier implements EventStreamTransformation {
switch (action) {
case MotionEvent.ACTION_DOWN: {
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
- if (!mViewport.getBounds().contains((int) event.getX(),
+ if (!mMagnifiedBounds.contains((int) event.getX(),
(int) event.getY())) {
transitionToDelegatingStateAndClear();
return;
@@ -601,7 +715,7 @@ public final class ScreenMagnifier implements EventStreamTransformation {
return;
}
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
- if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) {
+ if (!mMagnifiedBounds.contains((int) event.getX(), (int) event.getY())) {
transitionToDelegatingStateAndClear();
return;
}
@@ -727,10 +841,8 @@ public final class ScreenMagnifier implements EventStreamTransformation {
if (!mMagnificationController.isMagnifying()) {
mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
up.getX(), up.getY(), true);
- mViewport.setFrameShown(true, true);
} else {
mMagnificationController.reset(true);
- mViewport.setFrameShown(false, true);
}
}
@@ -742,7 +854,6 @@ public final class ScreenMagnifier implements EventStreamTransformation {
mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
down.getX(), down.getY(), true);
- mViewport.setFrameShown(true, true);
transitionToState(STATE_VIEWPORT_DRAGGING);
}
}
@@ -837,443 +948,35 @@ public final class ScreenMagnifier implements EventStreamTransformation {
}
}
- private static final class ScreenStateObserver extends BroadcastReceiver {
-
- private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MESSAGE_ON_SCREEN_STATE_CHANGE: {
- String action = (String) message.obj;
- handleOnScreenStateChange(action);
- } break;
- }
- }
- };
-
- private final Context mContext;
- private final Viewport mViewport;
- private final MagnificationController mMagnificationController;
-
- public ScreenStateObserver(Context context, Viewport viewport,
- MagnificationController magnificationController) {
- mContext = context;
- mViewport = viewport;
- mMagnificationController = magnificationController;
- mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
- }
-
- public void destroy() {
- mContext.unregisterReceiver(this);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
- intent.getAction()).sendToTarget();
- }
-
- private void handleOnScreenStateChange(String action) {
- if (action.equals(Intent.ACTION_SCREEN_OFF)
- && mMagnificationController.isMagnifying()
- && isScreenMagnificationAutoUpdateEnabled(mContext)) {
- mMagnificationController.reset(false);
- mViewport.setFrameShown(false, false);
- }
- }
- }
-
- private static final class DisplayContentObserver {
-
- private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1;
- private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 3;
- private static final int MESSAGE_ON_WINDOW_TRANSITION = 4;
- private static final int MESSAGE_ON_ROTATION_CHANGED = 5;
- private static final int MESSAGE_ON_WINDOW_LAYERS_CHANGED = 6;
-
- private final Handler mHandler = new MyHandler();
-
- private final Rect mTempRect = new Rect();
-
- private final IDisplayContentChangeListener mDisplayContentChangeListener;
-
- private final Context mContext;
- private final Viewport mViewport;
- private final MagnificationController mMagnificationController;
- private final IWindowManager mWindowManagerService;
- private final DisplayProvider mDisplayProvider;
- private final long mLongAnimationDuration;
- private final float mWindowAnimationScale;
-
- public DisplayContentObserver(Context context, Viewport viewport,
- MagnificationController magnificationController,
- IWindowManager windowManagerService, DisplayProvider displayProvider,
- long longAnimationDuration, float windowAnimationScale) {
- mContext = context;
- mViewport = viewport;
- mMagnificationController = magnificationController;
- mWindowManagerService = windowManagerService;
- mDisplayProvider = displayProvider;
- mLongAnimationDuration = longAnimationDuration;
- mWindowAnimationScale = windowAnimationScale;
-
- mDisplayContentChangeListener = new IDisplayContentChangeListener.Stub() {
- @Override
- public void onWindowTransition(int displayId, int transition, WindowInfo info) {
- mHandler.obtainMessage(MESSAGE_ON_WINDOW_TRANSITION,
- transition, 0, WindowInfo.obtain(info)).sendToTarget();
- }
-
- @Override
- public void onRectangleOnScreenRequested(int dsiplayId, Rect rectangle,
- boolean immediate) {
- SomeArgs args = SomeArgs.obtain();
- args.argi1 = rectangle.left;
- args.argi2 = rectangle.top;
- args.argi3 = rectangle.right;
- args.argi4 = rectangle.bottom;
- mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, 0,
- immediate ? 1 : 0, args).sendToTarget();
- }
-
- @Override
- public void onRotationChanged(int rotation) throws RemoteException {
- mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0)
- .sendToTarget();
- }
-
- @Override
- public void onWindowLayersChanged(int displayId) throws RemoteException {
- mHandler.sendEmptyMessage(MESSAGE_ON_WINDOW_LAYERS_CHANGED);
- }
- };
-
- try {
- mWindowManagerService.addDisplayContentChangeListener(
- mDisplayProvider.getDisplay().getDisplayId(),
- mDisplayContentChangeListener);
- } catch (RemoteException re) {
- /* ignore */
- }
- }
-
- public void destroy() {
- try {
- mWindowManagerService.removeDisplayContentChangeListener(
- mDisplayProvider.getDisplay().getDisplayId(),
- mDisplayContentChangeListener);
- } catch (RemoteException re) {
- /* ignore*/
- }
- }
-
- private void handleOnRotationChanged(int rotation) {
- if (DEBUG_ROTATION) {
- Slog.i(LOG_TAG, "Rotation: " + rotationToString(rotation));
- }
- resetMagnificationIfNeeded();
- mViewport.setFrameShown(false, false);
- mViewport.rotationChanged();
- mViewport.recomputeBounds(false);
- if (mMagnificationController.isMagnifying()) {
- final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale);
- Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME);
- mHandler.sendMessageDelayed(message, delay);
- }
- }
-
- private void handleOnWindowTransition(int transition, WindowInfo info) {
- if (DEBUG_WINDOW_TRANSITIONS) {
- Slog.i(LOG_TAG, "Window transitioning: "
- + windowTransitionToString(transition));
- }
- try {
- final boolean magnifying = mMagnificationController.isMagnifying();
- if (magnifying) {
- switch (transition) {
- case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
- case WindowManagerPolicy.TRANSIT_TASK_OPEN:
- case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
- case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
- case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
- case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
- resetMagnificationIfNeeded();
- }
- }
- }
- if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
- || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
- || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG
- || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD
- || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
- switch (transition) {
- case WindowManagerPolicy.TRANSIT_ENTER:
- case WindowManagerPolicy.TRANSIT_SHOW:
- case WindowManagerPolicy.TRANSIT_EXIT:
- case WindowManagerPolicy.TRANSIT_HIDE: {
- mViewport.recomputeBounds(mMagnificationController.isMagnifying());
- } break;
- }
- }
- switch (transition) {
- case WindowManagerPolicy.TRANSIT_ENTER:
- case WindowManagerPolicy.TRANSIT_SHOW: {
- if (!magnifying || !isScreenMagnificationAutoUpdateEnabled(mContext)) {
- break;
- }
- final int type = info.type;
- switch (type) {
- // TODO: Are these all the windows we want to make
- // visible when they appear on the screen?
- // Do we need to take some of them out?
- case WindowManager.LayoutParams.TYPE_APPLICATION:
- case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
- case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
- case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
- case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
- case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
- case WindowManager.LayoutParams.TYPE_PHONE:
- case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
- case WindowManager.LayoutParams.TYPE_TOAST:
- case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
- case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
- case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
- case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
- case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
- case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
- case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
- case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
- Rect magnifiedRegionBounds = mMagnificationController
- .getMagnifiedRegionBounds();
- Rect touchableRegion = info.touchableRegion;
- if (!magnifiedRegionBounds.intersect(touchableRegion)) {
- ensureRectangleInMagnifiedRegionBounds(
- magnifiedRegionBounds, touchableRegion);
- }
- } break;
- } break;
- }
- }
- } finally {
- if (info != null) {
- info.recycle();
- }
- }
- }
-
- private void handleOnRectangleOnScreenRequested(Rect rectangle, boolean immediate) {
- if (!mMagnificationController.isMagnifying()) {
- return;
- }
- Rect magnifiedRegionBounds = mMagnificationController.getMagnifiedRegionBounds();
- if (magnifiedRegionBounds.contains(rectangle)) {
- return;
- }
- ensureRectangleInMagnifiedRegionBounds(magnifiedRegionBounds, rectangle);
- }
-
- private void ensureRectangleInMagnifiedRegionBounds(Rect magnifiedRegionBounds,
- Rect rectangle) {
- if (!Rect.intersects(rectangle, mViewport.getBounds())) {
- return;
- }
- final float scrollX;
- final float scrollY;
- if (rectangle.width() > magnifiedRegionBounds.width()) {
- final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
- if (direction == View.LAYOUT_DIRECTION_LTR) {
- scrollX = rectangle.left - magnifiedRegionBounds.left;
- } else {
- scrollX = rectangle.right - magnifiedRegionBounds.right;
- }
- } else if (rectangle.left < magnifiedRegionBounds.left) {
- scrollX = rectangle.left - magnifiedRegionBounds.left;
- } else if (rectangle.right > magnifiedRegionBounds.right) {
- scrollX = rectangle.right - magnifiedRegionBounds.right;
- } else {
- scrollX = 0;
- }
- if (rectangle.height() > magnifiedRegionBounds.height()) {
- scrollY = rectangle.top - magnifiedRegionBounds.top;
- } else if (rectangle.top < magnifiedRegionBounds.top) {
- scrollY = rectangle.top - magnifiedRegionBounds.top;
- } else if (rectangle.bottom > magnifiedRegionBounds.bottom) {
- scrollY = rectangle.bottom - magnifiedRegionBounds.bottom;
- } else {
- scrollY = 0;
- }
- final float viewportCenterX = mMagnificationController.getMagnifiedRegionCenterX()
- + scrollX;
- final float viewportCenterY = mMagnificationController.getMagnifiedRegionCenterY()
- + scrollY;
- mMagnificationController.setMagnifiedRegionCenter(viewportCenterX, viewportCenterY,
- true);
- }
-
- private void resetMagnificationIfNeeded() {
- if (mMagnificationController.isMagnifying()
- && isScreenMagnificationAutoUpdateEnabled(mContext)) {
- mMagnificationController.reset(true);
- mViewport.setFrameShown(false, true);
- }
- }
-
- private String windowTransitionToString(int transition) {
- switch (transition) {
- case WindowManagerPolicy.TRANSIT_UNSET: {
- return "TRANSIT_UNSET";
- }
- case WindowManagerPolicy.TRANSIT_NONE: {
- return "TRANSIT_NONE";
- }
- case WindowManagerPolicy.TRANSIT_ENTER: {
- return "TRANSIT_ENTER";
- }
- case WindowManagerPolicy.TRANSIT_EXIT: {
- return "TRANSIT_EXIT";
- }
- case WindowManagerPolicy.TRANSIT_SHOW: {
- return "TRANSIT_SHOW";
- }
- case WindowManagerPolicy.TRANSIT_EXIT_MASK: {
- return "TRANSIT_EXIT_MASK";
- }
- case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: {
- return "TRANSIT_PREVIEW_DONE";
- }
- case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: {
- return "TRANSIT_ACTIVITY_OPEN";
- }
- case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: {
- return "TRANSIT_ACTIVITY_CLOSE";
- }
- case WindowManagerPolicy.TRANSIT_TASK_OPEN: {
- return "TRANSIT_TASK_OPEN";
- }
- case WindowManagerPolicy.TRANSIT_TASK_CLOSE: {
- return "TRANSIT_TASK_CLOSE";
- }
- case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: {
- return "TRANSIT_TASK_TO_FRONT";
- }
- case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: {
- return "TRANSIT_TASK_TO_BACK";
- }
- case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: {
- return "TRANSIT_WALLPAPER_CLOSE";
- }
- case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: {
- return "TRANSIT_WALLPAPER_OPEN";
- }
- case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
- return "TRANSIT_WALLPAPER_INTRA_OPEN";
- }
- case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: {
- return "TRANSIT_WALLPAPER_INTRA_CLOSE";
- }
- default: {
- return "<UNKNOWN>";
- }
- }
- }
-
- private String rotationToString(int rotation) {
- switch (rotation) {
- case Surface.ROTATION_0: {
- return "ROTATION_0";
- }
- case Surface.ROTATION_90: {
- return "ROATATION_90";
- }
- case Surface.ROTATION_180: {
- return "ROATATION_180";
- }
- case Surface.ROTATION_270: {
- return "ROATATION_270";
- }
- default: {
- throw new IllegalArgumentException("Invalid rotation: "
- + rotation);
- }
- }
- }
-
- private final class MyHandler extends Handler {
- @Override
- public void handleMessage(Message message) {
- final int action = message.what;
- switch (action) {
- case MESSAGE_SHOW_VIEWPORT_FRAME: {
- mViewport.setFrameShown(true, true);
- } break;
- case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
- SomeArgs args = (SomeArgs) message.obj;
- try {
- mTempRect.set(args.argi1, args.argi2, args.argi3, args.argi4);
- final boolean immediate = (message.arg1 == 1);
- handleOnRectangleOnScreenRequested(mTempRect, immediate);
- } finally {
- args.recycle();
- }
- } break;
- case MESSAGE_ON_WINDOW_TRANSITION: {
- final int transition = message.arg1;
- WindowInfo info = (WindowInfo) message.obj;
- handleOnWindowTransition(transition, info);
- } break;
- case MESSAGE_ON_ROTATION_CHANGED: {
- final int rotation = message.arg1;
- handleOnRotationChanged(rotation);
- } break;
- case MESSAGE_ON_WINDOW_LAYERS_CHANGED: {
- mViewport.recomputeBounds(mMagnificationController.isMagnifying());
- } break;
- default: {
- throw new IllegalArgumentException("Unknown message: " + action);
- }
- }
- }
- }
- }
-
private final class MagnificationController {
- private static final String PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION =
- "accessibilityTransformation";
+ private static final String PROPERTY_NAME_MAGNIFICATION_SPEC =
+ "magnificationSpec";
- private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec();
+ private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
- private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec();
+ private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
private final Rect mTempRect = new Rect();
private final ValueAnimator mTransformationAnimator;
- public MagnificationController(int animationDuration) {
+ public MagnificationController(long animationDuration) {
Property<MagnificationController, MagnificationSpec> property =
Property.of(MagnificationController.class, MagnificationSpec.class,
- PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION);
+ PROPERTY_NAME_MAGNIFICATION_SPEC);
TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() {
- private final MagnificationSpec mTempTransformationSpec = new MagnificationSpec();
+ private final MagnificationSpec mTempTransformationSpec =
+ MagnificationSpec.obtain();
@Override
public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
MagnificationSpec toSpec) {
MagnificationSpec result = mTempTransformationSpec;
- result.mScale = fromSpec.mScale
- + (toSpec.mScale - fromSpec.mScale) * fraction;
- result.mMagnifiedRegionCenterX = fromSpec.mMagnifiedRegionCenterX
- + (toSpec.mMagnifiedRegionCenterX - fromSpec.mMagnifiedRegionCenterX)
- * fraction;
- result.mMagnifiedRegionCenterY = fromSpec.mMagnifiedRegionCenterY
- + (toSpec.mMagnifiedRegionCenterY - fromSpec.mMagnifiedRegionCenterY)
+ result.scale = fromSpec.scale
+ + (toSpec.scale - fromSpec.scale) * fraction;
+ result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX)
* fraction;
- result.mScaledOffsetX = fromSpec.mScaledOffsetX
- + (toSpec.mScaledOffsetX - fromSpec.mScaledOffsetX)
- * fraction;
- result.mScaledOffsetY = fromSpec.mScaledOffsetY
- + (toSpec.mScaledOffsetY - fromSpec.mScaledOffsetY)
+ result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY)
* fraction;
return result;
}
@@ -1281,61 +984,50 @@ public final class ScreenMagnifier implements EventStreamTransformation {
mTransformationAnimator = ObjectAnimator.ofObject(this, property,
evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec);
mTransformationAnimator.setDuration((long) (animationDuration));
- mTransformationAnimator.setInterpolator(mInterpolator);
+ mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
}
public boolean isMagnifying() {
- return mCurrentMagnificationSpec.mScale > 1.0f;
+ return mCurrentMagnificationSpec.scale > 1.0f;
}
public void reset(boolean animate) {
if (mTransformationAnimator.isRunning()) {
mTransformationAnimator.cancel();
}
- mCurrentMagnificationSpec.reset();
+ mCurrentMagnificationSpec.clear();
if (animate) {
- animateAccessibilityTranformation(mSentMagnificationSpec,
+ animateMangificationSpec(mSentMagnificationSpec,
mCurrentMagnificationSpec);
} else {
- setAccessibilityTransformation(mCurrentMagnificationSpec);
+ setMagnificationSpec(mCurrentMagnificationSpec);
}
- }
-
- public Rect getMagnifiedRegionBounds() {
- mTempRect.set(mViewport.getBounds());
- mTempRect.offset((int) -mCurrentMagnificationSpec.mScaledOffsetX,
- (int) -mCurrentMagnificationSpec.mScaledOffsetY);
- mTempRect.scale(1.0f / mCurrentMagnificationSpec.mScale);
- return mTempRect;
+ Rect bounds = mTempRect;
+ bounds.setEmpty();
+ mAms.onMagnificationStateChanged();
}
public float getScale() {
- return mCurrentMagnificationSpec.mScale;
- }
-
- public float getMagnifiedRegionCenterX() {
- return mCurrentMagnificationSpec.mMagnifiedRegionCenterX;
+ return mCurrentMagnificationSpec.scale;
}
- public float getMagnifiedRegionCenterY() {
- return mCurrentMagnificationSpec.mMagnifiedRegionCenterY;
+ public float getOffsetX() {
+ return mCurrentMagnificationSpec.offsetX;
}
- public float getScaledOffsetX() {
- return mCurrentMagnificationSpec.mScaledOffsetX;
- }
-
- public float getScaledOffsetY() {
- return mCurrentMagnificationSpec.mScaledOffsetY;
+ public float getOffsetY() {
+ return mCurrentMagnificationSpec.offsetY;
}
public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
+ Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
MagnificationSpec spec = mCurrentMagnificationSpec;
- final float oldScale = spec.mScale;
- final float oldCenterX = spec.mMagnifiedRegionCenterX;
- final float oldCenterY = spec.mMagnifiedRegionCenterY;
- final float normPivotX = (-spec.mScaledOffsetX + pivotX) / oldScale;
- final float normPivotY = (-spec.mScaledOffsetY + pivotY) / oldScale;
+ final float oldScale = spec.scale;
+ final float oldCenterX = (-spec.offsetX + magnifiedFrame.width() / 2) / oldScale;
+ final float oldCenterY = (-spec.offsetY + magnifiedFrame.height() / 2) / oldScale;
+ final float normPivotX = (-spec.offsetX + pivotX) / oldScale;
+ final float normPivotY = (-spec.offsetY + pivotY) / oldScale;
final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
final float centerX = normPivotX + offsetX;
@@ -1344,16 +1036,26 @@ public final class ScreenMagnifier implements EventStreamTransformation {
}
public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
- setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.mScale, centerX, centerY,
+ setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.scale, centerX, centerY,
animate);
}
+ public void offsetMagnifiedRegionCenter(float offsetX, float offsetY) {
+ final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+ mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
+ getMinOffsetX()), 0);
+ final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+ mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
+ getMinOffsetY()), 0);
+ setMagnificationSpec(mCurrentMagnificationSpec);
+ }
+
public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
boolean animate) {
- if (Float.compare(mCurrentMagnificationSpec.mScale, scale) == 0
- && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterX,
+ if (Float.compare(mCurrentMagnificationSpec.scale, scale) == 0
+ && Float.compare(mCurrentMagnificationSpec.offsetX,
centerX) == 0
- && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterY,
+ && Float.compare(mCurrentMagnificationSpec.offsetY,
centerY) == 0) {
return;
}
@@ -1361,501 +1063,114 @@ public final class ScreenMagnifier implements EventStreamTransformation {
mTransformationAnimator.cancel();
}
if (DEBUG_MAGNIFICATION_CONTROLLER) {
- Slog.i(LOG_TAG, "scale: " + scale + " centerX: " + centerX
- + " centerY: " + centerY);
+ Slog.i(LOG_TAG, "scale: " + scale + " offsetX: " + centerX
+ + " offsetY: " + centerY);
}
- mCurrentMagnificationSpec.initialize(scale, centerX, centerY);
+ updateMagnificationSpec(scale, centerX, centerY);
if (animate) {
- animateAccessibilityTranformation(mSentMagnificationSpec,
+ animateMangificationSpec(mSentMagnificationSpec,
mCurrentMagnificationSpec);
} else {
- setAccessibilityTransformation(mCurrentMagnificationSpec);
+ setMagnificationSpec(mCurrentMagnificationSpec);
}
+ mAms.onMagnificationStateChanged();
+ }
+
+ public void updateMagnificationSpec(float scale, float magnifiedCenterX,
+ float magnifiedCenterY) {
+ Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
+ mCurrentMagnificationSpec.scale = scale;
+ final int viewportWidth = magnifiedFrame.width();
+ final float nonNormOffsetX = viewportWidth / 2 - magnifiedCenterX * scale;
+ mCurrentMagnificationSpec.offsetX = Math.min(Math.max(nonNormOffsetX,
+ getMinOffsetX()), 0);
+ final int viewportHeight = magnifiedFrame.height();
+ final float nonNormOffsetY = viewportHeight / 2 - magnifiedCenterY * scale;
+ mCurrentMagnificationSpec.offsetY = Math.min(Math.max(nonNormOffsetY,
+ getMinOffsetY()), 0);
}
- private void animateAccessibilityTranformation(MagnificationSpec fromSpec,
+ private float getMinOffsetX() {
+ Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
+ final float viewportWidth = magnifiedFrame.width();
+ return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
+ }
+
+ private float getMinOffsetY() {
+ Rect magnifiedFrame = mTempRect;
+ mMagnifiedBounds.getBounds(magnifiedFrame);
+ final float viewportHeight = magnifiedFrame.height();
+ return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
+ }
+
+ private void animateMangificationSpec(MagnificationSpec fromSpec,
MagnificationSpec toSpec) {
mTransformationAnimator.setObjectValues(fromSpec, toSpec);
mTransformationAnimator.start();
}
- @SuppressWarnings("unused")
- // Called from an animator.
- public MagnificationSpec getAccessibilityTransformation() {
+ public MagnificationSpec getMagnificationSpec() {
return mSentMagnificationSpec;
}
- public void setAccessibilityTransformation(MagnificationSpec transformation) {
- if (DEBUG_TRANSFORMATION) {
- Slog.i(LOG_TAG, "Transformation scale: " + transformation.mScale
- + " offsetX: " + transformation.mScaledOffsetX
- + " offsetY: " + transformation.mScaledOffsetY);
+ public void setMagnificationSpec(MagnificationSpec spec) {
+ if (DEBUG_SET_MAGNIFICATION_SPEC) {
+ Slog.i(LOG_TAG, "Sending: " + spec);
}
try {
- mSentMagnificationSpec.updateFrom(transformation);
- mWindowManagerService.magnifyDisplay(mDisplayProvider.getDisplay().getDisplayId(),
- transformation.mScale, transformation.mScaledOffsetX,
- transformation.mScaledOffsetY);
+ mSentMagnificationSpec.scale = spec.scale;
+ mSentMagnificationSpec.offsetX = spec.offsetX;
+ mSentMagnificationSpec.offsetY = spec.offsetY;
+ mWindowManager.setMagnificationSpec(
+ MagnificationSpec.obtain(spec));
} catch (RemoteException re) {
/* ignore */
}
}
-
- private class MagnificationSpec {
-
- private static final float DEFAULT_SCALE = 1.0f;
-
- public float mScale = DEFAULT_SCALE;
-
- public float mMagnifiedRegionCenterX;
-
- public float mMagnifiedRegionCenterY;
-
- public float mScaledOffsetX;
-
- public float mScaledOffsetY;
-
- public void initialize(float scale, float magnifiedRegionCenterX,
- float magnifiedRegionCenterY) {
- mScale = scale;
-
- final int viewportWidth = mViewport.getBounds().width();
- final int viewportHeight = mViewport.getBounds().height();
- final float minMagnifiedRegionCenterX = (viewportWidth / 2) / scale;
- final float minMagnifiedRegionCenterY = (viewportHeight / 2) / scale;
- final float maxMagnifiedRegionCenterX = viewportWidth - minMagnifiedRegionCenterX;
- final float maxMagnifiedRegionCenterY = viewportHeight - minMagnifiedRegionCenterY;
-
- mMagnifiedRegionCenterX = Math.min(Math.max(magnifiedRegionCenterX,
- minMagnifiedRegionCenterX), maxMagnifiedRegionCenterX);
- mMagnifiedRegionCenterY = Math.min(Math.max(magnifiedRegionCenterY,
- minMagnifiedRegionCenterY), maxMagnifiedRegionCenterY);
-
- mScaledOffsetX = -(mMagnifiedRegionCenterX * scale - viewportWidth / 2);
- mScaledOffsetY = -(mMagnifiedRegionCenterY * scale - viewportHeight / 2);
- }
-
- public void updateFrom(MagnificationSpec other) {
- mScale = other.mScale;
- mMagnifiedRegionCenterX = other.mMagnifiedRegionCenterX;
- mMagnifiedRegionCenterY = other.mMagnifiedRegionCenterY;
- mScaledOffsetX = other.mScaledOffsetX;
- mScaledOffsetY = other.mScaledOffsetY;
- }
-
- public void reset() {
- mScale = DEFAULT_SCALE;
- mMagnifiedRegionCenterX = 0;
- mMagnifiedRegionCenterY = 0;
- mScaledOffsetX = 0;
- mScaledOffsetY = 0;
- }
- }
}
- private static final class Viewport {
-
- private static final String PROPERTY_NAME_ALPHA = "alpha";
-
- private static final String PROPERTY_NAME_BOUNDS = "bounds";
-
- private static final int MIN_ALPHA = 0;
-
- private static final int MAX_ALPHA = 255;
-
- private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>();
-
- private final Rect mTempRect1 = new Rect();
- private final Rect mTempRect2 = new Rect();
- private final Rect mTempRect3 = new Rect();
-
- private final IWindowManager mWindowManagerService;
- private final DisplayProvider mDisplayProvider;
-
- private final ViewportWindow mViewportFrame;
-
- private final ValueAnimator mResizeFrameAnimator;
-
- private final ValueAnimator mShowHideFrameAnimator;
-
- public Viewport(Context context, WindowManager windowManager,
- IWindowManager windowManagerService, DisplayProvider displayInfoProvider,
- Interpolator animationInterpolator, long animationDuration) {
- mWindowManagerService = windowManagerService;
- mDisplayProvider = displayInfoProvider;
- mViewportFrame = new ViewportWindow(context, windowManager, displayInfoProvider);
-
- mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA,
- MIN_ALPHA, MAX_ALPHA);
- mShowHideFrameAnimator.setInterpolator(animationInterpolator);
- mShowHideFrameAnimator.setDuration(animationDuration);
- mShowHideFrameAnimator.addListener(new AnimatorListener() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) {
- mViewportFrame.hide();
- }
- }
- @Override
- public void onAnimationStart(Animator animation) {
- /* do nothing - stub */
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- /* do nothing - stub */
- }
- @Override
- public void onAnimationRepeat(Animator animation) {
- /* do nothing - stub */
- }
- });
-
- Property<ViewportWindow, Rect> property = Property.of(ViewportWindow.class,
- Rect.class, PROPERTY_NAME_BOUNDS);
- TypeEvaluator<Rect> evaluator = new TypeEvaluator<Rect>() {
- private final Rect mReusableResultRect = new Rect();
- @Override
- public Rect evaluate(float fraction, Rect fromFrame, Rect toFrame) {
- Rect result = mReusableResultRect;
- result.left = (int) (fromFrame.left
- + (toFrame.left - fromFrame.left) * fraction);
- result.top = (int) (fromFrame.top
- + (toFrame.top - fromFrame.top) * fraction);
- result.right = (int) (fromFrame.right
- + (toFrame.right - fromFrame.right) * fraction);
- result.bottom = (int) (fromFrame.bottom
- + (toFrame.bottom - fromFrame.bottom) * fraction);
- return result;
- }
- };
- mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property,
- evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds);
- mResizeFrameAnimator.setDuration((long) (animationDuration));
- mResizeFrameAnimator.setInterpolator(animationInterpolator);
+ private final class ScreenStateObserver extends BroadcastReceiver {
+ private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
- recomputeBounds(false);
- }
+ private final Context mContext;
+ private final MagnificationController mMagnificationController;
- private final Comparator<WindowInfo> mWindowInfoInverseComparator =
- new Comparator<WindowInfo>() {
+ private final Handler mHandler = new Handler() {
@Override
- public int compare(WindowInfo lhs, WindowInfo rhs) {
- if (lhs.layer != rhs.layer) {
- return rhs.layer - lhs.layer;
- }
- if (lhs.touchableRegion.top != rhs.touchableRegion.top) {
- return rhs.touchableRegion.top - lhs.touchableRegion.top;
- }
- if (lhs.touchableRegion.left != rhs.touchableRegion.left) {
- return rhs.touchableRegion.left - lhs.touchableRegion.left;
- }
- if (lhs.touchableRegion.right != rhs.touchableRegion.right) {
- return rhs.touchableRegion.right - lhs.touchableRegion.right;
- }
- if (lhs.touchableRegion.bottom != rhs.touchableRegion.bottom) {
- return rhs.touchableRegion.bottom - lhs.touchableRegion.bottom;
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_ON_SCREEN_STATE_CHANGE: {
+ String action = (String) message.obj;
+ handleOnScreenStateChange(action);
+ } break;
}
- return 0;
}
};
- public void recomputeBounds(boolean animate) {
- Rect magnifiedFrame = mTempRect1;
- magnifiedFrame.set(0, 0, 0, 0);
-
- DisplayInfo displayInfo = mDisplayProvider.getDisplayInfo();
-
- Rect availableFrame = mTempRect2;
- availableFrame.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
-
- ArrayList<WindowInfo> infos = mTempWindowInfoList;
- infos.clear();
- int windowCount = 0;
- try {
- mWindowManagerService.getVisibleWindowsForDisplay(
- mDisplayProvider.getDisplay().getDisplayId(), infos);
- Collections.sort(infos, mWindowInfoInverseComparator);
- windowCount = infos.size();
- for (int i = 0; i < windowCount; i++) {
- WindowInfo info = infos.get(i);
- if (info.type == WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
- continue;
- }
- Rect windowFrame = mTempRect3;
- windowFrame.set(info.touchableRegion);
- if (isWindowMagnified(info.type)) {
- magnifiedFrame.union(windowFrame);
- magnifiedFrame.intersect(availableFrame);
- } else {
- subtract(windowFrame, magnifiedFrame);
- subtract(availableFrame, windowFrame);
- }
- if (availableFrame.equals(magnifiedFrame)) {
- break;
- }
- }
- } catch (RemoteException re) {
- /* ignore */
- } finally {
- for (int i = windowCount - 1; i >= 0; i--) {
- infos.remove(i).recycle();
- }
- }
-
- final int displayWidth = mDisplayProvider.getDisplayInfo().logicalWidth;
- final int displayHeight = mDisplayProvider.getDisplayInfo().logicalHeight;
- magnifiedFrame.intersect(0, 0, displayWidth, displayHeight);
-
- resize(magnifiedFrame, animate);
- }
-
- private boolean isWindowMagnified(int type) {
- return (type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
- && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD
- && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
- }
-
- public void rotationChanged() {
- mViewportFrame.rotationChanged();
- }
-
- public Rect getBounds() {
- return mViewportFrame.getBounds();
- }
-
- public void setFrameShown(boolean shown, boolean animate) {
- if (mViewportFrame.isShown() == shown) {
- return;
- }
- if (animate) {
- if (mShowHideFrameAnimator.isRunning()) {
- mShowHideFrameAnimator.reverse();
- } else {
- if (shown) {
- mViewportFrame.show();
- mShowHideFrameAnimator.start();
- } else {
- mShowHideFrameAnimator.reverse();
- }
- }
- } else {
- mShowHideFrameAnimator.cancel();
- if (shown) {
- mViewportFrame.show();
- } else {
- mViewportFrame.hide();
- }
- }
- }
-
- private void resize(Rect bounds, boolean animate) {
- if (mViewportFrame.getBounds().equals(bounds)) {
- return;
- }
- if (animate) {
- if (mResizeFrameAnimator.isRunning()) {
- mResizeFrameAnimator.cancel();
- }
- mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds);
- mResizeFrameAnimator.start();
- } else {
- mViewportFrame.setBounds(bounds);
- }
- }
-
- private boolean subtract(Rect lhs, Rect rhs) {
- if (lhs.right < rhs.left || lhs.left > rhs.right
- || lhs.bottom < rhs.top || lhs.top > rhs.bottom) {
- return false;
- }
- if (lhs.left < rhs.left) {
- lhs.right = rhs.left;
- }
- if (lhs.top < rhs.top) {
- lhs.bottom = rhs.top;
- }
- if (lhs.right > rhs.right) {
- lhs.left = rhs.right;
- }
- if (lhs.bottom > rhs.bottom) {
- lhs.top = rhs.bottom;
- }
- return true;
- }
-
- private static final class ViewportWindow {
- private static final String WINDOW_TITLE = "Magnification Overlay";
-
- private final WindowManager mWindowManager;
- private final DisplayProvider mDisplayProvider;
-
- private final ContentView mWindowContent;
- private final WindowManager.LayoutParams mWindowParams;
-
- private final Rect mBounds = new Rect();
- private boolean mShown;
- private int mAlpha;
-
- public ViewportWindow(Context context, WindowManager windowManager,
- DisplayProvider displayProvider) {
- mWindowManager = windowManager;
- mDisplayProvider = displayProvider;
-
- ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- mWindowContent = new ContentView(context);
- mWindowContent.setLayoutParams(contentParams);
- mWindowContent.setBackgroundColor(R.color.transparent);
-
- mWindowParams = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY);
- mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
- mWindowParams.setTitle(WINDOW_TITLE);
- mWindowParams.gravity = Gravity.CENTER;
- mWindowParams.width = displayProvider.getDisplayInfo().logicalWidth;
- mWindowParams.height = displayProvider.getDisplayInfo().logicalHeight;
- mWindowParams.format = PixelFormat.TRANSLUCENT;
- }
-
- public boolean isShown() {
- return mShown;
- }
-
- public void show() {
- if (mShown) {
- return;
- }
- mShown = true;
- mWindowManager.addView(mWindowContent, mWindowParams);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow shown.");
- }
- }
-
- public void hide() {
- if (!mShown) {
- return;
- }
- mShown = false;
- mWindowManager.removeView(mWindowContent);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow hidden.");
- }
- }
-
- @SuppressWarnings("unused")
- // Called reflectively from an animator.
- public int getAlpha() {
- return mAlpha;
- }
-
- @SuppressWarnings("unused")
- // Called reflectively from an animator.
- public void setAlpha(int alpha) {
- if (mAlpha == alpha) {
- return;
- }
- mAlpha = alpha;
- if (mShown) {
- mWindowContent.invalidate();
- }
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha);
- }
- }
-
- public Rect getBounds() {
- return mBounds;
- }
-
- public void rotationChanged() {
- mWindowParams.width = mDisplayProvider.getDisplayInfo().logicalWidth;
- mWindowParams.height = mDisplayProvider.getDisplayInfo().logicalHeight;
- if (mShown) {
- mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
- }
- }
-
- public void setBounds(Rect bounds) {
- if (mBounds.equals(bounds)) {
- return;
- }
- mBounds.set(bounds);
- if (mShown) {
- mWindowContent.invalidate();
- }
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds);
- }
- }
-
- private final class ContentView extends View {
- private final Drawable mHighlightFrame;
-
- public ContentView(Context context) {
- super(context);
- mHighlightFrame = context.getResources().getDrawable(
- R.drawable.magnified_region_frame);
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
- mHighlightFrame.setBounds(mBounds);
- mHighlightFrame.setAlpha(mAlpha);
- mHighlightFrame.draw(canvas);
- }
- }
- }
- }
-
- private static class DisplayProvider implements DisplayListener {
- private final WindowManager mWindowManager;
- private final DisplayManager mDisplayManager;
- private final Display mDefaultDisplay;
- private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
-
- public DisplayProvider(Context context, WindowManager windowManager) {
- mWindowManager = windowManager;
- mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
- mDefaultDisplay = mWindowManager.getDefaultDisplay();
- mDisplayManager.registerDisplayListener(this, null);
- updateDisplayInfo();
- }
-
- public DisplayInfo getDisplayInfo() {
- return mDefaultDisplayInfo;
- }
-
- public Display getDisplay() {
- return mDefaultDisplay;
- }
-
- private void updateDisplayInfo() {
- if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
- Slog.e(LOG_TAG, "Default display is not valid.");
- }
+ public ScreenStateObserver(Context context,
+ MagnificationController magnificationController) {
+ mContext = context;
+ mMagnificationController = magnificationController;
+ mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
public void destroy() {
- mDisplayManager.unregisterDisplayListener(this);
- }
-
- @Override
- public void onDisplayAdded(int displayId) {
- /* do noting */
+ mContext.unregisterReceiver(this);
}
@Override
- public void onDisplayRemoved(int displayId) {
- // Having no default display
+ public void onReceive(Context context, Intent intent) {
+ mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
+ intent.getAction()).sendToTarget();
}
- @Override
- public void onDisplayChanged(int displayId) {
- updateDisplayInfo();
+ private void handleOnScreenStateChange(String action) {
+ if (mMagnificationController.isMagnifying()
+ && isScreenMagnificationAutoUpdateEnabled(mContext)) {
+ mMagnificationController.reset(false);
+ }
}
}
}
diff --git a/services/java/com/android/server/accounts/AccountAuthenticatorCache.java b/services/java/com/android/server/accounts/AccountAuthenticatorCache.java
new file mode 100644
index 0000000..7552368
--- /dev/null
+++ b/services/java/com/android/server/accounts/AccountAuthenticatorCache.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.accounts.IAccountAuthenticator;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.RegisteredServicesCache;
+import android.content.pm.XmlSerializerAndParser;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * A cache of services that export the {@link IAccountAuthenticator} interface. This cache
+ * is built by interrogating the {@link PackageManager} and is updated as packages are added,
+ * removed and changed. The authenticators are referred to by their account type and
+ * are made available via the {@link RegisteredServicesCache#getServiceInfo} method.
+ * @hide
+ */
+/* package private */ class AccountAuthenticatorCache
+ extends RegisteredServicesCache<AuthenticatorDescription>
+ implements IAccountAuthenticatorCache {
+ private static final String TAG = "Account";
+ private static final MySerializer sSerializer = new MySerializer();
+
+ public AccountAuthenticatorCache(Context context) {
+ super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT,
+ AccountManager.AUTHENTICATOR_META_DATA_NAME,
+ AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
+ }
+
+ public AuthenticatorDescription parseServiceAttributes(Resources res,
+ String packageName, AttributeSet attrs) {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AccountAuthenticator);
+ try {
+ final String accountType =
+ sa.getString(com.android.internal.R.styleable.AccountAuthenticator_accountType);
+ final int labelId = sa.getResourceId(
+ com.android.internal.R.styleable.AccountAuthenticator_label, 0);
+ final int iconId = sa.getResourceId(
+ com.android.internal.R.styleable.AccountAuthenticator_icon, 0);
+ final int smallIconId = sa.getResourceId(
+ com.android.internal.R.styleable.AccountAuthenticator_smallIcon, 0);
+ final int prefId = sa.getResourceId(
+ com.android.internal.R.styleable.AccountAuthenticator_accountPreferences, 0);
+ final boolean customTokens = sa.getBoolean(
+ com.android.internal.R.styleable.AccountAuthenticator_customTokens, false);
+ if (TextUtils.isEmpty(accountType)) {
+ return null;
+ }
+ return new AuthenticatorDescription(accountType, packageName, labelId, iconId,
+ smallIconId, prefId, customTokens);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private static class MySerializer implements XmlSerializerAndParser<AuthenticatorDescription> {
+ public void writeAsXml(AuthenticatorDescription item, XmlSerializer out)
+ throws IOException {
+ out.attribute(null, "type", item.type);
+ }
+
+ public AuthenticatorDescription createFromXml(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ return AuthenticatorDescription.newKey(parser.getAttributeValue(null, "type"));
+ }
+ }
+}
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
new file mode 100644
index 0000000..0fbde37
--- /dev/null
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -0,0 +1,2975 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.Manifest;
+import android.accounts.Account;
+import android.accounts.AccountAndUser;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.accounts.CantAddAccountActivity;
+import android.accounts.GrantCredentialsPermissionActivity;
+import android.accounts.IAccountAuthenticator;
+import android.accounts.IAccountAuthenticatorResponse;
+import android.accounts.IAccountManager;
+import android.accounts.IAccountManagerResponse;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.RegisteredServicesCache;
+import android.content.pm.RegisteredServicesCacheListener;
+import android.content.pm.UserInfo;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A system service that provides account, password, and authtoken management for all
+ * accounts on the device. Some of these calls are implemented with the help of the corresponding
+ * {@link IAccountAuthenticator} services. This service is not accessed by users directly,
+ * instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
+ * AccountManager accountManager = AccountManager.get(context);
+ * @hide
+ */
+public class AccountManagerService
+ extends IAccountManager.Stub
+ implements RegisteredServicesCacheListener<AuthenticatorDescription> {
+ private static final String TAG = "AccountManagerService";
+
+ private static final int TIMEOUT_DELAY_MS = 1000 * 60;
+ private static final String DATABASE_NAME = "accounts.db";
+ private static final int DATABASE_VERSION = 5;
+
+ private final Context mContext;
+
+ private final PackageManager mPackageManager;
+ private UserManager mUserManager;
+
+ private HandlerThread mMessageThread;
+ private final MessageHandler mMessageHandler;
+
+ // Messages that can be sent on mHandler
+ private static final int MESSAGE_TIMED_OUT = 3;
+ private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4;
+
+ private final IAccountAuthenticatorCache mAuthenticatorCache;
+
+ private static final String TABLE_ACCOUNTS = "accounts";
+ private static final String ACCOUNTS_ID = "_id";
+ private static final String ACCOUNTS_NAME = "name";
+ private static final String ACCOUNTS_TYPE = "type";
+ private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
+ private static final String ACCOUNTS_PASSWORD = "password";
+
+ private static final String TABLE_AUTHTOKENS = "authtokens";
+ private static final String AUTHTOKENS_ID = "_id";
+ private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
+ private static final String AUTHTOKENS_TYPE = "type";
+ private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
+
+ private static final String TABLE_GRANTS = "grants";
+ private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
+ private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
+ private static final String GRANTS_GRANTEE_UID = "uid";
+
+ private static final String TABLE_EXTRAS = "extras";
+ private static final String EXTRAS_ID = "_id";
+ private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
+ private static final String EXTRAS_KEY = "key";
+ private static final String EXTRAS_VALUE = "value";
+
+ private static final String TABLE_META = "meta";
+ private static final String META_KEY = "key";
+ private static final String META_VALUE = "value";
+
+ private static final String TABLE_SHARED_ACCOUNTS = "shared_accounts";
+
+ private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
+ new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
+ private static final Intent ACCOUNTS_CHANGED_INTENT;
+
+ private static final String COUNT_OF_MATCHING_GRANTS = ""
+ + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
+ + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
+ + " AND " + GRANTS_GRANTEE_UID + "=?"
+ + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?"
+ + " AND " + ACCOUNTS_NAME + "=?"
+ + " AND " + ACCOUNTS_TYPE + "=?";
+
+ private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
+ AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
+ private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
+ AUTHTOKENS_AUTHTOKEN};
+
+ private static final String SELECTION_USERDATA_BY_ACCOUNT =
+ EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
+ private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
+
+ private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
+ private final AtomicInteger mNotificationIds = new AtomicInteger(1);
+
+ static class UserAccounts {
+ private final int userId;
+ private final DatabaseHelper openHelper;
+ private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
+ credentialsPermissionNotificationIds =
+ new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
+ private final HashMap<Account, Integer> signinRequiredNotificationIds =
+ new HashMap<Account, Integer>();
+ private final Object cacheLock = new Object();
+ /** protected by the {@link #cacheLock} */
+ private final HashMap<String, Account[]> accountCache =
+ new LinkedHashMap<String, Account[]>();
+ /** protected by the {@link #cacheLock} */
+ private HashMap<Account, HashMap<String, String>> userDataCache =
+ new HashMap<Account, HashMap<String, String>>();
+ /** protected by the {@link #cacheLock} */
+ private HashMap<Account, HashMap<String, String>> authTokenCache =
+ new HashMap<Account, HashMap<String, String>>();
+
+ UserAccounts(Context context, int userId) {
+ this.userId = userId;
+ synchronized (cacheLock) {
+ openHelper = new DatabaseHelper(context, userId);
+ }
+ }
+ }
+
+ private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>();
+
+ private static AtomicReference<AccountManagerService> sThis =
+ new AtomicReference<AccountManagerService>();
+ private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
+
+ static {
+ ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
+ ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ }
+
+
+ /**
+ * This should only be called by system code. One should only call this after the service
+ * has started.
+ * @return a reference to the AccountManagerService instance
+ * @hide
+ */
+ public static AccountManagerService getSingleton() {
+ return sThis.get();
+ }
+
+ public AccountManagerService(Context context) {
+ this(context, context.getPackageManager(), new AccountAuthenticatorCache(context));
+ }
+
+ public AccountManagerService(Context context, PackageManager packageManager,
+ IAccountAuthenticatorCache authenticatorCache) {
+ mContext = context;
+ mPackageManager = packageManager;
+
+ mMessageThread = new HandlerThread("AccountManagerService");
+ mMessageThread.start();
+ mMessageHandler = new MessageHandler(mMessageThread.getLooper());
+
+ mAuthenticatorCache = authenticatorCache;
+ mAuthenticatorCache.setListener(this, null /* Handler */);
+
+ sThis.set(this);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addDataScheme("package");
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context1, Intent intent) {
+ purgeOldGrantsAll();
+ }
+ }, intentFilter);
+
+ IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ userFilter.addAction(Intent.ACTION_USER_STARTED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ onUserRemoved(intent);
+ } else if (Intent.ACTION_USER_STARTED.equals(action)) {
+ onUserStarted(intent);
+ }
+ }
+ }, UserHandle.ALL, userFilter, null, null);
+ }
+
+ public void systemReady() {
+ }
+
+ private UserManager getUserManager() {
+ if (mUserManager == null) {
+ mUserManager = UserManager.get(mContext);
+ }
+ return mUserManager;
+ }
+
+ private UserAccounts initUser(int userId) {
+ synchronized (mUsers) {
+ UserAccounts accounts = mUsers.get(userId);
+ if (accounts == null) {
+ accounts = new UserAccounts(mContext, userId);
+ mUsers.append(userId, accounts);
+ purgeOldGrants(accounts);
+ validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
+ }
+ return accounts;
+ }
+ }
+
+ private void purgeOldGrantsAll() {
+ synchronized (mUsers) {
+ for (int i = 0; i < mUsers.size(); i++) {
+ purgeOldGrants(mUsers.valueAt(i));
+ }
+ }
+ }
+
+ private void purgeOldGrants(UserAccounts accounts) {
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ final Cursor cursor = db.query(TABLE_GRANTS,
+ new String[]{GRANTS_GRANTEE_UID},
+ null, null, GRANTS_GRANTEE_UID, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ final int uid = cursor.getInt(0);
+ final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
+ if (packageExists) {
+ continue;
+ }
+ Log.d(TAG, "deleting grants for UID " + uid
+ + " because its package is no longer installed");
+ db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
+ new String[]{Integer.toString(uid)});
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ /**
+ * Validate internal set of accounts against installed authenticators for
+ * given user. Clears cached authenticators before validating.
+ */
+ public void validateAccounts(int userId) {
+ final UserAccounts accounts = getUserAccounts(userId);
+
+ // Invalidate user-specific cache to make sure we catch any
+ // removed authenticators.
+ validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
+ }
+
+ /**
+ * Validate internal set of accounts against installed authenticators for
+ * given user. Clear cached authenticators before validating when requested.
+ */
+ private void validateAccountsInternal(
+ UserAccounts accounts, boolean invalidateAuthenticatorCache) {
+ if (invalidateAuthenticatorCache) {
+ mAuthenticatorCache.invalidateCache(accounts.userId);
+ }
+
+ final HashSet<AuthenticatorDescription> knownAuth = Sets.newHashSet();
+ for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> service :
+ mAuthenticatorCache.getAllServices(accounts.userId)) {
+ knownAuth.add(service.type);
+ }
+
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ boolean accountDeleted = false;
+ Cursor cursor = db.query(TABLE_ACCOUNTS,
+ new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
+ null, null, null, null, null);
+ try {
+ accounts.accountCache.clear();
+ final HashMap<String, ArrayList<String>> accountNamesByType =
+ new LinkedHashMap<String, ArrayList<String>>();
+ while (cursor.moveToNext()) {
+ final long accountId = cursor.getLong(0);
+ final String accountType = cursor.getString(1);
+ final String accountName = cursor.getString(2);
+
+ if (!knownAuth.contains(AuthenticatorDescription.newKey(accountType))) {
+ Slog.w(TAG, "deleting account " + accountName + " because type "
+ + accountType + " no longer has a registered authenticator");
+ db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
+ accountDeleted = true;
+ final Account account = new Account(accountName, accountType);
+ accounts.userDataCache.remove(account);
+ accounts.authTokenCache.remove(account);
+ } else {
+ ArrayList<String> accountNames = accountNamesByType.get(accountType);
+ if (accountNames == null) {
+ accountNames = new ArrayList<String>();
+ accountNamesByType.put(accountType, accountNames);
+ }
+ accountNames.add(accountName);
+ }
+ }
+ for (Map.Entry<String, ArrayList<String>> cur
+ : accountNamesByType.entrySet()) {
+ final String accountType = cur.getKey();
+ final ArrayList<String> accountNames = cur.getValue();
+ final Account[] accountsForType = new Account[accountNames.size()];
+ int i = 0;
+ for (String accountName : accountNames) {
+ accountsForType[i] = new Account(accountName, accountType);
+ ++i;
+ }
+ accounts.accountCache.put(accountType, accountsForType);
+ }
+ } finally {
+ cursor.close();
+ if (accountDeleted) {
+ sendAccountsChangedBroadcast(accounts.userId);
+ }
+ }
+ }
+ }
+
+ private UserAccounts getUserAccountsForCaller() {
+ return getUserAccounts(UserHandle.getCallingUserId());
+ }
+
+ protected UserAccounts getUserAccounts(int userId) {
+ synchronized (mUsers) {
+ UserAccounts accounts = mUsers.get(userId);
+ if (accounts == null) {
+ accounts = initUser(userId);
+ mUsers.append(userId, accounts);
+ }
+ return accounts;
+ }
+ }
+
+ private void onUserRemoved(Intent intent) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId < 1) return;
+
+ UserAccounts accounts;
+ synchronized (mUsers) {
+ accounts = mUsers.get(userId);
+ mUsers.remove(userId);
+ }
+ if (accounts == null) {
+ File dbFile = new File(getDatabaseName(userId));
+ dbFile.delete();
+ return;
+ }
+
+ synchronized (accounts.cacheLock) {
+ accounts.openHelper.close();
+ File dbFile = new File(getDatabaseName(userId));
+ dbFile.delete();
+ }
+ }
+
+ private void onUserStarted(Intent intent) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId < 1) return;
+
+ // Check if there's a shared account that needs to be created as an account
+ Account[] sharedAccounts = getSharedAccountsAsUser(userId);
+ if (sharedAccounts == null || sharedAccounts.length == 0) return;
+ Account[] accounts = getAccountsAsUser(null, userId);
+ for (Account sa : sharedAccounts) {
+ if (ArrayUtils.contains(accounts, sa)) continue;
+ // Account doesn't exist. Copy it now.
+ copyAccountToUser(sa, UserHandle.USER_OWNER, userId);
+ }
+ }
+
+ @Override
+ public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) {
+ validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */);
+ }
+
+ public String getPassword(Account account) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getPassword: " + account
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (account == null) throw new IllegalArgumentException("account is null");
+ checkAuthenticateAccountsPermission(account);
+
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ return readPasswordInternal(accounts, account);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private String readPasswordInternal(UserAccounts accounts, Account account) {
+ if (account == null) {
+ return null;
+ }
+
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+ Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
+ ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+ new String[]{account.name, account.type}, null, null, null);
+ try {
+ if (cursor.moveToNext()) {
+ return cursor.getString(0);
+ }
+ return null;
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ public String getUserData(Account account, String key) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getUserData: " + account
+ + ", key " + key
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (key == null) throw new IllegalArgumentException("key is null");
+ checkAuthenticateAccountsPermission(account);
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ return readUserDataInternal(accounts, account, key);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public AuthenticatorDescription[] getAuthenticatorTypes() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthenticatorTypes: "
+ + "caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ final int userId = UserHandle.getCallingUserId();
+ final long identityToken = clearCallingIdentity();
+ try {
+ Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
+ authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
+ AuthenticatorDescription[] types =
+ new AuthenticatorDescription[authenticatorCollection.size()];
+ int i = 0;
+ for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
+ : authenticatorCollection) {
+ types[i] = authenticator.type;
+ i++;
+ }
+ return types;
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public boolean addAccountExplicitly(Account account, String password, Bundle extras) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccountExplicitly: " + account
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (account == null) throw new IllegalArgumentException("account is null");
+ checkAuthenticateAccountsPermission(account);
+ /*
+ * Child users are not allowed to add accounts. Only the accounts that are
+ * shared by the parent profile can be added to child profile.
+ *
+ * TODO: Only allow accounts that were shared to be added by
+ * a limited user.
+ */
+
+ UserAccounts accounts = getUserAccountsForCaller();
+ // fails if the account already exists
+ long identityToken = clearCallingIdentity();
+ try {
+ return addAccountInternal(accounts, account, password, extras, false);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private boolean copyAccountToUser(final Account account, int userFrom, int userTo) {
+ final UserAccounts fromAccounts = getUserAccounts(userFrom);
+ final UserAccounts toAccounts = getUserAccounts(userTo);
+ if (fromAccounts == null || toAccounts == null) {
+ return false;
+ }
+
+ long identityToken = clearCallingIdentity();
+ try {
+ new Session(fromAccounts, null, account.type, false,
+ false /* stripAuthTokenFromResult */) {
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", getAccountCredentialsForClone"
+ + ", " + account.type;
+ }
+
+ public void run() throws RemoteException {
+ mAuthenticator.getAccountCredentialsForCloning(this, account);
+ }
+
+ public void onResult(Bundle result) {
+ if (result != null) {
+ if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+ // Create a Session for the target user and pass in the bundle
+ completeCloningAccount(result, account, toAccounts);
+ }
+ return;
+ } else {
+ super.onResult(result);
+ }
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return true;
+ }
+
+ void completeCloningAccount(final Bundle result, final Account account,
+ final UserAccounts targetUser) {
+ long id = clearCallingIdentity();
+ try {
+ new Session(targetUser, null, account.type, false,
+ false /* stripAuthTokenFromResult */) {
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", getAccountCredentialsForClone"
+ + ", " + account.type;
+ }
+
+ public void run() throws RemoteException {
+ // Confirm that the owner's account still exists before this step.
+ UserAccounts owner = getUserAccounts(UserHandle.USER_OWNER);
+ synchronized (owner.cacheLock) {
+ Account[] ownerAccounts = getAccounts(UserHandle.USER_OWNER);
+ for (Account acc : ownerAccounts) {
+ if (acc.equals(account)) {
+ mAuthenticator.addAccountFromCredentials(this, account, result);
+ break;
+ }
+ }
+ }
+ }
+
+ public void onResult(Bundle result) {
+ if (result != null) {
+ if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+ // TODO: Anything?
+ } else {
+ // TODO: Show error notification
+ // TODO: Should we remove the shadow account to avoid retries?
+ }
+ return;
+ } else {
+ super.onResult(result);
+ }
+ }
+
+ public void onError(int errorCode, String errorMessage) {
+ super.onError(errorCode, errorMessage);
+ // TODO: Show error notification to user
+ // TODO: Should we remove the shadow account so that it doesn't keep trying?
+ }
+
+ }.bind();
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+
+ private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
+ Bundle extras, boolean restricted) {
+ if (account == null) {
+ return false;
+ }
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ long numMatches = DatabaseUtils.longForQuery(db,
+ "select count(*) from " + TABLE_ACCOUNTS
+ + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+ new String[]{account.name, account.type});
+ if (numMatches > 0) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account
+ + ", skipping since the account already exists");
+ return false;
+ }
+ ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_NAME, account.name);
+ values.put(ACCOUNTS_TYPE, account.type);
+ values.put(ACCOUNTS_PASSWORD, password);
+ long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
+ if (accountId < 0) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account
+ + ", skipping the DB insert failed");
+ return false;
+ }
+ if (extras != null) {
+ for (String key : extras.keySet()) {
+ final String value = extras.getString(key);
+ if (insertExtraLocked(db, accountId, key, value) < 0) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account
+ + ", skipping since insertExtra failed for key " + key);
+ return false;
+ }
+ }
+ }
+ db.setTransactionSuccessful();
+ insertAccountIntoCacheLocked(accounts, account);
+ } finally {
+ db.endTransaction();
+ }
+ sendAccountsChangedBroadcast(accounts.userId);
+ }
+ if (accounts.userId == UserHandle.USER_OWNER) {
+ addAccountToLimitedUsers(account);
+ }
+ return true;
+ }
+
+ /**
+ * Adds the account to all limited users as shared accounts. If the user is currently
+ * running, then clone the account too.
+ * @param account the account to share with limited users
+ */
+ private void addAccountToLimitedUsers(Account account) {
+ List<UserInfo> users = getUserManager().getUsers();
+ for (UserInfo user : users) {
+ if (user.isRestricted()) {
+ addSharedAccountAsUser(account, user.id);
+ try {
+ if (ActivityManagerNative.getDefault().isUserRunning(user.id, false)) {
+ mMessageHandler.sendMessage(mMessageHandler.obtainMessage(
+ MESSAGE_COPY_SHARED_ACCOUNT, UserHandle.USER_OWNER, user.id,
+ account));
+ }
+ } catch (RemoteException re) {
+ // Shouldn't happen
+ }
+ }
+ }
+ }
+
+ private long insertExtraLocked(SQLiteDatabase db, long accountId, String key, String value) {
+ ContentValues values = new ContentValues();
+ values.put(EXTRAS_KEY, key);
+ values.put(EXTRAS_ACCOUNTS_ID, accountId);
+ values.put(EXTRAS_VALUE, value);
+ return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
+ }
+
+ public void hasFeatures(IAccountManagerResponse response,
+ Account account, String[] features) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "hasFeatures: " + account
+ + ", response " + response
+ + ", features " + stringArrayToString(features)
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (features == null) throw new IllegalArgumentException("features is null");
+ checkReadAccountsPermission();
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ new TestFeaturesSession(accounts, response, account, features).bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private class TestFeaturesSession extends Session {
+ private final String[] mFeatures;
+ private final Account mAccount;
+
+ public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response,
+ Account account, String[] features) {
+ super(accounts, response, account.type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
+ mFeatures = features;
+ mAccount = account;
+ }
+
+ public void run() throws RemoteException {
+ try {
+ mAuthenticator.hasFeatures(this, mAccount, mFeatures);
+ } catch (RemoteException e) {
+ onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
+ }
+ }
+
+ public void onResult(Bundle result) {
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response != null) {
+ try {
+ if (result == null) {
+ response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
+ return;
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+ + response);
+ }
+ final Bundle newResult = new Bundle();
+ newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
+ result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
+ response.onResult(newResult);
+ } catch (RemoteException e) {
+ // if the caller is dead then there is no one to care about remote exceptions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "failure while notifying response", e);
+ }
+ }
+ }
+ }
+
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", hasFeatures"
+ + ", " + mAccount
+ + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
+ }
+ }
+
+ public void removeAccount(IAccountManagerResponse response, Account account) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "removeAccount: " + account
+ + ", response " + response
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+ checkManageAccountsPermission();
+ UserHandle user = Binder.getCallingUserHandle();
+ UserAccounts accounts = getUserAccountsForCaller();
+ if (!canUserModifyAccounts(Binder.getCallingUid())) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
+ "User cannot modify accounts");
+ } catch (RemoteException re) {
+ }
+ }
+
+ long identityToken = clearCallingIdentity();
+
+ cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
+ synchronized(accounts.credentialsPermissionNotificationIds) {
+ for (Pair<Pair<Account, String>, Integer> pair:
+ accounts.credentialsPermissionNotificationIds.keySet()) {
+ if (account.equals(pair.first.first)) {
+ int id = accounts.credentialsPermissionNotificationIds.get(pair);
+ cancelNotification(id, user);
+ }
+ }
+ }
+
+ try {
+ new RemoveAccountSession(accounts, response, account).bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private class RemoveAccountSession extends Session {
+ final Account mAccount;
+ public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response,
+ Account account) {
+ super(accounts, response, account.type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
+ mAccount = account;
+ }
+
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", removeAccount"
+ + ", account " + mAccount;
+ }
+
+ public void run() throws RemoteException {
+ mAuthenticator.getAccountRemovalAllowed(this, mAccount);
+ }
+
+ public void onResult(Bundle result) {
+ if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
+ && !result.containsKey(AccountManager.KEY_INTENT)) {
+ final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ if (removalAllowed) {
+ removeAccountInternal(mAccounts, mAccount);
+ }
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response != null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+ + response);
+ }
+ Bundle result2 = new Bundle();
+ result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
+ try {
+ response.onResult(result2);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ }
+ }
+ super.onResult(result);
+ }
+ }
+
+ /* For testing */
+ protected void removeAccountInternal(Account account) {
+ removeAccountInternal(getUserAccountsForCaller(), account);
+ }
+
+ private void removeAccountInternal(UserAccounts accounts, Account account) {
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+ new String[]{account.name, account.type});
+ removeAccountFromCacheLocked(accounts, account);
+ sendAccountsChangedBroadcast(accounts.userId);
+ }
+ if (accounts.userId == UserHandle.USER_OWNER) {
+ // Owner's account was removed, remove from any users that are sharing
+ // this account.
+ long id = Binder.clearCallingIdentity();
+ try {
+ List<UserInfo> users = mUserManager.getUsers(true);
+ for (UserInfo user : users) {
+ if (!user.isPrimary() && user.isRestricted()) {
+ removeSharedAccountAsUser(account, user.id);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(id);
+ }
+ }
+ }
+
+ @Override
+ public void invalidateAuthToken(String accountType, String authToken) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "invalidateAuthToken: accountType " + accountType
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ if (authToken == null) throw new IllegalArgumentException("authToken is null");
+ checkManageAccountsOrUseCredentialsPermissions();
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ invalidateAuthTokenLocked(accounts, db, accountType, authToken);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db,
+ String accountType, String authToken) {
+ if (authToken == null || accountType == null) {
+ return;
+ }
+ Cursor cursor = db.rawQuery(
+ "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
+ + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
+ + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
+ + " FROM " + TABLE_ACCOUNTS
+ + " JOIN " + TABLE_AUTHTOKENS
+ + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
+ + " = " + AUTHTOKENS_ACCOUNTS_ID
+ + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
+ + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
+ new String[]{authToken, accountType});
+ try {
+ while (cursor.moveToNext()) {
+ long authTokenId = cursor.getLong(0);
+ String accountName = cursor.getString(1);
+ String authTokenType = cursor.getString(2);
+ db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
+ writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType),
+ authTokenType, null);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type,
+ String authToken) {
+ if (account == null || type == null) {
+ return false;
+ }
+ cancelNotification(getSigninRequiredNotificationId(accounts, account),
+ new UserHandle(accounts.userId));
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ long accountId = getAccountIdLocked(db, account);
+ if (accountId < 0) {
+ return false;
+ }
+ db.delete(TABLE_AUTHTOKENS,
+ AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
+ new String[]{type});
+ ContentValues values = new ContentValues();
+ values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
+ values.put(AUTHTOKENS_TYPE, type);
+ values.put(AUTHTOKENS_AUTHTOKEN, authToken);
+ if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
+ db.setTransactionSuccessful();
+ writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken);
+ return true;
+ }
+ return false;
+ } finally {
+ db.endTransaction();
+ }
+ }
+ }
+
+ public String peekAuthToken(Account account, String authTokenType) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "peekAuthToken: " + account
+ + ", authTokenType " + authTokenType
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ checkAuthenticateAccountsPermission(account);
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ return readAuthTokenInternal(accounts, account, authTokenType);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void setAuthToken(Account account, String authTokenType, String authToken) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setAuthToken: " + account
+ + ", authTokenType " + authTokenType
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ checkAuthenticateAccountsPermission(account);
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ saveAuthTokenToDatabase(accounts, account, authTokenType, authToken);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void setPassword(Account account, String password) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setAuthToken: " + account
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (account == null) throw new IllegalArgumentException("account is null");
+ checkAuthenticateAccountsPermission(account);
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ setPasswordInternal(accounts, account, password);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private void setPasswordInternal(UserAccounts accounts, Account account, String password) {
+ if (account == null) {
+ return;
+ }
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_PASSWORD, password);
+ final long accountId = getAccountIdLocked(db, account);
+ if (accountId >= 0) {
+ final String[] argsAccountId = {String.valueOf(accountId)};
+ db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
+ db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
+ accounts.authTokenCache.remove(account);
+ db.setTransactionSuccessful();
+ }
+ } finally {
+ db.endTransaction();
+ }
+ sendAccountsChangedBroadcast(accounts.userId);
+ }
+ }
+
+ private void sendAccountsChangedBroadcast(int userId) {
+ Log.i(TAG, "the accounts changed, sending broadcast of "
+ + ACCOUNTS_CHANGED_INTENT.getAction());
+ mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
+ }
+
+ public void clearPassword(Account account) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "clearPassword: " + account
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (account == null) throw new IllegalArgumentException("account is null");
+ checkManageAccountsPermission();
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ setPasswordInternal(accounts, account, null);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void setUserData(Account account, String key, String value) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setUserData: " + account
+ + ", key " + key
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (key == null) throw new IllegalArgumentException("key is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+ checkAuthenticateAccountsPermission(account);
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ setUserdataInternal(accounts, account, key, value);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private void setUserdataInternal(UserAccounts accounts, Account account, String key,
+ String value) {
+ if (account == null || key == null) {
+ return;
+ }
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ long accountId = getAccountIdLocked(db, account);
+ if (accountId < 0) {
+ return;
+ }
+ long extrasId = getExtrasIdLocked(db, accountId, key);
+ if (extrasId < 0 ) {
+ extrasId = insertExtraLocked(db, accountId, key, value);
+ if (extrasId < 0) {
+ return;
+ }
+ } else {
+ ContentValues values = new ContentValues();
+ values.put(EXTRAS_VALUE, value);
+ if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
+ return;
+ }
+
+ }
+ writeUserDataIntoCacheLocked(accounts, db, account, key, value);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+ }
+
+ private void onResult(IAccountManagerResponse response, Bundle result) {
+ if (result == null) {
+ Log.e(TAG, "the result is unexpectedly null", new Exception());
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+ + response);
+ }
+ try {
+ response.onResult(result);
+ } catch (RemoteException e) {
+ // if the caller is dead then there is no one to care about remote
+ // exceptions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "failure while notifying response", e);
+ }
+ }
+ }
+
+ public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType,
+ final String authTokenType)
+ throws RemoteException {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+
+ final int callingUid = getCallingUid();
+ clearCallingIdentity();
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException("can only call from system");
+ }
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
+ long identityToken = clearCallingIdentity();
+ try {
+ new Session(accounts, response, accountType, false,
+ false /* stripAuthTokenFromResult */) {
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", getAuthTokenLabel"
+ + ", " + accountType
+ + ", authTokenType " + authTokenType;
+ }
+
+ public void run() throws RemoteException {
+ mAuthenticator.getAuthTokenLabel(this, authTokenType);
+ }
+
+ public void onResult(Bundle result) {
+ if (result != null) {
+ String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
+ Bundle bundle = new Bundle();
+ bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label);
+ super.onResult(bundle);
+ return;
+ } else {
+ super.onResult(result);
+ }
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void getAuthToken(IAccountManagerResponse response, final Account account,
+ final String authTokenType, final boolean notifyOnAuthFailure,
+ final boolean expectActivityLaunch, Bundle loginOptionsIn) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthToken: " + account
+ + ", response " + response
+ + ", authTokenType " + authTokenType
+ + ", notifyOnAuthFailure " + notifyOnAuthFailure
+ + ", expectActivityLaunch " + expectActivityLaunch
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
+ final UserAccounts accounts = getUserAccountsForCaller();
+ final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
+ authenticatorInfo = mAuthenticatorCache.getServiceInfo(
+ AuthenticatorDescription.newKey(account.type), accounts.userId);
+ final boolean customTokens =
+ authenticatorInfo != null && authenticatorInfo.type.customTokens;
+
+ // Check to see that the app is authorized to access the account, in case it's a
+ // restricted account.
+ if (!ArrayUtils.contains(getAccounts((String) null), account)) {
+ throw new IllegalArgumentException("no such account");
+ }
+ // skip the check if customTokens
+ final int callerUid = Binder.getCallingUid();
+ final boolean permissionGranted = customTokens ||
+ permissionIsGranted(account, authTokenType, callerUid);
+
+ final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() :
+ loginOptionsIn;
+ // let authenticator know the identity of the caller
+ loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid);
+ loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid());
+ if (notifyOnAuthFailure) {
+ loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true);
+ }
+
+ long identityToken = clearCallingIdentity();
+ try {
+ // if the caller has permission, do the peek. otherwise go the more expensive
+ // route of starting a Session
+ if (!customTokens && permissionGranted) {
+ String authToken = readAuthTokenInternal(accounts, account, authTokenType);
+ if (authToken != null) {
+ Bundle result = new Bundle();
+ result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
+ result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+ onResult(response, result);
+ return;
+ }
+ }
+
+ new Session(accounts, response, account.type, expectActivityLaunch,
+ false /* stripAuthTokenFromResult */) {
+ protected String toDebugString(long now) {
+ if (loginOptions != null) loginOptions.keySet();
+ return super.toDebugString(now) + ", getAuthToken"
+ + ", " + account
+ + ", authTokenType " + authTokenType
+ + ", loginOptions " + loginOptions
+ + ", notifyOnAuthFailure " + notifyOnAuthFailure;
+ }
+
+ public void run() throws RemoteException {
+ // If the caller doesn't have permission then create and return the
+ // "grant permission" intent instead of the "getAuthToken" intent.
+ if (!permissionGranted) {
+ mAuthenticator.getAuthTokenLabel(this, authTokenType);
+ } else {
+ mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
+ }
+ }
+
+ public void onResult(Bundle result) {
+ if (result != null) {
+ if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) {
+ Intent intent = newGrantCredentialsPermissionIntent(account, callerUid,
+ new AccountAuthenticatorResponse(this),
+ authTokenType,
+ result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL));
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+ onResult(bundle);
+ return;
+ }
+ String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
+ if (authToken != null) {
+ String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
+ String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
+ if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "the type and name should not be empty");
+ return;
+ }
+ if (!customTokens) {
+ saveAuthTokenToDatabase(mAccounts, new Account(name, type),
+ authTokenType, authToken);
+ }
+ }
+
+ Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+ if (intent != null && notifyOnAuthFailure && !customTokens) {
+ doNotification(mAccounts,
+ account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
+ intent, accounts.userId);
+ }
+ }
+ super.onResult(result);
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private void createNoCredentialsPermissionNotification(Account account, Intent intent,
+ int userId) {
+ int uid = intent.getIntExtra(
+ GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
+ String authTokenType = intent.getStringExtra(
+ GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE);
+ String authTokenLabel = intent.getStringExtra(
+ GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL);
+
+ Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
+ 0 /* when */);
+ final String titleAndSubtitle =
+ mContext.getString(R.string.permission_request_notification_with_subtitle,
+ account.name);
+ final int index = titleAndSubtitle.indexOf('\n');
+ String title = titleAndSubtitle;
+ String subtitle = "";
+ if (index > 0) {
+ title = titleAndSubtitle.substring(0, index);
+ subtitle = titleAndSubtitle.substring(index + 1);
+ }
+ UserHandle user = new UserHandle(userId);
+ n.setLatestEventInfo(mContext, title, subtitle,
+ PendingIntent.getActivityAsUser(mContext, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, user));
+ installNotification(getCredentialPermissionNotificationId(
+ account, authTokenType, uid), n, user);
+ }
+
+ private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
+ AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
+
+ Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
+ // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
+ // Since it was set in Eclair+ we can't change it without breaking apps using
+ // the intent from a non-Activity context.
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addCategory(
+ String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid)));
+
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account);
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType);
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response);
+ intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid);
+
+ return intent;
+ }
+
+ private Integer getCredentialPermissionNotificationId(Account account, String authTokenType,
+ int uid) {
+ Integer id;
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+ synchronized (accounts.credentialsPermissionNotificationIds) {
+ final Pair<Pair<Account, String>, Integer> key =
+ new Pair<Pair<Account, String>, Integer>(
+ new Pair<Account, String>(account, authTokenType), uid);
+ id = accounts.credentialsPermissionNotificationIds.get(key);
+ if (id == null) {
+ id = mNotificationIds.incrementAndGet();
+ accounts.credentialsPermissionNotificationIds.put(key, id);
+ }
+ }
+ return id;
+ }
+
+ private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) {
+ Integer id;
+ synchronized (accounts.signinRequiredNotificationIds) {
+ id = accounts.signinRequiredNotificationIds.get(account);
+ if (id == null) {
+ id = mNotificationIds.incrementAndGet();
+ accounts.signinRequiredNotificationIds.put(account, id);
+ }
+ }
+ return id;
+ }
+
+ public void addAccount(final IAccountManagerResponse response, final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final boolean expectActivityLaunch, final Bundle optionsIn) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccount: accountType " + accountType
+ + ", response " + response
+ + ", authTokenType " + authTokenType
+ + ", requiredFeatures " + stringArrayToString(requiredFeatures)
+ + ", expectActivityLaunch " + expectActivityLaunch
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ checkManageAccountsPermission();
+
+ // Is user disallowed from modifying accounts?
+ if (!canUserModifyAccounts(Binder.getCallingUid())) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
+ "User is not allowed to add an account!");
+ } catch (RemoteException re) {
+ }
+ Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class);
+ cantAddAccount.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ long identityToken = clearCallingIdentity();
+ try {
+ mContext.startActivityAsUser(cantAddAccount, UserHandle.CURRENT);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return;
+ }
+
+ UserAccounts accounts = getUserAccountsForCaller();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
+ options.putInt(AccountManager.KEY_CALLER_UID, uid);
+ options.putInt(AccountManager.KEY_CALLER_PID, pid);
+
+ long identityToken = clearCallingIdentity();
+ try {
+ new Session(accounts, response, accountType, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
+ public void run() throws RemoteException {
+ mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
+ options);
+ }
+
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", addAccount"
+ + ", accountType " + accountType
+ + ", requiredFeatures "
+ + (requiredFeatures != null
+ ? TextUtils.join(",", requiredFeatures)
+ : null);
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public void confirmCredentialsAsUser(IAccountManagerResponse response,
+ final Account account, final Bundle options, final boolean expectActivityLaunch,
+ int userId) {
+ // Only allow the system process to read accounts of other users
+ if (userId != UserHandle.getCallingUserId()
+ && Binder.getCallingUid() != Process.myUid()) {
+ throw new SecurityException("User " + UserHandle.getCallingUserId()
+ + " trying to confirm account credentials for " + userId);
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "confirmCredentials: " + account
+ + ", response " + response
+ + ", expectActivityLaunch " + expectActivityLaunch
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+ checkManageAccountsPermission();
+ UserAccounts accounts = getUserAccounts(userId);
+ long identityToken = clearCallingIdentity();
+ try {
+ new Session(accounts, response, account.type, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
+ public void run() throws RemoteException {
+ mAuthenticator.confirmCredentials(this, account, options);
+ }
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", confirmCredentials"
+ + ", " + account;
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void updateCredentials(IAccountManagerResponse response, final Account account,
+ final String authTokenType, final boolean expectActivityLaunch,
+ final Bundle loginOptions) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "updateCredentials: " + account
+ + ", response " + response
+ + ", authTokenType " + authTokenType
+ + ", expectActivityLaunch " + expectActivityLaunch
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ checkManageAccountsPermission();
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ new Session(accounts, response, account.type, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
+ public void run() throws RemoteException {
+ mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
+ }
+ protected String toDebugString(long now) {
+ if (loginOptions != null) loginOptions.keySet();
+ return super.toDebugString(now) + ", updateCredentials"
+ + ", " + account
+ + ", authTokenType " + authTokenType
+ + ", loginOptions " + loginOptions;
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void editProperties(IAccountManagerResponse response, final String accountType,
+ final boolean expectActivityLaunch) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "editProperties: accountType " + accountType
+ + ", response " + response
+ + ", expectActivityLaunch " + expectActivityLaunch
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ checkManageAccountsPermission();
+ UserAccounts accounts = getUserAccountsForCaller();
+ long identityToken = clearCallingIdentity();
+ try {
+ new Session(accounts, response, accountType, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
+ public void run() throws RemoteException {
+ mAuthenticator.editProperties(this, mAccountType);
+ }
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", editProperties"
+ + ", accountType " + accountType;
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private class GetAccountsByTypeAndFeatureSession extends Session {
+ private final String[] mFeatures;
+ private volatile Account[] mAccountsOfType = null;
+ private volatile ArrayList<Account> mAccountsWithFeatures = null;
+ private volatile int mCurrentAccount = 0;
+ private int mCallingUid;
+
+ public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
+ IAccountManagerResponse response, String type, String[] features, int callingUid) {
+ super(accounts, response, type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
+ mCallingUid = callingUid;
+ mFeatures = features;
+ }
+
+ public void run() throws RemoteException {
+ synchronized (mAccounts.cacheLock) {
+ mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid,
+ null);
+ }
+ // check whether each account matches the requested features
+ mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
+ mCurrentAccount = 0;
+
+ checkAccount();
+ }
+
+ public void checkAccount() {
+ if (mCurrentAccount >= mAccountsOfType.length) {
+ sendResult();
+ return;
+ }
+
+ final IAccountAuthenticator accountAuthenticator = mAuthenticator;
+ if (accountAuthenticator == null) {
+ // It is possible that the authenticator has died, which is indicated by
+ // mAuthenticator being set to null. If this happens then just abort.
+ // There is no need to send back a result or error in this case since
+ // that already happened when mAuthenticator was cleared.
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "checkAccount: aborting session since we are no longer"
+ + " connected to the authenticator, " + toDebugString());
+ }
+ return;
+ }
+ try {
+ accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
+ } catch (RemoteException e) {
+ onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
+ }
+ }
+
+ public void onResult(Bundle result) {
+ mNumResults++;
+ if (result == null) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
+ return;
+ }
+ if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+ mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
+ }
+ mCurrentAccount++;
+ checkAccount();
+ }
+
+ public void sendResult() {
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response != null) {
+ try {
+ Account[] accounts = new Account[mAccountsWithFeatures.size()];
+ for (int i = 0; i < accounts.length; i++) {
+ accounts[i] = mAccountsWithFeatures.get(i);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+ + response);
+ }
+ Bundle result = new Bundle();
+ result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
+ response.onResult(result);
+ } catch (RemoteException e) {
+ // if the caller is dead then there is no one to care about remote exceptions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "failure while notifying response", e);
+ }
+ }
+ }
+ }
+
+
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
+ + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
+ }
+ }
+
+ /**
+ * Returns the accounts for a specific user
+ * @hide
+ */
+ public Account[] getAccounts(int userId) {
+ checkReadAccountsPermission();
+ UserAccounts accounts = getUserAccounts(userId);
+ int callingUid = Binder.getCallingUid();
+ long identityToken = clearCallingIdentity();
+ try {
+ synchronized (accounts.cacheLock) {
+ return getAccountsFromCacheLocked(accounts, null, callingUid, null);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /**
+ * Returns accounts for all running users.
+ *
+ * @hide
+ */
+ public AccountAndUser[] getRunningAccounts() {
+ final int[] runningUserIds;
+ try {
+ runningUserIds = ActivityManagerNative.getDefault().getRunningUserIds();
+ } catch (RemoteException e) {
+ // Running in system_server; should never happen
+ throw new RuntimeException(e);
+ }
+ return getAccounts(runningUserIds);
+ }
+
+ /** {@hide} */
+ public AccountAndUser[] getAllAccounts() {
+ final List<UserInfo> users = getUserManager().getUsers();
+ final int[] userIds = new int[users.size()];
+ for (int i = 0; i < userIds.length; i++) {
+ userIds[i] = users.get(i).id;
+ }
+ return getAccounts(userIds);
+ }
+
+ private AccountAndUser[] getAccounts(int[] userIds) {
+ final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList();
+ synchronized (mUsers) {
+ for (int userId : userIds) {
+ UserAccounts userAccounts = getUserAccounts(userId);
+ if (userAccounts == null) continue;
+ synchronized (userAccounts.cacheLock) {
+ Account[] accounts = getAccountsFromCacheLocked(userAccounts, null,
+ Binder.getCallingUid(), null);
+ for (int a = 0; a < accounts.length; a++) {
+ runningAccounts.add(new AccountAndUser(accounts[a], userId));
+ }
+ }
+ }
+ }
+
+ AccountAndUser[] accountsArray = new AccountAndUser[runningAccounts.size()];
+ return runningAccounts.toArray(accountsArray);
+ }
+
+ @Override
+ public Account[] getAccountsAsUser(String type, int userId) {
+ return getAccountsAsUser(type, userId, null, -1);
+ }
+
+ private Account[] getAccountsAsUser(String type, int userId, String callingPackage,
+ int packageUid) {
+ int callingUid = Binder.getCallingUid();
+ // Only allow the system process to read accounts of other users
+ if (userId != UserHandle.getCallingUserId()
+ && callingUid != Process.myUid()) {
+ throw new SecurityException("User " + UserHandle.getCallingUserId()
+ + " trying to get account for " + userId);
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAccounts: accountType " + type
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ // If the original calling app was using the framework account chooser activity, we'll
+ // be passed in the original caller's uid here, which is what should be used for filtering.
+ if (packageUid != -1 && UserHandle.isSameApp(callingUid, Process.myUid())) {
+ callingUid = packageUid;
+ }
+ checkReadAccountsPermission();
+ UserAccounts accounts = getUserAccounts(userId);
+ long identityToken = clearCallingIdentity();
+ try {
+ synchronized (accounts.cacheLock) {
+ return getAccountsFromCacheLocked(accounts, type, callingUid, callingPackage);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public boolean addSharedAccountAsUser(Account account, int userId) {
+ userId = handleIncomingUser(userId);
+ SQLiteDatabase db = getUserAccounts(userId).openHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_NAME, account.name);
+ values.put(ACCOUNTS_TYPE, account.type);
+ db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+ new String[] {account.name, account.type});
+ long accountId = db.insert(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values);
+ if (accountId < 0) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account
+ + ", skipping the DB insert failed");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean removeSharedAccountAsUser(Account account, int userId) {
+ userId = handleIncomingUser(userId);
+ UserAccounts accounts = getUserAccounts(userId);
+ SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ int r = db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+ new String[] {account.name, account.type});
+ if (r > 0) {
+ removeAccountInternal(accounts, account);
+ }
+ return r > 0;
+ }
+
+ @Override
+ public Account[] getSharedAccountsAsUser(int userId) {
+ userId = handleIncomingUser(userId);
+ UserAccounts accounts = getUserAccounts(userId);
+ ArrayList<Account> accountList = new ArrayList<Account>();
+ Cursor cursor = null;
+ try {
+ cursor = accounts.openHelper.getReadableDatabase()
+ .query(TABLE_SHARED_ACCOUNTS, new String[]{ACCOUNTS_NAME, ACCOUNTS_TYPE},
+ null, null, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME);
+ int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE);
+ do {
+ accountList.add(new Account(cursor.getString(nameIndex),
+ cursor.getString(typeIndex)));
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ Account[] accountArray = new Account[accountList.size()];
+ accountList.toArray(accountArray);
+ return accountArray;
+ }
+
+ @Override
+ public Account[] getAccounts(String type) {
+ return getAccountsAsUser(type, UserHandle.getCallingUserId());
+ }
+
+ @Override
+ public Account[] getAccountsForPackage(String packageName, int uid) {
+ int callingUid = Binder.getCallingUid();
+ if (!UserHandle.isSameApp(callingUid, Process.myUid())) {
+ throw new SecurityException("getAccountsForPackage() called from unauthorized uid "
+ + callingUid + " with uid=" + uid);
+ }
+ return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid);
+ }
+
+ @Override
+ public Account[] getAccountsByTypeForPackage(String type, String packageName) {
+ checkBinderPermission(android.Manifest.permission.INTERACT_ACROSS_USERS);
+ int packageUid = -1;
+ try {
+ packageUid = AppGlobals.getPackageManager().getPackageUid(
+ packageName, UserHandle.getCallingUserId());
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Couldn't determine the packageUid for " + packageName + re);
+ return new Account[0];
+ }
+ return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, packageUid);
+ }
+
+ public void getAccountsByFeatures(IAccountManagerResponse response,
+ String type, String[] features) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAccounts: accountType " + type
+ + ", response " + response
+ + ", features " + stringArrayToString(features)
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (type == null) throw new IllegalArgumentException("accountType is null");
+ checkReadAccountsPermission();
+ UserAccounts userAccounts = getUserAccountsForCaller();
+ int callingUid = Binder.getCallingUid();
+ long identityToken = clearCallingIdentity();
+ try {
+ if (features == null || features.length == 0) {
+ Account[] accounts;
+ synchronized (userAccounts.cacheLock) {
+ accounts = getAccountsFromCacheLocked(userAccounts, type, callingUid, null);
+ }
+ Bundle result = new Bundle();
+ result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
+ onResult(response, result);
+ return;
+ }
+ new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features,
+ callingUid).bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private long getAccountIdLocked(SQLiteDatabase db, Account account) {
+ Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
+ "name=? AND type=?", new String[]{account.name, account.type}, null, null, null);
+ try {
+ if (cursor.moveToNext()) {
+ return cursor.getLong(0);
+ }
+ return -1;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) {
+ Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
+ EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
+ new String[]{key}, null, null, null);
+ try {
+ if (cursor.moveToNext()) {
+ return cursor.getLong(0);
+ }
+ return -1;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private abstract class Session extends IAccountAuthenticatorResponse.Stub
+ implements IBinder.DeathRecipient, ServiceConnection {
+ IAccountManagerResponse mResponse;
+ final String mAccountType;
+ final boolean mExpectActivityLaunch;
+ final long mCreationTime;
+
+ public int mNumResults = 0;
+ private int mNumRequestContinued = 0;
+ private int mNumErrors = 0;
+
+ IAccountAuthenticator mAuthenticator = null;
+
+ private final boolean mStripAuthTokenFromResult;
+ protected final UserAccounts mAccounts;
+
+ public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
+ boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
+ super();
+ //if (response == null) throw new IllegalArgumentException("response is null");
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ mAccounts = accounts;
+ mStripAuthTokenFromResult = stripAuthTokenFromResult;
+ mResponse = response;
+ mAccountType = accountType;
+ mExpectActivityLaunch = expectActivityLaunch;
+ mCreationTime = SystemClock.elapsedRealtime();
+ synchronized (mSessions) {
+ mSessions.put(toString(), this);
+ }
+ if (response != null) {
+ try {
+ response.asBinder().linkToDeath(this, 0 /* flags */);
+ } catch (RemoteException e) {
+ mResponse = null;
+ binderDied();
+ }
+ }
+ }
+
+ IAccountManagerResponse getResponseAndClose() {
+ if (mResponse == null) {
+ // this session has already been closed
+ return null;
+ }
+ IAccountManagerResponse response = mResponse;
+ close(); // this clears mResponse so we need to save the response before this call
+ return response;
+ }
+
+ private void close() {
+ synchronized (mSessions) {
+ if (mSessions.remove(toString()) == null) {
+ // the session was already closed, so bail out now
+ return;
+ }
+ }
+ if (mResponse != null) {
+ // stop listening for response deaths
+ mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
+
+ // clear this so that we don't accidentally send any further results
+ mResponse = null;
+ }
+ cancelTimeout();
+ unbind();
+ }
+
+ public void binderDied() {
+ mResponse = null;
+ close();
+ }
+
+ protected String toDebugString() {
+ return toDebugString(SystemClock.elapsedRealtime());
+ }
+
+ protected String toDebugString(long now) {
+ return "Session: expectLaunch " + mExpectActivityLaunch
+ + ", connected " + (mAuthenticator != null)
+ + ", stats (" + mNumResults + "/" + mNumRequestContinued
+ + "/" + mNumErrors + ")"
+ + ", lifetime " + ((now - mCreationTime) / 1000.0);
+ }
+
+ void bind() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
+ }
+ if (!bindToAuthenticator(mAccountType)) {
+ Log.d(TAG, "bind attempt failed for " + toDebugString());
+ onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
+ }
+ }
+
+ private void unbind() {
+ if (mAuthenticator != null) {
+ mAuthenticator = null;
+ mContext.unbindService(this);
+ }
+ }
+
+ public void scheduleTimeout() {
+ mMessageHandler.sendMessageDelayed(
+ mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
+ }
+
+ public void cancelTimeout() {
+ mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
+ try {
+ run();
+ } catch (RemoteException e) {
+ onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+ "remote exception");
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mAuthenticator = null;
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response != null) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+ "disconnected");
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Session.onServiceDisconnected: "
+ + "caught RemoteException while responding", e);
+ }
+ }
+ }
+ }
+
+ public abstract void run() throws RemoteException;
+
+ public void onTimedOut() {
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response != null) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+ "timeout");
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Session.onTimedOut: caught RemoteException while responding",
+ e);
+ }
+ }
+ }
+ }
+
+ public void onResult(Bundle result) {
+ mNumResults++;
+ if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
+ String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
+ String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
+ if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+ Account account = new Account(accountName, accountType);
+ cancelNotification(getSigninRequiredNotificationId(mAccounts, account),
+ new UserHandle(mAccounts.userId));
+ }
+ }
+ IAccountManagerResponse response;
+ if (mExpectActivityLaunch && result != null
+ && result.containsKey(AccountManager.KEY_INTENT)) {
+ response = mResponse;
+ } else {
+ response = getResponseAndClose();
+ }
+ if (response != null) {
+ try {
+ if (result == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, getClass().getSimpleName()
+ + " calling onError() on response " + response);
+ }
+ response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "null bundle returned");
+ } else {
+ if (mStripAuthTokenFromResult) {
+ result.remove(AccountManager.KEY_AUTHTOKEN);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, getClass().getSimpleName()
+ + " calling onResult() on response " + response);
+ }
+ response.onResult(result);
+ }
+ } catch (RemoteException e) {
+ // if the caller is dead then there is no one to care about remote exceptions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "failure while notifying response", e);
+ }
+ }
+ }
+ }
+
+ public void onRequestContinued() {
+ mNumRequestContinued++;
+ }
+
+ public void onError(int errorCode, String errorMessage) {
+ mNumErrors++;
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response != null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, getClass().getSimpleName()
+ + " calling onError() on response " + response);
+ }
+ try {
+ response.onError(errorCode, errorMessage);
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
+ }
+ }
+ } else {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Session.onError: already closed");
+ }
+ }
+ }
+
+ /**
+ * find the component name for the authenticator and initiate a bind
+ * if no authenticator or the bind fails then return false, otherwise return true
+ */
+ private boolean bindToAuthenticator(String authenticatorType) {
+ final AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
+ authenticatorInfo = mAuthenticatorCache.getServiceInfo(
+ AuthenticatorDescription.newKey(authenticatorType), mAccounts.userId);
+ if (authenticatorInfo == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "there is no authenticator for " + authenticatorType
+ + ", bailing out");
+ }
+ return false;
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
+ intent.setComponent(authenticatorInfo.componentName);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
+ }
+ if (!mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
+ new UserHandle(mAccounts.userId))) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
+ }
+ return false;
+ }
+
+
+ return true;
+ }
+ }
+
+ private class MessageHandler extends Handler {
+ MessageHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_TIMED_OUT:
+ Session session = (Session)msg.obj;
+ session.onTimedOut();
+ break;
+
+ case MESSAGE_COPY_SHARED_ACCOUNT:
+ copyAccountToUser((Account) msg.obj, msg.arg1, msg.arg2);
+ break;
+
+ default:
+ throw new IllegalStateException("unhandled message: " + msg.what);
+ }
+ }
+ }
+
+ private static String getDatabaseName(int userId) {
+ File systemDir = Environment.getSystemSecureDirectory();
+ File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME);
+ if (userId == 0) {
+ // Migrate old file, if it exists, to the new location.
+ // Make sure the new file doesn't already exist. A dummy file could have been
+ // accidentally created in the old location, causing the new one to become corrupted
+ // as well.
+ File oldFile = new File(systemDir, DATABASE_NAME);
+ if (oldFile.exists() && !databaseFile.exists()) {
+ // Check for use directory; create if it doesn't exist, else renameTo will fail
+ File userDir = Environment.getUserSystemDirectory(userId);
+ if (!userDir.exists()) {
+ if (!userDir.mkdirs()) {
+ throw new IllegalStateException("User dir cannot be created: " + userDir);
+ }
+ }
+ if (!oldFile.renameTo(databaseFile)) {
+ throw new IllegalStateException("User dir cannot be migrated: " + databaseFile);
+ }
+ }
+ }
+ return databaseFile.getPath();
+ }
+
+ static class DatabaseHelper extends SQLiteOpenHelper {
+
+ public DatabaseHelper(Context context, int userId) {
+ super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION);
+ }
+
+ /**
+ * This call needs to be made while the mCacheLock is held. The way to
+ * ensure this is to get the lock any time a method is called ont the DatabaseHelper
+ * @param db The database.
+ */
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
+ + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + ACCOUNTS_NAME + " TEXT NOT NULL, "
+ + ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ + ACCOUNTS_PASSWORD + " TEXT, "
+ + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
+
+ db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
+ + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
+ + AUTHTOKENS_TYPE + " TEXT NOT NULL, "
+ + AUTHTOKENS_AUTHTOKEN + " TEXT, "
+ + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
+
+ createGrantsTable(db);
+
+ db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
+ + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + EXTRAS_ACCOUNTS_ID + " INTEGER, "
+ + EXTRAS_KEY + " TEXT NOT NULL, "
+ + EXTRAS_VALUE + " TEXT, "
+ + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
+
+ db.execSQL("CREATE TABLE " + TABLE_META + " ( "
+ + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
+ + META_VALUE + " TEXT)");
+
+ createSharedAccountsTable(db);
+
+ createAccountsDeletionTrigger(db);
+ }
+
+ private void createSharedAccountsTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
+ + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + ACCOUNTS_NAME + " TEXT NOT NULL, "
+ + ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
+ }
+
+ private void createAccountsDeletionTrigger(SQLiteDatabase db) {
+ db.execSQL(""
+ + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
+ + " BEGIN"
+ + " DELETE FROM " + TABLE_AUTHTOKENS
+ + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+ + " DELETE FROM " + TABLE_EXTRAS
+ + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+ + " DELETE FROM " + TABLE_GRANTS
+ + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+ + " END");
+ }
+
+ private void createGrantsTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
+ + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
+ + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
+ + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
+ + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
+ + "," + GRANTS_GRANTEE_UID + "))");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
+
+ if (oldVersion == 1) {
+ // no longer need to do anything since the work is done
+ // when upgrading from version 2
+ oldVersion++;
+ }
+
+ if (oldVersion == 2) {
+ createGrantsTable(db);
+ db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
+ createAccountsDeletionTrigger(db);
+ oldVersion++;
+ }
+
+ if (oldVersion == 3) {
+ db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE +
+ " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
+ oldVersion++;
+ }
+
+ if (oldVersion == 4) {
+ createSharedAccountsTable(db);
+ oldVersion++;
+ }
+
+ if (oldVersion != newVersion) {
+ Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
+ }
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
+ }
+ }
+
+ public IBinder onBind(Intent intent) {
+ return asBinder();
+ }
+
+ /**
+ * Searches array of arguments for the specified string
+ * @param args array of argument strings
+ * @param value value to search for
+ * @return true if the value is contained in the array
+ */
+ private static boolean scanArgs(String[] args, String value) {
+ if (args != null) {
+ for (String arg : args) {
+ if (value.equals(arg)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ fout.println("Permission Denial: can't dump AccountsManager from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " without permission " + android.Manifest.permission.DUMP);
+ return;
+ }
+ final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c");
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(fout, " ");
+
+ final List<UserInfo> users = getUserManager().getUsers();
+ for (UserInfo user : users) {
+ ipw.println("User " + user + ":");
+ ipw.increaseIndent();
+ dumpUser(getUserAccounts(user.id), fd, ipw, args, isCheckinRequest);
+ ipw.println();
+ ipw.decreaseIndent();
+ }
+ }
+
+ private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout,
+ String[] args, boolean isCheckinRequest) {
+ synchronized (userAccounts.cacheLock) {
+ final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase();
+
+ if (isCheckinRequest) {
+ // This is a checkin request. *Only* upload the account types and the count of each.
+ Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
+ null, null, ACCOUNTS_TYPE, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ // print type,count
+ fout.println(cursor.getString(0) + "," + cursor.getString(1));
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ } else {
+ Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
+ Process.myUid(), null);
+ fout.println("Accounts: " + accounts.length);
+ for (Account account : accounts) {
+ fout.println(" " + account);
+ }
+
+ fout.println();
+ synchronized (mSessions) {
+ final long now = SystemClock.elapsedRealtime();
+ fout.println("Active Sessions: " + mSessions.size());
+ for (Session session : mSessions.values()) {
+ fout.println(" " + session.toDebugString(now));
+ }
+ }
+
+ fout.println();
+ mAuthenticatorCache.dump(fd, fout, args, userAccounts.userId);
+ }
+ }
+ }
+
+ private void doNotification(UserAccounts accounts, Account account, CharSequence message,
+ Intent intent, int userId) {
+ long identityToken = clearCallingIdentity();
+ try {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "doNotification: " + message + " intent:" + intent);
+ }
+
+ if (intent.getComponent() != null &&
+ GrantCredentialsPermissionActivity.class.getName().equals(
+ intent.getComponent().getClassName())) {
+ createNoCredentialsPermissionNotification(account, intent, userId);
+ } else {
+ final Integer notificationId = getSigninRequiredNotificationId(accounts, account);
+ intent.addCategory(String.valueOf(notificationId));
+ Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
+ 0 /* when */);
+ UserHandle user = new UserHandle(userId);
+ final String notificationTitleFormat =
+ mContext.getText(R.string.notification_title).toString();
+ n.setLatestEventInfo(mContext,
+ String.format(notificationTitleFormat, account.name),
+ message, PendingIntent.getActivityAsUser(
+ mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT,
+ null, user));
+ installNotification(notificationId, n, user);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ protected void installNotification(final int notificationId, final Notification n,
+ UserHandle user) {
+ ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+ .notifyAsUser(null, notificationId, n, user);
+ }
+
+ protected void cancelNotification(int id, UserHandle user) {
+ long identityToken = clearCallingIdentity();
+ try {
+ ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+ .cancelAsUser(null, id, user);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /** Succeeds if any of the specified permissions are granted. */
+ private void checkBinderPermission(String... permissions) {
+ final int uid = Binder.getCallingUid();
+
+ for (String perm : permissions) {
+ if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, " caller uid " + uid + " has " + perm);
+ }
+ return;
+ }
+ }
+
+ String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
+ Log.w(TAG, " " + msg);
+ throw new SecurityException(msg);
+ }
+
+ private int handleIncomingUser(int userId) {
+ try {
+ return ActivityManagerNative.getDefault().handleIncomingUser(
+ Binder.getCallingPid(), Binder.getCallingUid(), userId, true, true, "", null);
+ } catch (RemoteException re) {
+ // Shouldn't happen, local.
+ }
+ return userId;
+ }
+
+ private boolean inSystemImage(int callingUid) {
+ final int callingUserId = UserHandle.getUserId(callingUid);
+
+ final PackageManager userPackageManager;
+ try {
+ userPackageManager = mContext.createPackageContextAsUser(
+ "android", 0, new UserHandle(callingUserId)).getPackageManager();
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+
+ String[] packages = userPackageManager.getPackagesForUid(callingUid);
+ for (String name : packages) {
+ try {
+ PackageInfo packageInfo = userPackageManager.getPackageInfo(name, 0 /* flags */);
+ if (packageInfo != null
+ && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
+ final boolean inSystemImage = inSystemImage(callerUid);
+ final boolean fromAuthenticator = account != null
+ && hasAuthenticatorUid(account.type, callerUid);
+ final boolean hasExplicitGrants = account != null
+ && hasExplicitlyGrantedPermission(account, authTokenType, callerUid);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
+ + callerUid + ", " + account
+ + ": is authenticator? " + fromAuthenticator
+ + ", has explicit permission? " + hasExplicitGrants);
+ }
+ return fromAuthenticator || hasExplicitGrants || inSystemImage;
+ }
+
+ private boolean hasAuthenticatorUid(String accountType, int callingUid) {
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
+ mAuthenticatorCache.getAllServices(callingUserId)) {
+ if (serviceInfo.type.type.equals(accountType)) {
+ return (serviceInfo.uid == callingUid) ||
+ (mPackageManager.checkSignatures(serviceInfo.uid, callingUid)
+ == PackageManager.SIGNATURE_MATCH);
+ }
+ }
+ return false;
+ }
+
+ private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType,
+ int callerUid) {
+ if (callerUid == Process.SYSTEM_UID) {
+ return true;
+ }
+ UserAccounts accounts = getUserAccountsForCaller();
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+ String[] args = { String.valueOf(callerUid), authTokenType,
+ account.name, account.type};
+ final boolean permissionGranted =
+ DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0;
+ if (!permissionGranted && ActivityManager.isRunningInTestHarness()) {
+ // TODO: Skip this check when running automated tests. Replace this
+ // with a more general solution.
+ Log.d(TAG, "no credentials permission for usage of " + account + ", "
+ + authTokenType + " by uid " + callerUid
+ + " but ignoring since device is in test harness.");
+ return true;
+ }
+ return permissionGranted;
+ }
+ }
+
+ private void checkCallingUidAgainstAuthenticator(Account account) {
+ final int uid = Binder.getCallingUid();
+ if (account == null || !hasAuthenticatorUid(account.type, uid)) {
+ String msg = "caller uid " + uid + " is different than the authenticator's uid";
+ Log.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid");
+ }
+ }
+
+ private void checkAuthenticateAccountsPermission(Account account) {
+ checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS);
+ checkCallingUidAgainstAuthenticator(account);
+ }
+
+ private void checkReadAccountsPermission() {
+ checkBinderPermission(Manifest.permission.GET_ACCOUNTS);
+ }
+
+ private void checkManageAccountsPermission() {
+ checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS);
+ }
+
+ private void checkManageAccountsOrUseCredentialsPermissions() {
+ checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS,
+ Manifest.permission.USE_CREDENTIALS);
+ }
+
+ private boolean canUserModifyAccounts(int callingUid) {
+ if (callingUid != Process.myUid()) {
+ if (getUserManager().getUserRestrictions(
+ new UserHandle(UserHandle.getUserId(callingUid)))
+ .getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void updateAppPermission(Account account, String authTokenType, int uid, boolean value)
+ throws RemoteException {
+ final int callingUid = getCallingUid();
+
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException();
+ }
+
+ if (value) {
+ grantAppPermission(account, authTokenType, uid);
+ } else {
+ revokeAppPermission(account, authTokenType, uid);
+ }
+ }
+
+ /**
+ * Allow callers with the given uid permission to get credentials for account/authTokenType.
+ * <p>
+ * Although this is public it can only be accessed via the AccountManagerService object
+ * which is in the system. This means we don't need to protect it with permissions.
+ * @hide
+ */
+ private void grantAppPermission(Account account, String authTokenType, int uid) {
+ if (account == null || authTokenType == null) {
+ Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception());
+ return;
+ }
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ long accountId = getAccountIdLocked(db, account);
+ if (accountId >= 0) {
+ ContentValues values = new ContentValues();
+ values.put(GRANTS_ACCOUNTS_ID, accountId);
+ values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
+ values.put(GRANTS_GRANTEE_UID, uid);
+ db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
+ db.setTransactionSuccessful();
+ }
+ } finally {
+ db.endTransaction();
+ }
+ cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
+ new UserHandle(accounts.userId));
+ }
+ }
+
+ /**
+ * Don't allow callers with the given uid permission to get credentials for
+ * account/authTokenType.
+ * <p>
+ * Although this is public it can only be accessed via the AccountManagerService object
+ * which is in the system. This means we don't need to protect it with permissions.
+ * @hide
+ */
+ private void revokeAppPermission(Account account, String authTokenType, int uid) {
+ if (account == null || authTokenType == null) {
+ Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception());
+ return;
+ }
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ long accountId = getAccountIdLocked(db, account);
+ if (accountId >= 0) {
+ db.delete(TABLE_GRANTS,
+ GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
+ + GRANTS_GRANTEE_UID + "=?",
+ new String[]{String.valueOf(accountId), authTokenType,
+ String.valueOf(uid)});
+ db.setTransactionSuccessful();
+ }
+ } finally {
+ db.endTransaction();
+ }
+ cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
+ new UserHandle(accounts.userId));
+ }
+ }
+
+ static final private String stringArrayToString(String[] value) {
+ return value != null ? ("[" + TextUtils.join(",", value) + "]") : null;
+ }
+
+ private void removeAccountFromCacheLocked(UserAccounts accounts, Account account) {
+ final Account[] oldAccountsForType = accounts.accountCache.get(account.type);
+ if (oldAccountsForType != null) {
+ ArrayList<Account> newAccountsList = new ArrayList<Account>();
+ for (Account curAccount : oldAccountsForType) {
+ if (!curAccount.equals(account)) {
+ newAccountsList.add(curAccount);
+ }
+ }
+ if (newAccountsList.isEmpty()) {
+ accounts.accountCache.remove(account.type);
+ } else {
+ Account[] newAccountsForType = new Account[newAccountsList.size()];
+ newAccountsForType = newAccountsList.toArray(newAccountsForType);
+ accounts.accountCache.put(account.type, newAccountsForType);
+ }
+ }
+ accounts.userDataCache.remove(account);
+ accounts.authTokenCache.remove(account);
+ }
+
+ /**
+ * This assumes that the caller has already checked that the account is not already present.
+ */
+ private void insertAccountIntoCacheLocked(UserAccounts accounts, Account account) {
+ Account[] accountsForType = accounts.accountCache.get(account.type);
+ int oldLength = (accountsForType != null) ? accountsForType.length : 0;
+ Account[] newAccountsForType = new Account[oldLength + 1];
+ if (accountsForType != null) {
+ System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength);
+ }
+ newAccountsForType[oldLength] = account;
+ accounts.accountCache.put(account.type, newAccountsForType);
+ }
+
+ private Account[] filterSharedAccounts(UserAccounts userAccounts, Account[] unfiltered,
+ int callingUid, String callingPackage) {
+ if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0
+ || callingUid == Process.myUid()) {
+ return unfiltered;
+ }
+ if (mUserManager.getUserInfo(userAccounts.userId).isRestricted()) {
+ String[] packages = mPackageManager.getPackagesForUid(callingUid);
+ // If any of the packages is a white listed package, return the full set,
+ // otherwise return non-shared accounts only.
+ // This might be a temporary way to specify a whitelist
+ String whiteList = mContext.getResources().getString(
+ com.android.internal.R.string.config_appsAuthorizedForSharedAccounts);
+ for (String packageName : packages) {
+ if (whiteList.contains(";" + packageName + ";")) {
+ return unfiltered;
+ }
+ }
+ ArrayList<Account> allowed = new ArrayList<Account>();
+ Account[] sharedAccounts = getSharedAccountsAsUser(userAccounts.userId);
+ if (sharedAccounts == null || sharedAccounts.length == 0) return unfiltered;
+ String requiredAccountType = "";
+ try {
+ // If there's an explicit callingPackage specified, check if that package
+ // opted in to see restricted accounts.
+ if (callingPackage != null) {
+ PackageInfo pi = mPackageManager.getPackageInfo(callingPackage, 0);
+ if (pi != null && pi.restrictedAccountType != null) {
+ requiredAccountType = pi.restrictedAccountType;
+ }
+ } else {
+ // Otherwise check if the callingUid has a package that has opted in
+ for (String packageName : packages) {
+ PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
+ if (pi != null && pi.restrictedAccountType != null) {
+ requiredAccountType = pi.restrictedAccountType;
+ break;
+ }
+ }
+ }
+ } catch (NameNotFoundException nnfe) {
+ }
+ for (Account account : unfiltered) {
+ if (account.type.equals(requiredAccountType)) {
+ allowed.add(account);
+ } else {
+ boolean found = false;
+ for (Account shared : sharedAccounts) {
+ if (shared.equals(account)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ allowed.add(account);
+ }
+ }
+ }
+ Account[] filtered = new Account[allowed.size()];
+ allowed.toArray(filtered);
+ return filtered;
+ } else {
+ return unfiltered;
+ }
+ }
+
+ /*
+ * packageName can be null. If not null, it should be used to filter out restricted accounts
+ * that the package is not allowed to access.
+ */
+ protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
+ int callingUid, String callingPackage) {
+ if (accountType != null) {
+ final Account[] accounts = userAccounts.accountCache.get(accountType);
+ if (accounts == null) {
+ return EMPTY_ACCOUNT_ARRAY;
+ } else {
+ return filterSharedAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
+ callingUid, callingPackage);
+ }
+ } else {
+ int totalLength = 0;
+ for (Account[] accounts : userAccounts.accountCache.values()) {
+ totalLength += accounts.length;
+ }
+ if (totalLength == 0) {
+ return EMPTY_ACCOUNT_ARRAY;
+ }
+ Account[] accounts = new Account[totalLength];
+ totalLength = 0;
+ for (Account[] accountsOfType : userAccounts.accountCache.values()) {
+ System.arraycopy(accountsOfType, 0, accounts, totalLength,
+ accountsOfType.length);
+ totalLength += accountsOfType.length;
+ }
+ return filterSharedAccounts(userAccounts, accounts, callingUid, callingPackage);
+ }
+ }
+
+ protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
+ Account account, String key, String value) {
+ HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
+ if (userDataForAccount == null) {
+ userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
+ accounts.userDataCache.put(account, userDataForAccount);
+ }
+ if (value == null) {
+ userDataForAccount.remove(key);
+ } else {
+ userDataForAccount.put(key, value);
+ }
+ }
+
+ protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
+ Account account, String key, String value) {
+ HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
+ if (authTokensForAccount == null) {
+ authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
+ accounts.authTokenCache.put(account, authTokensForAccount);
+ }
+ if (value == null) {
+ authTokensForAccount.remove(key);
+ } else {
+ authTokensForAccount.put(key, value);
+ }
+ }
+
+ protected String readAuthTokenInternal(UserAccounts accounts, Account account,
+ String authTokenType) {
+ synchronized (accounts.cacheLock) {
+ HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
+ if (authTokensForAccount == null) {
+ // need to populate the cache for this account
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+ authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
+ accounts.authTokenCache.put(account, authTokensForAccount);
+ }
+ return authTokensForAccount.get(authTokenType);
+ }
+ }
+
+ protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
+ synchronized (accounts.cacheLock) {
+ HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
+ if (userDataForAccount == null) {
+ // need to populate the cache for this account
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+ userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
+ accounts.userDataCache.put(account, userDataForAccount);
+ }
+ return userDataForAccount.get(key);
+ }
+ }
+
+ protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
+ final SQLiteDatabase db, Account account) {
+ HashMap<String, String> userDataForAccount = new HashMap<String, String>();
+ Cursor cursor = db.query(TABLE_EXTRAS,
+ COLUMNS_EXTRAS_KEY_AND_VALUE,
+ SELECTION_USERDATA_BY_ACCOUNT,
+ new String[]{account.name, account.type},
+ null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ final String tmpkey = cursor.getString(0);
+ final String value = cursor.getString(1);
+ userDataForAccount.put(tmpkey, value);
+ }
+ } finally {
+ cursor.close();
+ }
+ return userDataForAccount;
+ }
+
+ protected HashMap<String, String> readAuthTokensForAccountFromDatabaseLocked(
+ final SQLiteDatabase db, Account account) {
+ HashMap<String, String> authTokensForAccount = new HashMap<String, String>();
+ Cursor cursor = db.query(TABLE_AUTHTOKENS,
+ COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
+ SELECTION_AUTHTOKENS_BY_ACCOUNT,
+ new String[]{account.name, account.type},
+ null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ final String type = cursor.getString(0);
+ final String authToken = cursor.getString(1);
+ authTokensForAccount.put(type, authToken);
+ }
+ } finally {
+ cursor.close();
+ }
+ return authTokensForAccount;
+ }
+}
diff --git a/services/java/com/android/server/accounts/IAccountAuthenticatorCache.java b/services/java/com/android/server/accounts/IAccountAuthenticatorCache.java
new file mode 100644
index 0000000..bb09687
--- /dev/null
+++ b/services/java/com/android/server/accounts/IAccountAuthenticatorCache.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 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.accounts;
+
+import android.accounts.AuthenticatorDescription;
+import android.content.pm.RegisteredServicesCache;
+import android.content.pm.RegisteredServicesCacheListener;
+import android.os.Handler;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collection;
+
+/**
+ * An interface to the Authenticator specialization of RegisteredServicesCache. The use of
+ * this interface by the AccountManagerService makes it easier to unit test it.
+ * @hide
+ */
+public interface IAccountAuthenticatorCache {
+ /**
+ * Accessor for the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that
+ * matched the specified {@link android.accounts.AuthenticatorDescription} or null
+ * if none match.
+ * @param type the authenticator type to return
+ * @return the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that
+ * matches the account type or null if none is present
+ */
+ RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> getServiceInfo(
+ AuthenticatorDescription type, int userId);
+
+ /**
+ * @return A copy of a Collection of all the current Authenticators.
+ */
+ Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> getAllServices(
+ int userId);
+
+ /**
+ * Dumps the state of the cache. See
+ * {@link android.os.Binder#dump(java.io.FileDescriptor, java.io.PrintWriter, String[])}
+ */
+ void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId);
+
+ /**
+ * Sets a listener that will be notified whenever the authenticator set changes
+ * @param listener the listener to notify, or null
+ * @param handler the {@link Handler} on which the notification will be posted. If null
+ * the notification will be posted on the main thread.
+ */
+ void setListener(RegisteredServicesCacheListener<AuthenticatorDescription> listener,
+ Handler handler);
+
+ void invalidateCache(int userId);
+}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index c6efe15b..0081dfc 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -18,16 +18,21 @@ package com.android.server.am;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import android.app.AppOpsManager;
+import android.appwidget.AppWidgetManager;
import com.android.internal.R;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.ProcessStats;
+import com.android.server.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.IntentResolver;
import com.android.server.ProcessMap;
import com.android.server.SystemServer;
import com.android.server.Watchdog;
import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.firewall.IntentFirewall;
import com.android.server.pm.UserManagerService;
+import com.android.server.wm.AppTransition;
import com.android.server.wm.WindowManagerService;
import dalvik.system.Zygote;
@@ -49,6 +54,7 @@ import android.app.IProcessObserver;
import android.app.IServiceConnection;
import android.app.IStopUserCallback;
import android.app.IThumbnailReceiver;
+import android.app.IUiAutomationConnection;
import android.app.IUserSwitchObserver;
import android.app.Instrumentation;
import android.app.Notification;
@@ -130,7 +136,6 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -161,7 +166,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
-public final class ActivityManagerService extends ActivityManagerNative
+public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
private static final String USER_DATA_DIR = "/data/user/";
static final String TAG = "ActivityManager";
@@ -261,12 +266,17 @@ public final class ActivityManagerService extends ActivityManagerNative
// Maximum number of users we allow to be running at a time.
static final int MAX_RUNNING_USERS = 3;
+ // How long to wait in getTopActivityExtras for the activity to respond with the result.
+ static final int PENDING_ACTIVITY_RESULT_TIMEOUT = 2*2000;
+
static final int MY_PID = Process.myPid();
static final String[] EMPTY_STRING_ARRAY = new String[0];
public ActivityStack mMainStack;
+ public IntentFirewall mIntentFirewall;
+
private final boolean mHeadless;
// Whether we should show our dialogs (ANR, crash, etc) or just perform their
@@ -318,11 +328,32 @@ public final class ActivityManagerService extends ActivityManagerNative
* Activity we have told the window manager to have key focus.
*/
ActivityRecord mFocusedActivity = null;
+
/**
* List of intents that were used to start the most recent tasks.
*/
final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
+ public class PendingActivityExtras extends Binder implements Runnable {
+ public final ActivityRecord activity;
+ public boolean haveResult = false;
+ public Bundle result = null;
+ public PendingActivityExtras(ActivityRecord _activity) {
+ activity = _activity;
+ }
+ @Override
+ public void run() {
+ Slog.w(TAG, "getTopActivityExtras failed: timeout retrieving from " + activity);
+ synchronized (this) {
+ haveResult = true;
+ notifyAll();
+ }
+ }
+ }
+
+ final ArrayList<PendingActivityExtras> mPendingActivityExtras
+ = new ArrayList<PendingActivityExtras>();
+
/**
* Process management.
*/
@@ -542,8 +573,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- protected String packageForFilter(BroadcastFilter filter) {
- return filter.packageName;
+ protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
+ return packageName.equals(filter.packageName);
}
};
@@ -598,7 +629,6 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* Thread-local storage used to carry caller permissions over through
* indirect content-provider access.
- * @see #ActivityManagerService.openContentUri()
*/
private class Identity {
public int pid;
@@ -619,9 +649,14 @@ public final class ActivityManagerService extends ActivityManagerNative
final BatteryStatsService mBatteryStatsService;
/**
- * information about component usage
+ * Information about component usage
*/
final UsageStatsService mUsageStatsService;
+
+ /**
+ * Information about and control over application operations
+ */
+ final AppOpsService mAppOpsService;
/**
* Current configuration information. HistoryRecord objects are given
@@ -800,18 +835,6 @@ public final class ActivityManagerService extends ActivityManagerNative
= new ArrayList<ProcessChangeItem>();
/**
- * Callback of last caller to {@link #requestPss}.
- */
- Runnable mRequestPssCallback;
-
- /**
- * Remaining processes for which we are waiting results from the last
- * call to {@link #requestPss}.
- */
- final ArrayList<ProcessRecord> mRequestPssList
- = new ArrayList<ProcessRecord>();
-
- /**
* Runtime statistics collection thread. This object's lock is used to
* protect all related state.
*/
@@ -913,6 +936,12 @@ public final class ActivityManagerService extends ActivityManagerNative
CompatModeDialog mCompatModeDialog;
long mLastMemUsageReportTime = 0;
+ /**
+ * Flag whether the current user is a "monkey", i.e. whether
+ * the UI is driven by a UI automation tool.
+ */
+ private boolean mUserIsMonkey;
+
final Handler mHandler = new Handler() {
//public Handler() {
// if (localLOGV) Slog.v(TAG, "Handler started!");
@@ -974,7 +1003,7 @@ public final class ActivityManagerService extends ActivityManagerNative
| Intent.FLAG_RECEIVER_FOREGROUND);
}
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);
if (mShowDialogs) {
@@ -1200,7 +1229,7 @@ public final class ActivityManagerService extends ActivityManagerNative
try {
int[] outId = new int[1];
- inm.enqueueNotificationWithTag("android", null,
+ inm.enqueueNotificationWithTag("android", "android", null,
R.string.heavy_weight_notification,
notification, outId, root.userId);
} catch (RuntimeException e) {
@@ -1387,7 +1416,7 @@ public final class ActivityManagerService extends ActivityManagerNative
public static void setSystemProcess() {
try {
ActivityManagerService m = mSelf;
-
+
ServiceManager.addService("activity", m, true);
ServiceManager.addService("meminfo", new MemBinder(m));
ServiceManager.addService("gfxinfo", new GraphicsBinder(m));
@@ -1425,6 +1454,11 @@ public final class ActivityManagerService extends ActivityManagerNative
mWindowManager = wm;
}
+ public void startObservingNativeCrashes() {
+ final NativeCrashListener ncl = new NativeCrashListener();
+ ncl.start();
+ }
+
public static final Context main(int factoryTest) {
AThread thr = new AThread();
thr.start();
@@ -1446,11 +1480,13 @@ public final class ActivityManagerService extends ActivityManagerNative
context.setTheme(android.R.style.Theme_Holo);
m.mContext = context;
m.mFactoryTest = factoryTest;
- m.mMainStack = new ActivityStack(m, context, true);
-
+ m.mMainStack = new ActivityStack(m, context, true, thr.mLooper);
+ m.mIntentFirewall = new IntentFirewall(m.new IntentFirewallInterface());
+
m.mBatteryStatsService.publish(context);
m.mUsageStatsService.publish(context);
-
+ m.mAppOpsService.publish(context);
+
synchronized (thr) {
thr.mReady = true;
thr.notifyAll();
@@ -1467,6 +1503,7 @@ public final class ActivityManagerService extends ActivityManagerNative
static class AThread extends Thread {
ActivityManagerService mService;
+ Looper mLooper;
boolean mReady = false;
public AThread() {
@@ -1484,6 +1521,7 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (this) {
mService = m;
+ mLooper = Looper.myLooper();
notifyAll();
}
@@ -1611,9 +1649,10 @@ public final class ActivityManagerService extends ActivityManagerNative
mOnBattery = DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
mBatteryStatsService.getActiveStatistics().setCallback(this);
-
+
mUsageStatsService = new UsageStatsService(new File(
systemDir, "usagestats").toString());
+ mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"));
mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
// User 0 is the first and only user that runs at boot.
@@ -1968,9 +2007,9 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean isNextTransitionForward() {
int transit = mWindowManager.getPendingAppTransition();
- return transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN
- || transit == WindowManagerPolicy.TRANSIT_TASK_OPEN
- || transit == WindowManagerPolicy.TRANSIT_TASK_TO_FRONT;
+ return transit == AppTransition.TRANSIT_ACTIVITY_OPEN
+ || transit == AppTransition.TRANSIT_TASK_OPEN
+ || transit == AppTransition.TRANSIT_TASK_TO_FRONT;
}
final ProcessRecord startProcessLocked(String processName,
@@ -2274,7 +2313,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app == null || app.instrumentationClass == null) {
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
mMainStack.startActivityLocked(null, intent, null, aInfo,
- null, null, 0, 0, 0, 0, null, false, null);
+ null, null, 0, 0, 0, null, 0, null, false, null);
}
}
@@ -2352,7 +2391,7 @@ public final class ActivityManagerService extends ActivityManagerNative
intent.setComponent(new ComponentName(
ri.activityInfo.packageName, ri.activityInfo.name));
mMainStack.startActivityLocked(null, intent, null, ri.activityInfo,
- null, null, 0, 0, 0, 0, null, false, null);
+ null, null, 0, 0, 0, null, 0, null, false, null);
}
}
}
@@ -2487,27 +2526,28 @@ public final class ActivityManagerService extends ActivityManagerNative
mPendingActivityLaunches.clear();
}
- public final int startActivity(IApplicationThread caller,
+ public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags,
String profileFile, ParcelFileDescriptor profileFd, Bundle options) {
- return startActivityAsUser(caller, intent, resolvedType, resultTo, resultWho, requestCode,
+ return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
+ resultWho, requestCode,
startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId());
}
- public final int startActivityAsUser(IApplicationThread caller,
+ public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags,
String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivity");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, true, "startActivity", null);
- return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
+ return mMainStack.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
null, null, options, userId);
}
- public final WaitResult startActivityAndWait(IApplicationThread caller,
+ public final WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options, int userId) {
@@ -2515,20 +2555,20 @@ public final class ActivityManagerService extends ActivityManagerNative
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, true, "startActivityAndWait", null);
WaitResult res = new WaitResult();
- mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
+ mMainStack.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
res, null, options, UserHandle.getCallingUserId());
return res;
}
- public final int startActivityWithConfig(IApplicationThread caller,
+ public final int startActivityWithConfig(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, Configuration config,
Bundle options, int userId) {
enforceNotIsolatedCaller("startActivityWithConfig");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, true, "startActivityWithConfig", null);
- int ret = mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
+ int ret = mMainStack.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
null, null, null, config, options, userId);
return ret;
@@ -2649,7 +2689,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final long origId = Binder.clearCallingIdentity();
int res = mMainStack.startActivityLocked(r.app.thread, intent,
r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null,
- resultWho, requestCode, -1, r.launchedFromUid, 0,
+ resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, 0,
options, false, null);
Binder.restoreCallingIdentity(origId);
@@ -2661,38 +2701,38 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- final int startActivityInPackage(int uid,
+ final int startActivityInPackage(int uid, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, Bundle options, int userId) {
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, true, "startActivityInPackage", null);
- int ret = mMainStack.startActivityMayWait(null, uid, intent, resolvedType,
+ int ret = mMainStack.startActivityMayWait(null, uid, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
null, null, null, null, options, userId);
return ret;
}
- public final int startActivities(IApplicationThread caller,
+ public final int startActivities(IApplicationThread caller, String callingPackage,
Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options,
int userId) {
enforceNotIsolatedCaller("startActivities");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, true, "startActivity", null);
- int ret = mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo,
- options, userId);
+ int ret = mMainStack.startActivities(caller, -1, callingPackage, intents,
+ resolvedTypes, resultTo, options, userId);
return ret;
}
- final int startActivitiesInPackage(int uid,
+ final int startActivitiesInPackage(int uid, String callingPackage,
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
Bundle options, int userId) {
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, true, "startActivityInPackage", null);
- int ret = mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo,
- options, userId);
+ int ret = mMainStack.startActivities(null, uid, callingPackage, intents, resolvedTypes,
+ resultTo, options, userId);
return ret;
}
@@ -2782,6 +2822,7 @@ public final class ActivityManagerService extends ActivityManagerNative
resumeOK = mController.activityResuming(next.packageName);
} catch (RemoteException e) {
mController = null;
+ Watchdog.getInstance().setActivityController(null);
}
if (!resumeOK) {
@@ -3294,6 +3335,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (res < 0 && app.pid != MY_PID) Process.killProcess(app.pid);
} catch (RemoteException e) {
mController = null;
+ Watchdog.getInstance().setActivityController(null);
}
}
@@ -3397,6 +3439,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
} catch (RemoteException e) {
mController = null;
+ Watchdog.getInstance().setActivityController(null);
}
}
@@ -3475,7 +3518,14 @@ public final class ActivityManagerService extends ActivityManagerNative
} catch (RemoteException e) {
}
if (pkgUid == -1) {
- Slog.w(TAG, "Invalid packageName:" + packageName);
+ Slog.w(TAG, "Invalid packageName: " + packageName);
+ if (observer != null) {
+ try {
+ observer.onRemoveCompleted(packageName, false);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Observer no longer exists.");
+ }
+ }
return false;
}
if (uid == pkgUid || checkComponentPermission(
@@ -3702,7 +3752,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
broadcastIntentLocked(null, null, intent, null,
- null, 0, null, null, null, false, false, -1,
+ null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, -1,
Process.SYSTEM_UID, UserHandle.USER_ALL);
}
@@ -3765,7 +3815,7 @@ public final class ActivityManagerService extends ActivityManagerNative
intent.putExtra(Intent.EXTRA_UID, uid);
intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
false, false,
MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid));
}
@@ -3777,7 +3827,7 @@ public final class ActivityManagerService extends ActivityManagerNative
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
false, false,
MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
@@ -3817,6 +3867,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.userId != userId) {
continue;
}
+ if (appId >= 0 && UserHandle.getAppId(app.uid) != appId) {
+ continue;
+ }
// Package has been specified, we want to hit all processes
// that match it. We need to qualify this by the processes
// that are running under the specified app and user ID.
@@ -4249,8 +4302,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
thread.bindApplication(processName, appInfo, providers,
app.instrumentationClass, profileFile, profileFd, profileAutoStop,
- app.instrumentationArguments, app.instrumentationWatcher, testMode,
- enableOpenGlTrace, isRestrictedBackupMode || !normalMode, app.persistent,
+ app.instrumentationArguments, app.instrumentationWatcher,
+ app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
+ isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
mCoreSettingsObserver.getCoreSettingsLocked());
updateLruProcessLocked(app, false);
@@ -4455,7 +4509,8 @@ public final class ActivityManagerService extends ActivityManagerNative
broadcastIntentLocked(null, null, intent,
null, null, 0, null, null,
android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
- false, false, MY_PID, Process.SYSTEM_UID, userId);
+ AppOpsManager.OP_NONE, false, false, MY_PID, Process.SYSTEM_UID,
+ userId);
}
}
}
@@ -4531,7 +4586,7 @@ public final class ActivityManagerService extends ActivityManagerNative
public String getCallingPackage(IBinder token) {
synchronized (this) {
ActivityRecord r = getCallingRecordLocked(token);
- return r != null && r.app != null ? r.info.packageName : null;
+ return r != null ? r.info.packageName : null;
}
}
@@ -4909,6 +4964,18 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ class IntentFirewallInterface implements IntentFirewall.AMSInterface {
+ public int checkComponentPermission(String permission, int pid, int uid,
+ int owningUid, boolean exported) {
+ return ActivityManagerService.this.checkComponentPermission(permission, pid, uid,
+ owningUid, exported);
+ }
+
+ public Object getAMSLock() {
+ return ActivityManagerService.this;
+ }
+ }
+
/**
* This can be called with or without the global lock held.
*/
@@ -6707,7 +6774,6 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* Drop a content provider from a ProcessRecord's bookkeeping
- * @param cpr
*/
public void removeContentProvider(IBinder connection, boolean stable) {
enforceNotIsolatedCaller("removeContentProvider");
@@ -7095,7 +7161,7 @@ public final class ActivityManagerService extends ActivityManagerNative
sCallerIdentity.set(new Identity(
Binder.getCallingPid(), Binder.getCallingUid()));
try {
- pfd = cph.provider.openFile(uri, "r");
+ pfd = cph.provider.openFile(null, uri, "r");
} catch (FileNotFoundException e) {
// do nothing; pfd will be returned null
} finally {
@@ -7172,7 +7238,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+
+ mAppOpsService.shutdown();
mUsageStatsService.shutdown();
mBatteryStatsService.shutdown();
@@ -7377,47 +7444,96 @@ public final class ActivityManagerService extends ActivityManagerNative
"setActivityController()");
synchronized (this) {
mController = controller;
+ Watchdog.getInstance().setActivityController(controller);
+ }
+ }
+
+ public void setUserIsMonkey(boolean userIsMonkey) {
+ synchronized (this) {
+ synchronized (mPidsSelfLocked) {
+ final int callingPid = Binder.getCallingPid();
+ ProcessRecord precessRecord = mPidsSelfLocked.get(callingPid);
+ if (precessRecord == null) {
+ throw new SecurityException("Unknown process: " + callingPid);
+ }
+ if (precessRecord.instrumentationUiAutomationConnection == null) {
+ throw new SecurityException("Only an instrumentation process "
+ + "with a UiAutomation can call setUserIsMonkey");
+ }
+ }
+ mUserIsMonkey = userIsMonkey;
}
}
public boolean isUserAMonkey() {
- // For now the fact that there is a controller implies
- // we have a monkey.
synchronized (this) {
- return mController != null;
+ // If there is a controller also implies the user is a monkey.
+ return (mUserIsMonkey || mController != null);
}
}
public void requestBugReport() {
- // No permission check because this can't do anything harmful --
- // it will just eventually cause the user to be presented with
- // a UI to select where the bug report goes.
+ enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport");
SystemProperties.set("ctl.start", "bugreport");
}
- public long inputDispatchingTimedOut(int pid, boolean aboveSystem) {
+ public static long getInputDispatchingTimeoutLocked(ActivityRecord r) {
+ return r != null ? getInputDispatchingTimeoutLocked(r.app) : KEY_DISPATCHING_TIMEOUT;
+ }
+
+ public static long getInputDispatchingTimeoutLocked(ProcessRecord r) {
+ if (r != null && (r.instrumentationClass != null || r.usingWrapper)) {
+ return INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT;
+ }
+ return KEY_DISPATCHING_TIMEOUT;
+ }
+
+
+ public long inputDispatchingTimedOut(int pid, final boolean aboveSystem) {
if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission "
+ android.Manifest.permission.FILTER_EVENTS);
}
-
ProcessRecord proc;
-
- // TODO: Unify this code with ActivityRecord.keyDispatchingTimedOut().
+ long timeout;
synchronized (this) {
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(pid);
}
- if (proc != null) {
+ timeout = getInputDispatchingTimeoutLocked(proc);
+ }
+
+ if (!inputDispatchingTimedOut(proc, null, null, aboveSystem)) {
+ return -1;
+ }
+
+ return timeout;
+ }
+
+ /**
+ * Handle input dispatching timeouts.
+ * Returns whether input dispatching should be aborted or not.
+ */
+ public boolean inputDispatchingTimedOut(final ProcessRecord proc,
+ final ActivityRecord activity, final ActivityRecord parent,
+ final boolean aboveSystem) {
+ if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.FILTER_EVENTS);
+ }
+
+ if (proc != null) {
+ synchronized (this) {
if (proc.debugging) {
- return -1;
+ return false;
}
if (mDidDexOpt) {
// Give more time since we were dexopting.
mDidDexOpt = false;
- return -1;
+ return false;
}
if (proc.instrumentationClass != null) {
@@ -7425,19 +7541,75 @@ public final class ActivityManagerService extends ActivityManagerNative
info.putString("shortMsg", "keyDispatchingTimedOut");
info.putString("longMsg", "Timed out while dispatching key event");
finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info);
- proc = null;
+ return true;
}
}
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ appNotResponding(proc, activity, parent, aboveSystem, "keyDispatchingTimedOut");
+ }
+ });
}
- if (proc != null) {
- appNotResponding(proc, null, null, aboveSystem, "keyDispatchingTimedOut");
- if (proc.instrumentationClass != null || proc.usingWrapper) {
- return INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT;
+ return true;
+ }
+
+ public Bundle getTopActivityExtras(int requestType) {
+ enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
+ "getTopActivityExtras()");
+ PendingActivityExtras pae;
+ Bundle extras = new Bundle();
+ synchronized (this) {
+ ActivityRecord activity = mMainStack.mResumedActivity;
+ if (activity == null) {
+ Slog.w(TAG, "getTopActivityExtras failed: no resumed activity");
+ return null;
+ }
+ extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName);
+ if (activity.app == null || activity.app.thread == null) {
+ Slog.w(TAG, "getTopActivityExtras failed: no process for " + activity);
+ return extras;
+ }
+ if (activity.app.pid == Binder.getCallingPid()) {
+ Slog.w(TAG, "getTopActivityExtras failed: request process same as " + activity);
+ return extras;
+ }
+ pae = new PendingActivityExtras(activity);
+ try {
+ activity.app.thread.requestActivityExtras(activity.appToken, pae, requestType);
+ mPendingActivityExtras.add(pae);
+ mHandler.postDelayed(pae, PENDING_ACTIVITY_RESULT_TIMEOUT);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "getTopActivityExtras failed: crash calling " + activity);
+ return extras;
+ }
+ }
+ synchronized (pae) {
+ while (!pae.haveResult) {
+ try {
+ pae.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ if (pae.result != null) {
+ extras.putBundle(Intent.EXTRA_ASSIST_CONTEXT, pae.result);
}
}
+ synchronized (this) {
+ mPendingActivityExtras.remove(pae);
+ mHandler.removeCallbacks(pae);
+ }
+ return extras;
+ }
- return KEY_DISPATCHING_TIMEOUT;
+ public void reportTopActivityExtras(IBinder token, Bundle extras) {
+ PendingActivityExtras pae = (PendingActivityExtras)token;
+ synchronized (pae) {
+ pae.result = extras;
+ pae.haveResult = true;
+ pae.notifyAll();
+ }
}
public void registerProcessObserver(IProcessObserver observer) {
@@ -7594,6 +7766,18 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public void killUid(int uid, String reason) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("killUid only available to the system");
+ }
+ synchronized (this) {
+ killPackageProcessesLocked(null, UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+ ProcessList.FOREGROUND_APP_ADJ-1, false, true, true, false,
+ reason != null ? reason : "kill uid");
+ }
+ }
+
+ @Override
public boolean killProcessesBelowForeground(String reason) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("killProcessesBelowForeground() only available to system");
@@ -7629,6 +7813,45 @@ public final class ActivityManagerService extends ActivityManagerNative
return killed;
}
+ @Override
+ public void hang(final IBinder who, boolean allowRestart) {
+ if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+ }
+
+ final IBinder.DeathRecipient death = new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+ };
+
+ try {
+ who.linkToDeath(death, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "hang: given caller IBinder is already dead.");
+ return;
+ }
+
+ synchronized (this) {
+ Watchdog.getInstance().setAllowRestart(allowRestart);
+ Slog.i(TAG, "Hanging system process at request of pid " + Binder.getCallingPid());
+ synchronized (death) {
+ while (who.isBinderAlive()) {
+ try {
+ death.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ Watchdog.getInstance().setAllowRestart(true);
+ }
+ }
+
public final void startRunning(String pkg, String cls, String action,
String data) {
synchronized(this) {
@@ -7830,7 +8053,8 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.i(TAG, "Sending system update to " + intent.getComponent()
+ " for user " + users[j]);
broadcastIntentLocked(null, null, intent, null, finisher,
- 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID,
+ 0, null, null, null, AppOpsManager.OP_NONE,
+ true, false, MY_PID, Process.SYSTEM_UID,
users[j]);
if (finisher != null) {
mWaitingUpdate = true;
@@ -7843,7 +8067,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mDidUpdate = true;
}
-
+
+ mAppOpsService.systemReady();
mSystemReady = true;
if (!mStartRunning) {
return;
@@ -7963,7 +8188,7 @@ public final class ActivityManagerService extends ActivityManagerNative
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
false, false, MY_PID, Process.SYSTEM_UID, mCurrentUserId);
intent = new Intent(Intent.ACTION_USER_STARTING);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -7976,7 +8201,7 @@ public final class ActivityManagerService extends ActivityManagerNative
throws RemoteException {
}
}, 0, null, null,
- android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -8209,6 +8434,14 @@ public final class ActivityManagerService extends ActivityManagerNative
final String processName = app == null ? "system_server"
: (r == null ? "unknown" : r.processName);
+ handleApplicationCrashInner("crash", r, processName, crashInfo);
+ }
+
+ /* Native crash reporting uses this inner version because it needs to be somewhat
+ * decoupled from the AM-managed cleanup lifecycle
+ */
+ void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
+ ApplicationErrorReport.CrashInfo crashInfo) {
EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(),
UserHandle.getUserId(Binder.getCallingUid()), processName,
r == null ? -1 : r.info.flags,
@@ -8217,7 +8450,7 @@ public final class ActivityManagerService extends ActivityManagerNative
crashInfo.throwFileName,
crashInfo.throwLineNumber);
- addErrorToDropBox("crash", r, processName, null, null, null, null, null, crashInfo);
+ addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
crashApplication(r, crashInfo);
}
@@ -8642,6 +8875,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
} catch (RemoteException e) {
mController = null;
+ Watchdog.getInstance().setActivityController(null);
}
}
@@ -8722,7 +8956,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return null;
}
- if (!r.crashing && !r.notResponding) {
+ if (!r.crashing && !r.notResponding && !r.forceCrashReport) {
return null;
}
@@ -8733,7 +8967,7 @@ public final class ActivityManagerService extends ActivityManagerNative
report.time = timeMillis;
report.systemApp = (r.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- if (r.crashing) {
+ if (r.crashing || r.forceCrashReport) {
report.type = ApplicationErrorReport.TYPE_CRASH;
report.crashInfo = crashInfo;
} else if (r.notResponding) {
@@ -10743,7 +10977,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mProcessesToGc.remove(app);
// Dismiss any open dialogs.
- if (app.crashDialog != null) {
+ if (app.crashDialog != null && !app.forceCrashReport) {
app.crashDialog.dismiss();
app.crashDialog = null;
}
@@ -11471,8 +11705,8 @@ public final class ActivityManagerService extends ActivityManagerNative
Intent intent = (Intent)allSticky.get(i);
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
- null, -1, -1, null, receivers, null, 0, null, null,
- false, true, true, -1);
+ null, -1, -1, null, AppOpsManager.OP_NONE, receivers, null, 0,
+ null, null, false, true, true, -1);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
@@ -11621,7 +11855,7 @@ public final class ActivityManagerService extends ActivityManagerNative
private final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
- Bundle map, String requiredPermission,
+ Bundle map, String requiredPermission, int appOp,
boolean ordered, boolean sticky, int callingPid, int callingUid,
int userId) {
intent = new Intent(intent);
@@ -11668,6 +11902,32 @@ public final class ActivityManagerService extends ActivityManagerNative
+ callingPid + ", uid=" + callingUid;
Slog.w(TAG, msg);
throw new SecurityException(msg);
+ } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(intent.getAction())) {
+ // Special case for compatibility: we don't want apps to send this,
+ // but historically it has not been protected and apps may be using it
+ // to poke their own app widget. So, instead of making it protected,
+ // just limit it to the caller.
+ if (callerApp == null) {
+ String msg = "Permission Denial: not allowed to send broadcast "
+ + intent.getAction() + " from unknown caller.";
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ } else if (intent.getComponent() != null) {
+ // They are good enough to send to an explicit component... verify
+ // it is being sent to the calling app.
+ if (!intent.getComponent().getPackageName().equals(
+ callerApp.info.packageName)) {
+ String msg = "Permission Denial: not allowed to send broadcast "
+ + intent.getAction() + " to "
+ + intent.getComponent().getPackageName() + " from "
+ + callerApp.info.packageName;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ } else {
+ // Limit broadcast to their own package.
+ intent.setPackage(callerApp.info.packageName);
+ }
}
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception", e);
@@ -11697,6 +11957,7 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (bs) {
bs.removeUidStatsLocked(uid);
}
+ mAppOpsService.uidRemoved(uid);
}
} else {
// If resources are unavailable just force stop all
@@ -11722,6 +11983,10 @@ public final class ActivityManagerService extends ActivityManagerNative
if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED,
new String[] {ssp}, userId);
+ if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ mAppOpsService.packageRemoved(
+ intent.getIntExtra(Intent.EXTRA_UID, -1), ssp);
+ }
}
}
}
@@ -11867,7 +12132,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// components to be launched.
final BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
- callerPackage, callingPid, callingUid, requiredPermission,
+ callerPackage, callingPid, callingUid, requiredPermission, appOp,
registeredReceivers, resultTo, resultCode, resultData, map,
ordered, sticky, false, userId);
if (DEBUG_BROADCAST) Slog.v(
@@ -11957,7 +12222,7 @@ public final class ActivityManagerService extends ActivityManagerNative
|| resultTo != null) {
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
- callerPackage, callingPid, callingUid, requiredPermission,
+ callerPackage, callingPid, callingUid, requiredPermission, appOp,
receivers, resultTo, resultCode, resultData, map, ordered,
sticky, false, userId);
if (DEBUG_BROADCAST) Slog.v(
@@ -12009,7 +12274,7 @@ public final class ActivityManagerService extends ActivityManagerNative
public final int broadcastIntent(IApplicationThread caller,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle map,
- String requiredPermission, boolean serialized, boolean sticky, int userId) {
+ String requiredPermission, int appOp, boolean serialized, boolean sticky, int userId) {
enforceNotIsolatedCaller("broadcastIntent");
synchronized(this) {
intent = verifyBroadcastLocked(intent);
@@ -12021,7 +12286,7 @@ public final class ActivityManagerService extends ActivityManagerNative
int res = broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null,
intent, resolvedType, resultTo,
- resultCode, resultData, map, requiredPermission, serialized, sticky,
+ resultCode, resultData, map, requiredPermission, appOp, serialized, sticky,
callingPid, callingUid, userId);
Binder.restoreCallingIdentity(origId);
return res;
@@ -12038,7 +12303,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final long origId = Binder.clearCallingIdentity();
int res = broadcastIntentLocked(null, packageName, intent, resolvedType,
resultTo, resultCode, resultData, map, requiredPermission,
- serialized, sticky, -1, uid, userId);
+ AppOpsManager.OP_NONE, serialized, sticky, -1, uid, userId);
Binder.restoreCallingIdentity(origId);
return res;
}
@@ -12136,7 +12401,8 @@ public final class ActivityManagerService extends ActivityManagerNative
public boolean startInstrumentation(ComponentName className,
String profileFile, int flags, Bundle arguments,
- IInstrumentationWatcher watcher, int userId) {
+ IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
+ int userId) {
enforceNotIsolatedCaller("startInstrumentation");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, true, "startInstrumentation", null);
@@ -12190,6 +12456,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.instrumentationProfileFile = profileFile;
app.instrumentationArguments = arguments;
app.instrumentationWatcher = watcher;
+ app.instrumentationUiAutomationConnection = uiAutomationConnection;
app.instrumentationResultClass = className;
Binder.restoreCallingIdentity(origId);
}
@@ -12232,7 +12499,18 @@ public final class ActivityManagerService extends ActivityManagerNative
} catch (RemoteException e) {
}
}
+ if (app.instrumentationUiAutomationConnection != null) {
+ try {
+ app.instrumentationUiAutomationConnection.shutdown();
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ // Only a UiAutomation can set this flag and now that
+ // it is finished we make sure it is reset to its default.
+ mUserIsMonkey = false;
+ }
app.instrumentationWatcher = null;
+ app.instrumentationUiAutomationConnection = null;
app.instrumentationClass = null;
app.instrumentationInfo = null;
app.instrumentationProfileFile = null;
@@ -12414,13 +12692,14 @@ public final class ActivityManagerService extends ActivityManagerNative
| Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntentLocked(null, null, intent, null, null, 0, null, null,
- null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+ null, AppOpsManager.OP_NONE, false, false, MY_PID,
+ Process.SYSTEM_UID, UserHandle.USER_ALL);
if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null,
- null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
}
}
@@ -12530,6 +12809,7 @@ public final class ActivityManagerService extends ActivityManagerNative
resumeOK = mController.activityResuming(next.packageName);
} catch (RemoteException e) {
mController = null;
+ Watchdog.getInstance().setActivityController(null);
}
if (!resumeOK) {
@@ -12561,7 +12841,8 @@ public final class ActivityManagerService extends ActivityManagerNative
destIntent.getComponent(), 0, srec.userId);
int res = mMainStack.startActivityLocked(srec.app.thread, destIntent,
null, aInfo, parent.appToken, null,
- 0, -1, parent.launchedFromUid, 0, null, true, null);
+ 0, -1, parent.launchedFromUid, parent.launchedFromPackage,
+ 0, null, true, null);
foundParentInTask = res == ActivityManager.START_SUCCESS;
} catch (RemoteException e) {
foundParentInTask = false;
@@ -12583,6 +12864,14 @@ public final class ActivityManagerService extends ActivityManagerNative
return srec.launchedFromUid;
}
+ public String getLaunchedFromPackage(IBinder activityToken) {
+ ActivityRecord srec = ActivityRecord.forToken(activityToken);
+ if (srec == null) {
+ return null;
+ }
+ return srec.launchedFromPackage;
+ }
+
// =========================================================
// LIFETIME MANAGEMENT
// =========================================================
@@ -14255,7 +14544,7 @@ public final class ActivityManagerService extends ActivityManagerNative
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
false, false, MY_PID, Process.SYSTEM_UID, userId);
}
@@ -14270,7 +14559,8 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean sticky, int sendingUser) {
userInitialized(uss, userId);
}
- }, 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID,
+ }, 0, null, null, null, AppOpsManager.OP_NONE,
+ true, false, MY_PID, Process.SYSTEM_UID,
userId);
uss.initializing = true;
} else {
@@ -14298,7 +14588,7 @@ public final class ActivityManagerService extends ActivityManagerNative
throws RemoteException {
}
}, 0, null, null,
- android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
}
@@ -14319,7 +14609,7 @@ public final class ActivityManagerService extends ActivityManagerNative
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, oldUserId);
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
false, false, MY_PID, Process.SYSTEM_UID, oldUserId);
}
if (newUserId >= 0) {
@@ -14328,7 +14618,7 @@ public final class ActivityManagerService extends ActivityManagerNative
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
false, false, MY_PID, Process.SYSTEM_UID, newUserId);
intent = new Intent(Intent.ACTION_USER_SWITCHED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
@@ -14336,7 +14626,7 @@ public final class ActivityManagerService extends ActivityManagerNative
intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
broadcastIntentLocked(null, null, intent,
null, null, 0, null, null,
- android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.MANAGE_USERS, AppOpsManager.OP_NONE,
false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
} finally {
@@ -14441,7 +14731,7 @@ public final class ActivityManagerService extends ActivityManagerNative
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
broadcastIntentLocked(null, null, intent,
null, null, 0, null, null,
- android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
+ android.Manifest.permission.RECEIVE_BOOT_COMPLETED, AppOpsManager.OP_NONE,
false, false, MY_PID, Process.SYSTEM_UID, userId);
}
int num = mUserLru.size();
@@ -14557,14 +14847,14 @@ public final class ActivityManagerService extends ActivityManagerNative
uss.mState = UserStartedState.STATE_SHUTDOWN;
}
broadcastIntentLocked(null, null, shutdownIntent,
- null, shutdownReceiver, 0, null, null, null,
+ null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, userId);
}
};
// Kick things off.
broadcastIntentLocked(null, null, stoppingIntent,
null, stoppingReceiver, 0, null, null,
- android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -14594,10 +14884,6 @@ public final class ActivityManagerService extends ActivityManagerNative
// Clean up all state and processes associated with the user.
// Kill all the processes for the user.
forceStopUserLocked(userId);
- AttributeCache ac = AttributeCache.instance();
- if (ac != null) {
- ac.removeUser(userId);
- }
}
}
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index de0f9ca..aa82be3 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -59,6 +59,7 @@ final class ActivityRecord {
final IApplicationToken.Stub appToken; // window manager token
final ActivityInfo info; // all about me
final int launchedFromUid; // always the uid who started the activity.
+ final String launchedFromPackage; // always the package who started the activity.
final int userId; // Which user is this running for?
final Intent intent; // the original intent that generated us
final ComponentName realActivity; // the intent component, or target of an alias.
@@ -123,6 +124,8 @@ final class ActivityRecord {
boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
boolean immersive; // immersive mode (don't interrupt if possible)
boolean forceNewConfig; // force re-create with new config next time
+ int launchCount; // count of launches since last state
+ long lastLaunchTime; // time of last lauch of this activity
String stringName; // for caching of toString().
@@ -133,6 +136,7 @@ final class ActivityRecord {
pw.print(prefix); pw.print("packageName="); pw.print(packageName);
pw.print(" processName="); pw.println(processName);
pw.print(prefix); pw.print("launchedFromUid="); pw.print(launchedFromUid);
+ pw.print(" launchedFromPackage="); pw.println(launchedFromPackage);
pw.print(" userId="); pw.println(userId);
pw.print(prefix); pw.print("app="); pw.println(app);
pw.print(prefix); pw.println(intent.toInsecureStringWithClip());
@@ -201,7 +205,12 @@ final class ActivityRecord {
}
}
pw.print(prefix); pw.print("launchFailed="); pw.print(launchFailed);
- pw.print(" haveState="); pw.print(haveState);
+ pw.print(" launchCount="); pw.print(launchCount);
+ pw.print(" lastLaunchTime=");
+ if (lastLaunchTime == 0) pw.print("0");
+ else TimeUtils.formatDuration(lastLaunchTime, now, pw);
+ pw.println();
+ pw.print(prefix); pw.print(" haveState="); pw.print(haveState);
pw.print(" icicle="); pw.println(icicle);
pw.print(prefix); pw.print("state="); pw.print(state);
pw.print(" stopped="); pw.print(stopped);
@@ -318,7 +327,7 @@ final class ActivityRecord {
}
ActivityRecord(ActivityManagerService _service, ActivityStack _stack, ProcessRecord _caller,
- int _launchedFromUid, Intent _intent, String _resolvedType,
+ int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
ActivityInfo aInfo, Configuration _configuration,
ActivityRecord _resultTo, String _resultWho, int _reqCode,
boolean _componentSpecified) {
@@ -327,6 +336,7 @@ final class ActivityRecord {
appToken = new Token(this);
info = aInfo;
launchedFromUid = _launchedFromUid;
+ launchedFromPackage = _launchedFromPackage;
userId = UserHandle.getUserId(aInfo.applicationInfo.uid);
intent = _intent;
shortComponentName = _intent.getComponent().flattenToShortString();
@@ -407,8 +417,8 @@ final class ActivityRecord {
packageName = aInfo.applicationInfo.packageName;
launchMode = aInfo.launchMode;
- AttributeCache.Entry ent = AttributeCache.instance().get(userId, packageName,
- realTheme, com.android.internal.R.styleable.Window);
+ AttributeCache.Entry ent = AttributeCache.instance().get(packageName,
+ realTheme, com.android.internal.R.styleable.Window, userId);
fullscreen = ent != null && !ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsFloating, false)
&& !ent.array.getBoolean(
@@ -564,6 +574,9 @@ final class ActivityRecord {
*/
final void deliverNewIntentLocked(int callingUid, Intent intent) {
boolean sent = false;
+ // The activity now gets access to the data associated with this Intent.
+ service.grantUriPermissionFromIntentLocked(callingUid, packageName,
+ intent, getUriPermissionsLocked());
// We want to immediately deliver the intent to the activity if
// it is currently the top resumed activity... however, if the
// device is sleeping, then all activities are stopped, so in that
@@ -576,8 +589,6 @@ final class ActivityRecord {
ArrayList<Intent> ar = new ArrayList<Intent>();
intent = new Intent(intent);
ar.add(intent);
- service.grantUriPermissionFromIntentLocked(callingUid, packageName,
- intent, getUriPermissionsLocked());
app.thread.scheduleNewIntent(ar, appToken);
sent = true;
} catch (RemoteException e) {
@@ -856,51 +867,20 @@ final class ActivityRecord {
}
public boolean keyDispatchingTimedOut() {
- // TODO: Unify this code with ActivityManagerService.inputDispatchingTimedOut().
ActivityRecord r;
- ProcessRecord anrApp = null;
+ ProcessRecord anrApp;
synchronized(service) {
r = getWaitingHistoryRecordLocked();
- if (r != null && r.app != null) {
- if (r.app.debugging) {
- return false;
- }
-
- if (service.mDidDexOpt) {
- // Give more time since we were dexopting.
- service.mDidDexOpt = false;
- return false;
- }
-
- if (r.app.instrumentationClass == null) {
- anrApp = r.app;
- } else {
- Bundle info = new Bundle();
- info.putString("shortMsg", "keyDispatchingTimedOut");
- info.putString("longMsg", "Timed out while dispatching key event");
- service.finishInstrumentationLocked(
- r.app, Activity.RESULT_CANCELED, info);
- }
- }
+ anrApp = r != null ? r.app : null;
}
-
- if (anrApp != null) {
- service.appNotResponding(anrApp, r, this, false, "keyDispatchingTimedOut");
- }
-
- return true;
+ return service.inputDispatchingTimedOut(anrApp, r, this, false);
}
/** Returns the key dispatching timeout for this application token. */
public long getKeyDispatchingTimeout() {
synchronized(service) {
ActivityRecord r = getWaitingHistoryRecordLocked();
- if (r != null && r.app != null
- && (r.app.instrumentationClass != null || r.app.usingWrapper)) {
- return ActivityManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT;
- }
-
- return ActivityManagerService.KEY_DISPATCHING_TIMEOUT;
+ return ActivityManagerService.getInputDispatchingTimeoutLocked(r);
}
}
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 27dd732..c344023 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -22,6 +22,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.am.ActivityManagerService.PendingActivityLaunch;
+import com.android.server.wm.AppTransition;
import android.app.Activity;
import android.app.ActivityManager;
@@ -45,10 +46,13 @@ import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
@@ -59,7 +63,6 @@ import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.view.Display;
-import android.view.WindowManagerPolicy;
import java.io.IOException;
import java.lang.ref.WeakReference;
@@ -86,6 +89,7 @@ final class ActivityStack {
static final boolean DEBUG_STATES = false;
static final boolean DEBUG_ADD_REMOVE = false;
static final boolean DEBUG_SAVED_STATE = false;
+ static final boolean DEBUG_APP = false;
static final boolean VALIDATE_TOKENS = ActivityManagerService.VALIDATE_TOKENS;
@@ -279,6 +283,13 @@ final class ActivityStack {
*/
boolean mDismissKeyguardOnNextActivity = false;
+ /**
+ * Save the most recent screenshot for reuse. This keeps Recents from taking two identical
+ * screenshots, one for the Recents thumbnail and one for the pauseActivity thumbnail.
+ */
+ private ActivityRecord mLastScreenshotActivity = null;
+ private Bitmap mLastScreenshotBitmap = null;
+
int mThumbnailWidth = -1;
int mThumbnailHeight = -1;
@@ -306,11 +317,17 @@ final class ActivityStack {
}
}
- final Handler mHandler = new Handler() {
+ final Handler mHandler;
+
+ final class ActivityStackHandler extends Handler {
//public Handler() {
// if (localLOGV) Slog.v(TAG, "Handler started!");
//}
+ public ActivityStackHandler(Looper looper) {
+ super(looper);
+ }
+ @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SLEEP_TIMEOUT_MSG: {
@@ -408,9 +425,10 @@ final class ActivityStack {
}
}
}
- };
-
- ActivityStack(ActivityManagerService service, Context context, boolean mainStack) {
+ }
+
+ ActivityStack(ActivityManagerService service, Context context, boolean mainStack, Looper looper) {
+ mHandler = new ActivityStackHandler(looper);
mService = service;
mContext = context;
mMainStack = mainStack;
@@ -637,6 +655,8 @@ final class ActivityStack {
r.app = app;
app.waitingToKill = null;
+ r.launchCount++;
+ r.lastLaunchTime = SystemClock.uptimeMillis();
if (localLOGV) Slog.v(TAG, "Launching: " + r);
@@ -787,7 +807,7 @@ final class ActivityStack {
// Is this activity's application already running?
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid);
-
+
if (r.launchTime == 0) {
r.launchTime = SystemClock.uptimeMillis();
if (mInitialStartTime == 0) {
@@ -923,8 +943,16 @@ final class ActivityStack {
}
if (w > 0) {
- return mService.mWindowManager.screenshotApplications(who.appToken,
- Display.DEFAULT_DISPLAY, w, h);
+ if (who != mLastScreenshotActivity || mLastScreenshotBitmap == null
+ || mLastScreenshotBitmap.getWidth() != w
+ || mLastScreenshotBitmap.getHeight() != h) {
+ mLastScreenshotActivity = who;
+ mLastScreenshotBitmap = mService.mWindowManager.screenshotApplications(
+ who.appToken, Display.DEFAULT_DISPLAY, w, h);
+ }
+ if (mLastScreenshotBitmap != null) {
+ return mLastScreenshotBitmap.copy(Config.ARGB_8888, true);
+ }
}
return null;
}
@@ -1067,6 +1095,7 @@ final class ActivityStack {
// haven't really saved the state.
r.icicle = icicle;
r.haveState = true;
+ r.launchCount = 0;
r.updateThumbnail(thumbnail, description);
}
if (!r.stopped) {
@@ -1588,11 +1617,11 @@ final class ActivityStack {
"Prepare close transition: prev=" + prev);
if (mNoAnimActivities.contains(prev)) {
mService.mWindowManager.prepareAppTransition(
- WindowManagerPolicy.TRANSIT_NONE, false);
+ AppTransition.TRANSIT_NONE, false);
} else {
mService.mWindowManager.prepareAppTransition(prev.task == next.task
- ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE
- : WindowManagerPolicy.TRANSIT_TASK_CLOSE, false);
+ ? AppTransition.TRANSIT_ACTIVITY_CLOSE
+ : AppTransition.TRANSIT_TASK_CLOSE, false);
}
mService.mWindowManager.setAppWillBeHidden(prev.appToken);
mService.mWindowManager.setAppVisibility(prev.appToken, false);
@@ -1602,11 +1631,11 @@ final class ActivityStack {
if (mNoAnimActivities.contains(next)) {
noAnim = true;
mService.mWindowManager.prepareAppTransition(
- WindowManagerPolicy.TRANSIT_NONE, false);
+ AppTransition.TRANSIT_NONE, false);
} else {
mService.mWindowManager.prepareAppTransition(prev.task == next.task
- ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN
- : WindowManagerPolicy.TRANSIT_TASK_OPEN, false);
+ ? AppTransition.TRANSIT_ACTIVITY_OPEN
+ : AppTransition.TRANSIT_TASK_OPEN, false);
}
}
if (false) {
@@ -1619,10 +1648,10 @@ final class ActivityStack {
if (mNoAnimActivities.contains(next)) {
noAnim = true;
mService.mWindowManager.prepareAppTransition(
- WindowManagerPolicy.TRANSIT_NONE, false);
+ AppTransition.TRANSIT_NONE, false);
} else {
mService.mWindowManager.prepareAppTransition(
- WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, false);
+ AppTransition.TRANSIT_ACTIVITY_OPEN, false);
}
}
if (!noAnim) {
@@ -1810,8 +1839,8 @@ final class ActivityStack {
}
mHistory.add(addPos, r);
r.putInHistory();
- mService.mWindowManager.addAppToken(addPos, r.userId, r.appToken,
- r.task.taskId, r.info.screenOrientation, r.fullscreen,
+ mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId,
+ r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0);
if (VALIDATE_TOKENS) {
validateAppTokensLocked();
@@ -1866,17 +1895,17 @@ final class ActivityStack {
"Prepare open transition: starting " + r);
if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
mService.mWindowManager.prepareAppTransition(
- WindowManagerPolicy.TRANSIT_NONE, keepCurTransition);
+ AppTransition.TRANSIT_NONE, keepCurTransition);
mNoAnimActivities.add(r);
} else {
mService.mWindowManager.prepareAppTransition(newTask
- ? WindowManagerPolicy.TRANSIT_TASK_OPEN
- : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
+ ? AppTransition.TRANSIT_TASK_OPEN
+ : AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
mNoAnimActivities.remove(r);
}
r.updateOptionsLocked(options);
- mService.mWindowManager.addAppToken(addPos, r.userId, r.appToken,
- r.task.taskId, r.info.screenOrientation, r.fullscreen,
+ mService.mWindowManager.addAppToken(
+ addPos, r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0);
boolean doShow = true;
if (newTask) {
@@ -1914,8 +1943,8 @@ final class ActivityStack {
} else {
// If this is the first activity, don't do any fancy animations,
// because there is nothing for it to animate on top of.
- mService.mWindowManager.addAppToken(addPos, r.userId, r.appToken,
- r.task.taskId, r.info.screenOrientation, r.fullscreen,
+ mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId,
+ r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0);
ActivityOptions.abort(options);
}
@@ -2455,12 +2484,13 @@ final class ActivityStack {
final int startActivityLocked(IApplicationThread caller,
Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo,
String resultWho, int requestCode,
- int callingPid, int callingUid, int startFlags, Bundle options,
+ int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options,
boolean componentSpecified, ActivityRecord[] outActivity) {
int err = ActivityManager.START_SUCCESS;
ProcessRecord callerApp = null;
+
if (caller != null) {
callerApp = mService.getRecordForAppLocked(caller);
if (callerApp != null) {
@@ -2564,35 +2594,37 @@ final class ActivityStack {
throw new SecurityException(msg);
}
+ boolean abort = !mService.mIntentFirewall.checkStartActivity(intent,
+ callerApp==null?null:callerApp.info, callingUid, callingPid, resolvedType, aInfo);
+
if (mMainStack) {
if (mService.mController != null) {
- boolean abort = false;
try {
// The Intent we give to the watcher has the extra data
// stripped off, since it can contain private information.
Intent watchIntent = intent.cloneFilter();
- abort = !mService.mController.activityStarting(watchIntent,
+ abort |= !mService.mController.activityStarting(watchIntent,
aInfo.applicationInfo.packageName);
} catch (RemoteException e) {
mService.mController = null;
}
-
- if (abort) {
- if (resultRecord != null) {
- sendActivityResultLocked(-1,
- resultRecord, resultWho, requestCode,
- Activity.RESULT_CANCELED, null);
- }
- // We pretend to the caller that it was really started, but
- // they will just get a cancel result.
- mDismissKeyguardOnNextActivity = false;
- ActivityOptions.abort(options);
- return ActivityManager.START_SUCCESS;
- }
}
}
- ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid,
+ if (abort) {
+ if (resultRecord != null) {
+ sendActivityResultLocked(-1,
+ resultRecord, resultWho, requestCode,
+ Activity.RESULT_CANCELED, null);
+ }
+ // We pretend to the caller that it was really started, but
+ // they will just get a cancel result.
+ mDismissKeyguardOnNextActivity = false;
+ ActivityOptions.abort(options);
+ return ActivityManager.START_SUCCESS;
+ }
+
+ ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, callingPackage,
intent, resolvedType, aInfo, mService.mConfiguration,
resultRecord, resultWho, requestCode, componentSpecified);
if (outActivity != null) {
@@ -3067,7 +3099,7 @@ final class ActivityStack {
}
final int startActivityMayWait(IApplicationThread caller, int callingUid,
- Intent intent, String resolvedType, IBinder resultTo,
+ String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, String profileFile,
ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
Bundle options, int userId) {
@@ -3174,7 +3206,7 @@ final class ActivityStack {
int res = startActivityLocked(caller, intent, resolvedType,
aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,
- startFlags, options, componentSpecified, null);
+ callingPackage, startFlags, options, componentSpecified, null);
if (mConfigWillChange && mMainStack) {
// If the caller also wants to switch to a new configuration,
@@ -3225,7 +3257,7 @@ final class ActivityStack {
}
}
- final int startActivities(IApplicationThread caller, int callingUid,
+ final int startActivities(IApplicationThread caller, int callingUid, String callingPackage,
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
Bundle options, int userId) {
if (intents == null) {
@@ -3288,7 +3320,7 @@ final class ActivityStack {
theseOptions = null;
}
int res = startActivityLocked(caller, intent, resolvedTypes[i],
- aInfo, resultTo, null, -1, callingPid, callingUid,
+ aInfo, resultTo, null, -1, callingPid, callingUid, callingPackage,
0, theseOptions, componentSpecified, outActivity);
if (res < 0) {
return res;
@@ -3623,7 +3655,9 @@ final class ActivityStack {
}
if (activityRemoved) {
- resumeTopActivityLocked(null);
+ synchronized (mService) {
+ resumeTopActivityLocked(null);
+ }
}
return res;
@@ -3788,8 +3822,8 @@ final class ActivityStack {
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare close transition: finishing " + r);
mService.mWindowManager.prepareAppTransition(endTask
- ? WindowManagerPolicy.TRANSIT_TASK_CLOSE
- : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE, false);
+ ? AppTransition.TRANSIT_TASK_CLOSE
+ : AppTransition.TRANSIT_ACTIVITY_CLOSE, false);
// Tell window manager to prepare for this one to be removed.
mService.mWindowManager.setAppVisibility(r.appToken, false);
@@ -3907,6 +3941,7 @@ final class ActivityStack {
if (setState) {
if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r + " (cleaning up)");
r.state = ActivityState.DESTROYED;
+ if (DEBUG_APP) Slog.v(TAG, "Clearing app during cleanUp for activity " + r);
r.app = null;
}
@@ -3964,6 +3999,7 @@ final class ActivityStack {
if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r
+ " (removed from history)");
r.state = ActivityState.DESTROYED;
+ if (DEBUG_APP) Slog.v(TAG, "Clearing app during remove for activity " + r);
r.app = null;
mService.mWindowManager.removeAppToken(r.appToken);
if (VALIDATE_TOKENS) {
@@ -4106,6 +4142,7 @@ final class ActivityStack {
if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r
+ " (destroy skipped)");
r.state = ActivityState.DESTROYED;
+ if (DEBUG_APP) Slog.v(TAG, "Clearing app during destroy for activity " + r);
r.app = null;
}
} else {
@@ -4117,6 +4154,7 @@ final class ActivityStack {
if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r
+ " (no app)");
r.state = ActivityState.DESTROYED;
+ if (DEBUG_APP) Slog.v(TAG, "Clearing app during destroy for activity " + r);
r.app = null;
}
}
@@ -4191,7 +4229,21 @@ final class ActivityStack {
if (DEBUG_CLEANUP) Slog.v(
TAG, "Record #" + i + " " + r + ": app=" + r.app);
if (r.app == app) {
+ boolean remove;
if ((!r.haveState && !r.stateNotNeeded) || r.finishing) {
+ // Don't currently have state for the activity, or
+ // it is finishing -- always remove it.
+ remove = true;
+ } else if (r.launchCount > 2 &&
+ r.lastLaunchTime > (SystemClock.uptimeMillis()-60000)) {
+ // We have launched this activity too many times since it was
+ // able to run, so give up and remove it.
+ remove = true;
+ } else {
+ // The process may be gone, but the activity lives on!
+ remove = false;
+ }
+ if (remove) {
if (ActivityStack.DEBUG_ADD_REMOVE || DEBUG_CLEANUP) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
@@ -4218,6 +4270,8 @@ final class ActivityStack {
if (r.visible) {
hasVisibleActivities = true;
}
+ if (DEBUG_APP) Slog.v(TAG, "Clearing app during removeHistory for activity "
+ + r);
r.app = null;
r.nowVisible = false;
if (!r.haveState) {
@@ -4276,7 +4330,7 @@ final class ActivityStack {
(reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
ActivityOptions.abort(options);
} else {
- updateTransitLocked(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, options);
+ updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options);
}
return;
}
@@ -4314,14 +4368,14 @@ final class ActivityStack {
if (reason != null &&
(reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
mService.mWindowManager.prepareAppTransition(
- WindowManagerPolicy.TRANSIT_NONE, false);
+ AppTransition.TRANSIT_NONE, false);
ActivityRecord r = topRunningActivityLocked(null);
if (r != null) {
mNoAnimActivities.add(r);
}
ActivityOptions.abort(options);
} else {
- updateTransitLocked(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, options);
+ updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options);
}
mService.mWindowManager.moveAppTokensToTop(moved);
@@ -4407,14 +4461,14 @@ final class ActivityStack {
if (reason != null &&
(reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
mService.mWindowManager.prepareAppTransition(
- WindowManagerPolicy.TRANSIT_NONE, false);
+ AppTransition.TRANSIT_NONE, false);
ActivityRecord r = topRunningActivityLocked(null);
if (r != null) {
mNoAnimActivities.add(r);
}
} else {
mService.mWindowManager.prepareAppTransition(
- WindowManagerPolicy.TRANSIT_TASK_TO_BACK, false);
+ AppTransition.TRANSIT_TASK_TO_BACK, false);
}
mService.mWindowManager.moveAppTokensToBottom(moved);
if (VALIDATE_TOKENS) {
@@ -4549,11 +4603,13 @@ final class ActivityStack {
private final void logStartActivity(int tag, ActivityRecord r,
TaskRecord task) {
+ final Uri data = r.intent.getData();
+ final String strData = data != null ? data.toSafeString() : null;
+
EventLog.writeEvent(tag,
r.userId, System.identityHashCode(r), task.taskId,
r.shortComponentName, r.intent.getAction(),
- r.intent.getType(), r.intent.getDataString(),
- r.intent.getFlags());
+ r.intent.getType(), strData, r.intent.getFlags());
}
/**
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index ab20208..d19c7f6 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -144,6 +144,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
}
}
+ public void noteVibratorOn(int uid, long durationMillis) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteVibratorOnLocked(uid, durationMillis);
+ }
+ }
+
+ public void noteVibratorOff(int uid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteVibratorOffLocked(uid);
+ }
+ }
+
public void noteStartGps(int uid) {
enforceCallingPermission();
synchronized (mStats) {
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index bada7f0..ac7eb89 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -22,13 +22,13 @@ import java.util.ArrayList;
import android.app.ActivityManager;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -409,6 +409,16 @@ public class BroadcastQueue {
skip = true;
}
}
+ if (r.appOp != AppOpsManager.OP_NONE) {
+ int mode = mService.mAppOpsService.checkOperation(r.appOp,
+ filter.receiverList.uid, filter.packageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "App op " + r.appOp + " not allowed for broadcast to uid "
+ + filter.receiverList.uid + " pkg " + filter.packageName);
+ skip = true;
+ }
+ }
if (!skip) {
// If this is not being sent as an ordered broadcast, then we
@@ -706,6 +716,17 @@ public class BroadcastQueue {
skip = true;
}
}
+ if (r.appOp != AppOpsManager.OP_NONE) {
+ int mode = mService.mAppOpsService.checkOperation(r.appOp,
+ info.activityInfo.applicationInfo.uid, info.activityInfo.packageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "App op " + r.appOp + " not allowed for broadcast to uid "
+ + info.activityInfo.applicationInfo.uid + " pkg "
+ + info.activityInfo.packageName);
+ skip = true;
+ }
+ }
boolean isSingleton = false;
try {
isSingleton = mService.isSingleton(info.activityInfo.processName,
@@ -1058,6 +1079,9 @@ public class BroadcastQueue {
pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r);
pw.print(" ");
pw.println(r.intent.toShortString(false, true, true, false));
+ if (r.targetComp != null && r.targetComp != r.intent.getComponent()) {
+ pw.print(" targetComp: "); pw.println(r.targetComp.toShortString());
+ }
Bundle bundle = r.intent.getExtras();
if (bundle != null) {
pw.print(" extras: "); pw.println(bundle.toString());
diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index 1cf5b9c..83cc0ea 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.app.AppOpsManager;
import android.content.IIntentReceiver;
import android.content.ComponentName;
import android.content.Intent;
@@ -37,6 +38,7 @@ import java.util.List;
*/
class BroadcastRecord extends Binder {
final Intent intent; // the original intent that generated us
+ final ComponentName targetComp; // original component name set on the intent
final ProcessRecord callerApp; // process that sent this
final String callerPackage; // who sent this
final int callingPid; // the pid of who sent this
@@ -46,6 +48,7 @@ class BroadcastRecord extends Binder {
final boolean initialSticky; // initial broadcast from register to sticky?
final int userId; // user id this broadcast was for
final String requiredPermission; // a permission the caller has required
+ final int appOp; // an app op that is associated with this broadcast
final List receivers; // contains BroadcastFilter and ResolveInfo
IIntentReceiver resultTo; // who receives final result if non-null
long dispatchTime; // when dispatch started on this set of receivers
@@ -82,16 +85,20 @@ class BroadcastRecord extends Binder {
pw.print(prefix); pw.print(this); pw.print(" to user "); pw.println(userId);
pw.print(prefix); pw.println(intent.toInsecureString());
+ if (targetComp != null && targetComp != intent.getComponent()) {
+ pw.print(prefix); pw.print(" targetComp: "); pw.println(targetComp.toShortString());
+ }
Bundle bundle = intent.getExtras();
if (bundle != null) {
- pw.print(prefix); pw.print("extras: "); pw.println(bundle.toString());
+ pw.print(prefix); pw.print(" extras: "); pw.println(bundle.toString());
}
pw.print(prefix); pw.print("caller="); pw.print(callerPackage); pw.print(" ");
pw.print(callerApp != null ? callerApp.toShortString() : "null");
pw.print(" pid="); pw.print(callingPid);
pw.print(" uid="); pw.println(callingUid);
- if (requiredPermission != null) {
- pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission);
+ if (requiredPermission != null || appOp != AppOpsManager.OP_NONE) {
+ pw.print(prefix); pw.print("requiredPermission="); pw.print(requiredPermission);
+ pw.print(" appOp="); pw.println(appOp);
}
pw.print(prefix); pw.print("dispatchClockTime=");
pw.println(new Date(dispatchClockTime));
@@ -164,18 +171,20 @@ class BroadcastRecord extends Binder {
BroadcastRecord(BroadcastQueue _queue,
Intent _intent, ProcessRecord _callerApp, String _callerPackage,
- int _callingPid, int _callingUid, String _requiredPermission,
+ int _callingPid, int _callingUid, String _requiredPermission, int _appOp,
List _receivers, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized,
boolean _sticky, boolean _initialSticky,
int _userId) {
queue = _queue;
intent = _intent;
+ targetComp = _intent.getComponent();
callerApp = _callerApp;
callerPackage = _callerPackage;
callingPid = _callingPid;
callingUid = _callingUid;
requiredPermission = _requiredPermission;
+ appOp = _appOp;
receivers = _receivers;
resultTo = _resultTo;
resultCode = _resultCode;
diff --git a/services/java/com/android/server/am/NativeCrashListener.java b/services/java/com/android/server/am/NativeCrashListener.java
new file mode 100644
index 0000000..a9454bd
--- /dev/null
+++ b/services/java/com/android/server/am/NativeCrashListener.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2013 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 android.app.ApplicationErrorReport.CrashInfo;
+import android.util.Slog;
+
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import libcore.io.StructTimeval;
+import libcore.io.StructUcred;
+
+import static libcore.io.OsConstants.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.net.InetSocketAddress;
+import java.net.InetUnixAddress;
+
+/**
+ * Set up a Unix domain socket that debuggerd will connect() to in
+ * order to write a description of a native crash. The crash info is
+ * then parsed and forwarded to the ActivityManagerService's normal
+ * crash handling code.
+ *
+ * Note that this component runs in a separate thread.
+ */
+class NativeCrashListener extends Thread {
+ static final String TAG = "NativeCrashListener";
+ static final boolean DEBUG = false;
+ static final boolean MORE_DEBUG = DEBUG && false;
+
+ // Must match the path defined in debuggerd.c.
+ static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";
+
+ // Use a short timeout on socket operations and abandon the connection
+ // on hard errors
+ static final long SOCKET_TIMEOUT_MILLIS = 2000; // 2 seconds
+
+ final ActivityManagerService mAm;
+
+ /*
+ * Spin the actual work of handling a debuggerd crash report into a
+ * separate thread so that the listener can go immediately back to
+ * accepting incoming connections.
+ */
+ class NativeCrashReporter extends Thread {
+ ProcessRecord mApp;
+ int mSignal;
+ String mCrashReport;
+
+ NativeCrashReporter(ProcessRecord app, int signal, String report) {
+ super("NativeCrashReport");
+ mApp = app;
+ mSignal = signal;
+ mCrashReport = report;
+ }
+
+ @Override
+ public void run() {
+ try {
+ CrashInfo ci = new CrashInfo();
+ ci.exceptionClassName = "Native crash";
+ ci.exceptionMessage = Libcore.os.strsignal(mSignal);
+ ci.throwFileName = "unknown";
+ ci.throwClassName = "unknown";
+ ci.throwMethodName = "unknown";
+ ci.stackTrace = mCrashReport;
+
+ if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()");
+ mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
+ if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to report native crash", e);
+ }
+ }
+ }
+
+ /*
+ * Daemon thread that accept()s incoming domain socket connections from debuggerd
+ * and processes the crash dump that is passed through.
+ */
+ NativeCrashListener() {
+ mAm = ActivityManagerService.self();
+ }
+
+ @Override
+ public void run() {
+ final byte[] ackSignal = new byte[1];
+
+ if (DEBUG) Slog.i(TAG, "Starting up");
+
+ // The file system entity for this socket is created with 0700 perms, owned
+ // by system:system. debuggerd runs as root, so is capable of connecting to
+ // it, but 3rd party apps cannot.
+ {
+ File socketFile = new File(DEBUGGERD_SOCKET_PATH);
+ if (socketFile.exists()) {
+ socketFile.delete();
+ }
+ }
+
+ try {
+ FileDescriptor serverFd = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0);
+ final InetUnixAddress sockAddr = new InetUnixAddress(DEBUGGERD_SOCKET_PATH);
+ Libcore.os.bind(serverFd, sockAddr, 0);
+ Libcore.os.listen(serverFd, 1);
+
+ while (true) {
+ InetSocketAddress peer = new InetSocketAddress();
+ FileDescriptor peerFd = null;
+ try {
+ if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
+ peerFd = Libcore.os.accept(serverFd, peer);
+ if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
+ if (peerFd != null) {
+ // Only the superuser is allowed to talk to us over this socket
+ StructUcred credentials =
+ Libcore.os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
+ if (credentials.uid == 0) {
+ // the reporting thread may take responsibility for
+ // acking the debugger; make sure we play along.
+ consumeNativeCrashData(peerFd);
+ }
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Error handling connection", e);
+ } finally {
+ // Always ack debuggerd's connection to us. The actual
+ // byte written is irrelevant.
+ if (peerFd != null) {
+ try {
+ Libcore.os.write(peerFd, ackSignal, 0, 1);
+ } catch (Exception e) {
+ /* we don't care about failures here */
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Exception writing ack: " + e.getMessage());
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to init native debug socket!", e);
+ }
+ }
+
+ static int unpackInt(byte[] buf, int offset) {
+ int b0, b1, b2, b3;
+
+ b0 = ((int) buf[offset]) & 0xFF; // mask against sign extension
+ b1 = ((int) buf[offset+1]) & 0xFF;
+ b2 = ((int) buf[offset+2]) & 0xFF;
+ b3 = ((int) buf[offset+3]) & 0xFF;
+ return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
+ }
+
+ static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes)
+ throws ErrnoException {
+ int totalRead = 0;
+ while (numBytes > 0) {
+ int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes);
+ if (n <= 0) {
+ if (DEBUG) {
+ Slog.w(TAG, "Needed " + numBytes + " but saw " + n);
+ }
+ return -1; // premature EOF or timeout
+ }
+ numBytes -= n;
+ totalRead += n;
+ }
+ return totalRead;
+ }
+
+ // Read the crash report from the debuggerd connection
+ void consumeNativeCrashData(FileDescriptor fd) {
+ if (MORE_DEBUG) Slog.i(TAG, "debuggerd connected");
+ final byte[] buf = new byte[4096];
+ final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
+
+ try {
+ StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
+ Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
+ Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
+
+ // first, the pid and signal number
+ int headerBytes = readExactly(fd, buf, 0, 8);
+ if (headerBytes != 8) {
+ // protocol failure; give up
+ Slog.e(TAG, "Unable to read from debuggerd");
+ return;
+ }
+
+ int pid = unpackInt(buf, 0);
+ int signal = unpackInt(buf, 4);
+ if (DEBUG) {
+ Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
+ }
+
+ // now the text of the dump
+ if (pid > 0) {
+ final ProcessRecord pr;
+ synchronized (mAm.mPidsSelfLocked) {
+ pr = mAm.mPidsSelfLocked.get(pid);
+ }
+ if (pr != null) {
+ // Don't attempt crash reporting for persistent apps
+ if (pr.persistent) {
+ if (DEBUG) {
+ Slog.v(TAG, "Skipping report for persistent app " + pr);
+ }
+ return;
+ }
+
+ int bytes;
+ do {
+ // get some data
+ bytes = Libcore.os.read(fd, buf, 0, buf.length);
+ if (bytes > 0) {
+ if (MORE_DEBUG) {
+ String s = new String(buf, 0, bytes, "UTF-8");
+ Slog.v(TAG, "READ=" + bytes + "> " + s);
+ }
+ // did we just get the EOD null byte?
+ if (buf[bytes-1] == 0) {
+ os.write(buf, 0, bytes-1); // exclude the EOD token
+ break;
+ }
+ // no EOD, so collect it and read more
+ os.write(buf, 0, bytes);
+ }
+ } while (bytes > 0);
+
+ // Okay, we've got the report.
+ if (DEBUG) Slog.v(TAG, "processing");
+
+ // Mark the process record as being a native crash so that the
+ // cleanup mechanism knows we're still submitting the report
+ // even though the process will vanish as soon as we let
+ // debuggerd proceed.
+ synchronized (mAm) {
+ pr.crashing = true;
+ pr.forceCrashReport = true;
+ }
+
+ // Crash reporting is synchronous but we want to let debuggerd
+ // go about it business right away, so we spin off the actual
+ // reporting logic on a thread and let it take it's time.
+ final String reportString = new String(os.toByteArray(), "UTF-8");
+ (new NativeCrashReporter(pr, signal, reportString)).start();
+ } else {
+ Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
+ }
+ } else {
+ Slog.e(TAG, "Bogus pid!");
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception dealing with report", e);
+ // ugh, fail.
+ }
+ }
+
+}
diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java
index 8ee303f..8ab71dd 100644
--- a/services/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/java/com/android/server/am/PendingIntentRecord.java
@@ -246,11 +246,12 @@ class PendingIntentRecord extends IIntentSender.Stub {
}
allIntents[allIntents.length-1] = finalIntent;
allResolvedTypes[allResolvedTypes.length-1] = resolvedType;
- owner.startActivitiesInPackage(uid, allIntents,
+ owner.startActivitiesInPackage(uid, key.packageName, allIntents,
allResolvedTypes, resultTo, options, userId);
} else {
- owner.startActivityInPackage(uid, finalIntent, resolvedType,
- resultTo, resultWho, requestCode, 0, options, userId);
+ owner.startActivityInPackage(uid, key.packageName, finalIntent,
+ resolvedType, resultTo, resultWho, requestCode, 0,
+ options, userId);
}
} catch (RuntimeException e) {
Slog.w(ActivityManagerService.TAG,
diff --git a/services/java/com/android/server/am/ProcessList.java b/services/java/com/android/server/am/ProcessList.java
index 9e25e30..1a635a9 100644
--- a/services/java/com/android/server/am/ProcessList.java
+++ b/services/java/com/android/server/am/ProcessList.java
@@ -144,8 +144,8 @@ class ProcessList {
// These are the high-end OOM level limits. This is appropriate for a
// 1280x800 or larger screen with around 1GB RAM. Values are in KB.
private final long[] mOomMinFreeHigh = new long[] {
- 32768, 40960, 49152,
- 57344, 65536, 81920
+ 49152, 61440, 73728,
+ 86016, 98304, 122880
};
// The actual OOM killer memory levels we are using.
private final long[] mOomMinFree = new long[mOomAdj.length];
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 7fbab04..7929f96 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -22,6 +22,7 @@ import android.app.ActivityManager;
import android.app.Dialog;
import android.app.IApplicationThread;
import android.app.IInstrumentationWatcher;
+import android.app.IUiAutomationConnection;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -95,6 +96,7 @@ class ProcessRecord {
ApplicationInfo instrumentationInfo; // the application being instrumented
String instrumentationProfileFile; // where to save profiling
IInstrumentationWatcher instrumentationWatcher; // who is waiting
+ IUiAutomationConnection instrumentationUiAutomationConnection; // Connection to use the UI introspection APIs.
Bundle instrumentationArguments;// as given to us
ComponentName instrumentationResultClass;// copy of instrumentationClass
boolean usingWrapper; // Set to true when process was launched with a wrapper attached
@@ -136,6 +138,7 @@ class ProcessRecord {
boolean persistent; // always keep this application running?
boolean crashing; // are we in the process of crashing?
Dialog crashDialog; // dialog being displayed due to crash.
+ boolean forceCrashReport; // suppress normal auto-dismiss of crash dialog & report UI?
boolean notResponding; // does the app have a not responding dialog?
Dialog anrDialog; // dialog being displayed due to app not resp.
boolean removed; // has app package been removed from device?
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 84e824a..9fdd293 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.provider.Settings;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.NotificationManagerService;
@@ -368,9 +371,55 @@ class ServiceRecord extends Binder {
return;
}
try {
+ if (localForegroundNoti.icon == 0) {
+ // It is not correct for the caller to supply a notification
+ // icon, but this used to be able to slip through, so for
+ // those dirty apps give it the app's icon.
+ localForegroundNoti.icon = appInfo.icon;
+
+ // Do not allow apps to present a sneaky invisible content view either.
+ localForegroundNoti.contentView = null;
+ localForegroundNoti.bigContentView = null;
+ CharSequence appName = appInfo.loadLabel(
+ ams.mContext.getPackageManager());
+ if (appName == null) {
+ appName = appInfo.packageName;
+ }
+ Context ctx = null;
+ try {
+ ctx = ams.mContext.createPackageContext(
+ appInfo.packageName, 0);
+ Intent runningIntent = new Intent(
+ Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ runningIntent.setData(Uri.fromParts("package",
+ appInfo.packageName, null));
+ PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0,
+ runningIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ localForegroundNoti.setLatestEventInfo(ctx,
+ ams.mContext.getString(
+ com.android.internal.R.string
+ .app_running_notification_title,
+ appName),
+ ams.mContext.getString(
+ com.android.internal.R.string
+ .app_running_notification_text,
+ appName),
+ pi);
+ } catch (PackageManager.NameNotFoundException e) {
+ localForegroundNoti.icon = 0;
+ }
+ }
+ if (localForegroundNoti.icon == 0) {
+ // Notifications whose icon is 0 are defined to not show
+ // a notification, silently ignoring it. We don't want to
+ // just ignore it, we want to prevent the service from
+ // being foreground.
+ throw new RuntimeException("icon must be non-zero");
+ }
int[] outId = new int[1];
- nm.enqueueNotificationInternal(localPackageName, appUid, appPid,
- null, localForegroundId, localForegroundNoti, outId, userId);
+ nm.enqueueNotificationInternal(localPackageName, localPackageName,
+ appUid, appPid, null, localForegroundId, localForegroundNoti,
+ outId, userId);
} catch (RuntimeException e) {
Slog.w(ActivityManagerService.TAG,
"Error showing notification for service", e);
diff --git a/services/java/com/android/server/connectivity/Nat464Xlat.java b/services/java/com/android/server/connectivity/Nat464Xlat.java
new file mode 100644
index 0000000..59403c5
--- /dev/null
+++ b/services/java/com/android/server/connectivity/Nat464Xlat.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2012 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.connectivity;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+
+import java.net.Inet4Address;
+
+import android.content.Context;
+import android.net.IConnectivityManager;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkStateTracker;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.net.BaseNetworkObserver;
+
+/**
+ * @hide
+ *
+ * Class to manage a 464xlat CLAT daemon.
+ */
+public class Nat464Xlat extends BaseNetworkObserver {
+ private Context mContext;
+ private INetworkManagementService mNMService;
+ private IConnectivityManager mConnService;
+ private NetworkStateTracker mTracker;
+ private Handler mHandler;
+
+ // Whether we started clatd and expect it to be running.
+ private boolean mIsStarted;
+ // Whether the clatd interface exists (i.e., clatd is running).
+ private boolean mIsRunning;
+ // The LinkProperties of the clat interface.
+ private LinkProperties mLP;
+
+ // This must match the interface name in clatd.conf.
+ private static final String CLAT_INTERFACE_NAME = "clat4";
+
+ private static final String TAG = "Nat464Xlat";
+
+ public Nat464Xlat(Context context, INetworkManagementService nmService,
+ IConnectivityManager connService, Handler handler) {
+ mContext = context;
+ mNMService = nmService;
+ mConnService = connService;
+ mHandler = handler;
+
+ mIsStarted = false;
+ mIsRunning = false;
+ mLP = new LinkProperties();
+ }
+
+ /**
+ * Determines whether an interface requires clat.
+ * @param netType the network type (one of the
+ * android.net.ConnectivityManager.TYPE_* constants)
+ * @param tracker the NetworkStateTracker corresponding to the network type.
+ * @return true if the interface requires clat, false otherwise.
+ */
+ public boolean requiresClat(int netType, NetworkStateTracker tracker) {
+ LinkProperties lp = tracker.getLinkProperties();
+ // Only support clat on mobile for now.
+ Slog.d(TAG, "requiresClat: netType=" + netType + ", hasIPv4Address=" +
+ lp.hasIPv4Address());
+ return netType == TYPE_MOBILE && !lp.hasIPv4Address();
+ }
+
+ public static boolean isRunningClat(LinkProperties lp) {
+ return lp != null && lp.getAllInterfaceNames().contains(CLAT_INTERFACE_NAME);
+ }
+
+ /**
+ * Starts the clat daemon.
+ * @param lp The link properties of the interface to start clatd on.
+ */
+ public void startClat(NetworkStateTracker tracker) {
+ if (mIsStarted) {
+ Slog.e(TAG, "startClat: already started");
+ return;
+ }
+ mTracker = tracker;
+ LinkProperties lp = mTracker.getLinkProperties();
+ String iface = lp.getInterfaceName();
+ Slog.i(TAG, "Starting clatd on " + iface + ", lp=" + lp);
+ try {
+ mNMService.startClatd(iface);
+ } catch(RemoteException e) {
+ Slog.e(TAG, "Error starting clat daemon: " + e);
+ }
+ mIsStarted = true;
+ }
+
+ /**
+ * Stops the clat daemon.
+ */
+ public void stopClat() {
+ if (mIsStarted) {
+ Slog.i(TAG, "Stopping clatd");
+ try {
+ mNMService.stopClatd();
+ } catch(RemoteException e) {
+ Slog.e(TAG, "Error stopping clat daemon: " + e);
+ }
+ mIsStarted = false;
+ mIsRunning = false;
+ mTracker = null;
+ mLP.clear();
+ } else {
+ Slog.e(TAG, "stopClat: already stopped");
+ }
+ }
+
+ public boolean isStarted() {
+ return mIsStarted;
+ }
+
+ public boolean isRunning() {
+ return mIsRunning;
+ }
+
+ @Override
+ public void interfaceAdded(String iface) {
+ if (iface.equals(CLAT_INTERFACE_NAME)) {
+ Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
+ " added, mIsRunning = " + mIsRunning + " -> true");
+ mIsRunning = true;
+
+ // Get the network configuration of the clat interface, store it
+ // in our link properties, and stack it on top of the interface
+ // it's running on.
+ try {
+ InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
+ mLP.clear();
+ mLP.setInterfaceName(iface);
+ RouteInfo ipv4Default = new RouteInfo(new LinkAddress(Inet4Address.ANY, 0), null,
+ iface);
+ mLP.addRoute(ipv4Default);
+ mLP.addLinkAddress(config.getLinkAddress());
+ mTracker.addStackedLink(mLP);
+ Slog.i(TAG, "Adding stacked link. tracker LP: " +
+ mTracker.getLinkProperties());
+ } catch(RemoteException e) {
+ Slog.e(TAG, "Error getting link properties: " + e);
+ }
+
+ // Inform ConnectivityService that things have changed.
+ Message msg = mHandler.obtainMessage(
+ NetworkStateTracker.EVENT_CONFIGURATION_CHANGED,
+ mTracker.getNetworkInfo());
+ Slog.i(TAG, "sending message to ConnectivityService: " + msg);
+ msg.sendToTarget();
+ }
+ }
+
+ @Override
+ public void interfaceRemoved(String iface) {
+ if (iface == CLAT_INTERFACE_NAME) {
+ if (mIsRunning) {
+ NetworkUtils.resetConnections(
+ CLAT_INTERFACE_NAME,
+ NetworkUtils.RESET_IPV4_ADDRESSES);
+ }
+ Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
+ " removed, mIsRunning = " + mIsRunning + " -> false");
+ mIsRunning = false;
+ mTracker.removeStackedLink(mLP);
+ mLP.clear();
+ Slog.i(TAG, "mLP = " + mLP);
+ }
+ }
+};
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index e4a7ead..32f39b7 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -35,6 +35,7 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.NetworkUtils;
+import android.net.RouteInfo;
import android.os.Binder;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -1345,7 +1346,21 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
linkProperties = mConnService.getLinkProperties(upType);
} catch (RemoteException e) { }
if (linkProperties != null) {
- iface = linkProperties.getInterfaceName();
+ // Find the interface with the default IPv4 route. It may be the
+ // interface described by linkProperties, or one of the interfaces
+ // stacked on top of it.
+ Log.i(TAG, "Finding IPv4 upstream interface on: " + linkProperties);
+ RouteInfo ipv4Default = RouteInfo.selectBestRoute(
+ linkProperties.getAllRoutes(), Inet4Address.ANY);
+ if (ipv4Default != null) {
+ iface = ipv4Default.getInterface();
+ Log.i(TAG, "Found interface " + ipv4Default.getInterface());
+ } else {
+ Log.i(TAG, "No IPv4 upstream interface, giving up.");
+ }
+ }
+
+ if (iface != null) {
String[] dnsServers = mDefaultDnsServers;
Collection<InetAddress> dnses = linkProperties.getDnses();
if (dnses != null) {
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index bb7334a..e7d1fa4 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -21,9 +21,11 @@ import static android.Manifest.permission.BIND_VPN_SERVICE;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -33,6 +35,7 @@ import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.BaseNetworkStateTracker;
import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.LinkProperties;
import android.net.LocalSocket;
@@ -71,6 +74,7 @@ import java.net.Inet4Address;
import java.net.InetAddress;
import java.nio.charset.Charsets;
import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
import libcore.io.IoUtils;
@@ -91,13 +95,17 @@ public class Vpn extends BaseNetworkStateTracker {
private Connection mConnection;
private LegacyVpnRunner mLegacyVpnRunner;
private PendingIntent mStatusIntent;
- private boolean mEnableNotif = true;
+ private volatile boolean mEnableNotif = true;
+ private volatile boolean mEnableTeardown = true;
+ private final IConnectivityManager mConnService;
- public Vpn(Context context, VpnCallback callback, INetworkManagementService netService) {
+ public Vpn(Context context, VpnCallback callback, INetworkManagementService netService,
+ IConnectivityManager connService) {
// TODO: create dedicated TYPE_VPN network type
super(ConnectivityManager.TYPE_DUMMY);
mContext = context;
mCallback = callback;
+ mConnService = connService;
try {
netService.registerObserver(mObserver);
@@ -106,10 +114,23 @@ public class Vpn extends BaseNetworkStateTracker {
}
}
+ /**
+ * Set if this object is responsible for showing its own notifications. When
+ * {@code false}, notifications are handled externally by someone else.
+ */
public void setEnableNotifications(boolean enableNotif) {
mEnableNotif = enableNotif;
}
+ /**
+ * Set if this object is responsible for watching for {@link NetworkInfo}
+ * teardown. When {@code false}, teardown is handled externally by someone
+ * else.
+ */
+ public void setEnableTeardown(boolean enableTeardown) {
+ mEnableTeardown = enableTeardown;
+ }
+
@Override
protected void startMonitoringInternal() {
// Ignored; events are sent through callbacks for now
@@ -462,6 +483,7 @@ public class Vpn extends BaseNetworkStateTracker {
* secondary thread to perform connection work, returning quickly.
*/
public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
+ enforceControlPermission();
if (!keyStore.isUnlocked()) {
throw new IllegalStateException("KeyStore isn't unlocked");
}
@@ -561,7 +583,6 @@ public class Vpn extends BaseNetworkStateTracker {
if (!profile.searchDomains.isEmpty()) {
config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
}
-
startLegacyVpn(config, racoon, mtpd);
}
@@ -629,9 +650,34 @@ public class Vpn extends BaseNetworkStateTracker {
private final String[][] mArguments;
private final LocalSocket[] mSockets;
private final String mOuterInterface;
+ private final AtomicInteger mOuterConnection =
+ new AtomicInteger(ConnectivityManager.TYPE_NONE);
private long mTimer = -1;
+ /**
+ * Watch for the outer connection (passing in the constructor) going away.
+ */
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!mEnableTeardown) return;
+
+ if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
+ ConnectivityManager.TYPE_NONE) == mOuterConnection.get()) {
+ NetworkInfo info = (NetworkInfo)intent.getExtra(
+ ConnectivityManager.EXTRA_NETWORK_INFO);
+ if (info != null && !info.isConnectedOrConnecting()) {
+ try {
+ mObserver.interfaceStatusChanged(mOuterInterface, false);
+ } catch (RemoteException e) {}
+ }
+ }
+ }
+ }
+ };
+
public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
super(TAG);
mConfig = config;
@@ -643,7 +689,21 @@ public class Vpn extends BaseNetworkStateTracker {
// This is the interface which VPN is running on,
// mConfig.interfaze will change to point to OUR
// internal interface soon. TODO - add inner/outer to mconfig
+ // TODO - we have a race - if the outer iface goes away/disconnects before we hit this
+ // we will leave the VPN up. We should check that it's still there/connected after
+ // registering
mOuterInterface = mConfig.interfaze;
+
+ try {
+ mOuterConnection.set(
+ mConnService.findConnectionTypeForIface(mOuterInterface));
+ } catch (Exception e) {
+ mOuterConnection.set(ConnectivityManager.TYPE_NONE);
+ }
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ mContext.registerReceiver(mBroadcastReceiver, filter);
}
public void check(String interfaze) {
@@ -660,6 +720,9 @@ public class Vpn extends BaseNetworkStateTracker {
IoUtils.closeQuietly(socket);
}
updateState(DetailedState.DISCONNECTED, "exit");
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException e) {}
}
@Override
diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java
new file mode 100644
index 0000000..f82cf01
--- /dev/null
+++ b/services/java/com/android/server/content/ContentService.java
@@ -0,0 +1,855 @@
+/*
+ * Copyright (C) 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
+ *
+ * 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.content;
+
+import android.Manifest;
+import android.accounts.Account;
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentService;
+import android.content.ISyncStatusObserver;
+import android.content.PeriodicSync;
+import android.content.SyncAdapterType;
+import android.content.SyncInfo;
+import android.content.SyncStatusInfo;
+import android.database.IContentObserver;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseIntArray;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+public final class ContentService extends IContentService.Stub {
+ private static final String TAG = "ContentService";
+ private Context mContext;
+ private boolean mFactoryTest;
+ private final ObserverNode mRootNode = new ObserverNode("");
+ private SyncManager mSyncManager = null;
+ private final Object mSyncManagerLock = new Object();
+
+ private SyncManager getSyncManager() {
+ synchronized(mSyncManagerLock) {
+ try {
+ // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
+ if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Can't create SyncManager", e);
+ }
+ return mSyncManager;
+ }
+ }
+
+ @Override
+ protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP,
+ "caller doesn't have the DUMP permission");
+
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ long identityToken = clearCallingIdentity();
+ try {
+ if (mSyncManager == null) {
+ pw.println("No SyncManager created! (Disk full?)");
+ } else {
+ mSyncManager.dump(fd, pw);
+ }
+ pw.println();
+ pw.println("Observer tree:");
+ synchronized (mRootNode) {
+ int[] counts = new int[2];
+ final SparseIntArray pidCounts = new SparseIntArray();
+ mRootNode.dumpLocked(fd, pw, args, "", " ", counts, pidCounts);
+ pw.println();
+ ArrayList<Integer> sorted = new ArrayList<Integer>();
+ for (int i=0; i<pidCounts.size(); i++) {
+ sorted.add(pidCounts.keyAt(i));
+ }
+ Collections.sort(sorted, new Comparator<Integer>() {
+ @Override
+ public int compare(Integer lhs, Integer rhs) {
+ int lc = pidCounts.get(lhs);
+ int rc = pidCounts.get(rhs);
+ if (lc < rc) {
+ return 1;
+ } else if (lc > rc) {
+ return -1;
+ }
+ return 0;
+ }
+
+ });
+ for (int i=0; i<sorted.size(); i++) {
+ int pid = sorted.get(i);
+ pw.print(" pid "); pw.print(pid); pw.print(": ");
+ pw.print(pidCounts.get(pid)); pw.println(" observers");
+ }
+ pw.println();
+ pw.print(" Total number of nodes: "); pw.println(counts[0]);
+ pw.print(" Total number of observers: "); pw.println(counts[1]);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ // The content service only throws security exceptions, so let's
+ // log all others.
+ if (!(e instanceof SecurityException)) {
+ Log.e(TAG, "Content Service Crash", e);
+ }
+ throw e;
+ }
+ }
+
+ /*package*/ ContentService(Context context, boolean factoryTest) {
+ mContext = context;
+ mFactoryTest = factoryTest;
+ }
+
+ public void systemReady() {
+ getSyncManager();
+ }
+
+ /**
+ * Register a content observer tied to a specific user's view of the provider.
+ * @param userHandle the user whose view of the provider is to be observed. May be
+ * the calling user without requiring any permission, otherwise the caller needs to
+ * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and
+ * USER_CURRENT are properly handled; all other pseudousers are forbidden.
+ */
+ @Override
+ public void registerContentObserver(Uri uri, boolean notifyForDescendants,
+ IContentObserver observer, int userHandle) {
+ if (observer == null || uri == null) {
+ throw new IllegalArgumentException("You must pass a valid uri and observer");
+ }
+
+ final int callingUser = UserHandle.getCallingUserId();
+ if (callingUser != userHandle) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "no permission to observe other users' provider view");
+ }
+
+ if (userHandle < 0) {
+ if (userHandle == UserHandle.USER_CURRENT) {
+ userHandle = ActivityManager.getCurrentUser();
+ } else if (userHandle != UserHandle.USER_ALL) {
+ throw new InvalidParameterException("Bad user handle for registerContentObserver: "
+ + userHandle);
+ }
+ }
+
+ synchronized (mRootNode) {
+ mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
+ Binder.getCallingUid(), Binder.getCallingPid(), userHandle);
+ if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
+ " with notifyForDescendants " + notifyForDescendants);
+ }
+ }
+
+ public void registerContentObserver(Uri uri, boolean notifyForDescendants,
+ IContentObserver observer) {
+ registerContentObserver(uri, notifyForDescendants, observer,
+ UserHandle.getCallingUserId());
+ }
+
+ public void unregisterContentObserver(IContentObserver observer) {
+ if (observer == null) {
+ throw new IllegalArgumentException("You must pass a valid observer");
+ }
+ synchronized (mRootNode) {
+ mRootNode.removeObserverLocked(observer);
+ if (false) Log.v(TAG, "Unregistered observer " + observer);
+ }
+ }
+
+ /**
+ * Notify observers of a particular user's view of the provider.
+ * @param userHandle the user whose view of the provider is to be notified. May be
+ * the calling user without requiring any permission, otherwise the caller needs to
+ * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and
+ * USER_CURRENT are properly interpreted; no other pseudousers are allowed.
+ */
+ @Override
+ public void notifyChange(Uri uri, IContentObserver observer,
+ boolean observerWantsSelfNotifications, boolean syncToNetwork,
+ int userHandle) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle
+ + " from observer " + observer + ", syncToNetwork " + syncToNetwork);
+ }
+
+ // Notify for any user other than the caller's own requires permission.
+ final int callingUserHandle = UserHandle.getCallingUserId();
+ if (userHandle != callingUserHandle) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "no permission to notify other users");
+ }
+
+ // We passed the permission check; resolve pseudouser targets as appropriate
+ if (userHandle < 0) {
+ if (userHandle == UserHandle.USER_CURRENT) {
+ userHandle = ActivityManager.getCurrentUser();
+ } else if (userHandle != UserHandle.USER_ALL) {
+ throw new InvalidParameterException("Bad user handle for notifyChange: "
+ + userHandle);
+ }
+ }
+
+ final int uid = Binder.getCallingUid();
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ long identityToken = clearCallingIdentity();
+ try {
+ ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
+ synchronized (mRootNode) {
+ mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
+ userHandle, calls);
+ }
+ final int numCalls = calls.size();
+ for (int i=0; i<numCalls; i++) {
+ ObserverCall oc = calls.get(i);
+ try {
+ oc.mObserver.onChange(oc.mSelfChange, uri);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
+ }
+ } catch (RemoteException ex) {
+ synchronized (mRootNode) {
+ Log.w(TAG, "Found dead observer, removing");
+ IBinder binder = oc.mObserver.asBinder();
+ final ArrayList<ObserverNode.ObserverEntry> list
+ = oc.mNode.mObservers;
+ int numList = list.size();
+ for (int j=0; j<numList; j++) {
+ ObserverNode.ObserverEntry oe = list.get(j);
+ if (oe.observer.asBinder() == binder) {
+ list.remove(j);
+ j--;
+ numList--;
+ }
+ }
+ }
+ }
+ }
+ if (syncToNetwork) {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,
+ uri.getAuthority());
+ }
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void notifyChange(Uri uri, IContentObserver observer,
+ boolean observerWantsSelfNotifications, boolean syncToNetwork) {
+ notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork,
+ UserHandle.getCallingUserId());
+ }
+
+ /**
+ * Hide this class since it is not part of api,
+ * but current unittest framework requires it to be public
+ * @hide
+ *
+ */
+ public static final class ObserverCall {
+ final ObserverNode mNode;
+ final IContentObserver mObserver;
+ final boolean mSelfChange;
+
+ ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) {
+ mNode = node;
+ mObserver = observer;
+ mSelfChange = selfChange;
+ }
+ }
+
+ public void requestSync(Account account, String authority, Bundle extras) {
+ ContentResolver.validateSyncExtrasBundle(extras);
+ int userId = UserHandle.getCallingUserId();
+ int uId = Binder.getCallingUid();
+
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ syncManager.scheduleSync(account, userId, uId, authority, extras, 0 /* no delay */,
+ false /* onlyThoseWithUnkownSyncableState */);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /**
+ * Clear all scheduled sync operations that match the uri and cancel the active sync
+ * if they match the authority and account, if they are present.
+ * @param account filter the pending and active syncs to cancel using this account
+ * @param authority filter the pending and active syncs to cancel using this authority
+ */
+ public void cancelSync(Account account, String authority) {
+ int userId = UserHandle.getCallingUserId();
+
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ syncManager.clearScheduledSyncOperations(account, userId, authority);
+ syncManager.cancelActiveSync(account, userId, authority);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /**
+ * Get information about the SyncAdapters that are known to the system.
+ * @return an array of SyncAdapters that have registered with the system
+ */
+ public SyncAdapterType[] getSyncAdapterTypes() {
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ final int userId = UserHandle.getCallingUserId();
+ final long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ return syncManager.getSyncAdapterTypes(userId);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public boolean getSyncAutomatically(Account account, String providerName) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+ "no permission to read the sync settings");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ return syncManager.getSyncStorageEngine().getSyncAutomatically(
+ account, userId, providerName);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return false;
+ }
+
+ public void setSyncAutomatically(Account account, String providerName, boolean sync) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ syncManager.getSyncStorageEngine().setSyncAutomatically(
+ account, userId, providerName, sync);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void addPeriodicSync(Account account, String authority, Bundle extras,
+ long pollFrequency) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ int userId = UserHandle.getCallingUserId();
+
+ if (pollFrequency < 60) {
+ Slog.w(TAG, "Requested poll frequency of " + pollFrequency
+ + " seconds being rounded up to 60 seconds.");
+ pollFrequency = 60;
+ }
+
+ long identityToken = clearCallingIdentity();
+ try {
+ getSyncManager().getSyncStorageEngine().addPeriodicSync(
+ account, userId, authority, extras, pollFrequency);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void removePeriodicSync(Account account, String authority, Bundle extras) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority,
+ extras);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+ "no permission to read the sync settings");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
+ account, userId, providerName);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public int getIsSyncable(Account account, String providerName) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+ "no permission to read the sync settings");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ return syncManager.getIsSyncable(
+ account, userId, providerName);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return -1;
+ }
+
+ public void setIsSyncable(Account account, String providerName, int syncable) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ syncManager.getSyncStorageEngine().setIsSyncable(
+ account, userId, providerName, syncable);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public boolean getMasterSyncAutomatically() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+ "no permission to read the sync settings");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return false;
+ }
+
+ public void setMasterSyncAutomatically(boolean flag) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public boolean isSyncActive(Account account, String authority) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+ "no permission to read the sync stats");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ return syncManager.getSyncStorageEngine().isSyncActive(
+ account, userId, authority);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return false;
+ }
+
+ public List<SyncInfo> getCurrentSyncs() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+ "no permission to read the sync stats");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public SyncStatusInfo getSyncStatus(Account account, String authority) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+ "no permission to read the sync stats");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
+ account, userId, authority);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return null;
+ }
+
+ public boolean isSyncPending(Account account, String authority) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+ "no permission to read the sync stats");
+ int userId = UserHandle.getCallingUserId();
+
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ return false;
+ }
+
+ public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null && callback != null) {
+ syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void removeStatusChangeListener(ISyncStatusObserver callback) {
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null && callback != null) {
+ syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public static ContentService main(Context context, boolean factoryTest) {
+ ContentService service = new ContentService(context, factoryTest);
+ ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
+ return service;
+ }
+
+ /**
+ * Hide this class since it is not part of api,
+ * but current unittest framework requires it to be public
+ * @hide
+ */
+ public static final class ObserverNode {
+ private class ObserverEntry implements IBinder.DeathRecipient {
+ public final IContentObserver observer;
+ public final int uid;
+ public final int pid;
+ public final boolean notifyForDescendants;
+ private final int userHandle;
+ private final Object observersLock;
+
+ public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
+ int _uid, int _pid, int _userHandle) {
+ this.observersLock = observersLock;
+ observer = o;
+ uid = _uid;
+ pid = _pid;
+ userHandle = _userHandle;
+ notifyForDescendants = n;
+ try {
+ observer.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+
+ public void binderDied() {
+ synchronized (observersLock) {
+ removeObserverLocked(observer);
+ }
+ }
+
+ public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ String name, String prefix, SparseIntArray pidCounts) {
+ pidCounts.put(pid, pidCounts.get(pid)+1);
+ pw.print(prefix); pw.print(name); pw.print(": pid=");
+ pw.print(pid); pw.print(" uid=");
+ pw.print(uid); pw.print(" user=");
+ pw.print(userHandle); pw.print(" target=");
+ pw.println(Integer.toHexString(System.identityHashCode(
+ observer != null ? observer.asBinder() : null)));
+ }
+ }
+
+ public static final int INSERT_TYPE = 0;
+ public static final int UPDATE_TYPE = 1;
+ public static final int DELETE_TYPE = 2;
+
+ private String mName;
+ private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
+ private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
+
+ public ObserverNode(String name) {
+ mName = name;
+ }
+
+ public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+ String name, String prefix, int[] counts, SparseIntArray pidCounts) {
+ String innerName = null;
+ if (mObservers.size() > 0) {
+ if ("".equals(name)) {
+ innerName = mName;
+ } else {
+ innerName = name + "/" + mName;
+ }
+ for (int i=0; i<mObservers.size(); i++) {
+ counts[1]++;
+ mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix,
+ pidCounts);
+ }
+ }
+ if (mChildren.size() > 0) {
+ if (innerName == null) {
+ if ("".equals(name)) {
+ innerName = mName;
+ } else {
+ innerName = name + "/" + mName;
+ }
+ }
+ for (int i=0; i<mChildren.size(); i++) {
+ counts[0]++;
+ mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix,
+ counts, pidCounts);
+ }
+ }
+ }
+
+ private String getUriSegment(Uri uri, int index) {
+ if (uri != null) {
+ if (index == 0) {
+ return uri.getAuthority();
+ } else {
+ return uri.getPathSegments().get(index - 1);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ private int countUriSegments(Uri uri) {
+ if (uri == null) {
+ return 0;
+ }
+ return uri.getPathSegments().size() + 1;
+ }
+
+ // Invariant: userHandle is either a hard user number or is USER_ALL
+ public void addObserverLocked(Uri uri, IContentObserver observer,
+ boolean notifyForDescendants, Object observersLock,
+ int uid, int pid, int userHandle) {
+ addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
+ uid, pid, userHandle);
+ }
+
+ private void addObserverLocked(Uri uri, int index, IContentObserver observer,
+ boolean notifyForDescendants, Object observersLock,
+ int uid, int pid, int userHandle) {
+ // If this is the leaf node add the observer
+ if (index == countUriSegments(uri)) {
+ mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
+ uid, pid, userHandle));
+ return;
+ }
+
+ // Look to see if the proper child already exists
+ String segment = getUriSegment(uri, index);
+ if (segment == null) {
+ throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
+ }
+ int N = mChildren.size();
+ for (int i = 0; i < N; i++) {
+ ObserverNode node = mChildren.get(i);
+ if (node.mName.equals(segment)) {
+ node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
+ observersLock, uid, pid, userHandle);
+ return;
+ }
+ }
+
+ // No child found, create one
+ ObserverNode node = new ObserverNode(segment);
+ mChildren.add(node);
+ node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
+ observersLock, uid, pid, userHandle);
+ }
+
+ public boolean removeObserverLocked(IContentObserver observer) {
+ int size = mChildren.size();
+ for (int i = 0; i < size; i++) {
+ boolean empty = mChildren.get(i).removeObserverLocked(observer);
+ if (empty) {
+ mChildren.remove(i);
+ i--;
+ size--;
+ }
+ }
+
+ IBinder observerBinder = observer.asBinder();
+ size = mObservers.size();
+ for (int i = 0; i < size; i++) {
+ ObserverEntry entry = mObservers.get(i);
+ if (entry.observer.asBinder() == observerBinder) {
+ mObservers.remove(i);
+ // We no longer need to listen for death notifications. Remove it.
+ observerBinder.unlinkToDeath(entry, 0);
+ break;
+ }
+ }
+
+ if (mChildren.size() == 0 && mObservers.size() == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
+ boolean observerWantsSelfNotifications, int targetUserHandle,
+ ArrayList<ObserverCall> calls) {
+ int N = mObservers.size();
+ IBinder observerBinder = observer == null ? null : observer.asBinder();
+ for (int i = 0; i < N; i++) {
+ ObserverEntry entry = mObservers.get(i);
+
+ // Don't notify the observer if it sent the notification and isn't interested
+ // in self notifications
+ boolean selfChange = (entry.observer.asBinder() == observerBinder);
+ if (selfChange && !observerWantsSelfNotifications) {
+ continue;
+ }
+
+ // Does this observer match the target user?
+ if (targetUserHandle == UserHandle.USER_ALL
+ || entry.userHandle == UserHandle.USER_ALL
+ || targetUserHandle == entry.userHandle) {
+ // Make sure the observer is interested in the notification
+ if (leaf || (!leaf && entry.notifyForDescendants)) {
+ calls.add(new ObserverCall(this, entry.observer, selfChange));
+ }
+ }
+ }
+ }
+
+ /**
+ * targetUserHandle is either a hard user handle or is USER_ALL
+ */
+ public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
+ boolean observerWantsSelfNotifications, int targetUserHandle,
+ ArrayList<ObserverCall> calls) {
+ String segment = null;
+ int segmentCount = countUriSegments(uri);
+ if (index >= segmentCount) {
+ // This is the leaf node, notify all observers
+ collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
+ targetUserHandle, calls);
+ } else if (index < segmentCount){
+ segment = getUriSegment(uri, index);
+ // Notify any observers at this level who are interested in descendants
+ collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
+ targetUserHandle, calls);
+ }
+
+ int N = mChildren.size();
+ for (int i = 0; i < N; i++) {
+ ObserverNode node = mChildren.get(i);
+ if (segment == null || node.mName.equals(segment)) {
+ // We found the child,
+ node.collectObserversLocked(uri, index + 1,
+ observer, observerWantsSelfNotifications, targetUserHandle, calls);
+ if (segment != null) {
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java
new file mode 100644
index 0000000..ff1281e
--- /dev/null
+++ b/services/java/com/android/server/content/SyncManager.java
@@ -0,0 +1,2822 @@
+/*
+ * Copyright (C) 2008 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.content;
+
+import android.accounts.Account;
+import android.accounts.AccountAndUser;
+import android.accounts.AccountManager;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ISyncAdapter;
+import android.content.ISyncContext;
+import android.content.ISyncStatusObserver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.SyncActivityTooManyDeletes;
+import android.content.SyncAdapterType;
+import android.content.SyncAdaptersCache;
+import android.content.SyncInfo;
+import android.content.SyncResult;
+import android.content.SyncStatusInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.RegisteredServicesCache;
+import android.content.pm.RegisteredServicesCacheListener;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.accounts.AccountManagerService;
+import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+
+import java.io.FileDescriptor;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * @hide
+ */
+public class SyncManager {
+ private static final String TAG = "SyncManager";
+
+ /** Delay a sync due to local changes this long. In milliseconds */
+ private static final long LOCAL_SYNC_DELAY;
+
+ /**
+ * If a sync takes longer than this and the sync queue is not empty then we will
+ * cancel it and add it back to the end of the sync queue. In milliseconds.
+ */
+ private static final long MAX_TIME_PER_SYNC;
+
+ static {
+ final boolean isLargeRAM = ActivityManager.isLargeRAM();
+ int defaultMaxInitSyncs = isLargeRAM ? 5 : 2;
+ int defaultMaxRegularSyncs = isLargeRAM ? 2 : 1;
+ MAX_SIMULTANEOUS_INITIALIZATION_SYNCS =
+ SystemProperties.getInt("sync.max_init_syncs", defaultMaxInitSyncs);
+ MAX_SIMULTANEOUS_REGULAR_SYNCS =
+ SystemProperties.getInt("sync.max_regular_syncs", defaultMaxRegularSyncs);
+ LOCAL_SYNC_DELAY =
+ SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */);
+ MAX_TIME_PER_SYNC =
+ SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */);
+ SYNC_NOTIFICATION_DELAY =
+ SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */);
+ }
+
+ private static final long SYNC_NOTIFICATION_DELAY;
+
+ /**
+ * When retrying a sync for the first time use this delay. After that
+ * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
+ * In milliseconds.
+ */
+ private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
+
+ /**
+ * Default the max sync retry time to this value.
+ */
+ private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
+
+ /**
+ * How long to wait before retrying a sync that failed due to one already being in progress.
+ */
+ private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
+
+ private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
+
+ private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
+ private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
+ private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
+
+ private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS;
+ private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS;
+
+ private Context mContext;
+
+ private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0];
+
+ // TODO: add better locking around mRunningAccounts
+ private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
+
+ volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
+ volatile private PowerManager.WakeLock mSyncManagerWakeLock;
+ volatile private boolean mDataConnectionIsConnected = false;
+ volatile private boolean mStorageIsLow = false;
+
+ private final NotificationManager mNotificationMgr;
+ private AlarmManager mAlarmService = null;
+
+ private SyncStorageEngine mSyncStorageEngine;
+
+ @GuardedBy("mSyncQueue")
+ private final SyncQueue mSyncQueue;
+
+ protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
+
+ // set if the sync active indicator should be reported
+ private boolean mNeedSyncActiveNotification = false;
+
+ private final PendingIntent mSyncAlarmIntent;
+ // Synchronized on "this". Instead of using this directly one should instead call
+ // its accessor, getConnManager().
+ private ConnectivityManager mConnManagerDoNotUseDirectly;
+
+ protected SyncAdaptersCache mSyncAdapters;
+
+ private BroadcastReceiver mStorageIntentReceiver =
+ new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Internal storage is low.");
+ }
+ mStorageIsLow = true;
+ cancelActiveSync(null /* any account */, UserHandle.USER_ALL,
+ null /* any authority */);
+ } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Internal storage is ok.");
+ }
+ mStorageIsLow = false;
+ sendCheckAlarmsMessage();
+ }
+ }
+ };
+
+ private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ mSyncHandler.onBootCompleted();
+ }
+ };
+
+ private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ if (getConnectivityManager().getBackgroundDataSetting()) {
+ scheduleSync(null /* account */, UserHandle.USER_ALL,
+ SyncOperation.REASON_BACKGROUND_DATA_SETTINGS_CHANGED,
+ null /* authority */,
+ new Bundle(), 0 /* delay */,
+ false /* onlyThoseWithUnknownSyncableState */);
+ }
+ }
+ };
+
+ private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ updateRunningAccounts();
+
+ // Kick off sync for everyone, since this was a radical account change
+ scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_ACCOUNTS_UPDATED, null,
+ null, 0 /* no delay */, false);
+ }
+ };
+
+ private final PowerManager mPowerManager;
+
+ // Use this as a random offset to seed all periodic syncs
+ private int mSyncRandomOffsetMillis;
+
+ private final UserManager mUserManager;
+
+ private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
+ private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
+
+ private List<UserInfo> getAllUsers() {
+ return mUserManager.getUsers();
+ }
+
+ private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) {
+ boolean found = false;
+ for (int i = 0; i < accounts.length; i++) {
+ if (accounts[i].userId == userId
+ && accounts[i].account.equals(account)) {
+ found = true;
+ break;
+ }
+ }
+ return found;
+ }
+
+ public void updateRunningAccounts() {
+ mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
+
+ if (mBootCompleted) {
+ doDatabaseCleanup();
+ }
+
+ for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+ if (!containsAccountAndUser(mRunningAccounts,
+ currentSyncContext.mSyncOperation.account,
+ currentSyncContext.mSyncOperation.userId)) {
+ Log.d(TAG, "canceling sync since the account is no longer running");
+ sendSyncFinishedOrCanceledMessage(currentSyncContext,
+ null /* no result since this is a cancel */);
+ }
+ }
+
+ // we must do this since we don't bother scheduling alarms when
+ // the accounts are not set yet
+ sendCheckAlarmsMessage();
+ }
+
+ private void doDatabaseCleanup() {
+ for (UserInfo user : mUserManager.getUsers(true)) {
+ // Skip any partially created/removed users
+ if (user.partial) continue;
+ Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id);
+ mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
+ }
+ }
+
+ private BroadcastReceiver mConnectivityIntentReceiver =
+ new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ final boolean wasConnected = mDataConnectionIsConnected;
+
+ // don't use the intent to figure out if network is connected, just check
+ // ConnectivityManager directly.
+ mDataConnectionIsConnected = readDataConnectionState();
+ if (mDataConnectionIsConnected) {
+ if (!wasConnected) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Reconnection detected: clearing all backoffs");
+ }
+ mSyncStorageEngine.clearAllBackoffs(mSyncQueue);
+ }
+ sendCheckAlarmsMessage();
+ }
+ }
+ };
+
+ private boolean readDataConnectionState() {
+ NetworkInfo networkInfo = getConnectivityManager().getActiveNetworkInfo();
+ return (networkInfo != null) && networkInfo.isConnected();
+ }
+
+ private BroadcastReceiver mShutdownIntentReceiver =
+ new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ Log.w(TAG, "Writing sync state before shutdown...");
+ getSyncStorageEngine().writeAllState();
+ }
+ };
+
+ private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId == UserHandle.USER_NULL) return;
+
+ if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ onUserRemoved(userId);
+ } else if (Intent.ACTION_USER_STARTING.equals(action)) {
+ onUserStarting(userId);
+ } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
+ onUserStopping(userId);
+ }
+ }
+ };
+
+ private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
+ private final SyncHandler mSyncHandler;
+
+ private volatile boolean mBootCompleted = false;
+
+ private ConnectivityManager getConnectivityManager() {
+ synchronized (this) {
+ if (mConnManagerDoNotUseDirectly == null) {
+ mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ }
+ return mConnManagerDoNotUseDirectly;
+ }
+ }
+
+ /**
+ * Should only be created after {@link ContentService#systemReady()} so that
+ * {@link PackageManager} is ready to query.
+ */
+ public SyncManager(Context context, boolean factoryTest) {
+ // Initialize the SyncStorageEngine first, before registering observers
+ // and creating threads and so on; it may fail if the disk is full.
+ mContext = context;
+
+ SyncStorageEngine.init(context);
+ mSyncStorageEngine = SyncStorageEngine.getSingleton();
+ mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
+ public void onSyncRequest(Account account, int userId, int reason, String authority,
+ Bundle extras) {
+ scheduleSync(account, userId, reason, authority, extras, 0, false);
+ }
+ });
+
+ mSyncAdapters = new SyncAdaptersCache(mContext);
+ mSyncQueue = new SyncQueue(mContext.getPackageManager(), mSyncStorageEngine, mSyncAdapters);
+
+ HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ syncThread.start();
+ mSyncHandler = new SyncHandler(syncThread.getLooper());
+
+ mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
+ @Override
+ public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) {
+ if (!removed) {
+ scheduleSync(null, UserHandle.USER_ALL,
+ SyncOperation.REASON_SERVICE_CHANGED,
+ type.authority, null, 0 /* no delay */,
+ false /* onlyThoseWithUnkownSyncableState */);
+ }
+ }
+ }, mSyncHandler);
+
+ mSyncAlarmIntent = PendingIntent.getBroadcast(
+ mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
+
+ IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+ context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
+
+ if (!factoryTest) {
+ intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+ context.registerReceiver(mBootCompletedReceiver, intentFilter);
+ }
+
+ intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
+ context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);
+
+ intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+ intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ context.registerReceiver(mStorageIntentReceiver, intentFilter);
+
+ intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+ intentFilter.setPriority(100);
+ context.registerReceiver(mShutdownIntentReceiver, intentFilter);
+
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+ intentFilter.addAction(Intent.ACTION_USER_STARTING);
+ intentFilter.addAction(Intent.ACTION_USER_STOPPING);
+ mContext.registerReceiverAsUser(
+ mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+
+ if (!factoryTest) {
+ mNotificationMgr = (NotificationManager)
+ context.getSystemService(Context.NOTIFICATION_SERVICE);
+ context.registerReceiver(new SyncAlarmIntentReceiver(),
+ new IntentFilter(ACTION_SYNC_ALARM));
+ } else {
+ mNotificationMgr = null;
+ }
+ mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+ // This WakeLock is used to ensure that we stay awake between the time that we receive
+ // a sync alarm notification and when we finish processing it. We need to do this
+ // because we don't do the work in the alarm handler, rather we do it in a message
+ // handler.
+ mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ HANDLE_SYNC_ALARM_WAKE_LOCK);
+ mHandleAlarmWakeLock.setReferenceCounted(false);
+
+ // This WakeLock is used to ensure that we stay awake while running the sync loop
+ // message handler. Normally we will hold a sync adapter wake lock while it is being
+ // synced but during the execution of the sync loop it might finish a sync for
+ // one sync adapter before starting the sync for the other sync adapter and we
+ // don't want the device to go to sleep during that window.
+ mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ SYNC_LOOP_WAKE_LOCK);
+ mSyncManagerWakeLock.setReferenceCounted(false);
+
+ mSyncStorageEngine.addStatusChangeListener(
+ ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
+ public void onStatusChanged(int which) {
+ // force the sync loop to run if the settings change
+ sendCheckAlarmsMessage();
+ }
+ });
+
+ if (!factoryTest) {
+ // Register for account list updates for all users
+ mContext.registerReceiverAsUser(mAccountsUpdatedReceiver,
+ UserHandle.ALL,
+ new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION),
+ null, null);
+ }
+
+ // Pick a random second in a day to seed all periodic syncs
+ mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
+ }
+
+ /**
+ * Return a random value v that satisfies minValue <= v < maxValue. The difference between
+ * maxValue and minValue must be less than Integer.MAX_VALUE.
+ */
+ private long jitterize(long minValue, long maxValue) {
+ Random random = new Random(SystemClock.elapsedRealtime());
+ long spread = maxValue - minValue;
+ if (spread > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("the difference between the maxValue and the "
+ + "minValue must be less than " + Integer.MAX_VALUE);
+ }
+ return minValue + random.nextInt((int)spread);
+ }
+
+ public SyncStorageEngine getSyncStorageEngine() {
+ return mSyncStorageEngine;
+ }
+
+ public int getIsSyncable(Account account, int userId, String providerName) {
+ int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName);
+ UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId);
+
+ // If it's not a restricted user, return isSyncable
+ if (userInfo == null || !userInfo.isRestricted()) return isSyncable;
+
+ // Else check if the sync adapter has opted-in or not
+ RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
+ mSyncAdapters.getServiceInfo(
+ SyncAdapterType.newKey(providerName, account.type), userId);
+ if (syncAdapterInfo == null) return isSyncable;
+
+ PackageInfo pInfo = null;
+ try {
+ pInfo = AppGlobals.getPackageManager().getPackageInfo(
+ syncAdapterInfo.componentName.getPackageName(), 0, userId);
+ if (pInfo == null) return isSyncable;
+ } catch (RemoteException re) {
+ // Shouldn't happen
+ return isSyncable;
+ }
+ if (pInfo.restrictedAccountType != null
+ && pInfo.restrictedAccountType.equals(account.type)) {
+ return isSyncable;
+ } else {
+ return 0;
+ }
+ }
+
+ private void ensureAlarmService() {
+ if (mAlarmService == null) {
+ mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ }
+ }
+
+ /**
+ * Initiate a sync. This can start a sync for all providers
+ * (pass null to url, set onlyTicklable to false), only those
+ * providers that are marked as ticklable (pass null to url,
+ * set onlyTicklable to true), or a specific provider (set url
+ * to the content url of the provider).
+ *
+ * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is
+ * true then initiate a sync that just checks for local changes to send
+ * to the server, otherwise initiate a sync that first gets any
+ * changes from the server before sending local changes back to
+ * the server.
+ *
+ * <p>If a specific provider is being synced (the url is non-null)
+ * then the extras can contain SyncAdapter-specific information
+ * to control what gets synced (e.g. which specific feed to sync).
+ *
+ * <p>You'll start getting callbacks after this.
+ *
+ * @param requestedAccount the account to sync, may be null to signify all accounts
+ * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
+ * then all users' accounts are considered.
+ * @param reason for sync request. If this is a positive integer, it is the Linux uid
+ * assigned to the process that requested the sync. If it's negative, the sync was requested by
+ * the SyncManager itself and could be one of the following:
+ * {@link SyncOperation#REASON_BACKGROUND_DATA_SETTINGS_CHANGED}
+ * {@link SyncOperation#REASON_ACCOUNTS_UPDATED}
+ * {@link SyncOperation#REASON_SERVICE_CHANGED}
+ * {@link SyncOperation#REASON_PERIODIC}
+ * {@link SyncOperation#REASON_IS_SYNCABLE}
+ * {@link SyncOperation#REASON_SYNC_AUTO}
+ * {@link SyncOperation#REASON_MASTER_SYNC_AUTO}
+ * {@link SyncOperation#REASON_USER_START}
+ * @param requestedAuthority the authority to sync, may be null to indicate all authorities
+ * @param extras a Map of SyncAdapter-specific information to control
+ * syncs of a specific provider. Can be null. Is ignored
+ * if the url is null.
+ * @param delay how many milliseconds in the future to wait before performing this
+ * @param onlyThoseWithUnkownSyncableState
+ */
+ public void scheduleSync(Account requestedAccount, int userId, int reason,
+ String requestedAuthority, Bundle extras, long delay,
+ boolean onlyThoseWithUnkownSyncableState) {
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+
+ final boolean backgroundDataUsageAllowed = !mBootCompleted ||
+ getConnectivityManager().getBackgroundDataSetting();
+
+ if (extras == null) extras = new Bundle();
+
+ Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+ if (expedited) {
+ delay = -1; // this means schedule at the front of the queue
+ }
+
+ AccountAndUser[] accounts;
+ if (requestedAccount != null && userId != UserHandle.USER_ALL) {
+ accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
+ } else {
+ // if the accounts aren't configured yet then we can't support an account-less
+ // sync request
+ accounts = mRunningAccounts;
+ if (accounts.length == 0) {
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync: no accounts configured, dropping");
+ }
+ return;
+ }
+ }
+
+ final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
+ final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+ if (manualSync) {
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ }
+ final boolean ignoreSettings =
+ extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
+
+ int source;
+ if (uploadOnly) {
+ source = SyncStorageEngine.SOURCE_LOCAL;
+ } else if (manualSync) {
+ source = SyncStorageEngine.SOURCE_USER;
+ } else if (requestedAuthority == null) {
+ source = SyncStorageEngine.SOURCE_POLL;
+ } else {
+ // this isn't strictly server, since arbitrary callers can (and do) request
+ // a non-forced two-way sync on a specific url
+ source = SyncStorageEngine.SOURCE_SERVER;
+ }
+
+ for (AccountAndUser account : accounts) {
+ // Compile a list of authorities that have sync adapters.
+ // For each authority sync each account that matches a sync adapter.
+ final HashSet<String> syncableAuthorities = new HashSet<String>();
+ for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
+ mSyncAdapters.getAllServices(account.userId)) {
+ syncableAuthorities.add(syncAdapter.type.authority);
+ }
+
+ // if the url was specified then replace the list of authorities
+ // with just this authority or clear it if this authority isn't
+ // syncable
+ if (requestedAuthority != null) {
+ final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
+ syncableAuthorities.clear();
+ if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
+ }
+
+ for (String authority : syncableAuthorities) {
+ int isSyncable = getIsSyncable(account.account, account.userId,
+ authority);
+ if (isSyncable == 0) {
+ continue;
+ }
+ final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+ syncAdapterInfo = mSyncAdapters.getServiceInfo(
+ SyncAdapterType.newKey(authority, account.account.type), account.userId);
+ if (syncAdapterInfo == null) {
+ continue;
+ }
+ final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
+ final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
+ if (isSyncable < 0 && isAlwaysSyncable) {
+ mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
+ isSyncable = 1;
+ }
+ if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
+ continue;
+ }
+ if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
+ continue;
+ }
+
+ // always allow if the isSyncable state is unknown
+ boolean syncAllowed =
+ (isSyncable < 0)
+ || ignoreSettings
+ || (backgroundDataUsageAllowed
+ && mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
+ && mSyncStorageEngine.getSyncAutomatically(account.account,
+ account.userId, authority));
+ if (!syncAllowed) {
+ if (isLoggable) {
+ Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
+ + " is not allowed, dropping request");
+ }
+ continue;
+ }
+
+ Pair<Long, Long> backoff = mSyncStorageEngine
+ .getBackoff(account.account, account.userId, authority);
+ long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
+ account.userId, authority);
+ final long backoffTime = backoff != null ? backoff.first : 0;
+ if (isSyncable < 0) {
+ Bundle newExtras = new Bundle();
+ newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync:"
+ + " delay " + delay
+ + ", source " + source
+ + ", account " + account
+ + ", authority " + authority
+ + ", extras " + newExtras);
+ }
+ scheduleSyncOperation(
+ new SyncOperation(account.account, account.userId, reason, source,
+ authority, newExtras, 0, backoffTime, delayUntil,
+ allowParallelSyncs));
+ }
+ if (!onlyThoseWithUnkownSyncableState) {
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync:"
+ + " delay " + delay
+ + ", source " + source
+ + ", account " + account
+ + ", authority " + authority
+ + ", extras " + extras);
+ }
+ scheduleSyncOperation(
+ new SyncOperation(account.account, account.userId, reason, source,
+ authority, extras, delay, backoffTime, delayUntil,
+ allowParallelSyncs));
+ }
+ }
+ }
+ }
+
+ public void scheduleLocalSync(Account account, int userId, int reason, String authority) {
+ final Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+ scheduleSync(account, userId, reason, authority, extras, LOCAL_SYNC_DELAY,
+ false /* onlyThoseWithUnkownSyncableState */);
+ }
+
+ public SyncAdapterType[] getSyncAdapterTypes(int userId) {
+ final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos;
+ serviceInfos = mSyncAdapters.getAllServices(userId);
+ SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
+ int i = 0;
+ for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) {
+ types[i] = serviceInfo.type;
+ ++i;
+ }
+ return types;
+ }
+
+ private void sendSyncAlarmMessage() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
+ mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
+ }
+
+ private void sendCheckAlarmsMessage() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
+ mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS);
+ mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
+ }
+
+ private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext,
+ SyncResult syncResult) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
+ Message msg = mSyncHandler.obtainMessage();
+ msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
+ msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
+ mSyncHandler.sendMessage(msg);
+ }
+
+ private void sendCancelSyncsMessage(final Account account, final int userId,
+ final String authority) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
+ Message msg = mSyncHandler.obtainMessage();
+ msg.what = SyncHandler.MESSAGE_CANCEL;
+ msg.obj = Pair.create(account, authority);
+ msg.arg1 = userId;
+ mSyncHandler.sendMessage(msg);
+ }
+
+ class SyncHandlerMessagePayload {
+ public final ActiveSyncContext activeSyncContext;
+ public final SyncResult syncResult;
+
+ SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
+ this.activeSyncContext = syncContext;
+ this.syncResult = syncResult;
+ }
+ }
+
+ class SyncAlarmIntentReceiver extends BroadcastReceiver {
+ public void onReceive(Context context, Intent intent) {
+ mHandleAlarmWakeLock.acquire();
+ sendSyncAlarmMessage();
+ }
+ }
+
+ private void clearBackoffSetting(SyncOperation op) {
+ mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
+ synchronized (mSyncQueue) {
+ mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0);
+ }
+ }
+
+ private void increaseBackoffSetting(SyncOperation op) {
+ // TODO: Use this function to align it to an already scheduled sync
+ // operation in the specified window
+ final long now = SystemClock.elapsedRealtime();
+
+ final Pair<Long, Long> previousSettings =
+ mSyncStorageEngine.getBackoff(op.account, op.userId, op.authority);
+ long newDelayInMs = -1;
+ if (previousSettings != null) {
+ // don't increase backoff before current backoff is expired. This will happen for op's
+ // with ignoreBackoff set.
+ if (now < previousSettings.first) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Still in backoff, do not increase it. "
+ + "Remaining: " + ((previousSettings.first - now) / 1000) + " seconds.");
+ }
+ return;
+ }
+ // Subsequent delays are the double of the previous delay
+ newDelayInMs = previousSettings.second * 2;
+ }
+ if (newDelayInMs <= 0) {
+ // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
+ newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
+ (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
+ }
+
+ // Cap the delay
+ long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+ DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
+ if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
+ newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
+ }
+
+ final long backoff = now + newDelayInMs;
+
+ mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
+ backoff, newDelayInMs);
+
+ op.backoff = backoff;
+ op.updateEffectiveRunTime();
+
+ synchronized (mSyncQueue) {
+ mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff);
+ }
+ }
+
+ private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
+ final long delayUntil = delayUntilSeconds * 1000;
+ final long absoluteNow = System.currentTimeMillis();
+ long newDelayUntilTime;
+ if (delayUntil > absoluteNow) {
+ newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
+ } else {
+ newDelayUntilTime = 0;
+ }
+ mSyncStorageEngine
+ .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime);
+ synchronized (mSyncQueue) {
+ mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
+ }
+ }
+
+ /**
+ * Cancel the active sync if it matches the authority and account.
+ * @param account limit the cancelations to syncs with this account, if non-null
+ * @param authority limit the cancelations to syncs with this authority, if non-null
+ */
+ public void cancelActiveSync(Account account, int userId, String authority) {
+ sendCancelSyncsMessage(account, userId, authority);
+ }
+
+ /**
+ * Create and schedule a SyncOperation.
+ *
+ * @param syncOperation the SyncOperation to schedule
+ */
+ public void scheduleSyncOperation(SyncOperation syncOperation) {
+ boolean queueChanged;
+ synchronized (mSyncQueue) {
+ queueChanged = mSyncQueue.add(syncOperation);
+ }
+
+ if (queueChanged) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
+ }
+ sendCheckAlarmsMessage();
+ } else {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
+ + syncOperation);
+ }
+ }
+ }
+
+ /**
+ * Remove scheduled sync operations.
+ * @param account limit the removals to operations with this account, if non-null
+ * @param authority limit the removals to operations with this authority, if non-null
+ */
+ public void clearScheduledSyncOperations(Account account, int userId, String authority) {
+ synchronized (mSyncQueue) {
+ mSyncQueue.remove(account, userId, authority);
+ }
+ mSyncStorageEngine.setBackoff(account, userId, authority,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
+ }
+
+ void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
+ boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
+ if (isLoggable) {
+ Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
+ }
+
+ operation = new SyncOperation(operation);
+
+ // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
+ // request. Retries of the request will always honor the backoff, so clear the
+ // flag in case we retry this request.
+ if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
+ operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+ }
+
+ // If this sync aborted because the internal sync loop retried too many times then
+ // don't reschedule. Otherwise we risk getting into a retry loop.
+ // If the operation succeeded to some extent then retry immediately.
+ // If this was a two-way sync then retry soft errors with an exponential backoff.
+ // If this was an upward sync then schedule a two-way sync immediately.
+ // Otherwise do not reschedule.
+ if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
+ Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
+ + operation);
+ } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
+ && !syncResult.syncAlreadyInProgress) {
+ operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
+ Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+ + "encountered an error: " + operation);
+ scheduleSyncOperation(operation);
+ } else if (syncResult.tooManyRetries) {
+ Log.d(TAG, "not retrying sync operation because it retried too many times: "
+ + operation);
+ } else if (syncResult.madeSomeProgress()) {
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation because even though it had an error "
+ + "it achieved some success");
+ }
+ scheduleSyncOperation(operation);
+ } else if (syncResult.syncAlreadyInProgress) {
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation that failed because there was already a "
+ + "sync in progress: " + operation);
+ }
+ scheduleSyncOperation(new SyncOperation(operation.account, operation.userId,
+ operation.reason,
+ operation.syncSource,
+ operation.authority, operation.extras,
+ DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000,
+ operation.backoff, operation.delayUntil, operation.allowParallelSyncs));
+ } else if (syncResult.hasSoftError()) {
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation because it encountered a soft error: "
+ + operation);
+ }
+ scheduleSyncOperation(operation);
+ } else {
+ Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+ + operation);
+ }
+ }
+
+ private void onUserStarting(int userId) {
+ // Make sure that accounts we're about to use are valid
+ AccountManagerService.getSingleton().validateAccounts(userId);
+
+ mSyncAdapters.invalidateCache(userId);
+
+ updateRunningAccounts();
+
+ synchronized (mSyncQueue) {
+ mSyncQueue.addPendingOperations(userId);
+ }
+
+ // Schedule sync for any accounts under started user
+ final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId);
+ for (Account account : accounts) {
+ scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null,
+ 0 /* no delay */, true /* onlyThoseWithUnknownSyncableState */);
+ }
+
+ sendCheckAlarmsMessage();
+ }
+
+ private void onUserStopping(int userId) {
+ updateRunningAccounts();
+
+ cancelActiveSync(
+ null /* any account */,
+ userId,
+ null /* any authority */);
+ }
+
+ private void onUserRemoved(int userId) {
+ updateRunningAccounts();
+
+ // Clean up the storage engine database
+ mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
+ synchronized (mSyncQueue) {
+ mSyncQueue.removeUser(userId);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ class ActiveSyncContext extends ISyncContext.Stub
+ implements ServiceConnection, IBinder.DeathRecipient {
+ final SyncOperation mSyncOperation;
+ final long mHistoryRowId;
+ ISyncAdapter mSyncAdapter;
+ final long mStartTime;
+ long mTimeoutStartTime;
+ boolean mBound;
+ final PowerManager.WakeLock mSyncWakeLock;
+ final int mSyncAdapterUid;
+ SyncInfo mSyncInfo;
+ boolean mIsLinkedToDeath = false;
+
+ /**
+ * Create an ActiveSyncContext for an impending sync and grab the wakelock for that
+ * sync adapter. Since this grabs the wakelock you need to be sure to call
+ * close() when you are done with this ActiveSyncContext, whether the sync succeeded
+ * or not.
+ * @param syncOperation the SyncOperation we are about to sync
+ * @param historyRowId the row in which to record the history info for this sync
+ * @param syncAdapterUid the UID of the application that contains the sync adapter
+ * for this sync. This is used to attribute the wakelock hold to that application.
+ */
+ public ActiveSyncContext(SyncOperation syncOperation, long historyRowId,
+ int syncAdapterUid) {
+ super();
+ mSyncAdapterUid = syncAdapterUid;
+ mSyncOperation = syncOperation;
+ mHistoryRowId = historyRowId;
+ mSyncAdapter = null;
+ mStartTime = SystemClock.elapsedRealtime();
+ mTimeoutStartTime = mStartTime;
+ mSyncWakeLock = mSyncHandler.getSyncWakeLock(
+ mSyncOperation.account, mSyncOperation.authority);
+ mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
+ mSyncWakeLock.acquire();
+ }
+
+ public void sendHeartbeat() {
+ // heartbeats are no longer used
+ }
+
+ public void onFinished(SyncResult result) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this);
+ // include "this" in the message so that the handler can ignore it if this
+ // ActiveSyncContext is no longer the mActiveSyncContext at message handling
+ // time
+ sendSyncFinishedOrCanceledMessage(this, result);
+ }
+
+ public void toString(StringBuilder sb) {
+ sb.append("startTime ").append(mStartTime)
+ .append(", mTimeoutStartTime ").append(mTimeoutStartTime)
+ .append(", mHistoryRowId ").append(mHistoryRowId)
+ .append(", syncOperation ").append(mSyncOperation);
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Message msg = mSyncHandler.obtainMessage();
+ msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
+ msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
+ mSyncHandler.sendMessage(msg);
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ Message msg = mSyncHandler.obtainMessage();
+ msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED;
+ msg.obj = new ServiceConnectionData(this, null);
+ mSyncHandler.sendMessage(msg);
+ }
+
+ boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
+ }
+ Intent intent = new Intent();
+ intent.setAction("android.content.SyncAdapter");
+ intent.setComponent(info.componentName);
+ intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
+ com.android.internal.R.string.sync_binding_label);
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
+ mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0,
+ null, new UserHandle(userId)));
+ mBound = true;
+ final boolean bindResult = mContext.bindServiceAsUser(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_ALLOW_OOM_MANAGEMENT,
+ new UserHandle(mSyncOperation.userId));
+ if (!bindResult) {
+ mBound = false;
+ }
+ return bindResult;
+ }
+
+ /**
+ * Performs the required cleanup, which is the releasing of the wakelock and
+ * unbinding from the sync adapter (if actually bound).
+ */
+ protected void close() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
+ }
+ if (mBound) {
+ mBound = false;
+ mContext.unbindService(this);
+ }
+ mSyncWakeLock.release();
+ mSyncWakeLock.setWorkSource(null);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ toString(sb);
+ return sb.toString();
+ }
+
+ @Override
+ public void binderDied() {
+ sendSyncFinishedOrCanceledMessage(this, null);
+ }
+ }
+
+ protected void dump(FileDescriptor fd, PrintWriter pw) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ dumpSyncState(ipw);
+ dumpSyncHistory(ipw);
+ dumpSyncAdapters(ipw);
+ }
+
+ static String formatTime(long time) {
+ Time tobj = new Time();
+ tobj.set(time);
+ return tobj.format("%Y-%m-%d %H:%M:%S");
+ }
+
+ protected void dumpSyncState(PrintWriter pw) {
+ pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
+ pw.print("auto sync: ");
+ List<UserInfo> users = getAllUsers();
+ if (users != null) {
+ for (UserInfo user : users) {
+ pw.print("u" + user.id + "="
+ + mSyncStorageEngine.getMasterSyncAutomatically(user.id) + " ");
+ }
+ pw.println();
+ }
+ pw.print("memory low: "); pw.println(mStorageIsLow);
+
+ final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
+
+ pw.print("accounts: ");
+ if (accounts != INITIAL_ACCOUNTS_ARRAY) {
+ pw.println(accounts.length);
+ } else {
+ pw.println("not known yet");
+ }
+ final long now = SystemClock.elapsedRealtime();
+ pw.print("now: "); pw.print(now);
+ pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
+ pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000));
+ pw.println(" (HH:MM:SS)");
+ pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
+ pw.println(" (HH:MM:SS)");
+ pw.print("time spent syncing: ");
+ pw.print(DateUtils.formatElapsedTime(
+ mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000));
+ pw.print(" (HH:MM:SS), sync ");
+ pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ");
+ pw.println("in progress");
+ if (mSyncHandler.mAlarmScheduleTime != null) {
+ pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime);
+ pw.print(" (");
+ pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000));
+ pw.println(" (HH:MM:SS) from now)");
+ } else {
+ pw.println("no alarm is scheduled (there had better not be any pending syncs)");
+ }
+
+ pw.print("notification info: ");
+ final StringBuilder sb = new StringBuilder();
+ mSyncHandler.mSyncNotificationInfo.toString(sb);
+ pw.println(sb.toString());
+
+ pw.println();
+ pw.println("Active Syncs: " + mActiveSyncContexts.size());
+ final PackageManager pm = mContext.getPackageManager();
+ for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+ final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000;
+ pw.print(" ");
+ pw.print(DateUtils.formatElapsedTime(durationInSeconds));
+ pw.print(" - ");
+ pw.print(activeSyncContext.mSyncOperation.dump(pm, false));
+ pw.println();
+ }
+
+ synchronized (mSyncQueue) {
+ sb.setLength(0);
+ mSyncQueue.dump(sb);
+ }
+ pw.println();
+ pw.print(sb.toString());
+
+ // join the installed sync adapter with the accounts list and emit for everything
+ pw.println();
+ pw.println("Sync Status");
+ for (AccountAndUser account : accounts) {
+ pw.printf("Account %s u%d %s\n",
+ account.account.name, account.userId, account.account.type);
+
+ pw.println("=======================================================================");
+ final PrintTable table = new PrintTable(13);
+ table.set(0, 0,
+ "Authority", // 0
+ "Syncable", // 1
+ "Enabled", // 2
+ "Delay", // 3
+ "Loc", // 4
+ "Poll", // 5
+ "Per", // 6
+ "Serv", // 7
+ "User", // 8
+ "Tot", // 9
+ "Time", // 10
+ "Last Sync", // 11
+ "Periodic" // 12
+ );
+
+ final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted =
+ Lists.newArrayList();
+ sorted.addAll(mSyncAdapters.getAllServices(account.userId));
+ Collections.sort(sorted,
+ new Comparator<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>() {
+ @Override
+ public int compare(RegisteredServicesCache.ServiceInfo<SyncAdapterType> lhs,
+ RegisteredServicesCache.ServiceInfo<SyncAdapterType> rhs) {
+ return lhs.type.authority.compareTo(rhs.type.authority);
+ }
+ });
+ for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : sorted) {
+ if (!syncAdapterType.type.accountType.equals(account.account.type)) {
+ continue;
+ }
+ int row = table.getNumRows();
+ SyncStorageEngine.AuthorityInfo settings =
+ mSyncStorageEngine.getOrCreateAuthority(
+ account.account, account.userId, syncAdapterType.type.authority);
+ SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings);
+
+ String authority = settings.authority;
+ if (authority.length() > 50) {
+ authority = authority.substring(authority.length() - 50);
+ }
+ table.set(row, 0, authority, settings.syncable, settings.enabled);
+ table.set(row, 4,
+ status.numSourceLocal,
+ status.numSourcePoll,
+ status.numSourcePeriodic,
+ status.numSourceServer,
+ status.numSourceUser,
+ status.numSyncs,
+ DateUtils.formatElapsedTime(status.totalElapsedTime / 1000));
+
+
+ for (int i = 0; i < settings.periodicSyncs.size(); i++) {
+ final Pair<Bundle, Long> pair = settings.periodicSyncs.get(0);
+ final String period = String.valueOf(pair.second);
+ final String extras = pair.first.size() > 0 ? pair.first.toString() : "";
+ final String next = formatTime(status.getPeriodicSyncTime(0)
+ + pair.second * 1000);
+ table.set(row + i * 2, 12, period + extras);
+ table.set(row + i * 2 + 1, 12, next);
+ }
+
+ int row1 = row;
+ if (settings.delayUntil > now) {
+ table.set(row1++, 12, "D: " + (settings.delayUntil - now) / 1000);
+ if (settings.backoffTime > now) {
+ table.set(row1++, 12, "B: " + (settings.backoffTime - now) / 1000);
+ table.set(row1++, 12, settings.backoffDelay / 1000);
+ }
+ }
+
+ if (status.lastSuccessTime != 0) {
+ table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastSuccessSource]
+ + " " + "SUCCESS");
+ table.set(row1++, 11, formatTime(status.lastSuccessTime));
+ }
+ if (status.lastFailureTime != 0) {
+ table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastFailureSource]
+ + " " + "FAILURE");
+ table.set(row1++, 11, formatTime(status.lastFailureTime));
+ //noinspection UnusedAssignment
+ table.set(row1++, 11, status.lastFailureMesg);
+ }
+ }
+ table.writeTo(pw);
+ }
+ }
+
+ private String getLastFailureMessage(int code) {
+ switch (code) {
+ case ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS:
+ return "sync already in progress";
+
+ case ContentResolver.SYNC_ERROR_AUTHENTICATION:
+ return "authentication error";
+
+ case ContentResolver.SYNC_ERROR_IO:
+ return "I/O error";
+
+ case ContentResolver.SYNC_ERROR_PARSE:
+ return "parse error";
+
+ case ContentResolver.SYNC_ERROR_CONFLICT:
+ return "conflict error";
+
+ case ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS:
+ return "too many deletions error";
+
+ case ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES:
+ return "too many retries error";
+
+ case ContentResolver.SYNC_ERROR_INTERNAL:
+ return "internal error";
+
+ default:
+ return "unknown";
+ }
+ }
+
+ private void dumpTimeSec(PrintWriter pw, long time) {
+ pw.print(time/1000); pw.print('.'); pw.print((time/100)%10);
+ pw.print('s');
+ }
+
+ private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) {
+ pw.print("Success ("); pw.print(ds.successCount);
+ if (ds.successCount > 0) {
+ pw.print(" for "); dumpTimeSec(pw, ds.successTime);
+ pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount);
+ }
+ pw.print(") Failure ("); pw.print(ds.failureCount);
+ if (ds.failureCount > 0) {
+ pw.print(" for "); dumpTimeSec(pw, ds.failureTime);
+ pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount);
+ }
+ pw.println(")");
+ }
+
+ protected void dumpSyncHistory(PrintWriter pw) {
+ dumpRecentHistory(pw);
+ dumpDayStatistics(pw);
+ }
+
+ private void dumpRecentHistory(PrintWriter pw) {
+ final ArrayList<SyncStorageEngine.SyncHistoryItem> items
+ = mSyncStorageEngine.getSyncHistory();
+ if (items != null && items.size() > 0) {
+ final Map<String, AuthoritySyncStats> authorityMap = Maps.newHashMap();
+ long totalElapsedTime = 0;
+ long totalTimes = 0;
+ final int N = items.size();
+
+ int maxAuthority = 0;
+ int maxAccount = 0;
+ for (SyncStorageEngine.SyncHistoryItem item : items) {
+ SyncStorageEngine.AuthorityInfo authority
+ = mSyncStorageEngine.getAuthority(item.authorityId);
+ final String authorityName;
+ final String accountKey;
+ if (authority != null) {
+ authorityName = authority.authority;
+ accountKey = authority.account.name + "/" + authority.account.type
+ + " u" + authority.userId;
+ } else {
+ authorityName = "Unknown";
+ accountKey = "Unknown";
+ }
+
+ int length = authorityName.length();
+ if (length > maxAuthority) {
+ maxAuthority = length;
+ }
+ length = accountKey.length();
+ if (length > maxAccount) {
+ maxAccount = length;
+ }
+
+ final long elapsedTime = item.elapsedTime;
+ totalElapsedTime += elapsedTime;
+ totalTimes++;
+ AuthoritySyncStats authoritySyncStats = authorityMap.get(authorityName);
+ if (authoritySyncStats == null) {
+ authoritySyncStats = new AuthoritySyncStats(authorityName);
+ authorityMap.put(authorityName, authoritySyncStats);
+ }
+ authoritySyncStats.elapsedTime += elapsedTime;
+ authoritySyncStats.times++;
+ final Map<String, AccountSyncStats> accountMap = authoritySyncStats.accountMap;
+ AccountSyncStats accountSyncStats = accountMap.get(accountKey);
+ if (accountSyncStats == null) {
+ accountSyncStats = new AccountSyncStats(accountKey);
+ accountMap.put(accountKey, accountSyncStats);
+ }
+ accountSyncStats.elapsedTime += elapsedTime;
+ accountSyncStats.times++;
+
+ }
+
+ if (totalElapsedTime > 0) {
+ pw.println();
+ pw.printf("Detailed Statistics (Recent history): "
+ + "%d (# of times) %ds (sync time)\n",
+ totalTimes, totalElapsedTime / 1000);
+
+ final List<AuthoritySyncStats> sortedAuthorities =
+ new ArrayList<AuthoritySyncStats>(authorityMap.values());
+ Collections.sort(sortedAuthorities, new Comparator<AuthoritySyncStats>() {
+ @Override
+ public int compare(AuthoritySyncStats lhs, AuthoritySyncStats rhs) {
+ // reverse order
+ int compare = Integer.compare(rhs.times, lhs.times);
+ if (compare == 0) {
+ compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
+ }
+ return compare;
+ }
+ });
+
+ final int maxLength = Math.max(maxAuthority, maxAccount + 3);
+ final int padLength = 2 + 2 + maxLength + 2 + 10 + 11;
+ final char chars[] = new char[padLength];
+ Arrays.fill(chars, '-');
+ final String separator = new String(chars);
+
+ final String authorityFormat =
+ String.format(" %%-%ds: %%-9s %%-11s\n", maxLength + 2);
+ final String accountFormat =
+ String.format(" %%-%ds: %%-9s %%-11s\n", maxLength);
+
+ pw.println(separator);
+ for (AuthoritySyncStats authoritySyncStats : sortedAuthorities) {
+ String name = authoritySyncStats.name;
+ long elapsedTime;
+ int times;
+ String timeStr;
+ String timesStr;
+
+ elapsedTime = authoritySyncStats.elapsedTime;
+ times = authoritySyncStats.times;
+ timeStr = String.format("%ds/%d%%",
+ elapsedTime / 1000,
+ elapsedTime * 100 / totalElapsedTime);
+ timesStr = String.format("%d/%d%%",
+ times,
+ times * 100 / totalTimes);
+ pw.printf(authorityFormat, name, timesStr, timeStr);
+
+ final List<AccountSyncStats> sortedAccounts =
+ new ArrayList<AccountSyncStats>(
+ authoritySyncStats.accountMap.values());
+ Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() {
+ @Override
+ public int compare(AccountSyncStats lhs, AccountSyncStats rhs) {
+ // reverse order
+ int compare = Integer.compare(rhs.times, lhs.times);
+ if (compare == 0) {
+ compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
+ }
+ return compare;
+ }
+ });
+ for (AccountSyncStats stats: sortedAccounts) {
+ elapsedTime = stats.elapsedTime;
+ times = stats.times;
+ timeStr = String.format("%ds/%d%%",
+ elapsedTime / 1000,
+ elapsedTime * 100 / totalElapsedTime);
+ timesStr = String.format("%d/%d%%",
+ times,
+ times * 100 / totalTimes);
+ pw.printf(accountFormat, stats.name, timesStr, timeStr);
+ }
+ pw.println(separator);
+ }
+ }
+
+ pw.println();
+ pw.println("Recent Sync History");
+ final String format = " %-" + maxAccount + "s %-" + maxAuthority + "s %s\n";
+ final Map<String, Long> lastTimeMap = Maps.newHashMap();
+ final PackageManager pm = mContext.getPackageManager();
+ for (int i = 0; i < N; i++) {
+ SyncStorageEngine.SyncHistoryItem item = items.get(i);
+ SyncStorageEngine.AuthorityInfo authority
+ = mSyncStorageEngine.getAuthority(item.authorityId);
+ final String authorityName;
+ final String accountKey;
+ if (authority != null) {
+ authorityName = authority.authority;
+ accountKey = authority.account.name + "/" + authority.account.type
+ + " u" + authority.userId;
+ } else {
+ authorityName = "Unknown";
+ accountKey = "Unknown";
+ }
+ final long elapsedTime = item.elapsedTime;
+ final Time time = new Time();
+ final long eventTime = item.eventTime;
+ time.set(eventTime);
+
+ final String key = authorityName + "/" + accountKey;
+ final Long lastEventTime = lastTimeMap.get(key);
+ final String diffString;
+ if (lastEventTime == null) {
+ diffString = "";
+ } else {
+ final long diff = (lastEventTime - eventTime) / 1000;
+ if (diff < 60) {
+ diffString = String.valueOf(diff);
+ } else if (diff < 3600) {
+ diffString = String.format("%02d:%02d", diff / 60, diff % 60);
+ } else {
+ final long sec = diff % 3600;
+ diffString = String.format("%02d:%02d:%02d",
+ diff / 3600, sec / 60, sec % 60);
+ }
+ }
+ lastTimeMap.put(key, eventTime);
+
+ pw.printf(" #%-3d: %s %8s %5.1fs %8s",
+ i + 1,
+ formatTime(eventTime),
+ SyncStorageEngine.SOURCES[item.source],
+ ((float) elapsedTime) / 1000,
+ diffString);
+ pw.printf(format, accountKey, authorityName,
+ SyncOperation.reasonToString(pm, item.reason));
+
+ if (item.event != SyncStorageEngine.EVENT_STOP
+ || item.upstreamActivity != 0
+ || item.downstreamActivity != 0) {
+ pw.printf(" event=%d upstreamActivity=%d downstreamActivity=%d\n",
+ item.event,
+ item.upstreamActivity,
+ item.downstreamActivity);
+ }
+ if (item.mesg != null
+ && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) {
+ pw.printf(" mesg=%s\n", item.mesg);
+ }
+ }
+ pw.println();
+ pw.println("Recent Sync History Extras");
+ for (int i = 0; i < N; i++) {
+ final SyncStorageEngine.SyncHistoryItem item = items.get(i);
+ final Bundle extras = item.extras;
+ if (extras == null || extras.size() == 0) {
+ continue;
+ }
+ final SyncStorageEngine.AuthorityInfo authority
+ = mSyncStorageEngine.getAuthority(item.authorityId);
+ final String authorityName;
+ final String accountKey;
+ if (authority != null) {
+ authorityName = authority.authority;
+ accountKey = authority.account.name + "/" + authority.account.type
+ + " u" + authority.userId;
+ } else {
+ authorityName = "Unknown";
+ accountKey = "Unknown";
+ }
+ final Time time = new Time();
+ final long eventTime = item.eventTime;
+ time.set(eventTime);
+
+ pw.printf(" #%-3d: %s %8s ",
+ i + 1,
+ formatTime(eventTime),
+ SyncStorageEngine.SOURCES[item.source]);
+
+ pw.printf(format, accountKey, authorityName, extras);
+ }
+ }
+ }
+
+ private void dumpDayStatistics(PrintWriter pw) {
+ SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics();
+ if (dses != null && dses[0] != null) {
+ pw.println();
+ pw.println("Sync Statistics");
+ pw.print(" Today: "); dumpDayStatistic(pw, dses[0]);
+ int today = dses[0].day;
+ int i;
+ SyncStorageEngine.DayStats ds;
+
+ // Print each day in the current week.
+ for (i=1; i<=6 && i < dses.length; i++) {
+ ds = dses[i];
+ if (ds == null) break;
+ int delta = today-ds.day;
+ if (delta > 6) break;
+
+ pw.print(" Day-"); pw.print(delta); pw.print(": ");
+ dumpDayStatistic(pw, ds);
+ }
+
+ // Aggregate all following days into weeks and print totals.
+ int weekDay = today;
+ while (i < dses.length) {
+ SyncStorageEngine.DayStats aggr = null;
+ weekDay -= 7;
+ while (i < dses.length) {
+ ds = dses[i];
+ if (ds == null) {
+ i = dses.length;
+ break;
+ }
+ int delta = weekDay-ds.day;
+ if (delta > 6) break;
+ i++;
+
+ if (aggr == null) {
+ aggr = new SyncStorageEngine.DayStats(weekDay);
+ }
+ aggr.successCount += ds.successCount;
+ aggr.successTime += ds.successTime;
+ aggr.failureCount += ds.failureCount;
+ aggr.failureTime += ds.failureTime;
+ }
+ if (aggr != null) {
+ pw.print(" Week-"); pw.print((today-weekDay)/7); pw.print(": ");
+ dumpDayStatistic(pw, aggr);
+ }
+ }
+ }
+ }
+
+ private void dumpSyncAdapters(IndentingPrintWriter pw) {
+ pw.println();
+ final List<UserInfo> users = getAllUsers();
+ if (users != null) {
+ for (UserInfo user : users) {
+ pw.println("Sync adapters for " + user + ":");
+ pw.increaseIndent();
+ for (RegisteredServicesCache.ServiceInfo<?> info :
+ mSyncAdapters.getAllServices(user.id)) {
+ pw.println(info);
+ }
+ pw.decreaseIndent();
+ pw.println();
+ }
+ }
+ }
+
+ private static class AuthoritySyncStats {
+ String name;
+ long elapsedTime;
+ int times;
+ Map<String, AccountSyncStats> accountMap = Maps.newHashMap();
+
+ private AuthoritySyncStats(String name) {
+ this.name = name;
+ }
+ }
+
+ private static class AccountSyncStats {
+ String name;
+ long elapsedTime;
+ int times;
+
+ private AccountSyncStats(String name) {
+ this.name = name;
+ }
+ }
+
+ /**
+ * A helper object to keep track of the time we have spent syncing since the last boot
+ */
+ private class SyncTimeTracker {
+ /** True if a sync was in progress on the most recent call to update() */
+ boolean mLastWasSyncing = false;
+ /** Used to track when lastWasSyncing was last set */
+ long mWhenSyncStarted = 0;
+ /** The cumulative time we have spent syncing */
+ private long mTimeSpentSyncing;
+
+ /** Call to let the tracker know that the sync state may have changed */
+ public synchronized void update() {
+ final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty();
+ if (isSyncInProgress == mLastWasSyncing) return;
+ final long now = SystemClock.elapsedRealtime();
+ if (isSyncInProgress) {
+ mWhenSyncStarted = now;
+ } else {
+ mTimeSpentSyncing += now - mWhenSyncStarted;
+ }
+ mLastWasSyncing = isSyncInProgress;
+ }
+
+ /** Get how long we have been syncing, in ms */
+ public synchronized long timeSpentSyncing() {
+ if (!mLastWasSyncing) return mTimeSpentSyncing;
+
+ final long now = SystemClock.elapsedRealtime();
+ return mTimeSpentSyncing + (now - mWhenSyncStarted);
+ }
+ }
+
+ class ServiceConnectionData {
+ public final ActiveSyncContext activeSyncContext;
+ public final ISyncAdapter syncAdapter;
+ ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) {
+ this.activeSyncContext = activeSyncContext;
+ this.syncAdapter = syncAdapter;
+ }
+ }
+
+ /**
+ * Handles SyncOperation Messages that are posted to the associated
+ * HandlerThread.
+ */
+ class SyncHandler extends Handler {
+ // Messages that can be sent on mHandler
+ private static final int MESSAGE_SYNC_FINISHED = 1;
+ private static final int MESSAGE_SYNC_ALARM = 2;
+ private static final int MESSAGE_CHECK_ALARMS = 3;
+ private static final int MESSAGE_SERVICE_CONNECTED = 4;
+ private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
+ private static final int MESSAGE_CANCEL = 6;
+
+ public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
+ private Long mAlarmScheduleTime = null;
+ public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
+ private final HashMap<Pair<Account, String>, PowerManager.WakeLock> mWakeLocks =
+ Maps.newHashMap();
+
+ private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
+
+ public void onBootCompleted() {
+ mBootCompleted = true;
+
+ doDatabaseCleanup();
+
+ if (mReadyToRunLatch != null) {
+ mReadyToRunLatch.countDown();
+ }
+ }
+
+ private PowerManager.WakeLock getSyncWakeLock(Account account, String authority) {
+ final Pair<Account, String> wakeLockKey = Pair.create(account, authority);
+ PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
+ if (wakeLock == null) {
+ final String name = SYNC_WAKE_LOCK_PREFIX + "_" + authority + "_" + account;
+ wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
+ wakeLock.setReferenceCounted(false);
+ mWakeLocks.put(wakeLockKey, wakeLock);
+ }
+ return wakeLock;
+ }
+
+ private void waitUntilReadyToRun() {
+ CountDownLatch latch = mReadyToRunLatch;
+ if (latch != null) {
+ while (true) {
+ try {
+ latch.await();
+ mReadyToRunLatch = null;
+ return;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+ /**
+ * Used to keep track of whether a sync notification is active and who it is for.
+ */
+ class SyncNotificationInfo {
+ // true iff the notification manager has been asked to send the notification
+ public boolean isActive = false;
+
+ // Set when we transition from not running a sync to running a sync, and cleared on
+ // the opposite transition.
+ public Long startTime = null;
+
+ public void toString(StringBuilder sb) {
+ sb.append("isActive ").append(isActive).append(", startTime ").append(startTime);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ toString(sb);
+ return sb.toString();
+ }
+ }
+
+ public SyncHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ long earliestFuturePollTime = Long.MAX_VALUE;
+ long nextPendingSyncTime = Long.MAX_VALUE;
+
+ // Setting the value here instead of a method because we want the dumpsys logs
+ // to have the most recent value used.
+ try {
+ waitUntilReadyToRun();
+ mDataConnectionIsConnected = readDataConnectionState();
+ mSyncManagerWakeLock.acquire();
+ // Always do this first so that we be sure that any periodic syncs that
+ // are ready to run have been converted into pending syncs. This allows the
+ // logic that considers the next steps to take based on the set of pending syncs
+ // to also take into account the periodic syncs.
+ earliestFuturePollTime = scheduleReadyPeriodicSyncs();
+ switch (msg.what) {
+ case SyncHandler.MESSAGE_CANCEL: {
+ Pair<Account, String> payload = (Pair<Account, String>)msg.obj;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
+ + payload.first + ", " + payload.second);
+ }
+ cancelActiveSyncLocked(payload.first, msg.arg1, payload.second);
+ nextPendingSyncTime = maybeStartNextSyncLocked();
+ break;
+ }
+
+ case SyncHandler.MESSAGE_SYNC_FINISHED:
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
+ }
+ SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
+ if (!isSyncStillActive(payload.activeSyncContext)) {
+ Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
+ + "sync is no longer active: "
+ + payload.activeSyncContext);
+ break;
+ }
+ runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
+
+ // since a sync just finished check if it is time to start a new sync
+ nextPendingSyncTime = maybeStartNextSyncLocked();
+ break;
+
+ case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
+ ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
+ + msgData.activeSyncContext);
+ }
+ // check that this isn't an old message
+ if (isSyncStillActive(msgData.activeSyncContext)) {
+ runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
+ }
+ break;
+ }
+
+ case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
+ final ActiveSyncContext currentSyncContext =
+ ((ServiceConnectionData)msg.obj).activeSyncContext;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
+ + currentSyncContext);
+ }
+ // check that this isn't an old message
+ if (isSyncStillActive(currentSyncContext)) {
+ // cancel the sync if we have a syncadapter, which means one is
+ // outstanding
+ if (currentSyncContext.mSyncAdapter != null) {
+ try {
+ currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
+ } catch (RemoteException e) {
+ // we don't need to retry this in this case
+ }
+ }
+
+ // pretend that the sync failed with an IOException,
+ // which is a soft error
+ SyncResult syncResult = new SyncResult();
+ syncResult.stats.numIoExceptions++;
+ runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext);
+
+ // since a sync just finished check if it is time to start a new sync
+ nextPendingSyncTime = maybeStartNextSyncLocked();
+ }
+
+ break;
+ }
+
+ case SyncHandler.MESSAGE_SYNC_ALARM: {
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) {
+ Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM");
+ }
+ mAlarmScheduleTime = null;
+ try {
+ nextPendingSyncTime = maybeStartNextSyncLocked();
+ } finally {
+ mHandleAlarmWakeLock.release();
+ }
+ break;
+ }
+
+ case SyncHandler.MESSAGE_CHECK_ALARMS:
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
+ }
+ nextPendingSyncTime = maybeStartNextSyncLocked();
+ break;
+ }
+ } finally {
+ manageSyncNotificationLocked();
+ manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
+ mSyncTimeTracker.update();
+ mSyncManagerWakeLock.release();
+ }
+ }
+
+ /**
+ * Turn any periodic sync operations that are ready to run into pending sync operations.
+ * @return the desired start time of the earliest future periodic sync operation,
+ * in milliseconds since boot
+ */
+ private long scheduleReadyPeriodicSyncs() {
+ final boolean backgroundDataUsageAllowed =
+ getConnectivityManager().getBackgroundDataSetting();
+ long earliestFuturePollTime = Long.MAX_VALUE;
+ if (!backgroundDataUsageAllowed) {
+ return earliestFuturePollTime;
+ }
+
+ AccountAndUser[] accounts = mRunningAccounts;
+
+ final long nowAbsolute = System.currentTimeMillis();
+ final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
+ ? (nowAbsolute - mSyncRandomOffsetMillis) : 0;
+
+ ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
+ for (SyncStorageEngine.AuthorityInfo info : infos) {
+ // skip the sync if the account of this operation no longer exists
+ if (!containsAccountAndUser(accounts, info.account, info.userId)) {
+ continue;
+ }
+
+ if (!mSyncStorageEngine.getMasterSyncAutomatically(info.userId)
+ || !mSyncStorageEngine.getSyncAutomatically(info.account, info.userId,
+ info.authority)) {
+ continue;
+ }
+
+ if (getIsSyncable(info.account, info.userId, info.authority)
+ == 0) {
+ continue;
+ }
+
+ SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
+ for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
+ final Bundle extras = info.periodicSyncs.get(i).first;
+ final Long periodInMillis = info.periodicSyncs.get(i).second * 1000;
+ // Skip if the period is invalid
+ if (periodInMillis <= 0) {
+ continue;
+ }
+ // find when this periodic sync was last scheduled to run
+ final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
+
+ long remainingMillis
+ = periodInMillis - (shiftedNowAbsolute % periodInMillis);
+
+ /*
+ * Sync scheduling strategy:
+ * Set the next periodic sync based on a random offset (in seconds).
+ *
+ * Also sync right now if any of the following cases hold
+ * and mark it as having been scheduled
+ *
+ * Case 1: This sync is ready to run now.
+ * Case 2: If the lastPollTimeAbsolute is in the future,
+ * sync now and reinitialize. This can happen for
+ * example if the user changed the time, synced and
+ * changed back.
+ * Case 3: If we failed to sync at the last scheduled time
+ */
+ if (remainingMillis == periodInMillis // Case 1
+ || lastPollTimeAbsolute > nowAbsolute // Case 2
+ || (nowAbsolute - lastPollTimeAbsolute
+ >= periodInMillis)) { // Case 3
+ // Sync now
+ final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
+ info.account, info.userId, info.authority);
+ final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+ syncAdapterInfo = mSyncAdapters.getServiceInfo(
+ SyncAdapterType.newKey(info.authority, info.account.type),
+ info.userId);
+ if (syncAdapterInfo == null) {
+ continue;
+ }
+ scheduleSyncOperation(
+ new SyncOperation(info.account, info.userId,
+ SyncOperation.REASON_PERIODIC,
+ SyncStorageEngine.SOURCE_PERIODIC,
+ info.authority, extras, 0 /* delay */,
+ backoff != null ? backoff.first : 0,
+ mSyncStorageEngine.getDelayUntilTime(
+ info.account, info.userId, info.authority),
+ syncAdapterInfo.type.allowParallelSyncs()));
+ status.setPeriodicSyncTime(i, nowAbsolute);
+ }
+ // Compute when this periodic sync should next run
+ final long nextPollTimeAbsolute = nowAbsolute + remainingMillis;
+
+ // remember this time if it is earlier than earliestFuturePollTime
+ if (nextPollTimeAbsolute < earliestFuturePollTime) {
+ earliestFuturePollTime = nextPollTimeAbsolute;
+ }
+ }
+ }
+
+ if (earliestFuturePollTime == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
+ }
+
+ // convert absolute time to elapsed time
+ return SystemClock.elapsedRealtime()
+ + ((earliestFuturePollTime < nowAbsolute)
+ ? 0
+ : (earliestFuturePollTime - nowAbsolute));
+ }
+
+ private long maybeStartNextSyncLocked() {
+ final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) Log.v(TAG, "maybeStartNextSync");
+
+ // If we aren't ready to run (e.g. the data connection is down), get out.
+ if (!mDataConnectionIsConnected) {
+ if (isLoggable) {
+ Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
+ }
+ return Long.MAX_VALUE;
+ }
+
+ if (mStorageIsLow) {
+ if (isLoggable) {
+ Log.v(TAG, "maybeStartNextSync: memory low, skipping");
+ }
+ return Long.MAX_VALUE;
+ }
+
+ // If the accounts aren't known yet then we aren't ready to run. We will be kicked
+ // when the account lookup request does complete.
+ AccountAndUser[] accounts = mRunningAccounts;
+ if (accounts == INITIAL_ACCOUNTS_ARRAY) {
+ if (isLoggable) {
+ Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
+ }
+ return Long.MAX_VALUE;
+ }
+
+ // Otherwise consume SyncOperations from the head of the SyncQueue until one is
+ // found that is runnable (not disabled, etc). If that one is ready to run then
+ // start it, otherwise just get out.
+ final boolean backgroundDataUsageAllowed =
+ getConnectivityManager().getBackgroundDataSetting();
+
+ final long now = SystemClock.elapsedRealtime();
+
+ // will be set to the next time that a sync should be considered for running
+ long nextReadyToRunTime = Long.MAX_VALUE;
+
+ // order the sync queue, dropping syncs that are not allowed
+ ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>();
+ synchronized (mSyncQueue) {
+ if (isLoggable) {
+ Log.v(TAG, "build the operation array, syncQueue size is "
+ + mSyncQueue.getOperations().size());
+ }
+ final Iterator<SyncOperation> operationIterator = mSyncQueue.getOperations()
+ .iterator();
+
+ final ActivityManager activityManager
+ = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ final Set<Integer> removedUsers = Sets.newHashSet();
+ while (operationIterator.hasNext()) {
+ final SyncOperation op = operationIterator.next();
+
+ // drop the sync if the account of this operation no longer exists
+ if (!containsAccountAndUser(accounts, op.account, op.userId)) {
+ operationIterator.remove();
+ mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+ continue;
+ }
+
+ // drop this sync request if it isn't syncable
+ int syncableState = getIsSyncable(
+ op.account, op.userId, op.authority);
+ if (syncableState == 0) {
+ operationIterator.remove();
+ mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+ continue;
+ }
+
+ // if the user in not running, drop the request
+ if (!activityManager.isUserRunning(op.userId)) {
+ final UserInfo userInfo = mUserManager.getUserInfo(op.userId);
+ if (userInfo == null) {
+ removedUsers.add(op.userId);
+ }
+ continue;
+ }
+
+ // if the next run time is in the future, meaning there are no syncs ready
+ // to run, return the time
+ if (op.effectiveRunTime > now) {
+ if (nextReadyToRunTime > op.effectiveRunTime) {
+ nextReadyToRunTime = op.effectiveRunTime;
+ }
+ continue;
+ }
+
+ final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+ syncAdapterInfo = mSyncAdapters.getServiceInfo(
+ SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
+
+ // only proceed if network is connected for requesting UID
+ final boolean uidNetworkConnected;
+ if (syncAdapterInfo != null) {
+ final NetworkInfo networkInfo = getConnectivityManager()
+ .getActiveNetworkInfoForUid(syncAdapterInfo.uid);
+ uidNetworkConnected = networkInfo != null && networkInfo.isConnected();
+ } else {
+ uidNetworkConnected = false;
+ }
+
+ // skip the sync if it isn't manual, and auto sync or
+ // background data usage is disabled or network is
+ // disconnected for the target UID.
+ if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+ && (syncableState > 0)
+ && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId)
+ || !backgroundDataUsageAllowed
+ || !uidNetworkConnected
+ || !mSyncStorageEngine.getSyncAutomatically(
+ op.account, op.userId, op.authority))) {
+ operationIterator.remove();
+ mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+ continue;
+ }
+
+ operations.add(op);
+ }
+ for (Integer user : removedUsers) {
+ // if it's still removed
+ if (mUserManager.getUserInfo(user) == null) {
+ onUserRemoved(user);
+ }
+ }
+ }
+
+ // find the next operation to dispatch, if one is ready
+ // iterate from the top, keep issuing (while potentially cancelling existing syncs)
+ // until the quotas are filled.
+ // once the quotas are filled iterate once more to find when the next one would be
+ // (also considering pre-emption reasons).
+ if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size());
+ Collections.sort(operations);
+ if (isLoggable) Log.v(TAG, "dispatch all ready sync operations");
+ for (int i = 0, N = operations.size(); i < N; i++) {
+ final SyncOperation candidate = operations.get(i);
+ final boolean candidateIsInitialization = candidate.isInitialization();
+
+ int numInit = 0;
+ int numRegular = 0;
+ ActiveSyncContext conflict = null;
+ ActiveSyncContext longRunning = null;
+ ActiveSyncContext toReschedule = null;
+ ActiveSyncContext oldestNonExpeditedRegular = null;
+
+ for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+ final SyncOperation activeOp = activeSyncContext.mSyncOperation;
+ if (activeOp.isInitialization()) {
+ numInit++;
+ } else {
+ numRegular++;
+ if (!activeOp.isExpedited()) {
+ if (oldestNonExpeditedRegular == null
+ || (oldestNonExpeditedRegular.mStartTime
+ > activeSyncContext.mStartTime)) {
+ oldestNonExpeditedRegular = activeSyncContext;
+ }
+ }
+ }
+ if (activeOp.account.type.equals(candidate.account.type)
+ && activeOp.authority.equals(candidate.authority)
+ && activeOp.userId == candidate.userId
+ && (!activeOp.allowParallelSyncs
+ || activeOp.account.name.equals(candidate.account.name))) {
+ conflict = activeSyncContext;
+ // don't break out since we want to do a full count of the varieties
+ } else {
+ if (candidateIsInitialization == activeOp.isInitialization()
+ && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
+ longRunning = activeSyncContext;
+ // don't break out since we want to do a full count of the varieties
+ }
+ }
+ }
+
+ if (isLoggable) {
+ Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate);
+ Log.v(TAG, " numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
+ Log.v(TAG, " longRunning: " + longRunning);
+ Log.v(TAG, " conflict: " + conflict);
+ Log.v(TAG, " oldestNonExpeditedRegular: " + oldestNonExpeditedRegular);
+ }
+
+ final boolean roomAvailable = candidateIsInitialization
+ ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS
+ : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
+
+ if (conflict != null) {
+ if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
+ && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
+ toReschedule = conflict;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since an initialization "
+ + "takes higher priority, " + conflict);
+ }
+ } else if (candidate.expedited && !conflict.mSyncOperation.expedited
+ && (candidateIsInitialization
+ == conflict.mSyncOperation.isInitialization())) {
+ toReschedule = conflict;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since an expedited "
+ + "takes higher priority, " + conflict);
+ }
+ } else {
+ continue;
+ }
+ } else if (roomAvailable) {
+ // dispatch candidate
+ } else if (candidate.isExpedited() && oldestNonExpeditedRegular != null
+ && !candidateIsInitialization) {
+ // We found an active, non-expedited regular sync. We also know that the
+ // candidate doesn't conflict with this active sync since conflict
+ // is null. Reschedule the active sync and start the candidate.
+ toReschedule = oldestNonExpeditedRegular;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to run, "
+ + oldestNonExpeditedRegular);
+ }
+ } else if (longRunning != null
+ && (candidateIsInitialization
+ == longRunning.mSyncOperation.isInitialization())) {
+ // We found an active, long-running sync. Reschedule the active
+ // sync and start the candidate.
+ toReschedule = longRunning;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
+ + longRunning);
+ }
+ } else {
+ // we were unable to find or make space to run this candidate, go on to
+ // the next one
+ continue;
+ }
+
+ if (toReschedule != null) {
+ runSyncFinishedOrCanceledLocked(null, toReschedule);
+ scheduleSyncOperation(toReschedule.mSyncOperation);
+ }
+ synchronized (mSyncQueue) {
+ mSyncQueue.remove(candidate);
+ }
+ dispatchSyncOperation(candidate);
+ }
+
+ return nextReadyToRunTime;
+ }
+
+ private boolean dispatchSyncOperation(SyncOperation op) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "dispatchSyncOperation: we are going to sync " + op);
+ Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
+ for (ActiveSyncContext syncContext : mActiveSyncContexts) {
+ Log.v(TAG, syncContext.toString());
+ }
+ }
+
+ // connect to the sync adapter
+ SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
+ final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+ syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, op.userId);
+ if (syncAdapterInfo == null) {
+ Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
+ + ", removing settings for it");
+ mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority);
+ return false;
+ }
+
+ ActiveSyncContext activeSyncContext =
+ new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
+ activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
+ mActiveSyncContexts.add(activeSyncContext);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
+ }
+ if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) {
+ Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
+ closeActiveSyncContext(activeSyncContext);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext,
+ ISyncAdapter syncAdapter) {
+ activeSyncContext.mSyncAdapter = syncAdapter;
+ final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
+ try {
+ activeSyncContext.mIsLinkedToDeath = true;
+ syncAdapter.asBinder().linkToDeath(activeSyncContext, 0);
+
+ syncAdapter.startSync(activeSyncContext, syncOperation.authority,
+ syncOperation.account, syncOperation.extras);
+ } catch (RemoteException remoteExc) {
+ Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
+ closeActiveSyncContext(activeSyncContext);
+ increaseBackoffSetting(syncOperation);
+ scheduleSyncOperation(new SyncOperation(syncOperation));
+ } catch (RuntimeException exc) {
+ closeActiveSyncContext(activeSyncContext);
+ Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
+ }
+ }
+
+ private void cancelActiveSyncLocked(Account account, int userId, String authority) {
+ ArrayList<ActiveSyncContext> activeSyncs =
+ new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
+ for (ActiveSyncContext activeSyncContext : activeSyncs) {
+ if (activeSyncContext != null) {
+ // if an account was specified then only cancel the sync if it matches
+ if (account != null) {
+ if (!account.equals(activeSyncContext.mSyncOperation.account)) {
+ continue;
+ }
+ }
+ // if an authority was specified then only cancel the sync if it matches
+ if (authority != null) {
+ if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
+ continue;
+ }
+ }
+ // check if the userid matches
+ if (userId != UserHandle.USER_ALL
+ && userId != activeSyncContext.mSyncOperation.userId) {
+ continue;
+ }
+ runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
+ activeSyncContext);
+ }
+ }
+ }
+
+ private void runSyncFinishedOrCanceledLocked(SyncResult syncResult,
+ ActiveSyncContext activeSyncContext) {
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+
+ if (activeSyncContext.mIsLinkedToDeath) {
+ activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
+ activeSyncContext.mIsLinkedToDeath = false;
+ }
+ closeActiveSyncContext(activeSyncContext);
+
+ final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
+
+ final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
+
+ String historyMessage;
+ int downstreamActivity;
+ int upstreamActivity;
+ if (syncResult != null) {
+ if (isLoggable) {
+ Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
+ + syncOperation + ", result " + syncResult);
+ }
+
+ if (!syncResult.hasError()) {
+ historyMessage = SyncStorageEngine.MESG_SUCCESS;
+ // TODO: set these correctly when the SyncResult is extended to include it
+ downstreamActivity = 0;
+ upstreamActivity = 0;
+ clearBackoffSetting(syncOperation);
+ } else {
+ Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
+ // the operation failed so increase the backoff time
+ if (!syncResult.syncAlreadyInProgress) {
+ increaseBackoffSetting(syncOperation);
+ }
+ // reschedule the sync if so indicated by the syncResult
+ maybeRescheduleSync(syncResult, syncOperation);
+ historyMessage = ContentResolver.syncErrorToString(
+ syncResultToErrorNumber(syncResult));
+ // TODO: set these correctly when the SyncResult is extended to include it
+ downstreamActivity = 0;
+ upstreamActivity = 0;
+ }
+
+ setDelayUntilTime(syncOperation, syncResult.delayUntil);
+ } else {
+ if (isLoggable) {
+ Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
+ }
+ if (activeSyncContext.mSyncAdapter != null) {
+ try {
+ activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext);
+ } catch (RemoteException e) {
+ // we don't need to retry this in this case
+ }
+ }
+ historyMessage = SyncStorageEngine.MESG_CANCELED;
+ downstreamActivity = 0;
+ upstreamActivity = 0;
+ }
+
+ stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
+ upstreamActivity, downstreamActivity, elapsedTime);
+
+ if (syncResult != null && syncResult.tooManyDeletions) {
+ installHandleTooManyDeletesNotification(syncOperation.account,
+ syncOperation.authority, syncResult.stats.numDeletes,
+ syncOperation.userId);
+ } else {
+ mNotificationMgr.cancelAsUser(null,
+ syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(),
+ new UserHandle(syncOperation.userId));
+ }
+
+ if (syncResult != null && syncResult.fullSyncRequested) {
+ scheduleSyncOperation(new SyncOperation(syncOperation.account, syncOperation.userId,
+ syncOperation.reason,
+ syncOperation.syncSource, syncOperation.authority, new Bundle(), 0,
+ syncOperation.backoff, syncOperation.delayUntil,
+ syncOperation.allowParallelSyncs));
+ }
+ // no need to schedule an alarm, as that will be done by our caller.
+ }
+
+ private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
+ activeSyncContext.close();
+ mActiveSyncContexts.remove(activeSyncContext);
+ mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo,
+ activeSyncContext.mSyncOperation.userId);
+ }
+
+ /**
+ * Convert the error-containing SyncResult into the Sync.History error number. Since
+ * the SyncResult may indicate multiple errors at once, this method just returns the
+ * most "serious" error.
+ * @param syncResult the SyncResult from which to read
+ * @return the most "serious" error set in the SyncResult
+ * @throws IllegalStateException if the SyncResult does not indicate any errors.
+ * If SyncResult.error() is true then it is safe to call this.
+ */
+ private int syncResultToErrorNumber(SyncResult syncResult) {
+ if (syncResult.syncAlreadyInProgress)
+ return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
+ if (syncResult.stats.numAuthExceptions > 0)
+ return ContentResolver.SYNC_ERROR_AUTHENTICATION;
+ if (syncResult.stats.numIoExceptions > 0)
+ return ContentResolver.SYNC_ERROR_IO;
+ if (syncResult.stats.numParseExceptions > 0)
+ return ContentResolver.SYNC_ERROR_PARSE;
+ if (syncResult.stats.numConflictDetectedExceptions > 0)
+ return ContentResolver.SYNC_ERROR_CONFLICT;
+ if (syncResult.tooManyDeletions)
+ return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS;
+ if (syncResult.tooManyRetries)
+ return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES;
+ if (syncResult.databaseError)
+ return ContentResolver.SYNC_ERROR_INTERNAL;
+ throw new IllegalStateException("we are not in an error state, " + syncResult);
+ }
+
+ private void manageSyncNotificationLocked() {
+ boolean shouldCancel;
+ boolean shouldInstall;
+
+ if (mActiveSyncContexts.isEmpty()) {
+ mSyncNotificationInfo.startTime = null;
+
+ // we aren't syncing. if the notification is active then remember that we need
+ // to cancel it and then clear out the info
+ shouldCancel = mSyncNotificationInfo.isActive;
+ shouldInstall = false;
+ } else {
+ // we are syncing
+ final long now = SystemClock.elapsedRealtime();
+ if (mSyncNotificationInfo.startTime == null) {
+ mSyncNotificationInfo.startTime = now;
+ }
+
+ // there are three cases:
+ // - the notification is up: do nothing
+ // - the notification is not up but it isn't time yet: don't install
+ // - the notification is not up and it is time: need to install
+
+ if (mSyncNotificationInfo.isActive) {
+ shouldInstall = shouldCancel = false;
+ } else {
+ // it isn't currently up, so there is nothing to cancel
+ shouldCancel = false;
+
+ final boolean timeToShowNotification =
+ now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
+ if (timeToShowNotification) {
+ shouldInstall = true;
+ } else {
+ // show the notification immediately if this is a manual sync
+ shouldInstall = false;
+ for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+ final boolean manualSync = activeSyncContext.mSyncOperation.extras
+ .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+ if (manualSync) {
+ shouldInstall = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (shouldCancel && !shouldInstall) {
+ mNeedSyncActiveNotification = false;
+ sendSyncStateIntent();
+ mSyncNotificationInfo.isActive = false;
+ }
+
+ if (shouldInstall) {
+ mNeedSyncActiveNotification = true;
+ sendSyncStateIntent();
+ mSyncNotificationInfo.isActive = true;
+ }
+ }
+
+ private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime,
+ long nextPendingEventElapsedTime) {
+ // in each of these cases the sync loop will be kicked, which will cause this
+ // method to be called again
+ if (!mDataConnectionIsConnected) return;
+ if (mStorageIsLow) return;
+
+ // When the status bar notification should be raised
+ final long notificationTime =
+ (!mSyncHandler.mSyncNotificationInfo.isActive
+ && mSyncHandler.mSyncNotificationInfo.startTime != null)
+ ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY
+ : Long.MAX_VALUE;
+
+ // When we should consider canceling an active sync
+ long earliestTimeoutTime = Long.MAX_VALUE;
+ for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+ final long currentSyncTimeoutTime =
+ currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is "
+ + currentSyncTimeoutTime);
+ }
+ if (earliestTimeoutTime > currentSyncTimeoutTime) {
+ earliestTimeoutTime = currentSyncTimeoutTime;
+ }
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime);
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime);
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is "
+ + nextPeriodicEventElapsedTime);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is "
+ + nextPendingEventElapsedTime);
+ }
+
+ long alarmTime = Math.min(notificationTime, earliestTimeoutTime);
+ alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime);
+ alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime);
+
+ // Bound the alarm time.
+ final long now = SystemClock.elapsedRealtime();
+ if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, "
+ + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
+ }
+ alarmTime = now + SYNC_ALARM_TIMEOUT_MIN;
+ } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, "
+ + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
+ }
+ alarmTime = now + SYNC_ALARM_TIMEOUT_MAX;
+ }
+
+ // determine if we need to set or cancel the alarm
+ boolean shouldSet = false;
+ boolean shouldCancel = false;
+ final boolean alarmIsActive = mAlarmScheduleTime != null;
+ final boolean needAlarm = alarmTime != Long.MAX_VALUE;
+ if (needAlarm) {
+ if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
+ shouldSet = true;
+ }
+ } else {
+ shouldCancel = alarmIsActive;
+ }
+
+ // set or cancel the alarm as directed
+ ensureAlarmService();
+ if (shouldSet) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time "
+ + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000)
+ + " secs from now");
+ }
+ mAlarmScheduleTime = alarmTime;
+ mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
+ mSyncAlarmIntent);
+ } else if (shouldCancel) {
+ mAlarmScheduleTime = null;
+ mAlarmService.cancel(mSyncAlarmIntent);
+ }
+ }
+
+ private void sendSyncStateIntent() {
+ Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
+ syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
+ syncStateIntent.putExtra("failing", false);
+ mContext.sendBroadcastAsUser(syncStateIntent, UserHandle.OWNER);
+ }
+
+ private void installHandleTooManyDeletesNotification(Account account, String authority,
+ long numDeletes, int userId) {
+ if (mNotificationMgr == null) return;
+
+ final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(
+ authority, 0 /* flags */);
+ if (providerInfo == null) {
+ return;
+ }
+ CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager());
+
+ Intent clickIntent = new Intent(mContext, SyncActivityTooManyDeletes.class);
+ clickIntent.putExtra("account", account);
+ clickIntent.putExtra("authority", authority);
+ clickIntent.putExtra("provider", authorityName.toString());
+ clickIntent.putExtra("numDeletes", numDeletes);
+
+ if (!isActivityAvailable(clickIntent)) {
+ Log.w(TAG, "No activity found to handle too many deletes.");
+ return;
+ }
+
+ final PendingIntent pendingIntent = PendingIntent
+ .getActivityAsUser(mContext, 0, clickIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(userId));
+
+ CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
+ R.string.contentServiceTooManyDeletesNotificationDesc);
+
+ Notification notification =
+ new Notification(R.drawable.stat_notify_sync_error,
+ mContext.getString(R.string.contentServiceSync),
+ System.currentTimeMillis());
+ notification.setLatestEventInfo(mContext,
+ mContext.getString(R.string.contentServiceSyncNotificationTitle),
+ String.format(tooManyDeletesDescFormat.toString(), authorityName),
+ pendingIntent);
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+ mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(),
+ notification, new UserHandle(userId));
+ }
+
+ /**
+ * Checks whether an activity exists on the system image for the given intent.
+ *
+ * @param intent The intent for an activity.
+ * @return Whether or not an activity exists.
+ */
+ private boolean isActivityAvailable(Intent intent) {
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+ int listSize = list.size();
+ for (int i = 0; i < listSize; i++) {
+ ResolveInfo resolveInfo = list.get(i);
+ if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+ != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public long insertStartSyncEvent(SyncOperation syncOperation) {
+ final int source = syncOperation.syncSource;
+ final long now = System.currentTimeMillis();
+
+ EventLog.writeEvent(2720, syncOperation.authority,
+ SyncStorageEngine.EVENT_START, source,
+ syncOperation.account.name.hashCode());
+
+ return mSyncStorageEngine.insertStartSyncEvent(
+ syncOperation.account, syncOperation.userId, syncOperation.reason,
+ syncOperation.authority,
+ now, source, syncOperation.isInitialization(), syncOperation.extras
+ );
+ }
+
+ public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
+ int upstreamActivity, int downstreamActivity, long elapsedTime) {
+ EventLog.writeEvent(2720, syncOperation.authority,
+ SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
+ syncOperation.account.name.hashCode());
+
+ mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
+ resultMessage, downstreamActivity, upstreamActivity);
+ }
+ }
+
+ private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
+ for (ActiveSyncContext sync : mActiveSyncContexts) {
+ if (sync == activeSyncContext) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static class PrintTable {
+ private ArrayList<Object[]> mTable = Lists.newArrayList();
+ private final int mCols;
+
+ PrintTable(int cols) {
+ mCols = cols;
+ }
+
+ void set(int row, int col, Object... values) {
+ if (col + values.length > mCols) {
+ throw new IndexOutOfBoundsException("Table only has " + mCols +
+ " columns. can't set " + values.length + " at column " + col);
+ }
+ for (int i = mTable.size(); i <= row; i++) {
+ final Object[] list = new Object[mCols];
+ mTable.add(list);
+ for (int j = 0; j < mCols; j++) {
+ list[j] = "";
+ }
+ }
+ System.arraycopy(values, 0, mTable.get(row), col, values.length);
+ }
+
+ void writeTo(PrintWriter out) {
+ final String[] formats = new String[mCols];
+ int totalLength = 0;
+ for (int col = 0; col < mCols; ++col) {
+ int maxLength = 0;
+ for (Object[] row : mTable) {
+ final int length = row[col].toString().length();
+ if (length > maxLength) {
+ maxLength = length;
+ }
+ }
+ totalLength += maxLength;
+ formats[col] = String.format("%%-%ds", maxLength);
+ }
+ printRow(out, formats, mTable.get(0));
+ totalLength += (mCols - 1) * 2;
+ for (int i = 0; i < totalLength; ++i) {
+ out.print("-");
+ }
+ out.println();
+ for (int i = 1, mTableSize = mTable.size(); i < mTableSize; i++) {
+ Object[] row = mTable.get(i);
+ printRow(out, formats, row);
+ }
+ }
+
+ private void printRow(PrintWriter out, String[] formats, Object[] row) {
+ for (int j = 0, rowLength = row.length; j < rowLength; j++) {
+ out.printf(String.format(formats[j], row[j].toString()));
+ out.print(" ");
+ }
+ out.println();
+ }
+
+ public int getNumRows() {
+ return mTable.size();
+ }
+ }
+}
diff --git a/services/java/com/android/server/content/SyncOperation.java b/services/java/com/android/server/content/SyncOperation.java
new file mode 100644
index 0000000..eaad982
--- /dev/null
+++ b/services/java/com/android/server/content/SyncOperation.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2010 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.content;
+
+import android.accounts.Account;
+import android.content.pm.PackageManager;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+/**
+ * Value type that represents a sync operation.
+ * @hide
+ */
+public class SyncOperation implements Comparable {
+ public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
+ public static final int REASON_ACCOUNTS_UPDATED = -2;
+ public static final int REASON_SERVICE_CHANGED = -3;
+ public static final int REASON_PERIODIC = -4;
+ public static final int REASON_IS_SYNCABLE = -5;
+ public static final int REASON_SYNC_AUTO = -6;
+ public static final int REASON_MASTER_SYNC_AUTO = -7;
+ public static final int REASON_USER_START = -8;
+
+ private static String[] REASON_NAMES = new String[] {
+ "DataSettingsChanged",
+ "AccountsUpdated",
+ "ServiceChanged",
+ "Periodic",
+ "IsSyncable",
+ "AutoSync",
+ "MasterSyncAuto",
+ "UserStart",
+ };
+
+ public final Account account;
+ public final int userId;
+ public final int reason;
+ public int syncSource;
+ public String authority;
+ public final boolean allowParallelSyncs;
+ public Bundle extras;
+ public final String key;
+ public long earliestRunTime;
+ public boolean expedited;
+ public SyncStorageEngine.PendingOperation pendingOperation;
+ public Long backoff;
+ public long delayUntil;
+ public long effectiveRunTime;
+
+ public SyncOperation(Account account, int userId, int reason, int source, String authority,
+ Bundle extras, long delayInMs, long backoff, long delayUntil,
+ boolean allowParallelSyncs) {
+ this.account = account;
+ this.userId = userId;
+ this.reason = reason;
+ this.syncSource = source;
+ this.authority = authority;
+ this.allowParallelSyncs = allowParallelSyncs;
+ this.extras = new Bundle(extras);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+ this.delayUntil = delayUntil;
+ this.backoff = backoff;
+ final long now = SystemClock.elapsedRealtime();
+ if (delayInMs < 0) {
+ this.expedited = true;
+ this.earliestRunTime = now;
+ } else {
+ this.expedited = false;
+ this.earliestRunTime = now + delayInMs;
+ }
+ updateEffectiveRunTime();
+ this.key = toKey();
+ }
+
+ private void removeFalseExtra(String extraName) {
+ if (!extras.getBoolean(extraName, false)) {
+ extras.remove(extraName);
+ }
+ }
+
+ SyncOperation(SyncOperation other) {
+ this.account = other.account;
+ this.userId = other.userId;
+ this.reason = other.reason;
+ this.syncSource = other.syncSource;
+ this.authority = other.authority;
+ this.extras = new Bundle(other.extras);
+ this.expedited = other.expedited;
+ this.earliestRunTime = SystemClock.elapsedRealtime();
+ this.backoff = other.backoff;
+ this.delayUntil = other.delayUntil;
+ this.allowParallelSyncs = other.allowParallelSyncs;
+ this.updateEffectiveRunTime();
+ this.key = toKey();
+ }
+
+ public String toString() {
+ return dump(null, true);
+ }
+
+ public String dump(PackageManager pm, boolean useOneLine) {
+ StringBuilder sb = new StringBuilder()
+ .append(account.name)
+ .append(" u")
+ .append(userId).append(" (")
+ .append(account.type)
+ .append(")")
+ .append(", ")
+ .append(authority)
+ .append(", ")
+ .append(SyncStorageEngine.SOURCES[syncSource])
+ .append(", earliestRunTime ")
+ .append(earliestRunTime);
+ if (expedited) {
+ sb.append(", EXPEDITED");
+ }
+ sb.append(", reason: ");
+ sb.append(reasonToString(pm, reason));
+ if (!useOneLine && !extras.keySet().isEmpty()) {
+ sb.append("\n ");
+ extrasToStringBuilder(extras, sb);
+ }
+ return sb.toString();
+ }
+
+ public static String reasonToString(PackageManager pm, int reason) {
+ if (reason >= 0) {
+ if (pm != null) {
+ final String[] packages = pm.getPackagesForUid(reason);
+ if (packages != null && packages.length == 1) {
+ return packages[0];
+ }
+ final String name = pm.getNameForUid(reason);
+ if (name != null) {
+ return name;
+ }
+ return String.valueOf(reason);
+ } else {
+ return String.valueOf(reason);
+ }
+ } else {
+ final int index = -reason - 1;
+ if (index >= REASON_NAMES.length) {
+ return String.valueOf(reason);
+ } else {
+ return REASON_NAMES[index];
+ }
+ }
+ }
+
+ public boolean isInitialization() {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+ }
+
+ public boolean isExpedited() {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+ }
+
+ public boolean ignoreBackoff() {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
+ }
+
+ private String toKey() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("authority: ").append(authority);
+ sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
+ + "}");
+ sb.append(" extras: ");
+ extrasToStringBuilder(extras, sb);
+ return sb.toString();
+ }
+
+ public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
+ sb.append("[");
+ for (String key : bundle.keySet()) {
+ sb.append(key).append("=").append(bundle.get(key)).append(" ");
+ }
+ sb.append("]");
+ }
+
+ public void updateEffectiveRunTime() {
+ effectiveRunTime = ignoreBackoff()
+ ? earliestRunTime
+ : Math.max(
+ Math.max(earliestRunTime, delayUntil),
+ backoff);
+ }
+
+ public int compareTo(Object o) {
+ SyncOperation other = (SyncOperation)o;
+
+ if (expedited != other.expedited) {
+ return expedited ? -1 : 1;
+ }
+
+ if (effectiveRunTime == other.effectiveRunTime) {
+ return 0;
+ }
+
+ return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
+ }
+}
diff --git a/services/java/com/android/server/content/SyncQueue.java b/services/java/com/android/server/content/SyncQueue.java
new file mode 100644
index 0000000..951e92c
--- /dev/null
+++ b/services/java/com/android/server/content/SyncQueue.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2010 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.content;
+
+import android.accounts.Account;
+import android.content.pm.PackageManager;
+import android.content.pm.RegisteredServicesCache;
+import android.content.SyncAdapterType;
+import android.content.SyncAdaptersCache;
+import android.content.pm.RegisteredServicesCache.ServiceInfo;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.google.android.collect.Maps;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Queue of pending sync operations. Not inherently thread safe, external
+ * callers are responsible for locking.
+ *
+ * @hide
+ */
+public class SyncQueue {
+ private static final String TAG = "SyncManager";
+ private final SyncStorageEngine mSyncStorageEngine;
+ private final SyncAdaptersCache mSyncAdapters;
+ private final PackageManager mPackageManager;
+
+ // A Map of SyncOperations operationKey -> SyncOperation that is designed for
+ // quick lookup of an enqueued SyncOperation.
+ private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
+
+ public SyncQueue(PackageManager packageManager, SyncStorageEngine syncStorageEngine,
+ final SyncAdaptersCache syncAdapters) {
+ mPackageManager = packageManager;
+ mSyncStorageEngine = syncStorageEngine;
+ mSyncAdapters = syncAdapters;
+ }
+
+ public void addPendingOperations(int userId) {
+ for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) {
+ if (op.userId != userId) continue;
+
+ final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
+ op.account, op.userId, op.authority);
+ final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
+ SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
+ if (syncAdapterInfo == null) {
+ Log.w(TAG, "Missing sync adapter info for authority " + op.authority + ", userId "
+ + op.userId);
+ continue;
+ }
+ SyncOperation syncOperation = new SyncOperation(
+ op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras,
+ 0 /* delay */, backoff != null ? backoff.first : 0,
+ mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
+ syncAdapterInfo.type.allowParallelSyncs());
+ syncOperation.expedited = op.expedited;
+ syncOperation.pendingOperation = op;
+ add(syncOperation, op);
+ }
+ }
+
+ public boolean add(SyncOperation operation) {
+ return add(operation, null /* this is not coming from the database */);
+ }
+
+ private boolean add(SyncOperation operation,
+ SyncStorageEngine.PendingOperation pop) {
+ // - if an operation with the same key exists and this one should run earlier,
+ // update the earliestRunTime of the existing to the new time
+ // - if an operation with the same key exists and if this one should run
+ // later, ignore it
+ // - if no operation exists then add the new one
+ final String operationKey = operation.key;
+ final SyncOperation existingOperation = mOperationsMap.get(operationKey);
+
+ if (existingOperation != null) {
+ boolean changed = false;
+ if (existingOperation.expedited == operation.expedited) {
+ final long newRunTime =
+ Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
+ if (existingOperation.earliestRunTime != newRunTime) {
+ existingOperation.earliestRunTime = newRunTime;
+ changed = true;
+ }
+ } else {
+ if (operation.expedited) {
+ existingOperation.expedited = true;
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ operation.pendingOperation = pop;
+ if (operation.pendingOperation == null) {
+ pop = new SyncStorageEngine.PendingOperation(
+ operation.account, operation.userId, operation.reason, operation.syncSource,
+ operation.authority, operation.extras, operation.expedited);
+ pop = mSyncStorageEngine.insertIntoPending(pop);
+ if (pop == null) {
+ throw new IllegalStateException("error adding pending sync operation "
+ + operation);
+ }
+ operation.pendingOperation = pop;
+ }
+
+ mOperationsMap.put(operationKey, operation);
+ return true;
+ }
+
+ public void removeUser(int userId) {
+ ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
+ for (SyncOperation op : mOperationsMap.values()) {
+ if (op.userId == userId) {
+ opsToRemove.add(op);
+ }
+ }
+
+ for (SyncOperation op : opsToRemove) {
+ remove(op);
+ }
+ }
+
+ /**
+ * Remove the specified operation if it is in the queue.
+ * @param operation the operation to remove
+ */
+ public void remove(SyncOperation operation) {
+ SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
+ if (operationToRemove == null) {
+ return;
+ }
+ if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
+ final String errorMessage = "unable to find pending row for " + operationToRemove;
+ Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+ }
+ }
+
+ public void onBackoffChanged(Account account, int userId, String providerName, long backoff) {
+ // for each op that matches the account and provider update its
+ // backoff and effectiveStartTime
+ for (SyncOperation op : mOperationsMap.values()) {
+ if (op.account.equals(account) && op.authority.equals(providerName)
+ && op.userId == userId) {
+ op.backoff = backoff;
+ op.updateEffectiveRunTime();
+ }
+ }
+ }
+
+ public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
+ // for each op that matches the account and provider update its
+ // delayUntilTime and effectiveStartTime
+ for (SyncOperation op : mOperationsMap.values()) {
+ if (op.account.equals(account) && op.authority.equals(providerName)) {
+ op.delayUntil = delayUntil;
+ op.updateEffectiveRunTime();
+ }
+ }
+ }
+
+ public void remove(Account account, int userId, String authority) {
+ Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
+ while (entries.hasNext()) {
+ Map.Entry<String, SyncOperation> entry = entries.next();
+ SyncOperation syncOperation = entry.getValue();
+ if (account != null && !syncOperation.account.equals(account)) {
+ continue;
+ }
+ if (authority != null && !syncOperation.authority.equals(authority)) {
+ continue;
+ }
+ if (userId != syncOperation.userId) {
+ continue;
+ }
+ entries.remove();
+ if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
+ final String errorMessage = "unable to find pending row for " + syncOperation;
+ Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+ }
+ }
+ }
+
+ public Collection<SyncOperation> getOperations() {
+ return mOperationsMap.values();
+ }
+
+ public void dump(StringBuilder sb) {
+ final long now = SystemClock.elapsedRealtime();
+ sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
+ for (SyncOperation operation : mOperationsMap.values()) {
+ sb.append(" ");
+ if (operation.effectiveRunTime <= now) {
+ sb.append("READY");
+ } else {
+ sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
+ }
+ sb.append(" - ");
+ sb.append(operation.dump(mPackageManager, false)).append("\n");
+ }
+ }
+}
diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java
new file mode 100644
index 0000000..5b8d26f
--- /dev/null
+++ b/services/java/com/android/server/content/SyncStorageEngine.java
@@ -0,0 +1,2303 @@
+/*
+ * Copyright (C) 2009 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.content;
+
+import android.accounts.Account;
+import android.accounts.AccountAndUser;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ISyncStatusObserver;
+import android.content.PeriodicSync;
+import android.content.SyncInfo;
+import android.content.SyncStatusInfo;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+
+/**
+ * Singleton that tracks the sync data and overall sync
+ * history on the device.
+ *
+ * @hide
+ */
+public class SyncStorageEngine extends Handler {
+
+ private static final String TAG = "SyncManager";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_FILE = false;
+
+ private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
+ private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
+ private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
+ private static final String XML_ATTR_ENABLED = "enabled";
+ private static final String XML_ATTR_USER = "user";
+ private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
+
+ private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
+
+ @VisibleForTesting
+ static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
+
+ /** Enum value for a sync start event. */
+ public static final int EVENT_START = 0;
+
+ /** Enum value for a sync stop event. */
+ public static final int EVENT_STOP = 1;
+
+ // TODO: i18n -- grab these out of resources.
+ /** String names for the sync event types. */
+ public static final String[] EVENTS = { "START", "STOP" };
+
+ /** Enum value for a server-initiated sync. */
+ public static final int SOURCE_SERVER = 0;
+
+ /** Enum value for a local-initiated sync. */
+ public static final int SOURCE_LOCAL = 1;
+ /**
+ * Enum value for a poll-based sync (e.g., upon connection to
+ * network)
+ */
+ public static final int SOURCE_POLL = 2;
+
+ /** Enum value for a user-initiated sync. */
+ public static final int SOURCE_USER = 3;
+
+ /** Enum value for a periodic sync. */
+ public static final int SOURCE_PERIODIC = 4;
+
+ public static final long NOT_IN_BACKOFF_MODE = -1;
+
+ // TODO: i18n -- grab these out of resources.
+ /** String names for the sync source types. */
+ public static final String[] SOURCES = { "SERVER",
+ "LOCAL",
+ "POLL",
+ "USER",
+ "PERIODIC" };
+
+ // The MESG column will contain one of these or one of the Error types.
+ public static final String MESG_SUCCESS = "success";
+ public static final String MESG_CANCELED = "canceled";
+
+ public static final int MAX_HISTORY = 100;
+
+ private static final int MSG_WRITE_STATUS = 1;
+ private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
+
+ private static final int MSG_WRITE_STATISTICS = 2;
+ private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
+
+ private static final boolean SYNC_ENABLED_DEFAULT = false;
+
+ // the version of the accounts xml file format
+ private static final int ACCOUNTS_VERSION = 2;
+
+ private static HashMap<String, String> sAuthorityRenames;
+
+ static {
+ sAuthorityRenames = new HashMap<String, String>();
+ sAuthorityRenames.put("contacts", "com.android.contacts");
+ sAuthorityRenames.put("calendar", "com.android.calendar");
+ }
+
+ public static class PendingOperation {
+ final Account account;
+ final int userId;
+ final int reason;
+ final int syncSource;
+ final String authority;
+ final Bundle extras; // note: read-only.
+ final boolean expedited;
+
+ int authorityId;
+ byte[] flatExtras;
+
+ PendingOperation(Account account, int userId, int reason,int source,
+ String authority, Bundle extras, boolean expedited) {
+ this.account = account;
+ this.userId = userId;
+ this.syncSource = source;
+ this.reason = reason;
+ this.authority = authority;
+ this.extras = extras != null ? new Bundle(extras) : extras;
+ this.expedited = expedited;
+ this.authorityId = -1;
+ }
+
+ PendingOperation(PendingOperation other) {
+ this.account = other.account;
+ this.userId = other.userId;
+ this.reason = other.reason;
+ this.syncSource = other.syncSource;
+ this.authority = other.authority;
+ this.extras = other.extras;
+ this.authorityId = other.authorityId;
+ this.expedited = other.expedited;
+ }
+ }
+
+ static class AccountInfo {
+ final AccountAndUser accountAndUser;
+ final HashMap<String, AuthorityInfo> authorities =
+ new HashMap<String, AuthorityInfo>();
+
+ AccountInfo(AccountAndUser accountAndUser) {
+ this.accountAndUser = accountAndUser;
+ }
+ }
+
+ public static class AuthorityInfo {
+ final Account account;
+ final int userId;
+ final String authority;
+ final int ident;
+ boolean enabled;
+ int syncable;
+ long backoffTime;
+ long backoffDelay;
+ long delayUntil;
+ final ArrayList<Pair<Bundle, Long>> periodicSyncs;
+
+ /**
+ * Copy constructor for making deep-ish copies. Only the bundles stored
+ * in periodic syncs can make unexpected changes.
+ *
+ * @param toCopy AuthorityInfo to be copied.
+ */
+ AuthorityInfo(AuthorityInfo toCopy) {
+ account = toCopy.account;
+ userId = toCopy.userId;
+ authority = toCopy.authority;
+ ident = toCopy.ident;
+ enabled = toCopy.enabled;
+ syncable = toCopy.syncable;
+ backoffTime = toCopy.backoffTime;
+ backoffDelay = toCopy.backoffDelay;
+ delayUntil = toCopy.delayUntil;
+ periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
+ for (Pair<Bundle, Long> sync : toCopy.periodicSyncs) {
+ // Still not a perfect copy, because we are just copying the mappings.
+ periodicSyncs.add(Pair.create(new Bundle(sync.first), sync.second));
+ }
+ }
+
+ AuthorityInfo(Account account, int userId, String authority, int ident) {
+ this.account = account;
+ this.userId = userId;
+ this.authority = authority;
+ this.ident = ident;
+ enabled = SYNC_ENABLED_DEFAULT;
+ syncable = -1; // default to "unknown"
+ backoffTime = -1; // if < 0 then we aren't in backoff mode
+ backoffDelay = -1; // if < 0 then we aren't in backoff mode
+ periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
+ periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS));
+ }
+ }
+
+ public static class SyncHistoryItem {
+ int authorityId;
+ int historyId;
+ long eventTime;
+ long elapsedTime;
+ int source;
+ int event;
+ long upstreamActivity;
+ long downstreamActivity;
+ String mesg;
+ boolean initialization;
+ Bundle extras;
+ int reason;
+ }
+
+ public static class DayStats {
+ public final int day;
+ public int successCount;
+ public long successTime;
+ public int failureCount;
+ public long failureTime;
+
+ public DayStats(int day) {
+ this.day = day;
+ }
+ }
+
+ interface OnSyncRequestListener {
+ /**
+ * Called when a sync is needed on an account(s) due to some change in state.
+ * @param account
+ * @param userId
+ * @param reason
+ * @param authority
+ * @param extras
+ */
+ public void onSyncRequest(Account account, int userId, int reason, String authority,
+ Bundle extras);
+ }
+
+ // Primary list of all syncable authorities. Also our global lock.
+ private final SparseArray<AuthorityInfo> mAuthorities =
+ new SparseArray<AuthorityInfo>();
+
+ private final HashMap<AccountAndUser, AccountInfo> mAccounts
+ = new HashMap<AccountAndUser, AccountInfo>();
+
+ private final ArrayList<PendingOperation> mPendingOperations =
+ new ArrayList<PendingOperation>();
+
+ private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs
+ = new SparseArray<ArrayList<SyncInfo>>();
+
+ private final SparseArray<SyncStatusInfo> mSyncStatus =
+ new SparseArray<SyncStatusInfo>();
+
+ private final ArrayList<SyncHistoryItem> mSyncHistory =
+ new ArrayList<SyncHistoryItem>();
+
+ private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
+ = new RemoteCallbackList<ISyncStatusObserver>();
+
+ private int mNextAuthorityId = 0;
+
+ // We keep 4 weeks of stats.
+ private final DayStats[] mDayStats = new DayStats[7*4];
+ private final Calendar mCal;
+ private int mYear;
+ private int mYearInDays;
+
+ private final Context mContext;
+
+ private static volatile SyncStorageEngine sSyncStorageEngine = null;
+
+ private int mSyncRandomOffset;
+
+ /**
+ * This file contains the core engine state: all accounts and the
+ * settings for them. It must never be lost, and should be changed
+ * infrequently, so it is stored as an XML file.
+ */
+ private final AtomicFile mAccountInfoFile;
+
+ /**
+ * This file contains the current sync status. We would like to retain
+ * it across boots, but its loss is not the end of the world, so we store
+ * this information as binary data.
+ */
+ private final AtomicFile mStatusFile;
+
+ /**
+ * This file contains sync statistics. This is purely debugging information
+ * so is written infrequently and can be thrown away at any time.
+ */
+ private final AtomicFile mStatisticsFile;
+
+ /**
+ * This file contains the pending sync operations. It is a binary file,
+ * which must be updated every time an operation is added or removed,
+ * so we have special handling of it.
+ */
+ private final AtomicFile mPendingFile;
+ private static final int PENDING_FINISH_TO_WRITE = 4;
+ private int mNumPendingFinished = 0;
+
+ private int mNextHistoryId = 0;
+ private SparseArray<Boolean> mMasterSyncAutomatically = new SparseArray<Boolean>();
+ private boolean mDefaultMasterSyncAutomatically;
+
+ private OnSyncRequestListener mSyncRequestListener;
+
+ private SyncStorageEngine(Context context, File dataDir) {
+ mContext = context;
+ sSyncStorageEngine = this;
+
+ mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
+
+ mDefaultMasterSyncAutomatically = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically);
+
+ File systemDir = new File(dataDir, "system");
+ File syncDir = new File(systemDir, "sync");
+ syncDir.mkdirs();
+ mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
+ mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
+ mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
+
+ readAccountInfoLocked();
+ readStatusLocked();
+ readPendingOperationsLocked();
+ readStatisticsLocked();
+ readAndDeleteLegacyAccountInfoLocked();
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ writePendingOperationsLocked();
+ writeStatisticsLocked();
+ }
+
+ public static SyncStorageEngine newTestInstance(Context context) {
+ return new SyncStorageEngine(context, context.getFilesDir());
+ }
+
+ public static void init(Context context) {
+ if (sSyncStorageEngine != null) {
+ return;
+ }
+ // This call will return the correct directory whether Encrypted File Systems is
+ // enabled or not.
+ File dataDir = Environment.getSecureDataDirectory();
+ sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
+ }
+
+ public static SyncStorageEngine getSingleton() {
+ if (sSyncStorageEngine == null) {
+ throw new IllegalStateException("not initialized");
+ }
+ return sSyncStorageEngine;
+ }
+
+ protected void setOnSyncRequestListener(OnSyncRequestListener listener) {
+ if (mSyncRequestListener == null) {
+ mSyncRequestListener = listener;
+ }
+ }
+
+ @Override public void handleMessage(Message msg) {
+ if (msg.what == MSG_WRITE_STATUS) {
+ synchronized (mAuthorities) {
+ writeStatusLocked();
+ }
+ } else if (msg.what == MSG_WRITE_STATISTICS) {
+ synchronized (mAuthorities) {
+ writeStatisticsLocked();
+ }
+ }
+ }
+
+ public int getSyncRandomOffset() {
+ return mSyncRandomOffset;
+ }
+
+ public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
+ synchronized (mAuthorities) {
+ mChangeListeners.register(callback, mask);
+ }
+ }
+
+ public void removeStatusChangeListener(ISyncStatusObserver callback) {
+ synchronized (mAuthorities) {
+ mChangeListeners.unregister(callback);
+ }
+ }
+
+ private void reportChange(int which) {
+ ArrayList<ISyncStatusObserver> reports = null;
+ synchronized (mAuthorities) {
+ int i = mChangeListeners.beginBroadcast();
+ while (i > 0) {
+ i--;
+ Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
+ if ((which & mask.intValue()) == 0) {
+ continue;
+ }
+ if (reports == null) {
+ reports = new ArrayList<ISyncStatusObserver>(i);
+ }
+ reports.add(mChangeListeners.getBroadcastItem(i));
+ }
+ mChangeListeners.finishBroadcast();
+ }
+
+ if (DEBUG) {
+ Log.v(TAG, "reportChange " + which + " to: " + reports);
+ }
+
+ if (reports != null) {
+ int i = reports.size();
+ while (i > 0) {
+ i--;
+ try {
+ reports.get(i).onStatusChanged(which);
+ } catch (RemoteException e) {
+ // The remote callback list will take care of this for us.
+ }
+ }
+ }
+ }
+
+ public boolean getSyncAutomatically(Account account, int userId, String providerName) {
+ synchronized (mAuthorities) {
+ if (account != null) {
+ AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+ "getSyncAutomatically");
+ return authority != null && authority.enabled;
+ }
+
+ int i = mAuthorities.size();
+ while (i > 0) {
+ i--;
+ AuthorityInfo authority = mAuthorities.valueAt(i);
+ if (authority.authority.equals(providerName)
+ && authority.userId == userId
+ && authority.enabled) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ public void setSyncAutomatically(Account account, int userId, String providerName,
+ boolean sync) {
+ if (DEBUG) {
+ Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
+ + ", user " + userId + " -> " + sync);
+ }
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
+ false);
+ if (authority.enabled == sync) {
+ if (DEBUG) {
+ Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
+ }
+ return;
+ }
+ authority.enabled = sync;
+ writeAccountInfoLocked();
+ }
+
+ if (sync) {
+ requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName,
+ new Bundle());
+ }
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public int getIsSyncable(Account account, int userId, String providerName) {
+ synchronized (mAuthorities) {
+ if (account != null) {
+ AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+ "getIsSyncable");
+ if (authority == null) {
+ return -1;
+ }
+ return authority.syncable;
+ }
+
+ int i = mAuthorities.size();
+ while (i > 0) {
+ i--;
+ AuthorityInfo authority = mAuthorities.valueAt(i);
+ if (authority.authority.equals(providerName)) {
+ return authority.syncable;
+ }
+ }
+ return -1;
+ }
+ }
+
+ public void setIsSyncable(Account account, int userId, String providerName, int syncable) {
+ if (syncable > 1) {
+ syncable = 1;
+ } else if (syncable < -1) {
+ syncable = -1;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName
+ + ", user " + userId + " -> " + syncable);
+ }
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
+ false);
+ if (authority.syncable == syncable) {
+ if (DEBUG) {
+ Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
+ }
+ return;
+ }
+ authority.syncable = syncable;
+ writeAccountInfoLocked();
+ }
+
+ if (syncable > 0) {
+ requestSync(account, userId, SyncOperation.REASON_IS_SYNCABLE, providerName,
+ new Bundle());
+ }
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public Pair<Long, Long> getBackoff(Account account, int userId, String providerName) {
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+ "getBackoff");
+ if (authority == null || authority.backoffTime < 0) {
+ return null;
+ }
+ return Pair.create(authority.backoffTime, authority.backoffDelay);
+ }
+ }
+
+ public void setBackoff(Account account, int userId, String providerName,
+ long nextSyncTime, long nextDelay) {
+ if (DEBUG) {
+ Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
+ + ", user " + userId
+ + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
+ }
+ boolean changed = false;
+ synchronized (mAuthorities) {
+ if (account == null || providerName == null) {
+ for (AccountInfo accountInfo : mAccounts.values()) {
+ if (account != null && !account.equals(accountInfo.accountAndUser.account)
+ && userId != accountInfo.accountAndUser.userId) {
+ continue;
+ }
+ for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+ if (providerName != null && !providerName.equals(authorityInfo.authority)) {
+ continue;
+ }
+ if (authorityInfo.backoffTime != nextSyncTime
+ || authorityInfo.backoffDelay != nextDelay) {
+ authorityInfo.backoffTime = nextSyncTime;
+ authorityInfo.backoffDelay = nextDelay;
+ changed = true;
+ }
+ }
+ }
+ } else {
+ AuthorityInfo authority =
+ getOrCreateAuthorityLocked(account, userId, providerName, -1 /* ident */,
+ true);
+ if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
+ return;
+ }
+ authority.backoffTime = nextSyncTime;
+ authority.backoffDelay = nextDelay;
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+ }
+
+ public void clearAllBackoffs(SyncQueue syncQueue) {
+ boolean changed = false;
+ synchronized (mAuthorities) {
+ synchronized (syncQueue) {
+ for (AccountInfo accountInfo : mAccounts.values()) {
+ for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+ if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
+ || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
+ if (DEBUG) {
+ Log.v(TAG, "clearAllBackoffs:"
+ + " authority:" + authorityInfo.authority
+ + " account:" + accountInfo.accountAndUser.account.name
+ + " user:" + accountInfo.accountAndUser.userId
+ + " backoffTime was: " + authorityInfo.backoffTime
+ + " backoffDelay was: " + authorityInfo.backoffDelay);
+ }
+ authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
+ authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
+ syncQueue.onBackoffChanged(accountInfo.accountAndUser.account,
+ accountInfo.accountAndUser.userId, authorityInfo.authority, 0);
+ changed = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (changed) {
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+ }
+
+ public void setDelayUntilTime(Account account, int userId, String providerName,
+ long delayUntil) {
+ if (DEBUG) {
+ Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
+ + ", user " + userId + " -> delayUntil " + delayUntil);
+ }
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getOrCreateAuthorityLocked(
+ account, userId, providerName, -1 /* ident */, true);
+ if (authority.delayUntil == delayUntil) {
+ return;
+ }
+ authority.delayUntil = delayUntil;
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public long getDelayUntilTime(Account account, int userId, String providerName) {
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+ "getDelayUntil");
+ if (authority == null) {
+ return 0;
+ }
+ return authority.delayUntil;
+ }
+ }
+
+ private void updateOrRemovePeriodicSync(Account account, int userId, String providerName,
+ Bundle extras,
+ long period, boolean add) {
+ if (period <= 0) {
+ period = 0;
+ }
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ if (DEBUG) {
+ Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", user " + userId
+ + ", provider " + providerName
+ + " -> period " + period + ", extras " + extras);
+ }
+ synchronized (mAuthorities) {
+ try {
+ AuthorityInfo authority =
+ getOrCreateAuthorityLocked(account, userId, providerName, -1, false);
+ if (add) {
+ // add this periodic sync if one with the same extras doesn't already
+ // exist in the periodicSyncs array
+ boolean alreadyPresent = false;
+ for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
+ Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
+ final Bundle existingExtras = syncInfo.first;
+ if (PeriodicSync.syncExtrasEquals(existingExtras, extras)) {
+ if (syncInfo.second == period) {
+ return;
+ }
+ authority.periodicSyncs.set(i, Pair.create(extras, period));
+ alreadyPresent = true;
+ break;
+ }
+ }
+ // if we added an entry to the periodicSyncs array also add an entry to
+ // the periodic syncs status to correspond to it
+ if (!alreadyPresent) {
+ authority.periodicSyncs.add(Pair.create(extras, period));
+ SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+ status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
+ }
+ } else {
+ // remove any periodic syncs that match the authority and extras
+ SyncStatusInfo status = mSyncStatus.get(authority.ident);
+ boolean changed = false;
+ Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
+ int i = 0;
+ while (iterator.hasNext()) {
+ Pair<Bundle, Long> syncInfo = iterator.next();
+ if (PeriodicSync.syncExtrasEquals(syncInfo.first, extras)) {
+ iterator.remove();
+ changed = true;
+ // if we removed an entry from the periodicSyncs array also
+ // remove the corresponding entry from the status
+ if (status != null) {
+ status.removePeriodicSyncTime(i);
+ }
+ } else {
+ i++;
+ }
+ }
+ if (!changed) {
+ return;
+ }
+ }
+ } finally {
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ }
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public void addPeriodicSync(Account account, int userId, String providerName, Bundle extras,
+ long pollFrequency) {
+ updateOrRemovePeriodicSync(account, userId, providerName, extras, pollFrequency,
+ true /* add */);
+ }
+
+ public void removePeriodicSync(Account account, int userId, String providerName,
+ Bundle extras) {
+ updateOrRemovePeriodicSync(account, userId, providerName, extras, 0 /* period, ignored */,
+ false /* remove */);
+ }
+
+ public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) {
+ ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+ "getPeriodicSyncs");
+ if (authority != null) {
+ for (Pair<Bundle, Long> item : authority.periodicSyncs) {
+ syncs.add(new PeriodicSync(account, providerName, item.first,
+ item.second));
+ }
+ }
+ }
+ return syncs;
+ }
+
+ public void setMasterSyncAutomatically(boolean flag, int userId) {
+ synchronized (mAuthorities) {
+ Boolean auto = mMasterSyncAutomatically.get(userId);
+ if (auto != null && (boolean) auto == flag) {
+ return;
+ }
+ mMasterSyncAutomatically.put(userId, flag);
+ writeAccountInfoLocked();
+ }
+ if (flag) {
+ requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null,
+ new Bundle());
+ }
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED);
+ }
+
+ public boolean getMasterSyncAutomatically(int userId) {
+ synchronized (mAuthorities) {
+ Boolean auto = mMasterSyncAutomatically.get(userId);
+ return auto == null ? mDefaultMasterSyncAutomatically : auto;
+ }
+ }
+
+ public AuthorityInfo getOrCreateAuthority(Account account, int userId, String authority) {
+ synchronized (mAuthorities) {
+ return getOrCreateAuthorityLocked(account, userId, authority,
+ -1 /* assign a new identifier if creating a new authority */,
+ true /* write to storage if this results in a change */);
+ }
+ }
+
+ public void removeAuthority(Account account, int userId, String authority) {
+ synchronized (mAuthorities) {
+ removeAuthorityLocked(account, userId, authority, true /* doWrite */);
+ }
+ }
+
+ public AuthorityInfo getAuthority(int authorityId) {
+ synchronized (mAuthorities) {
+ return mAuthorities.get(authorityId);
+ }
+ }
+
+ /**
+ * Returns true if there is currently a sync operation for the given
+ * account or authority actively being processed.
+ */
+ public boolean isSyncActive(Account account, int userId, String authority) {
+ synchronized (mAuthorities) {
+ for (SyncInfo syncInfo : getCurrentSyncs(userId)) {
+ AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
+ if (ainfo != null && ainfo.account.equals(account)
+ && ainfo.authority.equals(authority)
+ && ainfo.userId == userId) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public PendingOperation insertIntoPending(PendingOperation op) {
+ synchronized (mAuthorities) {
+ if (DEBUG) {
+ Log.v(TAG, "insertIntoPending: account=" + op.account
+ + " user=" + op.userId
+ + " auth=" + op.authority
+ + " src=" + op.syncSource
+ + " extras=" + op.extras);
+ }
+
+ AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, op.userId,
+ op.authority,
+ -1 /* desired identifier */,
+ true /* write accounts to storage */);
+ if (authority == null) {
+ return null;
+ }
+
+ op = new PendingOperation(op);
+ op.authorityId = authority.ident;
+ mPendingOperations.add(op);
+ appendPendingOperationLocked(op);
+
+ SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+ status.pending = true;
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
+ return op;
+ }
+
+ public boolean deleteFromPending(PendingOperation op) {
+ boolean res = false;
+ synchronized (mAuthorities) {
+ if (DEBUG) {
+ Log.v(TAG, "deleteFromPending: account=" + op.account
+ + " user=" + op.userId
+ + " auth=" + op.authority
+ + " src=" + op.syncSource
+ + " extras=" + op.extras);
+ }
+ if (mPendingOperations.remove(op)) {
+ if (mPendingOperations.size() == 0
+ || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
+ writePendingOperationsLocked();
+ mNumPendingFinished = 0;
+ } else {
+ mNumPendingFinished++;
+ }
+
+ AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority,
+ "deleteFromPending");
+ if (authority != null) {
+ if (DEBUG) Log.v(TAG, "removing - " + authority);
+ final int N = mPendingOperations.size();
+ boolean morePending = false;
+ for (int i=0; i<N; i++) {
+ PendingOperation cur = mPendingOperations.get(i);
+ if (cur.account.equals(op.account)
+ && cur.authority.equals(op.authority)
+ && cur.userId == op.userId) {
+ morePending = true;
+ break;
+ }
+ }
+
+ if (!morePending) {
+ if (DEBUG) Log.v(TAG, "no more pending!");
+ SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+ status.pending = false;
+ }
+ }
+
+ res = true;
+ }
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
+ return res;
+ }
+
+ /**
+ * Return a copy of the current array of pending operations. The
+ * PendingOperation objects are the real objects stored inside, so that
+ * they can be used with deleteFromPending().
+ */
+ public ArrayList<PendingOperation> getPendingOperations() {
+ synchronized (mAuthorities) {
+ return new ArrayList<PendingOperation>(mPendingOperations);
+ }
+ }
+
+ /**
+ * Return the number of currently pending operations.
+ */
+ public int getPendingOperationCount() {
+ synchronized (mAuthorities) {
+ return mPendingOperations.size();
+ }
+ }
+
+ /**
+ * Called when the set of account has changed, given the new array of
+ * active accounts.
+ */
+ public void doDatabaseCleanup(Account[] accounts, int userId) {
+ synchronized (mAuthorities) {
+ if (DEBUG) Log.v(TAG, "Updating for new accounts...");
+ SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
+ Iterator<AccountInfo> accIt = mAccounts.values().iterator();
+ while (accIt.hasNext()) {
+ AccountInfo acc = accIt.next();
+ if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
+ && acc.accountAndUser.userId == userId) {
+ // This account no longer exists...
+ if (DEBUG) {
+ Log.v(TAG, "Account removed: " + acc.accountAndUser);
+ }
+ for (AuthorityInfo auth : acc.authorities.values()) {
+ removing.put(auth.ident, auth);
+ }
+ accIt.remove();
+ }
+ }
+
+ // Clean out all data structures.
+ int i = removing.size();
+ if (i > 0) {
+ while (i > 0) {
+ i--;
+ int ident = removing.keyAt(i);
+ mAuthorities.remove(ident);
+ int j = mSyncStatus.size();
+ while (j > 0) {
+ j--;
+ if (mSyncStatus.keyAt(j) == ident) {
+ mSyncStatus.remove(mSyncStatus.keyAt(j));
+ }
+ }
+ j = mSyncHistory.size();
+ while (j > 0) {
+ j--;
+ if (mSyncHistory.get(j).authorityId == ident) {
+ mSyncHistory.remove(j);
+ }
+ }
+ }
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ writePendingOperationsLocked();
+ writeStatisticsLocked();
+ }
+ }
+ }
+
+ /**
+ * Called when a sync is starting. Supply a valid ActiveSyncContext with information
+ * about the sync.
+ */
+ public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+ final SyncInfo syncInfo;
+ synchronized (mAuthorities) {
+ if (DEBUG) {
+ Log.v(TAG, "setActiveSync: account="
+ + activeSyncContext.mSyncOperation.account
+ + " auth=" + activeSyncContext.mSyncOperation.authority
+ + " src=" + activeSyncContext.mSyncOperation.syncSource
+ + " extras=" + activeSyncContext.mSyncOperation.extras);
+ }
+ AuthorityInfo authority = getOrCreateAuthorityLocked(
+ activeSyncContext.mSyncOperation.account,
+ activeSyncContext.mSyncOperation.userId,
+ activeSyncContext.mSyncOperation.authority,
+ -1 /* assign a new identifier if creating a new authority */,
+ true /* write to storage if this results in a change */);
+ syncInfo = new SyncInfo(authority.ident,
+ authority.account, authority.authority,
+ activeSyncContext.mStartTime);
+ getCurrentSyncs(authority.userId).add(syncInfo);
+ }
+
+ reportActiveChange();
+ return syncInfo;
+ }
+
+ /**
+ * Called to indicate that a previously active sync is no longer active.
+ */
+ public void removeActiveSync(SyncInfo syncInfo, int userId) {
+ synchronized (mAuthorities) {
+ if (DEBUG) {
+ Log.v(TAG, "removeActiveSync: account=" + syncInfo.account
+ + " user=" + userId
+ + " auth=" + syncInfo.authority);
+ }
+ getCurrentSyncs(userId).remove(syncInfo);
+ }
+
+ reportActiveChange();
+ }
+
+ /**
+ * To allow others to send active change reports, to poke clients.
+ */
+ public void reportActiveChange() {
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
+ }
+
+ /**
+ * Note that sync has started for the given account and authority.
+ */
+ public long insertStartSyncEvent(Account accountName, int userId, int reason,
+ String authorityName, long now, int source, boolean initialization, Bundle extras) {
+ long id;
+ synchronized (mAuthorities) {
+ if (DEBUG) {
+ Log.v(TAG, "insertStartSyncEvent: account=" + accountName + "user=" + userId
+ + " auth=" + authorityName + " source=" + source);
+ }
+ AuthorityInfo authority = getAuthorityLocked(accountName, userId, authorityName,
+ "insertStartSyncEvent");
+ if (authority == null) {
+ return -1;
+ }
+ SyncHistoryItem item = new SyncHistoryItem();
+ item.initialization = initialization;
+ item.authorityId = authority.ident;
+ item.historyId = mNextHistoryId++;
+ if (mNextHistoryId < 0) mNextHistoryId = 0;
+ item.eventTime = now;
+ item.source = source;
+ item.reason = reason;
+ item.extras = extras;
+ item.event = EVENT_START;
+ mSyncHistory.add(0, item);
+ while (mSyncHistory.size() > MAX_HISTORY) {
+ mSyncHistory.remove(mSyncHistory.size()-1);
+ }
+ id = item.historyId;
+ if (DEBUG) Log.v(TAG, "returning historyId " + id);
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
+ return id;
+ }
+
+ public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
+ long downstreamActivity, long upstreamActivity) {
+ synchronized (mAuthorities) {
+ if (DEBUG) {
+ Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
+ }
+ SyncHistoryItem item = null;
+ int i = mSyncHistory.size();
+ while (i > 0) {
+ i--;
+ item = mSyncHistory.get(i);
+ if (item.historyId == historyId) {
+ break;
+ }
+ item = null;
+ }
+
+ if (item == null) {
+ Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
+ return;
+ }
+
+ item.elapsedTime = elapsedTime;
+ item.event = EVENT_STOP;
+ item.mesg = resultMessage;
+ item.downstreamActivity = downstreamActivity;
+ item.upstreamActivity = upstreamActivity;
+
+ SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
+
+ status.numSyncs++;
+ status.totalElapsedTime += elapsedTime;
+ switch (item.source) {
+ case SOURCE_LOCAL:
+ status.numSourceLocal++;
+ break;
+ case SOURCE_POLL:
+ status.numSourcePoll++;
+ break;
+ case SOURCE_USER:
+ status.numSourceUser++;
+ break;
+ case SOURCE_SERVER:
+ status.numSourceServer++;
+ break;
+ case SOURCE_PERIODIC:
+ status.numSourcePeriodic++;
+ break;
+ }
+
+ boolean writeStatisticsNow = false;
+ int day = getCurrentDayLocked();
+ if (mDayStats[0] == null) {
+ mDayStats[0] = new DayStats(day);
+ } else if (day != mDayStats[0].day) {
+ System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
+ mDayStats[0] = new DayStats(day);
+ writeStatisticsNow = true;
+ } else if (mDayStats[0] == null) {
+ }
+ final DayStats ds = mDayStats[0];
+
+ final long lastSyncTime = (item.eventTime + elapsedTime);
+ boolean writeStatusNow = false;
+ if (MESG_SUCCESS.equals(resultMessage)) {
+ // - if successful, update the successful columns
+ if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
+ writeStatusNow = true;
+ }
+ status.lastSuccessTime = lastSyncTime;
+ status.lastSuccessSource = item.source;
+ status.lastFailureTime = 0;
+ status.lastFailureSource = -1;
+ status.lastFailureMesg = null;
+ status.initialFailureTime = 0;
+ ds.successCount++;
+ ds.successTime += elapsedTime;
+ } else if (!MESG_CANCELED.equals(resultMessage)) {
+ if (status.lastFailureTime == 0) {
+ writeStatusNow = true;
+ }
+ status.lastFailureTime = lastSyncTime;
+ status.lastFailureSource = item.source;
+ status.lastFailureMesg = resultMessage;
+ if (status.initialFailureTime == 0) {
+ status.initialFailureTime = lastSyncTime;
+ }
+ ds.failureCount++;
+ ds.failureTime += elapsedTime;
+ }
+
+ if (writeStatusNow) {
+ writeStatusLocked();
+ } else if (!hasMessages(MSG_WRITE_STATUS)) {
+ sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
+ WRITE_STATUS_DELAY);
+ }
+ if (writeStatisticsNow) {
+ writeStatisticsLocked();
+ } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
+ sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
+ WRITE_STATISTICS_DELAY);
+ }
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
+ }
+
+ /**
+ * Return a list of the currently active syncs. Note that the returned items are the
+ * real, live active sync objects, so be careful what you do with it.
+ */
+ public List<SyncInfo> getCurrentSyncs(int userId) {
+ synchronized (mAuthorities) {
+ ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
+ if (syncs == null) {
+ syncs = new ArrayList<SyncInfo>();
+ mCurrentSyncs.put(userId, syncs);
+ }
+ return syncs;
+ }
+ }
+
+ /**
+ * Return an array of the current sync status for all authorities. Note
+ * that the objects inside the array are the real, live status objects,
+ * so be careful what you do with them.
+ */
+ public ArrayList<SyncStatusInfo> getSyncStatus() {
+ synchronized (mAuthorities) {
+ final int N = mSyncStatus.size();
+ ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
+ for (int i=0; i<N; i++) {
+ ops.add(mSyncStatus.valueAt(i));
+ }
+ return ops;
+ }
+ }
+
+ /**
+ * Return an array of the current authorities. Note
+ * that the objects inside the array are the real, live objects,
+ * so be careful what you do with them.
+ */
+ public ArrayList<AuthorityInfo> getAuthorities() {
+ synchronized (mAuthorities) {
+ final int N = mAuthorities.size();
+ ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N);
+ for (int i=0; i<N; i++) {
+ // Make deep copy because AuthorityInfo syncs are liable to change.
+ infos.add(new AuthorityInfo(mAuthorities.valueAt(i)));
+ }
+ return infos;
+ }
+ }
+
+ /**
+ * Returns the status that matches the authority and account.
+ *
+ * @param account the account we want to check
+ * @param authority the authority whose row should be selected
+ * @return the SyncStatusInfo for the authority
+ */
+ public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId,
+ String authority) {
+ if (account == null || authority == null) {
+ throw new IllegalArgumentException();
+ }
+ synchronized (mAuthorities) {
+ final int N = mSyncStatus.size();
+ for (int i=0; i<N; i++) {
+ SyncStatusInfo cur = mSyncStatus.valueAt(i);
+ AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
+
+ if (ainfo != null && ainfo.authority.equals(authority)
+ && ainfo.userId == userId
+ && account.equals(ainfo.account)) {
+ return cur;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Return true if the pending status is true of any matching authorities.
+ */
+ public boolean isSyncPending(Account account, int userId, String authority) {
+ synchronized (mAuthorities) {
+ final int N = mSyncStatus.size();
+ for (int i=0; i<N; i++) {
+ SyncStatusInfo cur = mSyncStatus.valueAt(i);
+ AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
+ if (ainfo == null) {
+ continue;
+ }
+ if (userId != ainfo.userId) {
+ continue;
+ }
+ if (account != null && !ainfo.account.equals(account)) {
+ continue;
+ }
+ if (ainfo.authority.equals(authority) && cur.pending) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Return an array of the current sync status for all authorities. Note
+ * that the objects inside the array are the real, live status objects,
+ * so be careful what you do with them.
+ */
+ public ArrayList<SyncHistoryItem> getSyncHistory() {
+ synchronized (mAuthorities) {
+ final int N = mSyncHistory.size();
+ ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
+ for (int i=0; i<N; i++) {
+ items.add(mSyncHistory.get(i));
+ }
+ return items;
+ }
+ }
+
+ /**
+ * Return an array of the current per-day statistics. Note
+ * that the objects inside the array are the real, live status objects,
+ * so be careful what you do with them.
+ */
+ public DayStats[] getDayStatistics() {
+ synchronized (mAuthorities) {
+ DayStats[] ds = new DayStats[mDayStats.length];
+ System.arraycopy(mDayStats, 0, ds, 0, ds.length);
+ return ds;
+ }
+ }
+
+ private int getCurrentDayLocked() {
+ mCal.setTimeInMillis(System.currentTimeMillis());
+ final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
+ if (mYear != mCal.get(Calendar.YEAR)) {
+ mYear = mCal.get(Calendar.YEAR);
+ mCal.clear();
+ mCal.set(Calendar.YEAR, mYear);
+ mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
+ }
+ return dayOfYear + mYearInDays;
+ }
+
+ /**
+ * Retrieve an authority, returning null if one does not exist.
+ *
+ * @param accountName The name of the account for the authority.
+ * @param authorityName The name of the authority itself.
+ * @param tag If non-null, this will be used in a log message if the
+ * requested authority does not exist.
+ */
+ private AuthorityInfo getAuthorityLocked(Account accountName, int userId, String authorityName,
+ String tag) {
+ AccountAndUser au = new AccountAndUser(accountName, userId);
+ AccountInfo accountInfo = mAccounts.get(au);
+ if (accountInfo == null) {
+ if (tag != null) {
+ if (DEBUG) {
+ Log.v(TAG, tag + ": unknown account " + au);
+ }
+ }
+ return null;
+ }
+ AuthorityInfo authority = accountInfo.authorities.get(authorityName);
+ if (authority == null) {
+ if (tag != null) {
+ if (DEBUG) {
+ Log.v(TAG, tag + ": unknown authority " + authorityName);
+ }
+ }
+ return null;
+ }
+
+ return authority;
+ }
+
+ private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId,
+ String authorityName, int ident, boolean doWrite) {
+ AccountAndUser au = new AccountAndUser(accountName, userId);
+ AccountInfo account = mAccounts.get(au);
+ if (account == null) {
+ account = new AccountInfo(au);
+ mAccounts.put(au, account);
+ }
+ AuthorityInfo authority = account.authorities.get(authorityName);
+ if (authority == null) {
+ if (ident < 0) {
+ ident = mNextAuthorityId;
+ mNextAuthorityId++;
+ doWrite = true;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "created a new AuthorityInfo for " + accountName
+ + ", user " + userId
+ + ", provider " + authorityName);
+ }
+ authority = new AuthorityInfo(accountName, userId, authorityName, ident);
+ account.authorities.put(authorityName, authority);
+ mAuthorities.put(ident, authority);
+ if (doWrite) {
+ writeAccountInfoLocked();
+ }
+ }
+
+ return authority;
+ }
+
+ private void removeAuthorityLocked(Account account, int userId, String authorityName,
+ boolean doWrite) {
+ AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
+ if (accountInfo != null) {
+ final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
+ if (authorityInfo != null) {
+ mAuthorities.remove(authorityInfo.ident);
+ if (doWrite) {
+ writeAccountInfoLocked();
+ }
+ }
+ }
+ }
+
+ public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) {
+ synchronized (mAuthorities) {
+ return getOrCreateSyncStatusLocked(authority.ident);
+ }
+ }
+
+ private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
+ SyncStatusInfo status = mSyncStatus.get(authorityId);
+ if (status == null) {
+ status = new SyncStatusInfo(authorityId);
+ mSyncStatus.put(authorityId, status);
+ }
+ return status;
+ }
+
+ public void writeAllState() {
+ synchronized (mAuthorities) {
+ // Account info is always written so no need to do it here.
+
+ if (mNumPendingFinished > 0) {
+ // Only write these if they are out of date.
+ writePendingOperationsLocked();
+ }
+
+ // Just always write these... they are likely out of date.
+ writeStatusLocked();
+ writeStatisticsLocked();
+ }
+ }
+
+ /**
+ * public for testing
+ */
+ public void clearAndReadState() {
+ synchronized (mAuthorities) {
+ mAuthorities.clear();
+ mAccounts.clear();
+ mPendingOperations.clear();
+ mSyncStatus.clear();
+ mSyncHistory.clear();
+
+ readAccountInfoLocked();
+ readStatusLocked();
+ readPendingOperationsLocked();
+ readStatisticsLocked();
+ readAndDeleteLegacyAccountInfoLocked();
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ writePendingOperationsLocked();
+ writeStatisticsLocked();
+ }
+ }
+
+ /**
+ * Read all account information back in to the initial engine state.
+ */
+ private void readAccountInfoLocked() {
+ int highestAuthorityId = -1;
+ FileInputStream fis = null;
+ try {
+ fis = mAccountInfoFile.openRead();
+ if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, null);
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG) {
+ eventType = parser.next();
+ }
+ String tagName = parser.getName();
+ if ("accounts".equals(tagName)) {
+ String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES);
+ String versionString = parser.getAttributeValue(null, "version");
+ int version;
+ try {
+ version = (versionString == null) ? 0 : Integer.parseInt(versionString);
+ } catch (NumberFormatException e) {
+ version = 0;
+ }
+ String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID);
+ try {
+ int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
+ mNextAuthorityId = Math.max(mNextAuthorityId, id);
+ } catch (NumberFormatException e) {
+ // don't care
+ }
+ String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
+ try {
+ mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
+ } catch (NumberFormatException e) {
+ mSyncRandomOffset = 0;
+ }
+ if (mSyncRandomOffset == 0) {
+ Random random = new Random(System.currentTimeMillis());
+ mSyncRandomOffset = random.nextInt(86400);
+ }
+ mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
+ eventType = parser.next();
+ AuthorityInfo authority = null;
+ Pair<Bundle, Long> periodicSync = null;
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ tagName = parser.getName();
+ if (parser.getDepth() == 2) {
+ if ("authority".equals(tagName)) {
+ authority = parseAuthority(parser, version);
+ periodicSync = null;
+ if (authority.ident > highestAuthorityId) {
+ highestAuthorityId = authority.ident;
+ }
+ } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
+ parseListenForTickles(parser);
+ }
+ } else if (parser.getDepth() == 3) {
+ if ("periodicSync".equals(tagName) && authority != null) {
+ periodicSync = parsePeriodicSync(parser, authority);
+ }
+ } else if (parser.getDepth() == 4 && periodicSync != null) {
+ if ("extra".equals(tagName)) {
+ parseExtra(parser, periodicSync);
+ }
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Error reading accounts", e);
+ return;
+ } catch (java.io.IOException e) {
+ if (fis == null) Log.i(TAG, "No initial accounts");
+ else Log.w(TAG, "Error reading accounts", e);
+ return;
+ } finally {
+ mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (java.io.IOException e1) {
+ }
+ }
+ }
+
+ maybeMigrateSettingsForRenamedAuthorities();
+ }
+
+ /**
+ * some authority names have changed. copy over their settings and delete the old ones
+ * @return true if a change was made
+ */
+ private boolean maybeMigrateSettingsForRenamedAuthorities() {
+ boolean writeNeeded = false;
+
+ ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
+ final int N = mAuthorities.size();
+ for (int i=0; i<N; i++) {
+ AuthorityInfo authority = mAuthorities.valueAt(i);
+ // skip this authority if it isn't one of the renamed ones
+ final String newAuthorityName = sAuthorityRenames.get(authority.authority);
+ if (newAuthorityName == null) {
+ continue;
+ }
+
+ // remember this authority so we can remove it later. we can't remove it
+ // now without messing up this loop iteration
+ authoritiesToRemove.add(authority);
+
+ // this authority isn't enabled, no need to copy it to the new authority name since
+ // the default is "disabled"
+ if (!authority.enabled) {
+ continue;
+ }
+
+ // if we already have a record of this new authority then don't copy over the settings
+ if (getAuthorityLocked(authority.account, authority.userId, newAuthorityName, "cleanup")
+ != null) {
+ continue;
+ }
+
+ AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
+ authority.userId, newAuthorityName, -1 /* ident */, false /* doWrite */);
+ newAuthority.enabled = true;
+ writeNeeded = true;
+ }
+
+ for (AuthorityInfo authorityInfo : authoritiesToRemove) {
+ removeAuthorityLocked(authorityInfo.account, authorityInfo.userId,
+ authorityInfo.authority, false /* doWrite */);
+ writeNeeded = true;
+ }
+
+ return writeNeeded;
+ }
+
+ private void parseListenForTickles(XmlPullParser parser) {
+ String user = parser.getAttributeValue(null, XML_ATTR_USER);
+ int userId = 0;
+ try {
+ userId = Integer.parseInt(user);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the user for listen-for-tickles", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the user in listen-for-tickles is null", e);
+ }
+ String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
+ boolean listen = enabled == null || Boolean.parseBoolean(enabled);
+ mMasterSyncAutomatically.put(userId, listen);
+ }
+
+ private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
+ AuthorityInfo authority = null;
+ int id = -1;
+ try {
+ id = Integer.parseInt(parser.getAttributeValue(
+ null, "id"));
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the id of the authority", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the id of the authority is null", e);
+ }
+ if (id >= 0) {
+ String authorityName = parser.getAttributeValue(null, "authority");
+ String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
+ String syncable = parser.getAttributeValue(null, "syncable");
+ String accountName = parser.getAttributeValue(null, "account");
+ String accountType = parser.getAttributeValue(null, "type");
+ String user = parser.getAttributeValue(null, XML_ATTR_USER);
+ int userId = user == null ? 0 : Integer.parseInt(user);
+ if (accountType == null) {
+ accountType = "com.google";
+ syncable = "unknown";
+ }
+ authority = mAuthorities.get(id);
+ if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
+ + accountName + " auth=" + authorityName
+ + " user=" + userId
+ + " enabled=" + enabled
+ + " syncable=" + syncable);
+ if (authority == null) {
+ if (DEBUG_FILE) Log.v(TAG, "Creating entry");
+ authority = getOrCreateAuthorityLocked(
+ new Account(accountName, accountType), userId, authorityName, id, false);
+ // If the version is 0 then we are upgrading from a file format that did not
+ // know about periodic syncs. In that case don't clear the list since we
+ // want the default, which is a daily periodioc sync.
+ // Otherwise clear out this default list since we will populate it later with
+ // the periodic sync descriptions that are read from the configuration file.
+ if (version > 0) {
+ authority.periodicSyncs.clear();
+ }
+ }
+ if (authority != null) {
+ authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
+ if ("unknown".equals(syncable)) {
+ authority.syncable = -1;
+ } else {
+ authority.syncable =
+ (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
+ }
+ } else {
+ Log.w(TAG, "Failure adding authority: account="
+ + accountName + " auth=" + authorityName
+ + " enabled=" + enabled
+ + " syncable=" + syncable);
+ }
+ }
+
+ return authority;
+ }
+
+ private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+ Bundle extras = new Bundle();
+ String periodValue = parser.getAttributeValue(null, "period");
+ final long period;
+ try {
+ period = Long.parseLong(periodValue);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the period of a periodic sync", e);
+ return null;
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the period of a periodic sync is null", e);
+ return null;
+ }
+ final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
+ authority.periodicSyncs.add(periodicSync);
+
+ return periodicSync;
+ }
+
+ private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
+ final Bundle extras = periodicSync.first;
+ String name = parser.getAttributeValue(null, "name");
+ String type = parser.getAttributeValue(null, "type");
+ String value1 = parser.getAttributeValue(null, "value1");
+ String value2 = parser.getAttributeValue(null, "value2");
+
+ try {
+ if ("long".equals(type)) {
+ extras.putLong(name, Long.parseLong(value1));
+ } else if ("integer".equals(type)) {
+ extras.putInt(name, Integer.parseInt(value1));
+ } else if ("double".equals(type)) {
+ extras.putDouble(name, Double.parseDouble(value1));
+ } else if ("float".equals(type)) {
+ extras.putFloat(name, Float.parseFloat(value1));
+ } else if ("boolean".equals(type)) {
+ extras.putBoolean(name, Boolean.parseBoolean(value1));
+ } else if ("string".equals(type)) {
+ extras.putString(name, value1);
+ } else if ("account".equals(type)) {
+ extras.putParcelable(name, new Account(value1, value2));
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ }
+ }
+
+ /**
+ * Write all account information to the account file.
+ */
+ private void writeAccountInfoLocked() {
+ if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
+ FileOutputStream fos = null;
+
+ try {
+ fos = mAccountInfoFile.startWrite();
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, "utf-8");
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ out.startTag(null, "accounts");
+ out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
+ out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
+ out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
+
+ // Write the Sync Automatically flags for each user
+ final int M = mMasterSyncAutomatically.size();
+ for (int m = 0; m < M; m++) {
+ int userId = mMasterSyncAutomatically.keyAt(m);
+ Boolean listen = mMasterSyncAutomatically.valueAt(m);
+ out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES);
+ out.attribute(null, XML_ATTR_USER, Integer.toString(userId));
+ out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen));
+ out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES);
+ }
+
+ final int N = mAuthorities.size();
+ for (int i=0; i<N; i++) {
+ AuthorityInfo authority = mAuthorities.valueAt(i);
+ out.startTag(null, "authority");
+ out.attribute(null, "id", Integer.toString(authority.ident));
+ out.attribute(null, "account", authority.account.name);
+ out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId));
+ out.attribute(null, "type", authority.account.type);
+ out.attribute(null, "authority", authority.authority);
+ out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
+ if (authority.syncable < 0) {
+ out.attribute(null, "syncable", "unknown");
+ } else {
+ out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
+ }
+ for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
+ out.startTag(null, "periodicSync");
+ out.attribute(null, "period", Long.toString(periodicSync.second));
+ final Bundle extras = periodicSync.first;
+ for (String key : extras.keySet()) {
+ out.startTag(null, "extra");
+ out.attribute(null, "name", key);
+ final Object value = extras.get(key);
+ if (value instanceof Long) {
+ out.attribute(null, "type", "long");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Integer) {
+ out.attribute(null, "type", "integer");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Boolean) {
+ out.attribute(null, "type", "boolean");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Float) {
+ out.attribute(null, "type", "float");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Double) {
+ out.attribute(null, "type", "double");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof String) {
+ out.attribute(null, "type", "string");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Account) {
+ out.attribute(null, "type", "account");
+ out.attribute(null, "value1", ((Account)value).name);
+ out.attribute(null, "value2", ((Account)value).type);
+ }
+ out.endTag(null, "extra");
+ }
+ out.endTag(null, "periodicSync");
+ }
+ out.endTag(null, "authority");
+ }
+
+ out.endTag(null, "accounts");
+
+ out.endDocument();
+
+ mAccountInfoFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Log.w(TAG, "Error writing accounts", e1);
+ if (fos != null) {
+ mAccountInfoFile.failWrite(fos);
+ }
+ }
+ }
+
+ static int getIntColumn(Cursor c, String name) {
+ return c.getInt(c.getColumnIndex(name));
+ }
+
+ static long getLongColumn(Cursor c, String name) {
+ return c.getLong(c.getColumnIndex(name));
+ }
+
+ /**
+ * Load sync engine state from the old syncmanager database, and then
+ * erase it. Note that we don't deal with pending operations, active
+ * sync, or history.
+ */
+ private void readAndDeleteLegacyAccountInfoLocked() {
+ // Look for old database to initialize from.
+ File file = mContext.getDatabasePath("syncmanager.db");
+ if (!file.exists()) {
+ return;
+ }
+ String path = file.getPath();
+ SQLiteDatabase db = null;
+ try {
+ db = SQLiteDatabase.openDatabase(path, null,
+ SQLiteDatabase.OPEN_READONLY);
+ } catch (SQLiteException e) {
+ }
+
+ if (db != null) {
+ final boolean hasType = db.getVersion() >= 11;
+
+ // Copy in all of the status information, as well as accounts.
+ if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables("stats, status");
+ HashMap<String,String> map = new HashMap<String,String>();
+ map.put("_id", "status._id as _id");
+ map.put("account", "stats.account as account");
+ if (hasType) {
+ map.put("account_type", "stats.account_type as account_type");
+ }
+ map.put("authority", "stats.authority as authority");
+ map.put("totalElapsedTime", "totalElapsedTime");
+ map.put("numSyncs", "numSyncs");
+ map.put("numSourceLocal", "numSourceLocal");
+ map.put("numSourcePoll", "numSourcePoll");
+ map.put("numSourceServer", "numSourceServer");
+ map.put("numSourceUser", "numSourceUser");
+ map.put("lastSuccessSource", "lastSuccessSource");
+ map.put("lastSuccessTime", "lastSuccessTime");
+ map.put("lastFailureSource", "lastFailureSource");
+ map.put("lastFailureTime", "lastFailureTime");
+ map.put("lastFailureMesg", "lastFailureMesg");
+ map.put("pending", "pending");
+ qb.setProjectionMap(map);
+ qb.appendWhere("stats._id = status.stats_id");
+ Cursor c = qb.query(db, null, null, null, null, null, null);
+ while (c.moveToNext()) {
+ String accountName = c.getString(c.getColumnIndex("account"));
+ String accountType = hasType
+ ? c.getString(c.getColumnIndex("account_type")) : null;
+ if (accountType == null) {
+ accountType = "com.google";
+ }
+ String authorityName = c.getString(c.getColumnIndex("authority"));
+ AuthorityInfo authority = this.getOrCreateAuthorityLocked(
+ new Account(accountName, accountType), 0 /* legacy is single-user */,
+ authorityName, -1, false);
+ if (authority != null) {
+ int i = mSyncStatus.size();
+ boolean found = false;
+ SyncStatusInfo st = null;
+ while (i > 0) {
+ i--;
+ st = mSyncStatus.valueAt(i);
+ if (st.authorityId == authority.ident) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ st = new SyncStatusInfo(authority.ident);
+ mSyncStatus.put(authority.ident, st);
+ }
+ st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
+ st.numSyncs = getIntColumn(c, "numSyncs");
+ st.numSourceLocal = getIntColumn(c, "numSourceLocal");
+ st.numSourcePoll = getIntColumn(c, "numSourcePoll");
+ st.numSourceServer = getIntColumn(c, "numSourceServer");
+ st.numSourceUser = getIntColumn(c, "numSourceUser");
+ st.numSourcePeriodic = 0;
+ st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
+ st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
+ st.lastFailureSource = getIntColumn(c, "lastFailureSource");
+ st.lastFailureTime = getLongColumn(c, "lastFailureTime");
+ st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
+ st.pending = getIntColumn(c, "pending") != 0;
+ }
+ }
+
+ c.close();
+
+ // Retrieve the settings.
+ qb = new SQLiteQueryBuilder();
+ qb.setTables("settings");
+ c = qb.query(db, null, null, null, null, null, null);
+ while (c.moveToNext()) {
+ String name = c.getString(c.getColumnIndex("name"));
+ String value = c.getString(c.getColumnIndex("value"));
+ if (name == null) continue;
+ if (name.equals("listen_for_tickles")) {
+ setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
+ } else if (name.startsWith("sync_provider_")) {
+ String provider = name.substring("sync_provider_".length(),
+ name.length());
+ int i = mAuthorities.size();
+ while (i > 0) {
+ i--;
+ AuthorityInfo authority = mAuthorities.valueAt(i);
+ if (authority.authority.equals(provider)) {
+ authority.enabled = value == null || Boolean.parseBoolean(value);
+ authority.syncable = 1;
+ }
+ }
+ }
+ }
+
+ c.close();
+
+ db.close();
+
+ (new File(path)).delete();
+ }
+ }
+
+ public static final int STATUS_FILE_END = 0;
+ public static final int STATUS_FILE_ITEM = 100;
+
+ /**
+ * Read all sync status back in to the initial engine state.
+ */
+ private void readStatusLocked() {
+ if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
+ try {
+ byte[] data = mStatusFile.readFully();
+ Parcel in = Parcel.obtain();
+ in.unmarshall(data, 0, data.length);
+ in.setDataPosition(0);
+ int token;
+ while ((token=in.readInt()) != STATUS_FILE_END) {
+ if (token == STATUS_FILE_ITEM) {
+ SyncStatusInfo status = new SyncStatusInfo(in);
+ if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
+ status.pending = false;
+ if (DEBUG_FILE) Log.v(TAG, "Adding status for id "
+ + status.authorityId);
+ mSyncStatus.put(status.authorityId, status);
+ }
+ } else {
+ // Ooops.
+ Log.w(TAG, "Unknown status token: " + token);
+ break;
+ }
+ }
+ } catch (java.io.IOException e) {
+ Log.i(TAG, "No initial status");
+ }
+ }
+
+ /**
+ * Write all sync status to the sync status file.
+ */
+ private void writeStatusLocked() {
+ if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
+
+ // The file is being written, so we don't need to have a scheduled
+ // write until the next change.
+ removeMessages(MSG_WRITE_STATUS);
+
+ FileOutputStream fos = null;
+ try {
+ fos = mStatusFile.startWrite();
+ Parcel out = Parcel.obtain();
+ final int N = mSyncStatus.size();
+ for (int i=0; i<N; i++) {
+ SyncStatusInfo status = mSyncStatus.valueAt(i);
+ out.writeInt(STATUS_FILE_ITEM);
+ status.writeToParcel(out, 0);
+ }
+ out.writeInt(STATUS_FILE_END);
+ fos.write(out.marshall());
+ out.recycle();
+
+ mStatusFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Log.w(TAG, "Error writing status", e1);
+ if (fos != null) {
+ mStatusFile.failWrite(fos);
+ }
+ }
+ }
+
+ public static final int PENDING_OPERATION_VERSION = 3;
+
+ /**
+ * Read all pending operations back in to the initial engine state.
+ */
+ private void readPendingOperationsLocked() {
+ if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
+ try {
+ byte[] data = mPendingFile.readFully();
+ Parcel in = Parcel.obtain();
+ in.unmarshall(data, 0, data.length);
+ in.setDataPosition(0);
+ final int SIZE = in.dataSize();
+ while (in.dataPosition() < SIZE) {
+ int version = in.readInt();
+ if (version != PENDING_OPERATION_VERSION && version != 1) {
+ Log.w(TAG, "Unknown pending operation version "
+ + version + "; dropping all ops");
+ break;
+ }
+ int authorityId = in.readInt();
+ int syncSource = in.readInt();
+ byte[] flatExtras = in.createByteArray();
+ boolean expedited;
+ if (version == PENDING_OPERATION_VERSION) {
+ expedited = in.readInt() != 0;
+ } else {
+ expedited = false;
+ }
+ int reason = in.readInt();
+ AuthorityInfo authority = mAuthorities.get(authorityId);
+ if (authority != null) {
+ Bundle extras;
+ if (flatExtras != null) {
+ extras = unflattenBundle(flatExtras);
+ } else {
+ // if we are unable to parse the extras for whatever reason convert this
+ // to a regular sync by creating an empty extras
+ extras = new Bundle();
+ }
+ PendingOperation op = new PendingOperation(
+ authority.account, authority.userId, reason, syncSource,
+ authority.authority, extras, expedited);
+ op.authorityId = authorityId;
+ op.flatExtras = flatExtras;
+ if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
+ + " auth=" + op.authority
+ + " src=" + op.syncSource
+ + " reason=" + op.reason
+ + " expedited=" + op.expedited
+ + " extras=" + op.extras);
+ mPendingOperations.add(op);
+ }
+ }
+ } catch (java.io.IOException e) {
+ Log.i(TAG, "No initial pending operations");
+ }
+ }
+
+ private void writePendingOperationLocked(PendingOperation op, Parcel out) {
+ out.writeInt(PENDING_OPERATION_VERSION);
+ out.writeInt(op.authorityId);
+ out.writeInt(op.syncSource);
+ if (op.flatExtras == null && op.extras != null) {
+ op.flatExtras = flattenBundle(op.extras);
+ }
+ out.writeByteArray(op.flatExtras);
+ out.writeInt(op.expedited ? 1 : 0);
+ out.writeInt(op.reason);
+ }
+
+ /**
+ * Write all currently pending ops to the pending ops file.
+ */
+ private void writePendingOperationsLocked() {
+ final int N = mPendingOperations.size();
+ FileOutputStream fos = null;
+ try {
+ if (N == 0) {
+ if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
+ mPendingFile.truncate();
+ return;
+ }
+
+ if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
+ fos = mPendingFile.startWrite();
+
+ Parcel out = Parcel.obtain();
+ for (int i=0; i<N; i++) {
+ PendingOperation op = mPendingOperations.get(i);
+ writePendingOperationLocked(op, out);
+ }
+ fos.write(out.marshall());
+ out.recycle();
+
+ mPendingFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Log.w(TAG, "Error writing pending operations", e1);
+ if (fos != null) {
+ mPendingFile.failWrite(fos);
+ }
+ }
+ }
+
+ /**
+ * Append the given operation to the pending ops file; if unable to,
+ * write all pending ops.
+ */
+ private void appendPendingOperationLocked(PendingOperation op) {
+ if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
+ FileOutputStream fos = null;
+ try {
+ fos = mPendingFile.openAppend();
+ } catch (java.io.IOException e) {
+ if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
+ writePendingOperationsLocked();
+ return;
+ }
+
+ try {
+ Parcel out = Parcel.obtain();
+ writePendingOperationLocked(op, out);
+ fos.write(out.marshall());
+ out.recycle();
+ } catch (java.io.IOException e1) {
+ Log.w(TAG, "Error writing pending operations", e1);
+ } finally {
+ try {
+ fos.close();
+ } catch (java.io.IOException e2) {
+ }
+ }
+ }
+
+ static private byte[] flattenBundle(Bundle bundle) {
+ byte[] flatData = null;
+ Parcel parcel = Parcel.obtain();
+ try {
+ bundle.writeToParcel(parcel, 0);
+ flatData = parcel.marshall();
+ } finally {
+ parcel.recycle();
+ }
+ return flatData;
+ }
+
+ static private Bundle unflattenBundle(byte[] flatData) {
+ Bundle bundle;
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.unmarshall(flatData, 0, flatData.length);
+ parcel.setDataPosition(0);
+ bundle = parcel.readBundle();
+ } catch (RuntimeException e) {
+ // A RuntimeException is thrown if we were unable to parse the parcel.
+ // Create an empty parcel in this case.
+ bundle = new Bundle();
+ } finally {
+ parcel.recycle();
+ }
+ return bundle;
+ }
+
+ private void requestSync(Account account, int userId, int reason, String authority,
+ Bundle extras) {
+ // If this is happening in the system process, then call the syncrequest listener
+ // to make a request back to the SyncManager directly.
+ // If this is probably a test instance, then call back through the ContentResolver
+ // which will know which userId to apply based on the Binder id.
+ if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
+ && mSyncRequestListener != null) {
+ mSyncRequestListener.onSyncRequest(account, userId, reason, authority, extras);
+ } else {
+ ContentResolver.requestSync(account, authority, extras);
+ }
+ }
+
+ public static final int STATISTICS_FILE_END = 0;
+ public static final int STATISTICS_FILE_ITEM_OLD = 100;
+ public static final int STATISTICS_FILE_ITEM = 101;
+
+ /**
+ * Read all sync statistics back in to the initial engine state.
+ */
+ private void readStatisticsLocked() {
+ try {
+ byte[] data = mStatisticsFile.readFully();
+ Parcel in = Parcel.obtain();
+ in.unmarshall(data, 0, data.length);
+ in.setDataPosition(0);
+ int token;
+ int index = 0;
+ while ((token=in.readInt()) != STATISTICS_FILE_END) {
+ if (token == STATISTICS_FILE_ITEM
+ || token == STATISTICS_FILE_ITEM_OLD) {
+ int day = in.readInt();
+ if (token == STATISTICS_FILE_ITEM_OLD) {
+ day = day - 2009 + 14245; // Magic!
+ }
+ DayStats ds = new DayStats(day);
+ ds.successCount = in.readInt();
+ ds.successTime = in.readLong();
+ ds.failureCount = in.readInt();
+ ds.failureTime = in.readLong();
+ if (index < mDayStats.length) {
+ mDayStats[index] = ds;
+ index++;
+ }
+ } else {
+ // Ooops.
+ Log.w(TAG, "Unknown stats token: " + token);
+ break;
+ }
+ }
+ } catch (java.io.IOException e) {
+ Log.i(TAG, "No initial statistics");
+ }
+ }
+
+ /**
+ * Write all sync statistics to the sync status file.
+ */
+ private void writeStatisticsLocked() {
+ if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
+
+ // The file is being written, so we don't need to have a scheduled
+ // write until the next change.
+ removeMessages(MSG_WRITE_STATISTICS);
+
+ FileOutputStream fos = null;
+ try {
+ fos = mStatisticsFile.startWrite();
+ Parcel out = Parcel.obtain();
+ final int N = mDayStats.length;
+ for (int i=0; i<N; i++) {
+ DayStats ds = mDayStats[i];
+ if (ds == null) {
+ break;
+ }
+ out.writeInt(STATISTICS_FILE_ITEM);
+ out.writeInt(ds.day);
+ out.writeInt(ds.successCount);
+ out.writeLong(ds.successTime);
+ out.writeInt(ds.failureCount);
+ out.writeLong(ds.failureTime);
+ }
+ out.writeInt(STATISTICS_FILE_END);
+ fos.write(out.marshall());
+ out.recycle();
+
+ mStatisticsFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Log.w(TAG, "Error writing stats", e1);
+ if (fos != null) {
+ mStatisticsFile.failWrite(fos);
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java
index a3ab3c1..4161147 100644
--- a/services/java/com/android/server/display/DisplayDevice.java
+++ b/services/java/com/android/server/display/DisplayDevice.java
@@ -19,6 +19,7 @@ package com.android.server.display;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.Surface;
+import android.view.SurfaceControl;
import java.io.PrintWriter;
@@ -122,7 +123,7 @@ abstract class DisplayDevice {
public final void setLayerStackInTransactionLocked(int layerStack) {
if (mCurrentLayerStack != layerStack) {
mCurrentLayerStack = layerStack;
- Surface.setDisplayLayerStack(mDisplayToken, layerStack);
+ SurfaceControl.setDisplayLayerStack(mDisplayToken, layerStack);
}
}
@@ -155,7 +156,7 @@ abstract class DisplayDevice {
}
mCurrentDisplayRect.set(displayRect);
- Surface.setDisplayProjection(mDisplayToken,
+ SurfaceControl.setDisplayProjection(mDisplayToken,
orientation, layerStackRect, displayRect);
}
}
@@ -166,7 +167,7 @@ abstract class DisplayDevice {
public final void setSurfaceInTransactionLocked(Surface surface) {
if (mCurrentSurface != surface) {
mCurrentSurface = surface;
- Surface.setDisplaySurface(mDisplayToken, surface);
+ SurfaceControl.setDisplaySurface(mDisplayToken, surface);
}
}
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index e09970e..17b0662 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -118,6 +118,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
// The synchronization root for the display manager.
// This lock guards most of the display manager's state.
+ // NOTE: This is synchronized on while holding WindowManagerService.mWindowMap so never call
+ // into WindowManagerService methods that require mWindowMap while holding this unless you are
+ // very very sure that no deadlock can occur.
private final SyncRoot mSyncRoot = new SyncRoot();
// True if in safe mode.
@@ -158,7 +161,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
new CopyOnWriteArrayList<DisplayTransactionListener>();
// Set to true if all displays have been blanked by the power manager.
- private int mAllDisplayBlankStateFromPowerManager;
+ private int mAllDisplayBlankStateFromPowerManager = DISPLAY_BLANK_STATE_UNKNOWN;
// Set to true when there are pending display changes that have yet to be applied
// to the surface flinger state.
@@ -321,6 +324,18 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
}
/**
+ * Sets the overscan insets for a particular display.
+ */
+ public void setOverscan(int displayId, int left, int top, int right, int bottom) {
+ synchronized (mSyncRoot) {
+ LogicalDisplay display = mLogicalDisplays.get(displayId);
+ if (display != null) {
+ display.setOverscan(left, top, right, bottom);
+ }
+ }
+ }
+
+ /**
* Called by the window manager to perform traversals while holding a
* surface flinger transaction.
*/
@@ -541,9 +556,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
synchronized (mSyncRoot) {
if (mWifiDisplayAdapter != null) {
return mWifiDisplayAdapter.getWifiDisplayStatusLocked();
- } else {
- return new WifiDisplayStatus();
}
+ return new WifiDisplayStatus();
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -812,11 +826,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
Slog.w(TAG, "Missing logical display to use for physical display device: "
+ device.getDisplayDeviceInfoLocked());
return;
- } else {
- boolean isBlanked = (mAllDisplayBlankStateFromPowerManager
- == DISPLAY_BLANK_STATE_BLANKED);
- display.configureDisplayInTransactionLocked(device, isBlanked);
}
+ boolean isBlanked = (mAllDisplayBlankStateFromPowerManager == DISPLAY_BLANK_STATE_BLANKED);
+ display.configureDisplayInTransactionLocked(device, isBlanked);
// Update the viewports if needed.
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java
index ee2d617..475f27b 100644
--- a/services/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/java/com/android/server/display/LocalDisplayAdapter.java
@@ -25,7 +25,8 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayEventReceiver;
import android.view.Surface;
-import android.view.Surface.PhysicalDisplayInfo;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.PhysicalDisplayInfo;
import java.io.PrintWriter;
@@ -39,15 +40,15 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private static final String TAG = "LocalDisplayAdapter";
private static final int[] BUILT_IN_DISPLAY_IDS_TO_SCAN = new int[] {
- Surface.BUILT_IN_DISPLAY_ID_MAIN,
- Surface.BUILT_IN_DISPLAY_ID_HDMI,
+ SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN,
+ SurfaceControl.BUILT_IN_DISPLAY_ID_HDMI,
};
private final SparseArray<LocalDisplayDevice> mDevices =
new SparseArray<LocalDisplayDevice>();
private HotplugDisplayEventReceiver mHotplugReceiver;
- private final PhysicalDisplayInfo mTempPhys = new PhysicalDisplayInfo();
+ private final SurfaceControl.PhysicalDisplayInfo mTempPhys = new SurfaceControl.PhysicalDisplayInfo();
// Called with SyncRoot lock held.
public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
@@ -67,8 +68,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
private void tryConnectDisplayLocked(int builtInDisplayId) {
- IBinder displayToken = Surface.getBuiltInDisplay(builtInDisplayId);
- if (displayToken != null && Surface.getDisplayInfo(displayToken, mTempPhys)) {
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId);
+ if (displayToken != null && SurfaceControl.getDisplayInfo(displayToken, mTempPhys)) {
LocalDisplayDevice device = mDevices.get(builtInDisplayId);
if (device == null) {
// Display was added.
@@ -97,20 +98,20 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private final class LocalDisplayDevice extends DisplayDevice {
private final int mBuiltInDisplayId;
- private final PhysicalDisplayInfo mPhys;
+ private final SurfaceControl.PhysicalDisplayInfo mPhys;
private DisplayDeviceInfo mInfo;
private boolean mHavePendingChanges;
private boolean mBlanked;
public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
- PhysicalDisplayInfo phys) {
+ SurfaceControl.PhysicalDisplayInfo phys) {
super(LocalDisplayAdapter.this, displayToken);
mBuiltInDisplayId = builtInDisplayId;
- mPhys = new PhysicalDisplayInfo(phys);
+ mPhys = new SurfaceControl.PhysicalDisplayInfo(phys);
}
- public boolean updatePhysicalDisplayInfoLocked(PhysicalDisplayInfo phys) {
+ public boolean updatePhysicalDisplayInfoLocked(SurfaceControl.PhysicalDisplayInfo phys) {
if (!mPhys.equals(phys)) {
mPhys.copyFrom(phys);
mHavePendingChanges = true;
@@ -142,7 +143,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
| DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
}
- if (mBuiltInDisplayId == Surface.BUILT_IN_DISPLAY_ID_MAIN) {
+ if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
mInfo.name = getContext().getResources().getString(
com.android.internal.R.string.display_manager_built_in_display_name);
mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
@@ -172,13 +173,13 @@ final class LocalDisplayAdapter extends DisplayAdapter {
@Override
public void blankLocked() {
mBlanked = true;
- Surface.blankDisplay(getDisplayTokenLocked());
+ SurfaceControl.blankDisplay(getDisplayTokenLocked());
}
@Override
public void unblankLocked() {
mBlanked = false;
- Surface.unblankDisplay(getDisplayTokenLocked());
+ SurfaceControl.unblankDisplay(getDisplayTokenLocked());
}
@Override
diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java
index 1583137..424ec36 100644
--- a/services/java/com/android/server/display/LogicalDisplay.java
+++ b/services/java/com/android/server/display/LogicalDisplay.java
@@ -143,6 +143,19 @@ final class LogicalDisplay {
}
}
+ public void setOverscan(int left, int top, int right, int bottom) {
+ mInfo.overscanLeft = left;
+ mInfo.overscanTop = top;
+ mInfo.overscanRight = right;
+ mInfo.overscanBottom = bottom;
+ if (mOverrideDisplayInfo != null) {
+ mOverrideDisplayInfo.overscanLeft = left;
+ mOverrideDisplayInfo.overscanTop = top;
+ mOverrideDisplayInfo.overscanRight = right;
+ mOverrideDisplayInfo.overscanBottom = bottom;
+ }
+ }
+
/**
* Returns true if the logical display is in a valid state.
* This method should be checked after calling {@link #updateLocked} to handle the
diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java
index 36e9f74..a18352c 100644
--- a/services/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -30,6 +30,7 @@ import android.util.Slog;
import android.view.Display;
import android.view.Gravity;
import android.view.Surface;
+import android.view.SurfaceControl;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -284,7 +285,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
@Override
public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate) {
synchronized (getSyncRoot()) {
- IBinder displayToken = Surface.createDisplay(mName, false);
+ IBinder displayToken = SurfaceControl.createDisplay(mName, false);
mDevice = new OverlayDisplayDevice(displayToken, mName,
mWidth, mHeight, refreshRate, mDensityDpi, surfaceTexture);
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
index c8a44d2..b655b3a 100644
--- a/services/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -41,6 +41,7 @@ import android.provider.Settings;
import android.util.Slog;
import android.view.Display;
import android.view.Surface;
+import android.view.SurfaceControl;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -332,7 +333,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
String name = display.getFriendlyDisplayName();
String address = display.getDeviceAddress();
- IBinder displayToken = Surface.createDisplay(name, secure);
+ IBinder displayToken = SurfaceControl.createDisplay(name, secure);
mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
refreshRate, deviceFlags, address, surface);
sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
index a83675e..8c8b360 100644
--- a/services/java/com/android/server/display/WifiDisplayController.java
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -481,6 +481,7 @@ final class WifiDisplayController implements DumpUtils.Dump {
mRemoteDisplayConnected = false;
mHandler.removeCallbacks(mRtspTimeout);
+ mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_DISABLED);
setRemoteSubmixOn(false);
unadvertiseDisplay();
@@ -626,6 +627,7 @@ final class WifiDisplayController implements DumpUtils.Dump {
}
setRemoteSubmixOn(true);
+ mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);
final WifiP2pDevice oldDevice = mConnectedDevice;
final int port = getPortNumber(mConnectedDevice);
diff --git a/services/java/com/android/server/dreams/DreamController.java b/services/java/com/android/server/dreams/DreamController.java
index 45ae2c5..85ef33e 100644
--- a/services/java/com/android/server/dreams/DreamController.java
+++ b/services/java/com/android/server/dreams/DreamController.java
@@ -116,8 +116,8 @@ final class DreamController {
intent.setComponent(name);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
try {
- if (!mContext.bindService(intent, mCurrentDream,
- Context.BIND_AUTO_CREATE, userId)) {
+ if (!mContext.bindServiceAsUser(intent, mCurrentDream,
+ Context.BIND_AUTO_CREATE, new UserHandle(userId))) {
Slog.e(TAG, "Unable to bind dream service: " + intent);
stopDream();
return;
diff --git a/services/java/com/android/server/firewall/AndFilter.java b/services/java/com/android/server/firewall/AndFilter.java
new file mode 100644
index 0000000..e4276d0
--- /dev/null
+++ b/services/java/com/android/server/firewall/AndFilter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class AndFilter extends FilterList {
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ for (int i=0; i<children.size(); i++) {
+ if (!children.get(i).matches(ifw, intent, callerApp, callerUid, callerPid, resolvedType,
+ resolvedApp)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static final FilterFactory FACTORY = new FilterFactory("and") {
+ @Override
+ public Filter newFilter(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ return new AndFilter().readFromXml(parser);
+ }
+ };
+}
diff --git a/services/java/com/android/server/firewall/CategoryFilter.java b/services/java/com/android/server/firewall/CategoryFilter.java
new file mode 100644
index 0000000..4938cb8
--- /dev/null
+++ b/services/java/com/android/server/firewall/CategoryFilter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Set;
+
+class CategoryFilter implements Filter {
+ private static final String ATTR_NAME = "name";
+
+ private final String mCategoryName;
+
+ private CategoryFilter(String categoryName) {
+ mCategoryName = categoryName;
+ }
+
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ Set<String> categories = intent.getCategories();
+ if (categories == null) {
+ return false;
+ }
+ return categories.contains(mCategoryName);
+ }
+
+ public static final FilterFactory FACTORY = new FilterFactory("category") {
+ @Override
+ public Filter newFilter(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String categoryName = parser.getAttributeValue(null, ATTR_NAME);
+ if (categoryName == null) {
+ throw new XmlPullParserException("Category name must be specified.",
+ parser, null);
+ }
+ return new CategoryFilter(categoryName);
+ }
+ };
+}
diff --git a/services/java/com/android/server/firewall/Filter.java b/services/java/com/android/server/firewall/Filter.java
new file mode 100644
index 0000000..0e783e8
--- /dev/null
+++ b/services/java/com/android/server/firewall/Filter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+
+interface Filter {
+ /**
+ * Does the given intent + context info match this filter?
+ *
+ * @param ifw The IntentFirewall instance
+ * @param intent The intent being started/bound/broadcast
+ * @param callerApp An ApplicationInfo of an application in the caller's process. This may not
+ * be the specific app that is actually sending the intent. This also may be
+ * null, if the caller is the system process, or an unrecognized process (e.g.
+ * am start)
+ * @param callerUid
+ * @param callerPid
+ * @param resolvedType The resolved mime type of the intent
+ * @param resolvedApp The application that contains the resolved component that the intent is
+ */
+ boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp);
+}
diff --git a/services/java/com/android/server/firewall/FilterFactory.java b/services/java/com/android/server/firewall/FilterFactory.java
new file mode 100644
index 0000000..dea8b40
--- /dev/null
+++ b/services/java/com/android/server/firewall/FilterFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+public abstract class FilterFactory {
+ private final String mTag;
+
+ protected FilterFactory(String tag) {
+ if (tag == null) {
+ throw new NullPointerException();
+ }
+ mTag = tag;
+ }
+
+ public String getTagName() {
+ return mTag;
+ }
+
+ public abstract Filter newFilter(XmlPullParser parser)
+ throws IOException, XmlPullParserException;
+}
diff --git a/services/java/com/android/server/firewall/FilterList.java b/services/java/com/android/server/firewall/FilterList.java
new file mode 100644
index 0000000..d34b203
--- /dev/null
+++ b/services/java/com/android/server/firewall/FilterList.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+abstract class FilterList implements Filter {
+ protected final ArrayList<Filter> children = new ArrayList<Filter>();
+
+ public FilterList readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ readChild(parser);
+ }
+ return this;
+ }
+
+ protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException {
+ Filter filter = IntentFirewall.parseFilter(parser);
+ children.add(filter);
+ }
+}
diff --git a/services/java/com/android/server/firewall/IntentFirewall.java b/services/java/com/android/server/firewall/IntentFirewall.java
new file mode 100644
index 0000000..4496aae
--- /dev/null
+++ b/services/java/com/android/server/firewall/IntentFirewall.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.os.FileObserver;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.XmlUtils;
+import com.android.server.EventLogTags;
+import com.android.server.IntentResolver;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class IntentFirewall {
+ private static final String TAG = "IntentFirewall";
+
+ // e.g. /data/system/ifw/ifw.xml or /data/secure/system/ifw/ifw.xml
+ private static final File RULES_FILE =
+ new File(Environment.getSystemSecureDirectory(), "ifw/ifw.xml");
+
+ private static final int LOG_PACKAGES_MAX_LENGTH = 150;
+ private static final int LOG_PACKAGES_SUFFICIENT_LENGTH = 125;
+
+ private static final String TAG_RULES = "rules";
+ private static final String TAG_ACTIVITY = "activity";
+ private static final String TAG_SERVICE = "service";
+ private static final String TAG_BROADCAST = "broadcast";
+
+ private static final int TYPE_ACTIVITY = 0;
+ private static final int TYPE_BROADCAST = 1;
+ private static final int TYPE_SERVICE = 2;
+
+ private static final HashMap<String, FilterFactory> factoryMap;
+
+ private final AMSInterface mAms;
+
+ private final RuleObserver mObserver;
+
+ private FirewallIntentResolver mActivityResolver = new FirewallIntentResolver();
+ private FirewallIntentResolver mBroadcastResolver = new FirewallIntentResolver();
+ private FirewallIntentResolver mServiceResolver = new FirewallIntentResolver();
+
+ static {
+ FilterFactory[] factories = new FilterFactory[] {
+ AndFilter.FACTORY,
+ OrFilter.FACTORY,
+ NotFilter.FACTORY,
+
+ StringFilter.ACTION,
+ StringFilter.COMPONENT,
+ StringFilter.COMPONENT_NAME,
+ StringFilter.COMPONENT_PACKAGE,
+ StringFilter.DATA,
+ StringFilter.HOST,
+ StringFilter.MIME_TYPE,
+ StringFilter.PATH,
+ StringFilter.SSP,
+
+ CategoryFilter.FACTORY,
+ SenderFilter.FACTORY,
+ SenderPermissionFilter.FACTORY,
+ PortFilter.FACTORY
+ };
+
+ // load factor ~= .75
+ factoryMap = new HashMap<String, FilterFactory>(factories.length * 4 / 3);
+ for (int i=0; i<factories.length; i++) {
+ FilterFactory factory = factories[i];
+ factoryMap.put(factory.getTagName(), factory);
+ }
+ }
+
+ public IntentFirewall(AMSInterface ams) {
+ mAms = ams;
+ File rulesFile = getRulesFile();
+ rulesFile.getParentFile().mkdirs();
+
+ readRules(rulesFile);
+
+ mObserver = new RuleObserver(rulesFile);
+ mObserver.startWatching();
+ }
+
+ /**
+ * This is called from ActivityManager to check if a start activity intent should be allowed.
+ * It is assumed the caller is already holding the global ActivityManagerService lock.
+ */
+ public boolean checkStartActivity(Intent intent, ApplicationInfo callerApp, int callerUid,
+ int callerPid, String resolvedType, ActivityInfo resolvedActivity) {
+ List<Rule> matchingRules = mActivityResolver.queryIntent(intent, resolvedType, false, 0);
+ boolean log = false;
+ boolean block = false;
+
+ for (int i=0; i< matchingRules.size(); i++) {
+ Rule rule = matchingRules.get(i);
+ if (rule.matches(this, intent, callerApp, callerUid, callerPid, resolvedType,
+ resolvedActivity.applicationInfo)) {
+ block |= rule.getBlock();
+ log |= rule.getLog();
+
+ // if we've already determined that we should both block and log, there's no need
+ // to continue trying rules
+ if (block && log) {
+ break;
+ }
+ }
+ }
+
+ if (log) {
+ logIntent(TYPE_ACTIVITY, intent, callerUid, resolvedType);
+ }
+
+ return !block;
+ }
+
+ private static void logIntent(int intentType, Intent intent, int callerUid,
+ String resolvedType) {
+ // The component shouldn't be null, but let's double check just to be safe
+ ComponentName cn = intent.getComponent();
+ String shortComponent = null;
+ if (cn != null) {
+ shortComponent = cn.flattenToShortString();
+ }
+
+ String callerPackages = null;
+ int callerPackageCount = 0;
+ IPackageManager pm = AppGlobals.getPackageManager();
+ if (pm != null) {
+ try {
+ String[] callerPackagesArray = pm.getPackagesForUid(callerUid);
+ if (callerPackagesArray != null) {
+ callerPackageCount = callerPackagesArray.length;
+ callerPackages = joinPackages(callerPackagesArray);
+ }
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Remote exception while retrieving packages", ex);
+ }
+ }
+
+ EventLogTags.writeIfwIntentMatched(intentType, shortComponent, callerUid,
+ callerPackageCount, callerPackages, intent.getAction(), resolvedType,
+ intent.getDataString(), intent.getFlags());
+ }
+
+ /**
+ * Joins a list of package names such that the resulting string is no more than
+ * LOG_PACKAGES_MAX_LENGTH.
+ *
+ * Only full package names will be added to the result, unless every package is longer than the
+ * limit, in which case one of the packages will be truncated and added. In this case, an
+ * additional '-' character will be added to the end of the string, to denote the truncation.
+ *
+ * If it encounters a package that won't fit in the remaining space, it will continue on to the
+ * next package, unless the total length of the built string so far is greater than
+ * LOG_PACKAGES_SUFFICIENT_LENGTH, in which case it will stop and return what it has.
+ */
+ private static String joinPackages(String[] packages) {
+ boolean first = true;
+ StringBuilder sb = new StringBuilder();
+ for (int i=0; i<packages.length; i++) {
+ String pkg = packages[i];
+
+ // + 1 length for the comma. This logic technically isn't correct for the first entry,
+ // but it's not critical.
+ if (sb.length() + pkg.length() + 1 < LOG_PACKAGES_MAX_LENGTH) {
+ if (!first) {
+ sb.append(',');
+ } else {
+ first = false;
+ }
+ sb.append(pkg);
+ } else if (sb.length() >= LOG_PACKAGES_SUFFICIENT_LENGTH) {
+ return sb.toString();
+ }
+ }
+ if (sb.length() == 0 && packages.length > 0) {
+ String pkg = packages[0];
+ // truncating from the end - the last part of the package name is more likely to be
+ // interesting/unique
+ return pkg.substring(pkg.length() - LOG_PACKAGES_MAX_LENGTH + 1) + '-';
+ }
+ return null;
+ }
+
+ public static File getRulesFile() {
+ return RULES_FILE;
+ }
+
+ /**
+ * Reads rules from the given file and replaces our set of rules with the newly read rules
+ *
+ * All calls to this method from the file observer come through a handler and are inherently
+ * serialized
+ */
+ private void readRules(File rulesFile) {
+ FirewallIntentResolver[] resolvers = new FirewallIntentResolver[3];
+ for (int i=0; i<resolvers.length; i++) {
+ resolvers[i] = new FirewallIntentResolver();
+ }
+
+ FileInputStream fis;
+ try {
+ fis = new FileInputStream(rulesFile);
+ } catch (FileNotFoundException ex) {
+ // Nope, no rules. Nothing else to do!
+ return;
+ }
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+
+ parser.setInput(fis, null);
+
+ XmlUtils.beginDocument(parser, TAG_RULES);
+
+ int[] numRules = new int[3];
+
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ int ruleType = -1;
+
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_ACTIVITY)) {
+ ruleType = TYPE_ACTIVITY;
+ } else if (tagName.equals(TAG_BROADCAST)) {
+ ruleType = TYPE_BROADCAST;
+ } else if (tagName.equals(TAG_SERVICE)) {
+ ruleType = TYPE_SERVICE;
+ }
+
+ if (ruleType != -1) {
+ Rule rule = new Rule();
+
+ FirewallIntentResolver resolver = resolvers[ruleType];
+
+ // if we get an error while parsing a particular rule, we'll just ignore
+ // that rule and continue on with the next rule
+ try {
+ rule.readFromXml(parser);
+ } catch (XmlPullParserException ex) {
+ Slog.e(TAG, "Error reading intent firewall rule", ex);
+ continue;
+ }
+
+ numRules[ruleType]++;
+
+ for (int i=0; i<rule.getIntentFilterCount(); i++) {
+ resolver.addFilter(rule.getIntentFilter(i));
+ }
+ }
+ }
+
+ Slog.i(TAG, "Read new rules (A:" + numRules[TYPE_ACTIVITY] +
+ " B:" + numRules[TYPE_BROADCAST] + " S:" + numRules[TYPE_SERVICE] + ")");
+
+ synchronized (mAms.getAMSLock()) {
+ mActivityResolver = resolvers[TYPE_ACTIVITY];
+ mBroadcastResolver = resolvers[TYPE_BROADCAST];
+ mServiceResolver = resolvers[TYPE_SERVICE];
+ }
+ } catch (XmlPullParserException ex) {
+ // if there was an error outside of a specific rule, then there are probably
+ // structural problems with the xml file, and we should completely ignore it
+ Slog.e(TAG, "Error reading intent firewall rules", ex);
+ clearRules();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Error reading intent firewall rules", ex);
+ clearRules();
+ } finally {
+ try {
+ fis.close();
+ } catch (IOException ex) {
+ Slog.e(TAG, "Error while closing " + rulesFile, ex);
+ }
+ }
+ }
+
+ /**
+ * Clears out all of our rules
+ *
+ * All calls to this method from the file observer come through a handler and are inherently
+ * serialized
+ */
+ private void clearRules() {
+ Slog.i(TAG, "Clearing all rules");
+
+ synchronized (mAms.getAMSLock()) {
+ mActivityResolver = new FirewallIntentResolver();
+ mBroadcastResolver = new FirewallIntentResolver();
+ mServiceResolver = new FirewallIntentResolver();
+ }
+ }
+
+ static Filter parseFilter(XmlPullParser parser) throws IOException, XmlPullParserException {
+ String elementName = parser.getName();
+
+ FilterFactory factory = factoryMap.get(elementName);
+
+ if (factory == null) {
+ throw new XmlPullParserException("Unknown element in filter list: " + elementName);
+ }
+ return factory.newFilter(parser);
+ }
+
+ private static class Rule extends AndFilter {
+ private static final String TAG_INTENT_FILTER = "intent-filter";
+
+ private static final String ATTR_BLOCK = "block";
+ private static final String ATTR_LOG = "log";
+
+ private final ArrayList<FirewallIntentFilter> mIntentFilters =
+ new ArrayList<FirewallIntentFilter>(1);
+ private boolean block;
+ private boolean log;
+
+ @Override
+ public Rule readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
+ block = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_BLOCK));
+ log = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_LOG));
+
+ super.readFromXml(parser);
+ return this;
+ }
+
+ @Override
+ protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException {
+ if (parser.getName().equals(TAG_INTENT_FILTER)) {
+ FirewallIntentFilter intentFilter = new FirewallIntentFilter(this);
+ intentFilter.readFromXml(parser);
+ mIntentFilters.add(intentFilter);
+ } else {
+ super.readChild(parser);
+ }
+ }
+
+ public int getIntentFilterCount() {
+ return mIntentFilters.size();
+ }
+
+ public FirewallIntentFilter getIntentFilter(int index) {
+ return mIntentFilters.get(index);
+ }
+
+ public boolean getBlock() {
+ return block;
+ }
+
+ public boolean getLog() {
+ return log;
+ }
+ }
+
+ private static class FirewallIntentFilter extends IntentFilter {
+ private final Rule rule;
+
+ public FirewallIntentFilter(Rule rule) {
+ this.rule = rule;
+ }
+ }
+
+ private static class FirewallIntentResolver
+ extends IntentResolver<FirewallIntentFilter, Rule> {
+ @Override
+ protected boolean allowFilterResult(FirewallIntentFilter filter, List<Rule> dest) {
+ return !dest.contains(filter.rule);
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName, FirewallIntentFilter filter) {
+ return true;
+ }
+
+ @Override
+ protected FirewallIntentFilter[] newArray(int size) {
+ return new FirewallIntentFilter[size];
+ }
+
+ @Override
+ protected Rule newResult(FirewallIntentFilter filter, int match, int userId) {
+ return filter.rule;
+ }
+
+ @Override
+ protected void sortResults(List<Rule> results) {
+ // there's no need to sort the results
+ return;
+ }
+ }
+
+ private static final int READ_RULES = 0;
+ private static final int CLEAR_RULES = 1;
+
+ final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case READ_RULES:
+ readRules(getRulesFile());
+ break;
+ case CLEAR_RULES:
+ clearRules();
+ break;
+ }
+ }
+ };
+
+ /**
+ * Monitors for the creation/deletion/modification of the rule file
+ */
+ private class RuleObserver extends FileObserver {
+ // The file name we're monitoring, with no path component
+ private final String mMonitoredFile;
+
+ private static final int CREATED_FLAGS = FileObserver.CREATE|FileObserver.MOVED_TO|
+ FileObserver.CLOSE_WRITE;
+ private static final int DELETED_FLAGS = FileObserver.DELETE|FileObserver.MOVED_FROM;
+
+ public RuleObserver(File monitoredFile) {
+ super(monitoredFile.getParentFile().getAbsolutePath(), CREATED_FLAGS|DELETED_FLAGS);
+ mMonitoredFile = monitoredFile.getName();
+ }
+
+ @Override
+ public void onEvent(int event, String path) {
+ if (path.equals(mMonitoredFile)) {
+ // we wait 250ms before taking any action on an event, in order to dedup multiple
+ // events. E.g. a delete event followed by a create event followed by a subsequent
+ // write+close event;
+ if ((event & CREATED_FLAGS) != 0) {
+ mHandler.removeMessages(READ_RULES);
+ mHandler.removeMessages(CLEAR_RULES);
+ mHandler.sendEmptyMessageDelayed(READ_RULES, 250);
+ } else if ((event & DELETED_FLAGS) != 0) {
+ mHandler.removeMessages(READ_RULES);
+ mHandler.removeMessages(CLEAR_RULES);
+ mHandler.sendEmptyMessageDelayed(CLEAR_RULES, 250);
+ }
+ }
+ }
+ }
+
+ /**
+ * This interface contains the methods we need from ActivityManagerService. This allows AMS to
+ * export these methods to us without making them public, and also makes it easier to test this
+ * component.
+ */
+ public interface AMSInterface {
+ int checkComponentPermission(String permission, int pid, int uid,
+ int owningUid, boolean exported);
+ Object getAMSLock();
+ }
+
+ /**
+ * Checks if the caller has access to a component
+ *
+ * @param permission If present, the caller must have this permission
+ * @param pid The pid of the caller
+ * @param uid The uid of the caller
+ * @param owningUid The uid of the application that owns the component
+ * @param exported Whether the component is exported
+ * @return True if the caller can access the described component
+ */
+ boolean checkComponentPermission(String permission, int pid, int uid, int owningUid,
+ boolean exported) {
+ return mAms.checkComponentPermission(permission, pid, uid, owningUid, exported) ==
+ PackageManager.PERMISSION_GRANTED;
+ }
+
+ boolean signaturesMatch(int uid1, int uid2) {
+ try {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ return pm.checkUidSignatures(uid1, uid2) == PackageManager.SIGNATURE_MATCH;
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Remote exception while checking signatures", ex);
+ return false;
+ }
+ }
+}
diff --git a/services/java/com/android/server/firewall/NotFilter.java b/services/java/com/android/server/firewall/NotFilter.java
new file mode 100644
index 0000000..f0fc337
--- /dev/null
+++ b/services/java/com/android/server/firewall/NotFilter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class NotFilter implements Filter {
+ private final Filter mChild;
+
+ private NotFilter(Filter child) {
+ mChild = child;
+ }
+
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ return !mChild.matches(ifw, intent, callerApp, callerUid, callerPid, resolvedType,
+ resolvedApp);
+ }
+
+ public static final FilterFactory FACTORY = new FilterFactory("not") {
+ @Override
+ public Filter newFilter(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ Filter child = null;
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ Filter filter = IntentFirewall.parseFilter(parser);
+ if (child == null) {
+ child = filter;
+ } else {
+ throw new XmlPullParserException(
+ "<not> tag can only contain a single child filter.", parser, null);
+ }
+ }
+ return new NotFilter(child);
+ }
+ };
+}
diff --git a/services/java/com/android/server/firewall/OrFilter.java b/services/java/com/android/server/firewall/OrFilter.java
new file mode 100644
index 0000000..72db31e
--- /dev/null
+++ b/services/java/com/android/server/firewall/OrFilter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class OrFilter extends FilterList {
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ for (int i=0; i<children.size(); i++) {
+ if (children.get(i).matches(ifw, intent, callerApp, callerUid, callerPid, resolvedType,
+ resolvedApp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static final FilterFactory FACTORY = new FilterFactory("or") {
+ @Override
+ public Filter newFilter(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ return new OrFilter().readFromXml(parser);
+ }
+ };
+}
diff --git a/services/java/com/android/server/firewall/PortFilter.java b/services/java/com/android/server/firewall/PortFilter.java
new file mode 100644
index 0000000..fe7e085
--- /dev/null
+++ b/services/java/com/android/server/firewall/PortFilter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class PortFilter implements Filter {
+ private static final String ATTR_EQUALS = "equals";
+ private static final String ATTR_MIN = "min";
+ private static final String ATTR_MAX = "max";
+
+ private static final int NO_BOUND = -1;
+
+ // both bounds are inclusive
+ private final int mLowerBound;
+ private final int mUpperBound;
+
+ private PortFilter(int lowerBound, int upperBound) {
+ mLowerBound = lowerBound;
+ mUpperBound = upperBound;
+ }
+
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ int port = -1;
+ Uri uri = intent.getData();
+ if (uri != null) {
+ port = uri.getPort();
+ }
+ return port != -1 &&
+ (mLowerBound == NO_BOUND || mLowerBound <= port) &&
+ (mUpperBound == NO_BOUND || mUpperBound >= port);
+ }
+
+ public static final FilterFactory FACTORY = new FilterFactory("port") {
+ @Override
+ public Filter newFilter(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ int lowerBound = NO_BOUND;
+ int upperBound = NO_BOUND;
+
+ String equalsValue = parser.getAttributeValue(null, ATTR_EQUALS);
+ if (equalsValue != null) {
+ int value;
+ try {
+ value = Integer.parseInt(equalsValue);
+ } catch (NumberFormatException ex) {
+ throw new XmlPullParserException("Invalid port value: " + equalsValue,
+ parser, null);
+ }
+ lowerBound = value;
+ upperBound = value;
+ }
+
+ String lowerBoundString = parser.getAttributeValue(null, ATTR_MIN);
+ String upperBoundString = parser.getAttributeValue(null, ATTR_MAX);
+ if (lowerBoundString != null || upperBoundString != null) {
+ if (equalsValue != null) {
+ throw new XmlPullParserException(
+ "Port filter cannot use both equals and range filtering",
+ parser, null);
+ }
+
+ if (lowerBoundString != null) {
+ try {
+ lowerBound = Integer.parseInt(lowerBoundString);
+ } catch (NumberFormatException ex) {
+ throw new XmlPullParserException(
+ "Invalid minimum port value: " + lowerBoundString,
+ parser, null);
+ }
+ }
+
+ if (upperBoundString != null) {
+ try {
+ upperBound = Integer.parseInt(upperBoundString);
+ } catch (NumberFormatException ex) {
+ throw new XmlPullParserException(
+ "Invalid maximum port value: " + upperBoundString,
+ parser, null);
+ }
+ }
+ }
+
+ // an empty port filter is explicitly allowed, and checks for the existence of a port
+ return new PortFilter(lowerBound, upperBound);
+ }
+ };
+}
diff --git a/services/java/com/android/server/firewall/SenderFilter.java b/services/java/com/android/server/firewall/SenderFilter.java
new file mode 100644
index 0000000..58bdd73
--- /dev/null
+++ b/services/java/com/android/server/firewall/SenderFilter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.Process;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class SenderFilter {
+ private static final String ATTR_TYPE = "type";
+
+ private static final String VAL_SIGNATURE = "signature";
+ private static final String VAL_SYSTEM = "system";
+ private static final String VAL_SYSTEM_OR_SIGNATURE = "system|signature";
+ private static final String VAL_USER_ID = "userId";
+
+ static boolean isSystemApp(ApplicationInfo callerApp, int callerUid, int callerPid) {
+ if (callerUid == Process.SYSTEM_UID ||
+ callerPid == Process.myPid()) {
+ return true;
+ }
+ if (callerApp == null) {
+ return false;
+ }
+ return (callerApp.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ public static final FilterFactory FACTORY = new FilterFactory("sender") {
+ @Override
+ public Filter newFilter(XmlPullParser parser) throws IOException, XmlPullParserException {
+ String typeString = parser.getAttributeValue(null, ATTR_TYPE);
+ if (typeString == null) {
+ throw new XmlPullParserException("type attribute must be specified for <sender>",
+ parser, null);
+ }
+ if (typeString.equals(VAL_SYSTEM)) {
+ return SYSTEM;
+ } else if (typeString.equals(VAL_SIGNATURE)) {
+ return SIGNATURE;
+ } else if (typeString.equals(VAL_SYSTEM_OR_SIGNATURE)) {
+ return SYSTEM_OR_SIGNATURE;
+ } else if (typeString.equals(VAL_USER_ID)) {
+ return USER_ID;
+ }
+ throw new XmlPullParserException(
+ "Invalid type attribute for <sender>: " + typeString, parser, null);
+ }
+ };
+
+ private static final Filter SIGNATURE = new Filter() {
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ if (callerApp == null) {
+ return false;
+ }
+ return ifw.signaturesMatch(callerUid, resolvedApp.uid);
+ }
+ };
+
+ private static final Filter SYSTEM = new Filter() {
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ if (callerApp == null) {
+ // if callerApp is null, the caller is the system process
+ return false;
+ }
+ return isSystemApp(callerApp, callerUid, callerPid);
+ }
+ };
+
+ private static final Filter SYSTEM_OR_SIGNATURE = new Filter() {
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ return isSystemApp(callerApp, callerUid, callerPid) ||
+ ifw.signaturesMatch(callerUid, resolvedApp.uid);
+ }
+ };
+
+ private static final Filter USER_ID = new Filter() {
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ // This checks whether the caller is either the system process, or has the same user id
+ // I.e. the same app, or an app that uses the same shared user id.
+ // This is the same set of applications that would be able to access the component if
+ // it wasn't exported.
+ return ifw.checkComponentPermission(null, callerPid, callerUid, resolvedApp.uid, false);
+ }
+ };
+}
diff --git a/services/java/com/android/server/firewall/SenderPermissionFilter.java b/services/java/com/android/server/firewall/SenderPermissionFilter.java
new file mode 100644
index 0000000..310da20
--- /dev/null
+++ b/services/java/com/android/server/firewall/SenderPermissionFilter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class SenderPermissionFilter implements Filter {
+ private static final String ATTR_NAME = "name";
+
+ private final String mPermission;
+
+ private SenderPermissionFilter(String permission) {
+ mPermission = permission;
+ }
+
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ // We assume the component is exported here. If the component is not exported, then
+ // ActivityManager would only resolve to this component for callers from the same uid.
+ // In this case, it doesn't matter whether the component is exported or not.
+ return ifw.checkComponentPermission(mPermission, callerPid, callerUid, resolvedApp.uid,
+ true);
+ }
+
+ public static final FilterFactory FACTORY = new FilterFactory("sender-permission") {
+ @Override
+ public Filter newFilter(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String permission = parser.getAttributeValue(null, ATTR_NAME);
+ if (permission == null) {
+ throw new XmlPullParserException("Permission name must be specified.",
+ parser, null);
+ }
+ return new SenderPermissionFilter(permission);
+ }
+ };
+}
diff --git a/services/java/com/android/server/firewall/StringFilter.java b/services/java/com/android/server/firewall/StringFilter.java
new file mode 100644
index 0000000..ed5d3f3
--- /dev/null
+++ b/services/java/com/android/server/firewall/StringFilter.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2013 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.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.PatternMatcher;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+abstract class StringFilter implements Filter {
+ private static final String ATTR_EQUALS = "equals";
+ private static final String ATTR_STARTS_WITH = "startsWith";
+ private static final String ATTR_CONTAINS = "contains";
+ private static final String ATTR_PATTERN = "pattern";
+ private static final String ATTR_REGEX = "regex";
+ private static final String ATTR_IS_NULL = "isNull";
+
+ private final ValueProvider mValueProvider;
+
+ private StringFilter(ValueProvider valueProvider) {
+ this.mValueProvider = valueProvider;
+ }
+
+ /**
+ * Constructs a new StringFilter based on the string filter attribute on the current
+ * element, and the given StringValueMatcher.
+ *
+ * The current node should contain exactly 1 string filter attribute. E.g. equals,
+ * contains, etc. Otherwise, an XmlPullParserException will be thrown.
+ *
+ * @param parser An XmlPullParser object positioned at an element that should
+ * contain a string filter attribute
+ * @return This StringFilter object
+ */
+ public static StringFilter readFromXml(ValueProvider valueProvider, XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ StringFilter filter = null;
+
+ for (int i=0; i<parser.getAttributeCount(); i++) {
+ StringFilter newFilter = getFilter(valueProvider, parser, i);
+ if (newFilter != null) {
+ if (filter != null) {
+ throw new XmlPullParserException("Multiple string filter attributes found");
+ }
+ filter = newFilter;
+ }
+ }
+
+ if (filter == null) {
+ // if there are no string filter attributes, we default to isNull="false" so that an
+ // empty filter is equivalent to an existence check
+ filter = new IsNullFilter(valueProvider, false);
+ }
+
+ return filter;
+ }
+
+ private static StringFilter getFilter(ValueProvider valueProvider, XmlPullParser parser,
+ int attributeIndex) {
+ String attributeName = parser.getAttributeName(attributeIndex);
+
+ switch (attributeName.charAt(0)) {
+ case 'e':
+ if (!attributeName.equals(ATTR_EQUALS)) {
+ return null;
+ }
+ return new EqualsFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+ case 'i':
+ if (!attributeName.equals(ATTR_IS_NULL)) {
+ return null;
+ }
+ return new IsNullFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+ case 's':
+ if (!attributeName.equals(ATTR_STARTS_WITH)) {
+ return null;
+ }
+ return new StartsWithFilter(valueProvider,
+ parser.getAttributeValue(attributeIndex));
+ case 'c':
+ if (!attributeName.equals(ATTR_CONTAINS)) {
+ return null;
+ }
+ return new ContainsFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+ case 'p':
+ if (!attributeName.equals(ATTR_PATTERN)) {
+ return null;
+ }
+ return new PatternStringFilter(valueProvider,
+ parser.getAttributeValue(attributeIndex));
+ case 'r':
+ if (!attributeName.equals(ATTR_REGEX)) {
+ return null;
+ }
+ return new RegexFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+ }
+ return null;
+ }
+
+ protected abstract boolean matchesValue(String value);
+
+ @Override
+ public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp,
+ int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+ String value = mValueProvider.getValue(intent, callerApp, resolvedType, resolvedApp);
+ return matchesValue(value);
+ }
+
+ private static abstract class ValueProvider extends FilterFactory {
+ protected ValueProvider(String tag) {
+ super(tag);
+ }
+
+ public Filter newFilter(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ return StringFilter.readFromXml(this, parser);
+ }
+
+ public abstract String getValue(Intent intent, ApplicationInfo callerApp,
+ String resolvedType, ApplicationInfo resolvedApp);
+ }
+
+ private static class EqualsFilter extends StringFilter {
+ private final String mFilterValue;
+
+ public EqualsFilter(ValueProvider valueProvider, String attrValue) {
+ super(valueProvider);
+ mFilterValue = attrValue;
+ }
+
+ @Override
+ public boolean matchesValue(String value) {
+ return value != null && value.equals(mFilterValue);
+ }
+ }
+
+ private static class ContainsFilter extends StringFilter {
+ private final String mFilterValue;
+
+ public ContainsFilter(ValueProvider valueProvider, String attrValue) {
+ super(valueProvider);
+ mFilterValue = attrValue;
+ }
+
+ @Override
+ public boolean matchesValue(String value) {
+ return value != null && value.contains(mFilterValue);
+ }
+ }
+
+ private static class StartsWithFilter extends StringFilter {
+ private final String mFilterValue;
+
+ public StartsWithFilter(ValueProvider valueProvider, String attrValue) {
+ super(valueProvider);
+ mFilterValue = attrValue;
+ }
+
+ @Override
+ public boolean matchesValue(String value) {
+ return value != null && value.startsWith(mFilterValue);
+ }
+ }
+
+ private static class PatternStringFilter extends StringFilter {
+ private final PatternMatcher mPattern;
+
+ public PatternStringFilter(ValueProvider valueProvider, String attrValue) {
+ super(valueProvider);
+ mPattern = new PatternMatcher(attrValue, PatternMatcher.PATTERN_SIMPLE_GLOB);
+ }
+
+ @Override
+ public boolean matchesValue(String value) {
+ return value != null && mPattern.match(value);
+ }
+ }
+
+ private static class RegexFilter extends StringFilter {
+ private final Pattern mPattern;
+
+ public RegexFilter(ValueProvider valueProvider, String attrValue) {
+ super(valueProvider);
+ this.mPattern = Pattern.compile(attrValue);
+ }
+
+ @Override
+ public boolean matchesValue(String value) {
+ return value != null && mPattern.matcher(value).matches();
+ }
+ }
+
+ private static class IsNullFilter extends StringFilter {
+ private final boolean mIsNull;
+
+ public IsNullFilter(ValueProvider valueProvider, String attrValue) {
+ super(valueProvider);
+ mIsNull = Boolean.parseBoolean(attrValue);
+ }
+
+ public IsNullFilter(ValueProvider valueProvider, boolean isNull) {
+ super(valueProvider);
+ mIsNull = isNull;
+ }
+
+ @Override
+ public boolean matchesValue(String value) {
+ return (value == null) == mIsNull;
+ }
+ }
+
+ public static final ValueProvider COMPONENT = new ValueProvider("component") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ ComponentName cn = intent.getComponent();
+ if (cn != null) {
+ return cn.flattenToString();
+ }
+ return null;
+ }
+ };
+
+ public static final ValueProvider COMPONENT_NAME = new ValueProvider("component-name") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ ComponentName cn = intent.getComponent();
+ if (cn != null) {
+ return cn.getClassName();
+ }
+ return null;
+ }
+ };
+
+ public static final ValueProvider COMPONENT_PACKAGE = new ValueProvider("component-package") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ ComponentName cn = intent.getComponent();
+ if (cn != null) {
+ return cn.getPackageName();
+ }
+ return null;
+ }
+ };
+
+ public static final FilterFactory ACTION = new ValueProvider("action") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ return intent.getAction();
+ }
+ };
+
+ public static final ValueProvider DATA = new ValueProvider("data") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ Uri data = intent.getData();
+ if (data != null) {
+ return data.toString();
+ }
+ return null;
+ }
+ };
+
+ public static final ValueProvider MIME_TYPE = new ValueProvider("mime-type") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ return resolvedType;
+ }
+ };
+
+ public static final ValueProvider SCHEME = new ValueProvider("scheme") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ Uri data = intent.getData();
+ if (data != null) {
+ return data.getScheme();
+ }
+ return null;
+ }
+ };
+
+ public static final ValueProvider SSP = new ValueProvider("scheme-specific-part") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ Uri data = intent.getData();
+ if (data != null) {
+ return data.getSchemeSpecificPart();
+ }
+ return null;
+ }
+ };
+
+ public static final ValueProvider HOST = new ValueProvider("host") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ Uri data = intent.getData();
+ if (data != null) {
+ return data.getHost();
+ }
+ return null;
+ }
+ };
+
+ public static final ValueProvider PATH = new ValueProvider("path") {
+ @Override
+ public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType,
+ ApplicationInfo resolvedApp) {
+ Uri data = intent.getData();
+ if (data != null) {
+ return data.getPath();
+ }
+ return null;
+ }
+ };
+}
diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java
index f5cc59f..5d4a770 100644
--- a/services/java/com/android/server/location/GeocoderProxy.java
+++ b/services/java/com/android/server/location/GeocoderProxy.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.location.Address;
import android.location.GeocoderParams;
import android.location.IGeocodeProvider;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -39,8 +40,10 @@ public class GeocoderProxy {
private final ServiceWatcher mServiceWatcher;
public static GeocoderProxy createAndBind(Context context,
- List<String> initialPackageNames, int userId) {
- GeocoderProxy proxy = new GeocoderProxy(context, initialPackageNames, userId);
+ int overlaySwitchResId, int defaultServicePackageNameResId,
+ int initialPackageNamesResId, Handler handler) {
+ GeocoderProxy proxy = new GeocoderProxy(context, overlaySwitchResId,
+ defaultServicePackageNameResId, initialPackageNamesResId, handler);
if (proxy.bind()) {
return proxy;
} else {
@@ -48,11 +51,13 @@ public class GeocoderProxy {
}
}
- public GeocoderProxy(Context context, List<String> initialPackageNames, int userId) {
+ private GeocoderProxy(Context context,
+ int overlaySwitchResId, int defaultServicePackageNameResId,
+ int initialPackageNamesResId, Handler handler) {
mContext = context;
- mServiceWatcher = new ServiceWatcher(mContext, TAG, SERVICE_ACTION, initialPackageNames,
- null, null, userId);
+ mServiceWatcher = new ServiceWatcher(mContext, TAG, SERVICE_ACTION, overlaySwitchResId,
+ defaultServicePackageNameResId, initialPackageNamesResId, null, handler);
}
private boolean bind () {
diff --git a/services/java/com/android/server/location/GeofenceManager.java b/services/java/com/android/server/location/GeofenceManager.java
index f9be719..e24bf76 100644
--- a/services/java/com/android/server/location/GeofenceManager.java
+++ b/services/java/com/android/server/location/GeofenceManager.java
@@ -21,6 +21,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -68,6 +69,7 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish
private final Context mContext;
private final LocationManager mLocationManager;
+ private final AppOpsManager mAppOps;
private final PowerManager.WakeLock mWakeLock;
private final GeofenceHandler mHandler;
private final LocationBlacklist mBlacklist;
@@ -107,6 +109,7 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish
public GeofenceManager(Context context, LocationBlacklist blacklist) {
mContext = context;
mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mHandler = new GeofenceHandler();
@@ -114,14 +117,14 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish
}
public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
- int uid, String packageName) {
+ int allowedResolutionLevel, int uid, String packageName) {
if (D) {
Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
+ ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName);
}
GeofenceState state = new GeofenceState(geofence,
- request.getExpireAt(), packageName, intent);
+ request.getExpireAt(), allowedResolutionLevel, uid, packageName, intent);
synchronized (mLock) {
// first make sure it doesn't already exist
for (int i = mFences.size() - 1; i >= 0; i--) {
@@ -261,6 +264,18 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish
continue;
}
+ int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
+ if (op >= 0) {
+ if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
+ state.mPackageName) != AppOpsManager.MODE_ALLOWED) {
+ if (D) {
+ Slog.d(TAG, "skipping geofence processing for no op app: "
+ + state.mPackageName);
+ }
+ continue;
+ }
+ }
+
needUpdates = true;
if (location != null) {
int event = state.processLocation(location);
diff --git a/services/java/com/android/server/location/GeofenceProxy.java b/services/java/com/android/server/location/GeofenceProxy.java
new file mode 100644
index 0000000..f6be27b
--- /dev/null
+++ b/services/java/com/android/server/location/GeofenceProxy.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 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.location;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.hardware.location.GeofenceHardwareService;
+import android.hardware.location.IGeofenceHardware;
+import android.location.IGeofenceProvider;
+import android.location.IGpsGeofenceHardware;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import com.android.server.ServiceWatcher;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+public final class GeofenceProxy {
+ private static final String TAG = "GeofenceProxy";
+ private static final String SERVICE_ACTION =
+ "com.android.location.service.GeofenceProvider";
+ private ServiceWatcher mServiceWatcher;
+ private Context mContext;
+ private IGeofenceHardware mGeofenceHardware;
+ private IGpsGeofenceHardware mGpsGeofenceHardware;
+
+ private static final int GEOFENCE_PROVIDER_CONNECTED = 1;
+ private static final int GEOFENCE_HARDWARE_CONNECTED = 2;
+ private static final int GEOFENCE_HARDWARE_DISCONNECTED = 3;
+ private static final int GEOFENCE_GPS_HARDWARE_CONNECTED = 4;
+ private static final int GEOFENCE_GPS_HARDWARE_DISCONNECTED = 5;
+
+ private Runnable mRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mHandler.sendEmptyMessage(GEOFENCE_PROVIDER_CONNECTED);
+ }
+ };
+
+ public static GeofenceProxy createAndBind(Context context,
+ int overlaySwitchResId, int defaultServicePackageNameResId,
+ int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence) {
+ GeofenceProxy proxy = new GeofenceProxy(context, overlaySwitchResId,
+ defaultServicePackageNameResId, initialPackageNamesResId, handler, gpsGeofence);
+ if (proxy.bindGeofenceProvider()) {
+ return proxy;
+ } else {
+ return null;
+ }
+ }
+
+ private GeofenceProxy(Context context,
+ int overlaySwitchResId, int defaultServicePackageNameResId,
+ int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence) {
+ mContext = context;
+ mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, overlaySwitchResId,
+ defaultServicePackageNameResId, initialPackageNamesResId, mRunnable, handler);
+ mGpsGeofenceHardware = gpsGeofence;
+ bindHardwareGeofence();
+ }
+
+ private boolean bindGeofenceProvider() {
+ return mServiceWatcher.start();
+ }
+
+ private IGeofenceProvider getGeofenceProviderService() {
+ return IGeofenceProvider.Stub.asInterface(mServiceWatcher.getBinder());
+ }
+
+ private void bindHardwareGeofence() {
+ mContext.bindServiceAsUser(new Intent(mContext, GeofenceHardwareService.class),
+ mServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.OWNER);
+ }
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mGeofenceHardware = IGeofenceHardware.Stub.asInterface(service);
+ mHandler.sendEmptyMessage(GEOFENCE_HARDWARE_CONNECTED);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mGeofenceHardware = null;
+ mHandler.sendEmptyMessage(GEOFENCE_HARDWARE_DISCONNECTED);
+ }
+ };
+
+ private void setGeofenceHardwareInProvider() {
+ try {
+ getGeofenceProviderService().setGeofenceHardware(mGeofenceHardware);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote Exception: setGeofenceHardwareInProvider: " + e);
+ }
+ }
+
+ private void setGpsGeofence() {
+ try {
+ mGeofenceHardware.setGpsGeofenceHardware(mGpsGeofenceHardware);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while connecting to GeofenceHardwareService");
+ }
+ }
+
+
+ // This needs to be reworked, when more services get added,
+ // Might need a state machine or add a framework utility class,
+ private Handler mHandler = new Handler() {
+ private boolean mGeofenceHardwareConnected = false;
+ private boolean mGeofenceProviderConnected = false;
+
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case GEOFENCE_PROVIDER_CONNECTED:
+ mGeofenceProviderConnected = true;
+ if (mGeofenceHardwareConnected) {
+ setGeofenceHardwareInProvider();
+ }
+ break;
+ case GEOFENCE_HARDWARE_CONNECTED:
+ setGpsGeofence();
+ mGeofenceHardwareConnected = true;
+ if (mGeofenceProviderConnected) {
+ setGeofenceHardwareInProvider();
+ }
+ break;
+ case GEOFENCE_HARDWARE_DISCONNECTED:
+ mGeofenceHardwareConnected = false;
+ setGeofenceHardwareInProvider();
+ break;
+ }
+ }
+ };
+}
diff --git a/services/java/com/android/server/location/GeofenceState.java b/services/java/com/android/server/location/GeofenceState.java
index 11705ff..3ebe20a 100644
--- a/services/java/com/android/server/location/GeofenceState.java
+++ b/services/java/com/android/server/location/GeofenceState.java
@@ -35,6 +35,8 @@ public class GeofenceState {
public final Geofence mFence;
private final Location mLocation;
public final long mExpireAt;
+ public final int mAllowedResolutionLevel;
+ public final int mUid;
public final String mPackageName;
public final PendingIntent mIntent;
@@ -42,12 +44,14 @@ public class GeofenceState {
double mDistanceToCenter; // current distance to center of fence
public GeofenceState(Geofence fence, long expireAt,
- String packageName, PendingIntent intent) {
+ int allowedResolutionLevel, int uid, String packageName, PendingIntent intent) {
mState = STATE_UNKNOWN;
mDistanceToCenter = Double.MAX_VALUE;
mFence = fence;
mExpireAt = expireAt;
+ mAllowedResolutionLevel = allowedResolutionLevel;
+ mUid = uid;
mPackageName = packageName;
mIntent = intent;
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index 22c52f4..8c88cab 100644
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -17,13 +17,17 @@
package com.android.server.location;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
+import android.hardware.location.GeofenceHardwareImpl;
+import android.hardware.location.IGeofenceHardware;
import android.location.Criteria;
+import android.location.IGpsGeofenceHardware;
import android.location.IGpsStatusListener;
import android.location.IGpsStatusProvider;
import android.location.ILocationManager;
@@ -32,6 +36,7 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
+import android.location.LocationRequest;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
@@ -40,6 +45,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -55,6 +61,8 @@ import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.util.Log;
import android.util.NtpTrustedTime;
+
+import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.ProviderProperties;
@@ -255,6 +263,9 @@ public class GpsLocationProvider implements LocationProviderInterface {
// true if we started navigation
private boolean mStarted;
+ // true if single shot request is in progress
+ private boolean mSingleShot;
+
// capabilities of the GPS engine
private int mEngineCapabilities;
@@ -304,10 +315,13 @@ public class GpsLocationProvider implements LocationProviderInterface {
private final PendingIntent mWakeupIntent;
private final PendingIntent mTimeoutIntent;
+ private final IAppOpsService mAppOpsService;
private final IBatteryStats mBatteryStats;
// only modified on handler thread
- private int[] mClientUids = new int[0];
+ private WorkSource mClientSource = new WorkSource();
+
+ private GeofenceHardwareImpl mGeofenceHardwareImpl;
private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
@Override
@@ -362,13 +376,17 @@ public class GpsLocationProvider implements LocationProviderInterface {
return mGpsStatusProvider;
}
+ public IGpsGeofenceHardware getGpsGeofenceProxy() {
+ return mGpsGeofenceBinder;
+ }
+
private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ALARM_WAKEUP)) {
if (DEBUG) Log.d(TAG, "ALARM_WAKEUP");
- startNavigating();
+ startNavigating(false);
} else if (action.equals(ALARM_TIMEOUT)) {
if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT");
hibernate();
@@ -413,7 +431,8 @@ public class GpsLocationProvider implements LocationProviderInterface {
return native_is_supported();
}
- public GpsLocationProvider(Context context, ILocationManager ilocationManager) {
+ public GpsLocationProvider(Context context, ILocationManager ilocationManager,
+ Looper looper) {
mContext = context;
mNtpTime = NtpTrustedTime.getInstance(context);
mILocationManager = ilocationManager;
@@ -432,6 +451,10 @@ public class GpsLocationProvider implements LocationProviderInterface {
mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ // App ops service to keep track of who is accessing the GPS
+ mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(
+ Context.APP_OPS_SERVICE));
+
// Battery statistics service to be notified when GPS turns on or off
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
@@ -466,7 +489,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
// construct handler, listen for events
- mHandler = new ProviderHandler();
+ mHandler = new ProviderHandler(looper);
listenForBroadcasts();
// also listen for PASSIVE_PROVIDER updates
@@ -694,17 +717,17 @@ public class GpsLocationProvider implements LocationProviderInterface {
*/
@Override
public void enable() {
+ synchronized (mLock) {
+ if (mEnabled) return;
+ mEnabled = true;
+ }
+
sendMessage(ENABLE, 1, null);
}
private void handleEnable() {
if (DEBUG) Log.d(TAG, "handleEnable");
- synchronized (mLock) {
- if (mEnabled) return;
- mEnabled = true;
- }
-
boolean enabled = native_init();
if (enabled) {
@@ -730,17 +753,17 @@ public class GpsLocationProvider implements LocationProviderInterface {
*/
@Override
public void disable() {
+ synchronized (mLock) {
+ if (!mEnabled) return;
+ mEnabled = false;
+ }
+
sendMessage(ENABLE, 0, null);
}
private void handleDisable() {
if (DEBUG) Log.d(TAG, "handleDisable");
- synchronized (mLock) {
- if (!mEnabled) return;
- mEnabled = false;
- }
-
stopNavigating();
mAlarmManager.cancel(mWakeupIntent);
mAlarmManager.cancel(mTimeoutIntent);
@@ -783,23 +806,26 @@ public class GpsLocationProvider implements LocationProviderInterface {
sendMessage(SET_REQUEST, 0, new GpsRequest(request, source));
}
- @Override
- public void switchUser(int userId) {
- // nothing to do here
- }
-
private void handleSetRequest(ProviderRequest request, WorkSource source) {
- if (DEBUG) Log.d(TAG, "setRequest " + request);
+ boolean singleShot = false;
+ // see if the request is for a single update
+ if (request.locationRequests != null && request.locationRequests.size() > 0) {
+ // if any request has zero or more than one updates
+ // requested, then this is not single-shot mode
+ singleShot = true;
+ for (LocationRequest lr : request.locationRequests) {
+ if (lr.getNumUpdates() != 1) {
+ singleShot = false;
+ }
+ }
+ }
+ if (DEBUG) Log.d(TAG, "setRequest " + request);
if (request.reportLocation) {
// update client uids
- int[] uids = new int[source.size()];
- for (int i=0; i < source.size(); i++) {
- uids[i] = source.get(i);
- }
- updateClientUids(uids);
+ updateClientUids(source);
mFixInterval = (int) request.interval;
@@ -818,10 +844,10 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
} else if (!mStarted) {
// start GPS
- startNavigating();
+ startNavigating(singleShot);
}
} else {
- updateClientUids(new int[0]);
+ updateClientUids(new WorkSource());
stopNavigating();
mAlarmManager.cancel(mWakeupIntent);
@@ -849,45 +875,48 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
}
- private void updateClientUids(int[] uids) {
- // Find uid's that were not previously tracked
- for (int uid1 : uids) {
- boolean newUid = true;
- for (int uid2 : mClientUids) {
- if (uid1 == uid2) {
- newUid = false;
- break;
- }
- }
- if (newUid) {
+ private void updateClientUids(WorkSource source) {
+ // Update work source.
+ WorkSource[] changes = mClientSource.setReturningDiffs(source);
+ if (changes == null) {
+ return;
+ }
+ WorkSource newWork = changes[0];
+ WorkSource goneWork = changes[1];
+
+ // Update sources that were not previously tracked.
+ if (newWork != null) {
+ int lastuid = -1;
+ for (int i=0; i<newWork.size(); i++) {
try {
- mBatteryStats.noteStartGps(uid1);
+ int uid = newWork.get(i);
+ mAppOpsService.startOperation(AppOpsManager.OP_GPS, uid, newWork.getName(i));
+ if (uid != lastuid) {
+ lastuid = uid;
+ mBatteryStats.noteStartGps(uid);
+ }
} catch (RemoteException e) {
Log.w(TAG, "RemoteException", e);
}
}
}
- // Find uid'd that were tracked but have now disappeared
- for (int uid1 : mClientUids) {
- boolean oldUid = true;
- for (int uid2 : uids) {
- if (uid1 == uid2) {
- oldUid = false;
- break;
- }
- }
- if (oldUid) {
+ // Update sources that are no longer tracked.
+ if (goneWork != null) {
+ int lastuid = -1;
+ for (int i=0; i<goneWork.size(); i++) {
try {
- mBatteryStats.noteStopGps(uid1);
+ int uid = goneWork.get(i);
+ mAppOpsService.finishOperation(AppOpsManager.OP_GPS, uid, goneWork.getName(i));
+ if (uid != lastuid) {
+ lastuid = uid;
+ mBatteryStats.noteStopGps(uid);
+ }
} catch (RemoteException e) {
Log.w(TAG, "RemoteException", e);
}
}
}
-
- // save current uids
- mClientUids = uids;
}
@Override
@@ -914,6 +943,31 @@ public class GpsLocationProvider implements LocationProviderInterface {
return result;
}
+ private IGpsGeofenceHardware mGpsGeofenceBinder = new IGpsGeofenceHardware.Stub() {
+ public boolean isHardwareGeofenceSupported() {
+ return native_is_geofence_supported();
+ }
+
+ public boolean addCircularHardwareGeofence(int geofenceId, double latitude,
+ double longitude, double radius, int lastTransition, int monitorTransitions,
+ int notificationResponsiveness, int unknownTimer) {
+ return native_add_geofence(geofenceId, latitude, longitude, radius,
+ lastTransition, monitorTransitions, notificationResponsiveness, unknownTimer);
+ }
+
+ public boolean removeHardwareGeofence(int geofenceId) {
+ return native_remove_geofence(geofenceId);
+ }
+
+ public boolean pauseHardwareGeofence(int geofenceId) {
+ return native_pause_geofence(geofenceId);
+ }
+
+ public boolean resumeHardwareGeofence(int geofenceId, int monitorTransition) {
+ return native_resume_geofence(geofenceId, monitorTransition);
+ }
+ };
+
private boolean deleteAidingData(Bundle extras) {
int flags;
@@ -944,21 +998,44 @@ public class GpsLocationProvider implements LocationProviderInterface {
return false;
}
- private void startNavigating() {
+ private void startNavigating(boolean singleShot) {
if (!mStarted) {
- if (DEBUG) Log.d(TAG, "startNavigating");
+ if (DEBUG) Log.d(TAG, "startNavigating, singleShot is " + singleShot);
mTimeToFirstFix = 0;
mLastFixTime = 0;
mStarted = true;
+ mSingleShot = singleShot;
mPositionMode = GPS_POSITION_MODE_STANDALONE;
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0) {
- if (hasCapability(GPS_CAPABILITY_MSB)) {
+ if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) {
+ mPositionMode = GPS_POSITION_MODE_MS_ASSISTED;
+ } else if (hasCapability(GPS_CAPABILITY_MSB)) {
mPositionMode = GPS_POSITION_MODE_MS_BASED;
}
}
+ if (DEBUG) {
+ String mode;
+
+ switch(mPositionMode) {
+ case GPS_POSITION_MODE_STANDALONE:
+ mode = "standalone";
+ break;
+ case GPS_POSITION_MODE_MS_ASSISTED:
+ mode = "MS_ASSISTED";
+ break;
+ case GPS_POSITION_MODE_MS_BASED:
+ mode = "MS_BASED";
+ break;
+ default:
+ mode = "unknown";
+ break;
+ }
+ Log.d(TAG, "setting position_mode to " + mode);
+ }
+
int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000);
if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC,
interval, 0, 0)) {
@@ -990,6 +1067,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
if (DEBUG) Log.d(TAG, "stopNavigating");
if (mStarted) {
mStarted = false;
+ mSingleShot = false;
native_stop();
mTimeToFirstFix = 0;
mLastFixTime = 0;
@@ -1013,6 +1091,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
return ((mEngineCapabilities & capability) != 0);
}
+
/**
* called from native code to update our position.
*/
@@ -1083,6 +1162,10 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
}
+ if (mSingleShot) {
+ stopNavigating();
+ }
+
if (mStarted && mStatus != LocationProvider.AVAILABLE) {
// we want to time out if we do not receive a fix
// within the time out and we are requesting infrequent fixes
@@ -1244,7 +1327,8 @@ public class GpsLocationProvider implements LocationProviderInterface {
if (DEBUG) Log.d(TAG, "PhoneConstants.APN_REQUEST_STARTED");
// Nothing to do here
} else {
- if (DEBUG) Log.d(TAG, "startUsingNetworkFeature failed");
+ if (DEBUG) Log.d(TAG, "startUsingNetworkFeature failed, value is " +
+ result);
mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
native_agps_data_conn_failed();
}
@@ -1316,6 +1400,73 @@ public class GpsLocationProvider implements LocationProviderInterface {
sendMessage(DOWNLOAD_XTRA_DATA, 0, null);
}
+ /**
+ * Called from native to report GPS Geofence transition
+ * All geofence callbacks are called on the same thread
+ */
+ private void reportGeofenceTransition(int geofenceId, int flags, double latitude,
+ double longitude, double altitude, float speed, float bearing, float accuracy,
+ long timestamp, int transition, long transitionTimestamp) {
+ if (mGeofenceHardwareImpl == null) {
+ mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+ }
+ mGeofenceHardwareImpl.reportGpsGeofenceTransition(geofenceId, flags, latitude, longitude,
+ altitude, speed, bearing, accuracy, timestamp, transition, transitionTimestamp);
+ }
+
+ /**
+ * called from native code to report GPS status change.
+ */
+ private void reportGeofenceStatus(int status, int flags, double latitude,
+ double longitude, double altitude, float speed, float bearing, float accuracy,
+ long timestamp) {
+ if (mGeofenceHardwareImpl == null) {
+ mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+ }
+ mGeofenceHardwareImpl.reportGpsGeofenceStatus(status, flags, latitude, longitude, altitude,
+ speed, bearing, accuracy, timestamp);
+ }
+
+ /**
+ * called from native code - Geofence Add callback
+ */
+ private void reportGeofenceAddStatus(int geofenceId, int status) {
+ if (mGeofenceHardwareImpl == null) {
+ mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+ }
+ mGeofenceHardwareImpl.reportGpsGeofenceAddStatus(geofenceId, status);
+ }
+
+ /**
+ * called from native code - Geofence Remove callback
+ */
+ private void reportGeofenceRemoveStatus(int geofenceId, int status) {
+ if (mGeofenceHardwareImpl == null) {
+ mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+ }
+ mGeofenceHardwareImpl.reportGpsGeofenceRemoveStatus(geofenceId, status);
+ }
+
+ /**
+ * called from native code - Geofence Pause callback
+ */
+ private void reportGeofencePauseStatus(int geofenceId, int status) {
+ if (mGeofenceHardwareImpl == null) {
+ mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+ }
+ mGeofenceHardwareImpl.reportGpsGeofencePauseStatus(geofenceId, status);
+ }
+
+ /**
+ * called from native code - Geofence Resume callback
+ */
+ private void reportGeofenceResumeStatus(int geofenceId, int status) {
+ if (mGeofenceHardwareImpl == null) {
+ mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+ }
+ mGeofenceHardwareImpl.reportGpsGeofenceResumeStatus(geofenceId, status);
+ }
+
//=============================================================
// NI Client support
//=============================================================
@@ -1452,11 +1603,11 @@ public class GpsLocationProvider implements LocationProviderInterface {
private void requestRefLocation(int flags) {
TelephonyManager phone = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
- if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+ final int phoneType = phone.getPhoneType();
+ if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
GsmCellLocation gsm_cell = (GsmCellLocation) phone.getCellLocation();
- if ((gsm_cell != null) && (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) &&
- (phone.getNetworkOperator() != null) &&
- (phone.getNetworkOperator().length() > 3)) {
+ if ((gsm_cell != null) && (phone.getNetworkOperator() != null)
+ && (phone.getNetworkOperator().length() > 3)) {
int type;
int mcc = Integer.parseInt(phone.getNetworkOperator().substring(0,3));
int mnc = Integer.parseInt(phone.getNetworkOperator().substring(3));
@@ -1475,9 +1626,8 @@ public class GpsLocationProvider implements LocationProviderInterface {
} else {
Log.e(TAG,"Error getting cell location info.");
}
- }
- else {
- Log.e(TAG,"CDMA not supported.");
+ } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
+ Log.e(TAG, "CDMA not supported.");
}
}
@@ -1490,8 +1640,8 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
private final class ProviderHandler extends Handler {
- public ProviderHandler() {
- super(true /*async*/);
+ public ProviderHandler(Looper looper) {
+ super(looper, null, true /*async*/);
}
@Override
@@ -1647,4 +1797,13 @@ public class GpsLocationProvider implements LocationProviderInterface {
private native void native_update_network_state(boolean connected, int type,
boolean roaming, boolean available, String extraInfo, String defaultAPN);
+
+ // Hardware Geofence support.
+ private static native boolean native_is_geofence_supported();
+ private static native boolean native_add_geofence(int geofenceId, double latitude,
+ double longitude, double radius, int lastTransition,int monitorTransitions,
+ int notificationResponsivenes, int unknownTimer);
+ private static native boolean native_remove_geofence(int geofenceId);
+ private static native boolean native_resume_geofence(int geofenceId, int transitions);
+ private static native boolean native_pause_geofence(int geofenceId);
}
diff --git a/services/java/com/android/server/location/LocationBlacklist.java b/services/java/com/android/server/location/LocationBlacklist.java
index 2437a37..6f22689 100644
--- a/services/java/com/android/server/location/LocationBlacklist.java
+++ b/services/java/com/android/server/location/LocationBlacklist.java
@@ -67,9 +67,9 @@ public final class LocationBlacklist extends ContentObserver {
private void reloadBlacklistLocked() {
mWhitelist = getStringArrayLocked(WHITELIST_CONFIG_NAME);
- Slog.i(TAG, "whitelist: " + Arrays.toString(mWhitelist));
+ if (D) Slog.d(TAG, "whitelist: " + Arrays.toString(mWhitelist));
mBlacklist = getStringArrayLocked(BLACKLIST_CONFIG_NAME);
- Slog.i(TAG, "blacklist: " + Arrays.toString(mBlacklist));
+ if (D) Slog.d(TAG, "blacklist: " + Arrays.toString(mBlacklist));
}
private void reloadBlacklist() {
diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java
index 80e71f1..6f09232 100644
--- a/services/java/com/android/server/location/LocationProviderInterface.java
+++ b/services/java/com/android/server/location/LocationProviderInterface.java
@@ -38,8 +38,6 @@ public interface LocationProviderInterface {
public boolean isEnabled();
public void setRequest(ProviderRequest request, WorkSource source);
- public void switchUser(int userId);
-
public void dump(FileDescriptor fd, PrintWriter pw, String[] args);
// --- deprecated (but still supported) ---
diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
index dd2e71c..14db862 100644
--- a/services/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -25,7 +25,6 @@ import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Log;
@@ -54,10 +53,13 @@ public class LocationProviderProxy implements LocationProviderInterface {
private ProviderRequest mRequest = null;
private WorkSource mWorksource = new WorkSource();
- public static LocationProviderProxy createAndBind(Context context, String name, String action,
- List<String> initialPackageNames, Handler handler, int userId) {
+ public static LocationProviderProxy createAndBind(
+ Context context, String name, String action,
+ int overlaySwitchResId, int defaultServicePackageNameResId,
+ int initialPackageNamesResId, Handler handler) {
LocationProviderProxy proxy = new LocationProviderProxy(context, name, action,
- initialPackageNames, handler, userId);
+ overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId,
+ handler);
if (proxy.bind()) {
return proxy;
} else {
@@ -66,11 +68,13 @@ public class LocationProviderProxy implements LocationProviderInterface {
}
private LocationProviderProxy(Context context, String name, String action,
- List<String> initialPackageNames, Handler handler, int userId) {
+ int overlaySwitchResId, int defaultServicePackageNameResId,
+ int initialPackageNamesResId, Handler handler) {
mContext = context;
mName = name;
- mServiceWatcher = new ServiceWatcher(mContext, TAG, action, initialPackageNames,
- mNewServiceWork, handler, userId);
+ mServiceWatcher = new ServiceWatcher(mContext, TAG + "-" + name, action, overlaySwitchResId,
+ defaultServicePackageNameResId, initialPackageNamesResId,
+ mNewServiceWork, handler);
}
private boolean bind () {
@@ -212,11 +216,6 @@ public class LocationProviderProxy implements LocationProviderInterface {
}
@Override
- public void switchUser(int userId) {
- mServiceWatcher.switchUser(userId);
- }
-
- @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.append("REMOTE SERVICE");
pw.append(" name=").append(mName);
diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java
index 1194cbc..36c43ff 100644
--- a/services/java/com/android/server/location/MockProvider.java
+++ b/services/java/com/android/server/location/MockProvider.java
@@ -156,11 +156,6 @@ public class MockProvider implements LocationProviderInterface {
public void setRequest(ProviderRequest request, WorkSource source) { }
@Override
- public void switchUser(int userId) {
- // nothing to do here
- }
-
- @Override
public boolean sendExtraCommand(String command, Bundle extras) {
return false;
}
diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java
index 734c572..71bae07 100644
--- a/services/java/com/android/server/location/PassiveProvider.java
+++ b/services/java/com/android/server/location/PassiveProvider.java
@@ -96,11 +96,6 @@ public class PassiveProvider implements LocationProviderInterface {
mReportLocation = request.reportLocation;
}
- @Override
- public void switchUser(int userId) {
- // nothing to do here
- }
-
public void updateLocation(Location location) {
if (mReportLocation) {
try {
diff --git a/services/java/com/android/server/net/LockdownVpnTracker.java b/services/java/com/android/server/net/LockdownVpnTracker.java
index f32dd09..e251925 100644
--- a/services/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/java/com/android/server/net/LockdownVpnTracker.java
@@ -56,7 +56,9 @@ public class LockdownVpnTracker {
private static final int MAX_ERROR_COUNT = 4;
private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
+
private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
+ private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
private final Context mContext;
private final INetworkManagementService mNetService;
@@ -66,7 +68,8 @@ public class LockdownVpnTracker {
private final Object mStateLock = new Object();
- private PendingIntent mResetIntent;
+ private final PendingIntent mConfigIntent;
+ private final PendingIntent mResetIntent;
private String mAcceptedEgressIface;
private String mAcceptedIface;
@@ -86,6 +89,10 @@ public class LockdownVpnTracker {
mVpn = Preconditions.checkNotNull(vpn);
mProfile = Preconditions.checkNotNull(profile);
+ final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
+ configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
+ mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
+
final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
@@ -121,7 +128,10 @@ public class LockdownVpnTracker {
mAcceptedEgressIface = null;
mVpn.stopLegacyVpn();
}
- if (egressDisconnected) return;
+ if (egressDisconnected) {
+ hideNotification();
+ return;
+ }
final int egressType = egressInfo.getType();
if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
@@ -185,6 +195,7 @@ public class LockdownVpnTracker {
Slog.d(TAG, "initLocked()");
mVpn.setEnableNotifications(false);
+ mVpn.setEnableTeardown(false);
final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
@@ -193,6 +204,7 @@ public class LockdownVpnTracker {
// TODO: support non-standard port numbers
mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
+ mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
} catch (RemoteException e) {
throw new RuntimeException("Problem setting firewall rules", e);
}
@@ -218,6 +230,7 @@ public class LockdownVpnTracker {
try {
mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
+ mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
} catch (RemoteException e) {
throw new RuntimeException("Problem setting firewall rules", e);
}
@@ -226,6 +239,7 @@ public class LockdownVpnTracker {
mContext.unregisterReceiver(mResetReceiver);
mVpn.setEnableNotifications(true);
+ mVpn.setEnableTeardown(true);
}
public void reset() {
@@ -281,10 +295,13 @@ public class LockdownVpnTracker {
builder.setWhen(0);
builder.setSmallIcon(iconRes);
builder.setContentTitle(mContext.getString(titleRes));
- builder.setContentText(mContext.getString(R.string.vpn_lockdown_reset));
- builder.setContentIntent(mResetIntent);
+ builder.setContentText(mContext.getString(R.string.vpn_lockdown_config));
+ builder.setContentIntent(mConfigIntent);
builder.setPriority(Notification.PRIORITY_LOW);
builder.setOngoing(true);
+ builder.addAction(
+ R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent);
+
NotificationManager.from(mContext).notify(TAG, 0, builder.build());
}
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index b09390c..a82f421 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -52,7 +52,6 @@ import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.TrafficStats.MB_IN_BYTES;
-import static android.net.wifi.WifiInfo.removeDoubleQuotes;
import static android.net.wifi.WifiManager.CHANGE_REASON_ADDED;
import static android.net.wifi.WifiManager.CHANGE_REASON_REMOVED;
import static android.net.wifi.WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION;
@@ -64,13 +63,13 @@ import static android.telephony.TelephonyManager.SIM_STATE_READY;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static com.android.internal.util.ArrayUtils.appendInt;
import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
-import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.readBooleanAttribute;
-import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.readIntAttribute;
-import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.readLongAttribute;
-import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeBooleanAttribute;
-import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeIntAttribute;
-import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.writeLongAttribute;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -150,7 +149,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -551,8 +549,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final WifiConfiguration config = intent.getParcelableExtra(
EXTRA_WIFI_CONFIGURATION);
if (config.SSID != null) {
- final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(
- removeDoubleQuotes(config.SSID));
+ final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(config.SSID);
synchronized (mRulesLock) {
if (mNetworkPolicy.containsKey(template)) {
mNetworkPolicy.remove(template);
@@ -581,8 +578,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final WifiInfo info = intent.getParcelableExtra(EXTRA_WIFI_INFO);
final boolean meteredHint = info.getMeteredHint();
- final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(
- removeDoubleQuotes(info.getSSID()));
+ final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(info.getSSID());
synchronized (mRulesLock) {
NetworkPolicy policy = mNetworkPolicy.get(template);
if (policy == null && meteredHint) {
@@ -831,7 +827,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final String packageName = mContext.getPackageName();
final int[] idReceived = new int[1];
mNotifManager.enqueueNotificationWithTag(
- packageName, tag, 0x0, builder.getNotification(), idReceived,
+ packageName, packageName, tag, 0x0, builder.getNotification(), idReceived,
UserHandle.USER_OWNER);
mActiveNotifs.add(tag);
} catch (RemoteException e) {
@@ -866,7 +862,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
try {
final String packageName = mContext.getPackageName();
final int[] idReceived = new int[1];
- mNotifManager.enqueueNotificationWithTag(packageName, tag,
+ mNotifManager.enqueueNotificationWithTag(packageName, packageName, tag,
0x0, builder.getNotification(), idReceived, UserHandle.USER_OWNER);
mActiveNotifs.add(tag);
} catch (RemoteException e) {
@@ -2091,44 +2087,4 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
fout.print("]");
}
-
- public static class XmlUtils {
- public static int readIntAttribute(XmlPullParser in, String name) throws IOException {
- final String value = in.getAttributeValue(null, name);
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- throw new ProtocolException("problem parsing " + name + "=" + value + " as int");
- }
- }
-
- public static void writeIntAttribute(XmlSerializer out, String name, int value)
- throws IOException {
- out.attribute(null, name, Integer.toString(value));
- }
-
- public static long readLongAttribute(XmlPullParser in, String name) throws IOException {
- final String value = in.getAttributeValue(null, name);
- try {
- return Long.parseLong(value);
- } catch (NumberFormatException e) {
- throw new ProtocolException("problem parsing " + name + "=" + value + " as long");
- }
- }
-
- public static void writeLongAttribute(XmlSerializer out, String name, long value)
- throws IOException {
- out.attribute(null, name, Long.toString(value));
- }
-
- public static boolean readBooleanAttribute(XmlPullParser in, String name) {
- final String value = in.getAttributeValue(null, name);
- return Boolean.parseBoolean(value);
- }
-
- public static void writeBooleanAttribute(XmlSerializer out, String name, boolean value)
- throws IOException {
- out.attribute(null, name, Boolean.toString(value));
- }
- }
}
diff --git a/services/java/com/android/server/os/SchedulingPolicyService.java b/services/java/com/android/server/os/SchedulingPolicyService.java
new file mode 100644
index 0000000..c0123bf
--- /dev/null
+++ b/services/java/com/android/server/os/SchedulingPolicyService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 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.os;
+
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.ISchedulingPolicyService;
+import android.os.Process;
+
+/**
+ * The implementation of the scheduling policy service interface.
+ *
+ * @hide
+ */
+public class SchedulingPolicyService extends ISchedulingPolicyService.Stub {
+
+ private static final String TAG = "SchedulingPolicyService";
+
+ // Minimum and maximum values allowed for requestPriority parameter prio
+ private static final int PRIORITY_MIN = 1;
+ private static final int PRIORITY_MAX = 3;
+
+ public SchedulingPolicyService() {
+ }
+
+ public int requestPriority(int pid, int tid, int prio) {
+ //Log.i(TAG, "requestPriority(pid=" + pid + ", tid=" + tid + ", prio=" + prio + ")");
+
+ // Verify that caller is mediaserver, priority is in range, and that the
+ // callback thread specified by app belongs to the app that called mediaserver.
+ // Once we've verified that the caller is mediaserver, we can trust the pid but
+ // we can't trust the tid. No need to explicitly check for pid == 0 || tid == 0,
+ // since if not the case then the getThreadGroupLeader() test will also fail.
+ if (Binder.getCallingUid() != Process.MEDIA_UID || prio < PRIORITY_MIN ||
+ prio > PRIORITY_MAX || Process.getThreadGroupLeader(tid) != pid) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ try {
+ // make good use of our CAP_SYS_NICE capability
+ Process.setThreadGroup(tid, Binder.getCallingPid() == pid ?
+ Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_AUDIO_APP);
+ // must be in this order or it fails the schedulability constraint
+ Process.setThreadScheduler(tid, Process.SCHED_FIFO, prio);
+ } catch (RuntimeException e) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
+}
diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java
index 6a071ef..734d071 100644
--- a/services/java/com/android/server/pm/Installer.java
+++ b/services/java/com/android/server/pm/Installer.java
@@ -265,7 +265,7 @@ public final class Installer {
return execute(builder.toString());
}
- public int createUserData(String name, int uid, int userId, String seinfo) {
+ public int createUserData(String name, int uid, int userId) {
StringBuilder builder = new StringBuilder("mkuserdata");
builder.append(' ');
builder.append(name);
@@ -273,8 +273,6 @@ public final class Installer {
builder.append(uid);
builder.append(' ');
builder.append(userId);
- builder.append(' ');
- builder.append(seinfo != null ? seinfo : "!");
return execute(builder.toString());
}
@@ -294,27 +292,6 @@ public final class Installer {
return execute(builder.toString());
}
- /**
- * Clone all the package data directories from srcUserId to targetUserId. If copyData is true,
- * some of the data is also copied, otherwise just empty directories are created with the
- * correct access rights.
- * @param srcUserId user to copy the data directories from
- * @param targetUserId user to copy the data directories to
- * @param copyData whether the data itself is to be copied. If false, empty directories are
- * created.
- * @return success/error code
- */
- public int cloneUserData(int srcUserId, int targetUserId, boolean copyData) {
- StringBuilder builder = new StringBuilder("cloneuserdata");
- builder.append(' ');
- builder.append(srcUserId);
- builder.append(' ');
- builder.append(targetUserId);
- builder.append(' ');
- builder.append(copyData ? '1' : '0');
- return execute(builder.toString());
- }
-
public boolean ping() {
if (execute("ping") < 0) {
return false;
@@ -330,8 +307,8 @@ public final class Installer {
return execute(builder.toString());
}
- public int getSizeInfo(String pkgName, int persona, String apkPath, String fwdLockApkPath,
- String asecPath, PackageStats pStats) {
+ public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath,
+ String fwdLockApkPath, String asecPath, PackageStats pStats) {
StringBuilder builder = new StringBuilder("getsize");
builder.append(' ');
builder.append(pkgName);
@@ -340,6 +317,8 @@ public final class Installer {
builder.append(' ');
builder.append(apkPath);
builder.append(' ');
+ builder.append(libDirPath != null ? libDirPath : "!");
+ builder.append(' ');
builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!");
builder.append(' ');
builder.append(asecPath != null ? asecPath : "!");
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index b8324ee..22ce284 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -20,11 +20,11 @@ import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static com.android.internal.util.ArrayUtils.appendInt;
import static com.android.internal.util.ArrayUtils.removeInt;
-import static libcore.io.OsConstants.S_ISLNK;
import static libcore.io.OsConstants.S_IRWXU;
import static libcore.io.OsConstants.S_IRGRP;
import static libcore.io.OsConstants.S_IXGRP;
@@ -111,7 +111,9 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.Environment.UserEnvironment;
+import android.os.UserManager;
import android.provider.Settings.Secure;
+import android.security.KeyStore;
import android.security.SystemKeyStore;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -148,13 +150,11 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
import libcore.io.ErrnoException;
import libcore.io.IoUtils;
import libcore.io.Libcore;
-import libcore.io.OsConstants;
import libcore.io.StructStat;
/**
@@ -172,7 +172,7 @@ adb shell am instrument -w -e class com.android.unit_tests.PackageManagerTests c
public class PackageManagerService extends IPackageManager.Stub {
static final String TAG = "PackageManager";
static final boolean DEBUG_SETTINGS = false;
- private static final boolean DEBUG_PREFERRED = false;
+ static final boolean DEBUG_PREFERRED = false;
static final boolean DEBUG_UPGRADE = false;
private static final boolean DEBUG_INSTALL = false;
private static final boolean DEBUG_REMOVE = false;
@@ -188,6 +188,7 @@ public class PackageManagerService extends IPackageManager.Stub {
private static final int LOG_UID = Process.LOG_UID;
private static final int NFC_UID = Process.NFC_UID;
private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID;
+ private static final int SHELL_UID = Process.SHELL_UID;
private static final boolean GET_CERTIFICATES = true;
@@ -340,9 +341,20 @@ public class PackageManagerService extends IPackageManager.Stub {
final SparseArray<HashSet<String>> mSystemPermissions =
new SparseArray<HashSet<String>>();
+ static final class SharedLibraryEntry {
+ final String path;
+ final String apk;
+
+ SharedLibraryEntry(String _path, String _apk) {
+ path = _path;
+ apk = _apk;
+ }
+ }
+
// These are the built-in shared libraries that were read from the
// etc/permissions.xml file.
- final HashMap<String, String> mSharedLibraries = new HashMap<String, String>();
+ final HashMap<String, SharedLibraryEntry> mSharedLibraries
+ = new HashMap<String, SharedLibraryEntry>();
// Temporary for building the final shared libraries for an .apk.
String[] mTmpSharedLibraries = null;
@@ -394,8 +406,7 @@ public class PackageManagerService extends IPackageManager.Stub {
final SparseArray<PackageVerificationState> mPendingVerification
= new SparseArray<PackageVerificationState>();
- final ArrayList<PackageParser.Package> mDeferredDexOpt =
- new ArrayList<PackageParser.Package>();
+ HashSet<PackageParser.Package> mDeferredDexOpt = null;
/** Token for keys in mPendingVerification. */
private int mPendingVerificationToken = 0;
@@ -411,8 +422,71 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageParser.Package mPlatformPackage;
// Set of pending broadcasts for aggregating enable/disable of components.
- final HashMap<String, ArrayList<String>> mPendingBroadcasts
- = new HashMap<String, ArrayList<String>>();
+ static class PendingPackageBroadcasts {
+ // for each user id, a map of <package name -> components within that package>
+ final SparseArray<HashMap<String, ArrayList<String>>> mUidMap;
+
+ public PendingPackageBroadcasts() {
+ mUidMap = new SparseArray<HashMap<String, ArrayList<String>>>();
+ }
+
+ public ArrayList<String> get(int userId, String packageName) {
+ HashMap<String, ArrayList<String>> packages = getOrAllocate(userId);
+ return packages.get(packageName);
+ }
+
+ public void put(int userId, String packageName, ArrayList<String> components) {
+ HashMap<String, ArrayList<String>> packages = getOrAllocate(userId);
+ packages.put(packageName, components);
+ }
+
+ public void remove(int userId, String packageName) {
+ HashMap<String, ArrayList<String>> packages = mUidMap.get(userId);
+ if (packages != null) {
+ packages.remove(packageName);
+ }
+ }
+
+ public void remove(int userId) {
+ mUidMap.remove(userId);
+ }
+
+ public int userIdCount() {
+ return mUidMap.size();
+ }
+
+ public int userIdAt(int n) {
+ return mUidMap.keyAt(n);
+ }
+
+ public HashMap<String, ArrayList<String>> packagesForUserId(int userId) {
+ return mUidMap.get(userId);
+ }
+
+ public int size() {
+ // total number of pending broadcast entries across all userIds
+ int num = 0;
+ for (int i = 0; i< mUidMap.size(); i++) {
+ num += mUidMap.valueAt(i).size();
+ }
+ return num;
+ }
+
+ public void clear() {
+ mUidMap.clear();
+ }
+
+ private HashMap<String, ArrayList<String>> getOrAllocate(int userId) {
+ HashMap<String, ArrayList<String>> map = mUidMap.get(userId);
+ if (map == null) {
+ map = new HashMap<String, ArrayList<String>>();
+ mUidMap.put(userId, map);
+ }
+ return map;
+ }
+ }
+ final PendingPackageBroadcasts mPendingBroadcasts = new PendingPackageBroadcasts();
+
// Service Connection to remote media container service to copy
// package uri's from external media onto secure containers
// or internal storage.
@@ -485,8 +559,8 @@ public class PackageManagerService extends IPackageManager.Stub {
" DefaultContainerService");
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
- if (mContext.bindService(service, mDefContainerConn,
- Context.BIND_AUTO_CREATE, UserHandle.USER_OWNER)) {
+ if (mContext.bindServiceAsUser(service, mDefContainerConn,
+ Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;
return true;
@@ -518,10 +592,9 @@ public class PackageManagerService extends IPackageManager.Stub {
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
- if (DEBUG_INSTALL) Slog.i(TAG, "init_copy");
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
- if (DEBUG_INSTALL) Slog.i(TAG, "idx=" + idx);
+ if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
// If a bind was already initiated we dont really
// need to do anything. The pending install
// will be processed later on.
@@ -657,16 +730,23 @@ public class PackageManagerService extends IPackageManager.Stub {
packages = new String[size];
components = new ArrayList[size];
uids = new int[size];
- Iterator<Map.Entry<String, ArrayList<String>>>
- it = mPendingBroadcasts.entrySet().iterator();
- int i = 0;
- while (it.hasNext() && i < size) {
- Map.Entry<String, ArrayList<String>> ent = it.next();
- packages[i] = ent.getKey();
- components[i] = ent.getValue();
- PackageSetting ps = mSettings.mPackages.get(ent.getKey());
- uids[i] = (ps != null) ? ps.appId : -1;
- i++;
+ int i = 0; // filling out the above arrays
+
+ for (int n = 0; n < mPendingBroadcasts.userIdCount(); n++) {
+ int packageUserId = mPendingBroadcasts.userIdAt(n);
+ Iterator<Map.Entry<String, ArrayList<String>>> it
+ = mPendingBroadcasts.packagesForUserId(packageUserId)
+ .entrySet().iterator();
+ while (it.hasNext() && i < size) {
+ Map.Entry<String, ArrayList<String>> ent = it.next();
+ packages[i] = ent.getKey();
+ components[i] = ent.getValue();
+ PackageSetting ps = mSettings.mPackages.get(ent.getKey());
+ uids[i] = (ps != null)
+ ? UserHandle.getUid(packageUserId, ps.appId)
+ : -1;
+ i++;
+ }
}
size = i;
mPendingBroadcasts.clear();
@@ -980,6 +1060,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mSettings.addSharedUserLPw("android.uid.log", LOG_UID, ApplicationInfo.FLAG_SYSTEM);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, ApplicationInfo.FLAG_SYSTEM);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, ApplicationInfo.FLAG_SYSTEM);
+ mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, ApplicationInfo.FLAG_SYSTEM);
String separateProcesses = SystemProperties.get("debug.separate_processes");
if (separateProcesses != null && separateProcesses.length() > 0) {
@@ -1025,8 +1106,9 @@ public class PackageManagerService extends IPackageManager.Stub {
mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
- mRestoredSettings = mSettings.readLPw(sUserManager.getUsers(false),
+ mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
mSdkVersion, mOnlyCore);
+
long startTime = SystemClock.uptimeMillis();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
@@ -1076,9 +1158,12 @@ public class PackageManagerService extends IPackageManager.Stub {
* Also ensure all external libraries have had dexopt run on them.
*/
if (mSharedLibraries.size() > 0) {
- Iterator<String> libs = mSharedLibraries.values().iterator();
+ Iterator<SharedLibraryEntry> libs = mSharedLibraries.values().iterator();
while (libs.hasNext()) {
- String lib = libs.next();
+ String lib = libs.next().path;
+ if (lib == null) {
+ continue;
+ }
try {
if (dalvik.system.DexFile.isDexOptNeeded(lib)) {
libFiles.add(lib);
@@ -1282,6 +1367,10 @@ public class PackageManagerService extends IPackageManager.Stub {
mDrmAppInstallObserver = null;
}
+ // Now that we know all of the shared libraries, update all clients to have
+ // the correct library paths.
+ updateAllSharedLibrariesLPw();
+
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
Slog.i(TAG, "Time to scan packages: "
@@ -1306,6 +1395,12 @@ public class PackageManagerService extends IPackageManager.Stub {
? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL)
: 0));
+ // If this is the first boot, and it is a normal boot, then
+ // we need to initialize the default preferred apps.
+ if (!mRestoredSettings && !onlyCore) {
+ mSettings.readDefaultPreferredAppsLPw(this, 0);
+ }
+
// can downgrade to reader
mSettings.writeLPr();
@@ -1352,7 +1447,8 @@ public class PackageManagerService extends IPackageManager.Stub {
continue;
}
- if (!ps.grantedPermissions
+ final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+ if (!gp.grantedPermissions
.contains(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)) {
continue;
}
@@ -1458,7 +1554,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if ("group".equals(name)) {
String gidStr = parser.getAttributeValue(null, "gid");
if (gidStr != null) {
- int gid = Integer.parseInt(gidStr);
+ int gid = Process.getGidForName(gidStr);
mGlobalGids = appendInt(mGlobalGids, gid);
} else {
Slog.w(TAG, "<group> without gid at "
@@ -1521,7 +1617,7 @@ public class PackageManagerService extends IPackageManager.Stub {
+ parser.getPositionDescription());
} else {
//Log.i(TAG, "Got library " + lname + " in " + lfile);
- mSharedLibraries.put(lname, lfile);
+ mSharedLibraries.put(lname, new SharedLibraryEntry(lfile, null));
}
XmlUtils.skipCurrentTag(parser);
continue;
@@ -1618,16 +1714,9 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
final PackageUserState state = ps.readUserState(userId);
- pi = PackageParser.generatePackageInfo(p, gp.gids, flags,
+ return PackageParser.generatePackageInfo(p, gp.gids, flags,
ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions,
state, userId);
- if (pi != null) {
- pi.applicationInfo.enabledSetting = state.enabled;
- pi.applicationInfo.enabled =
- pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_DEFAULT
- || pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_ENABLED;
- }
- return pi;
}
@Override
@@ -2231,6 +2320,34 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private static void checkGrantRevokePermissions(PackageParser.Package pkg, BasePermission bp) {
+ int index = pkg.requestedPermissions.indexOf(bp.name);
+ if (index == -1) {
+ throw new SecurityException("Package " + pkg.packageName
+ + " has not requested permission " + bp.name);
+ }
+ boolean isNormal =
+ ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE)
+ == PermissionInfo.PROTECTION_NORMAL);
+ boolean isDangerous =
+ ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE)
+ == PermissionInfo.PROTECTION_DANGEROUS);
+ boolean isDevelopment =
+ ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0);
+
+ if (!isNormal && !isDangerous && !isDevelopment) {
+ throw new SecurityException("Permission " + bp.name
+ + " is not a changeable permission type");
+ }
+
+ if (isNormal || isDangerous) {
+ if (pkg.requestedPermissionsRequired.get(index)) {
+ throw new SecurityException("Can't change " + bp.name
+ + ". It is required by the application");
+ }
+ }
+ }
+
public void grantPermission(String packageName, String permissionName) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null);
@@ -2241,21 +2358,16 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final BasePermission bp = mSettings.mPermissions.get(permissionName);
if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + packageName);
- }
- if (!pkg.requestedPermissions.contains(permissionName)) {
- throw new SecurityException("Package " + packageName
- + " has not requested permission " + permissionName);
- }
- if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) == 0) {
- throw new SecurityException("Permission " + permissionName
- + " is not a development permission");
+ throw new IllegalArgumentException("Unknown permission: " + permissionName);
}
+
+ checkGrantRevokePermissions(pkg, bp);
+
final PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
return;
}
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+ final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps;
if (gp.grantedPermissions.add(permissionName)) {
if (ps.haveGids) {
gp.gids = appendInts(gp.gids, bp.gids);
@@ -2266,6 +2378,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
public void revokePermission(String packageName, String permissionName) {
+ int changedAppId = -1;
+
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
@@ -2277,27 +2391,46 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final BasePermission bp = mSettings.mPermissions.get(permissionName);
if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + packageName);
- }
- if (!pkg.requestedPermissions.contains(permissionName)) {
- throw new SecurityException("Package " + packageName
- + " has not requested permission " + permissionName);
- }
- if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) == 0) {
- throw new SecurityException("Permission " + permissionName
- + " is not a development permission");
+ throw new IllegalArgumentException("Unknown permission: " + permissionName);
}
+
+ checkGrantRevokePermissions(pkg, bp);
+
final PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
return;
}
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+ final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps;
if (gp.grantedPermissions.remove(permissionName)) {
gp.grantedPermissions.remove(permissionName);
if (ps.haveGids) {
gp.gids = removeInts(gp.gids, bp.gids);
}
mSettings.writeLPr();
+ changedAppId = ps.appId;
+ }
+ }
+
+ if (changedAppId >= 0) {
+ // We changed the perm on someone, kill its processes.
+ IActivityManager am = ActivityManagerNative.getDefault();
+ if (am != null) {
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ //XXX we should only revoke for the calling user's app permissions,
+ // but for now we impact all users.
+ //am.killUid(UserHandle.getUid(callingUserId, changedAppId),
+ // "revoke " + permissionName);
+ int[] users = sUserManager.getUserIds();
+ for (int user : users) {
+ am.killUid(UserHandle.getUid(user, changedAppId),
+ "revoke " + permissionName);
+ }
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
}
}
@@ -2883,119 +3016,145 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- private static final int getContinuationPoint(final String[] keys, final String key) {
- final int index;
- if (key == null) {
- index = 0;
- } else {
- final int insertPoint = Arrays.binarySearch(keys, key);
- if (insertPoint < 0) {
- index = -insertPoint;
- } else {
- index = insertPoint + 1;
- }
- }
- return index;
- }
-
@Override
- public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, String lastRead,
- int userId) {
- final ParceledListSlice<PackageInfo> list = new ParceledListSlice<PackageInfo>();
+ public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) {
final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
- final String[] keys;
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "get installed packages");
// writer
synchronized (mPackages) {
+ ArrayList<PackageInfo> list;
if (listUninstalled) {
- keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]);
- } else {
- keys = mPackages.keySet().toArray(new String[mPackages.size()]);
- }
-
- Arrays.sort(keys);
- int i = getContinuationPoint(keys, lastRead);
- final int N = keys.length;
-
- while (i < N) {
- final String packageName = keys[i++];
-
- PackageInfo pi = null;
- if (listUninstalled) {
- final PackageSetting ps = mSettings.mPackages.get(packageName);
- if (ps != null) {
+ list = new ArrayList<PackageInfo>(mSettings.mPackages.size());
+ for (PackageSetting ps : mSettings.mPackages.values()) {
+ PackageInfo pi;
+ if (ps.pkg != null) {
+ pi = generatePackageInfo(ps.pkg, flags, userId);
+ } else {
pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId);
}
- } else {
- final PackageParser.Package p = mPackages.get(packageName);
- if (p != null) {
- pi = generatePackageInfo(p, flags, userId);
+ if (pi != null) {
+ list.add(pi);
}
}
-
- if (pi != null && list.append(pi)) {
- break;
+ } else {
+ list = new ArrayList<PackageInfo>(mPackages.size());
+ for (PackageParser.Package p : mPackages.values()) {
+ PackageInfo pi = generatePackageInfo(p, flags, userId);
+ if (pi != null) {
+ list.add(pi);
+ }
}
}
- if (i == N) {
- list.setLastSlice(true);
- }
+ return new ParceledListSlice<PackageInfo>(list);
}
+ }
- return list;
+ private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageSetting ps,
+ String[] permissions, boolean[] tmp, int flags, int userId) {
+ int numMatch = 0;
+ final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+ for (int i=0; i<permissions.length; i++) {
+ if (gp.grantedPermissions.contains(permissions[i])) {
+ tmp[i] = true;
+ numMatch++;
+ } else {
+ tmp[i] = false;
+ }
+ }
+ if (numMatch == 0) {
+ return;
+ }
+ PackageInfo pi;
+ if (ps.pkg != null) {
+ pi = generatePackageInfo(ps.pkg, flags, userId);
+ } else {
+ pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId);
+ }
+ if ((flags&PackageManager.GET_PERMISSIONS) == 0) {
+ if (numMatch == permissions.length) {
+ pi.requestedPermissions = permissions;
+ } else {
+ pi.requestedPermissions = new String[numMatch];
+ numMatch = 0;
+ for (int i=0; i<permissions.length; i++) {
+ if (tmp[i]) {
+ pi.requestedPermissions[numMatch] = permissions[i];
+ numMatch++;
+ }
+ }
+ }
+ }
+ list.add(pi);
}
@Override
- public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags,
- String lastRead, int userId) {
+ public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions(
+ String[] permissions, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
- final ParceledListSlice<ApplicationInfo> list = new ParceledListSlice<ApplicationInfo>();
final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
- final String[] keys;
// writer
synchronized (mPackages) {
+ ArrayList<PackageInfo> list = new ArrayList<PackageInfo>();
+ boolean[] tmpBools = new boolean[permissions.length];
if (listUninstalled) {
- keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]);
+ for (PackageSetting ps : mSettings.mPackages.values()) {
+ addPackageHoldingPermissions(list, ps, permissions, tmpBools, flags, userId);
+ }
} else {
- keys = mPackages.keySet().toArray(new String[mPackages.size()]);
+ for (PackageParser.Package pkg : mPackages.values()) {
+ PackageSetting ps = (PackageSetting)pkg.mExtras;
+ if (ps != null) {
+ addPackageHoldingPermissions(list, ps, permissions, tmpBools, flags,
+ userId);
+ }
+ }
}
- Arrays.sort(keys);
- int i = getContinuationPoint(keys, lastRead);
- final int N = keys.length;
+ return new ParceledListSlice<PackageInfo>(list);
+ }
+ }
- while (i < N) {
- final String packageName = keys[i++];
+ @Override
+ public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, int userId) {
+ if (!sUserManager.exists(userId)) return null;
+ final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
- ApplicationInfo ai = null;
- final PackageSetting ps = mSettings.mPackages.get(packageName);
- if (listUninstalled) {
- if (ps != null) {
+ // writer
+ synchronized (mPackages) {
+ ArrayList<ApplicationInfo> list;
+ if (listUninstalled) {
+ list = new ArrayList<ApplicationInfo>(mSettings.mPackages.size());
+ for (PackageSetting ps : mSettings.mPackages.values()) {
+ ApplicationInfo ai;
+ if (ps.pkg != null) {
+ ai = PackageParser.generateApplicationInfo(ps.pkg, flags,
+ ps.readUserState(userId), userId);
+ } else {
ai = generateApplicationInfoFromSettingsLPw(ps.name, flags, userId);
}
- } else {
- final PackageParser.Package p = mPackages.get(packageName);
- if (p != null && ps != null) {
- ai = PackageParser.generateApplicationInfo(p, flags,
- ps.readUserState(userId), userId);
+ if (ai != null) {
+ list.add(ai);
}
}
-
- if (ai != null && list.append(ai)) {
- break;
+ } else {
+ list = new ArrayList<ApplicationInfo>(mPackages.size());
+ for (PackageParser.Package p : mPackages.values()) {
+ if (p.mExtras != null) {
+ ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
+ ((PackageSetting)p.mExtras).readUserState(userId), userId);
+ if (ai != null) {
+ list.add(ai);
+ }
+ }
}
}
- if (i == N) {
- list.setLastSlice(true);
- }
+ return new ParceledListSlice<ApplicationInfo>(list);
}
-
- return list;
}
public List<ApplicationInfo> getPersistentApplications(int flags) {
@@ -3234,6 +3393,7 @@ public class PackageManagerService extends IPackageManager.Stub {
int parseFlags, int scanMode, long currentTime, UserHandle user) {
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
String scanPath = scanFile.getPath();
+ if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath);
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser(scanPath);
pp.setSeparateProcesses(mSeparateProcesses);
@@ -3263,6 +3423,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// package. Must look for it either under the original or real
// package name depending on our state.
updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+ if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
}
// First check if this is a system package that may involve an update
if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
@@ -3270,12 +3431,21 @@ public class PackageManagerService extends IPackageManager.Stub {
// The path has changed from what was last scanned... check the
// version of the new path against what we have stored to determine
// what to do.
+ if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
if (pkg.mVersionCode < ps.versionCode) {
// The system package has been updated and the code path does not match
// Ignore entry. Skip it.
Log.i(TAG, "Package " + ps.name + " at " + scanFile
+ " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
+ if (!updatedPkg.codePath.equals(scanFile)) {
+ Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg : "
+ + ps.name + " changing from " + updatedPkg.codePathString
+ + " to " + scanFile);
+ updatedPkg.codePath = scanFile;
+ updatedPkg.codePathString = scanFile.toString();
+ }
+ updatedPkg.pkg = pkg;
mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
return null;
} else {
@@ -3331,7 +3501,8 @@ public class PackageManagerService extends IPackageManager.Stub {
*/
if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
- deletePackageLI(pkg.packageName, null, true, 0, null, false);
+ if (DEBUG_INSTALL) Slog.d(TAG, "Signature mismatch!");
+ deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
ps = null;
} else {
/*
@@ -3462,28 +3633,28 @@ public class PackageManagerService extends IPackageManager.Stub {
}
public void performBootDexOpt() {
- ArrayList<PackageParser.Package> pkgs = null;
+ HashSet<PackageParser.Package> pkgs = null;
synchronized (mPackages) {
- if (mDeferredDexOpt.size() > 0) {
- pkgs = new ArrayList<PackageParser.Package>(mDeferredDexOpt);
- mDeferredDexOpt.clear();
- }
+ pkgs = mDeferredDexOpt;
+ mDeferredDexOpt = null;
}
if (pkgs != null) {
- for (int i=0; i<pkgs.size(); i++) {
+ int i = 0;
+ for (PackageParser.Package pkg : pkgs) {
if (!isFirstBoot()) {
+ i++;
try {
ActivityManagerNative.getDefault().showBootMessage(
mContext.getResources().getString(
com.android.internal.R.string.android_upgrading_apk,
- i+1, pkgs.size()), true);
+ i, pkgs.size()), true);
} catch (RemoteException e) {
}
}
- PackageParser.Package p = pkgs.get(i);
+ PackageParser.Package p = pkg;
synchronized (mInstallLock) {
if (!p.mDidDexOpt) {
- performDexOptLI(p, false, false);
+ performDexOptLI(p, false, false, true);
}
}
}
@@ -3505,7 +3676,27 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
synchronized (mInstallLock) {
- return performDexOptLI(p, false, false) == DEX_OPT_PERFORMED;
+ return performDexOptLI(p, false, false, true) == DEX_OPT_PERFORMED;
+ }
+ }
+
+ private void performDexOptLibsLI(ArrayList<String> libs, boolean forceDex, boolean defer,
+ HashSet<String> done) {
+ for (int i=0; i<libs.size(); i++) {
+ PackageParser.Package libPkg;
+ String libName;
+ synchronized (mPackages) {
+ libName = libs.get(i);
+ SharedLibraryEntry lib = mSharedLibraries.get(libName);
+ if (lib != null && lib.apk != null) {
+ libPkg = mPackages.get(lib.apk);
+ } else {
+ libPkg = null;
+ }
+ }
+ if (libPkg != null && !done.contains(libName)) {
+ performDexOptLI(libPkg, forceDex, defer, done);
+ }
}
}
@@ -3514,14 +3705,27 @@ public class PackageManagerService extends IPackageManager.Stub {
static final int DEX_OPT_DEFERRED = 2;
static final int DEX_OPT_FAILED = -1;
- private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer) {
+ private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+ HashSet<String> done) {
boolean performed = false;
+ if (done != null) {
+ done.add(pkg.packageName);
+ if (pkg.usesLibraries != null) {
+ performDexOptLibsLI(pkg.usesLibraries, forceDex, defer, done);
+ }
+ if (pkg.usesOptionalLibraries != null) {
+ performDexOptLibsLI(pkg.usesOptionalLibraries, forceDex, defer, done);
+ }
+ }
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
String path = pkg.mScanPath;
int ret = 0;
try {
if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
if (!forceDex && defer) {
+ if (mDeferredDexOpt == null) {
+ mDeferredDexOpt = new HashSet<PackageParser.Package>();
+ }
mDeferredDexOpt.add(pkg);
return DEX_OPT_DEFERRED;
} else {
@@ -3554,6 +3758,19 @@ public class PackageManagerService extends IPackageManager.Stub {
return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
}
+ private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+ boolean inclDependencies) {
+ HashSet<String> done;
+ boolean performed = false;
+ if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
+ done = new HashSet<String>();
+ done.add(pkg.packageName);
+ } else {
+ done = null;
+ }
+ return performDexOptLI(pkg, forceDex, defer, done);
+ }
+
private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) {
if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
Slog.w(TAG, "Unable to update from " + oldPkg.name
@@ -3596,7 +3813,7 @@ public class PackageManagerService extends IPackageManager.Stub {
for (int user : users) {
if (user != 0) {
res = mInstaller.createUserData(packageName,
- UserHandle.getUid(user, uid), user, seinfo);
+ UserHandle.getUid(user, uid), user);
if (res < 0) {
return res;
}
@@ -3624,6 +3841,113 @@ public class PackageManagerService extends IPackageManager.Stub {
return res;
}
+ private int addSharedLibraryLPw(final SharedLibraryEntry file, int num,
+ PackageParser.Package changingLib) {
+ if (file.path != null) {
+ mTmpSharedLibraries[num] = file.path;
+ return num+1;
+ }
+ PackageParser.Package p = mPackages.get(file.apk);
+ if (changingLib != null && changingLib.packageName.equals(file.apk)) {
+ // If we are doing this while in the middle of updating a library apk,
+ // then we need to make sure to use that new apk for determining the
+ // dependencies here. (We haven't yet finished committing the new apk
+ // to the package manager state.)
+ if (p == null || p.packageName.equals(changingLib.packageName)) {
+ p = changingLib;
+ }
+ }
+ if (p != null) {
+ String path = p.mPath;
+ for (int i=0; i<num; i++) {
+ if (mTmpSharedLibraries[i].equals(path)) {
+ return num;
+ }
+ }
+ mTmpSharedLibraries[num] = p.mPath;
+ return num+1;
+ }
+ return num;
+ }
+
+ private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
+ PackageParser.Package changingLib) {
+ if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
+ if (mTmpSharedLibraries == null ||
+ mTmpSharedLibraries.length < mSharedLibraries.size()) {
+ mTmpSharedLibraries = new String[mSharedLibraries.size()];
+ }
+ int num = 0;
+ int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
+ for (int i=0; i<N; i++) {
+ final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
+ if (file == null) {
+ Slog.e(TAG, "Package " + pkg.packageName
+ + " requires unavailable shared library "
+ + pkg.usesLibraries.get(i) + "; failing!");
+ mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+ return false;
+ }
+ num = addSharedLibraryLPw(file, num, changingLib);
+ }
+ N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
+ for (int i=0; i<N; i++) {
+ final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
+ if (file == null) {
+ Slog.w(TAG, "Package " + pkg.packageName
+ + " desires unavailable shared library "
+ + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
+ } else {
+ num = addSharedLibraryLPw(file, num, changingLib);
+ }
+ }
+ if (num > 0) {
+ pkg.usesLibraryFiles = new String[num];
+ System.arraycopy(mTmpSharedLibraries, 0,
+ pkg.usesLibraryFiles, 0, num);
+ } else {
+ pkg.usesLibraryFiles = null;
+ }
+ }
+ return true;
+ }
+
+ private static boolean hasString(List<String> list, List<String> which) {
+ if (list == null) {
+ return false;
+ }
+ for (int i=list.size()-1; i>=0; i--) {
+ for (int j=which.size()-1; j>=0; j--) {
+ if (which.get(j).equals(list.get(i))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void updateAllSharedLibrariesLPw() {
+ for (PackageParser.Package pkg : mPackages.values()) {
+ updateSharedLibrariesLPw(pkg, null);
+ }
+ }
+
+ private ArrayList<PackageParser.Package> updateAllSharedLibrariesLPw(
+ PackageParser.Package changingPkg) {
+ ArrayList<PackageParser.Package> res = null;
+ for (PackageParser.Package pkg : mPackages.values()) {
+ if (hasString(pkg.usesLibraries, changingPkg.libraryNames)
+ || hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames)) {
+ if (res == null) {
+ res = new ArrayList<PackageParser.Package>();
+ }
+ res.add(pkg);
+ updateSharedLibrariesLPw(pkg, changingPkg);
+ }
+ }
+ return res;
+ }
+
private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
int parseFlags, int scanMode, long currentTime, UserHandle user) {
File scanFile = new File(pkg.mScanPath);
@@ -3703,42 +4027,14 @@ public class PackageManagerService extends IPackageManager.Stub {
// writer
synchronized (mPackages) {
- // Check all shared libraries and map to their actual file path.
- if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
- if (mTmpSharedLibraries == null ||
- mTmpSharedLibraries.length < mSharedLibraries.size()) {
- mTmpSharedLibraries = new String[mSharedLibraries.size()];
- }
- int num = 0;
- int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
- for (int i=0; i<N; i++) {
- final String file = mSharedLibraries.get(pkg.usesLibraries.get(i));
- if (file == null) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " requires unavailable shared library "
- + pkg.usesLibraries.get(i) + "; failing!");
- mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
- return null;
- }
- mTmpSharedLibraries[num] = file;
- num++;
- }
- N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
- for (int i=0; i<N; i++) {
- final String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
- if (file == null) {
- Slog.w(TAG, "Package " + pkg.packageName
- + " desires unavailable shared library "
- + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
- } else {
- mTmpSharedLibraries[num] = file;
- num++;
- }
- }
- if (num > 0) {
- pkg.usesLibraryFiles = new String[num];
- System.arraycopy(mTmpSharedLibraries, 0,
- pkg.usesLibraryFiles, 0, num);
+ if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ // Check all shared libraries and map to their actual file path.
+ // We only do this here for apps not on a system dir, because those
+ // are the only ones that can fail an install due to this. We
+ // will take care of the system apps by updating all of their
+ // library paths after the scan is done.
+ if (!updateSharedLibrariesLPw(pkg, null)) {
+ return null;
}
}
@@ -4150,7 +4446,7 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.mScanPath = path;
if ((scanMode&SCAN_NO_DEX) == 0) {
- if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
+ if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
== DEX_OPT_FAILED) {
mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
return null;
@@ -4162,6 +4458,80 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
}
+ ArrayList<PackageParser.Package> clientLibPkgs = null;
+
+ // writer
+ synchronized (mPackages) {
+ if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Only system apps can add new shared libraries.
+ if (pkg.libraryNames != null) {
+ for (int i=0; i<pkg.libraryNames.size(); i++) {
+ String name = pkg.libraryNames.get(i);
+ boolean allowed = false;
+ if (isUpdatedSystemApp(pkg)) {
+ // New library entries can only be added through the
+ // system image. This is important to get rid of a lot
+ // of nasty edge cases: for example if we allowed a non-
+ // system update of the app to add a library, then uninstalling
+ // the update would make the library go away, and assumptions
+ // we made such as through app install filtering would now
+ // have allowed apps on the device which aren't compatible
+ // with it. Better to just have the restriction here, be
+ // conservative, and create many fewer cases that can negatively
+ // impact the user experience.
+ final PackageSetting sysPs = mSettings
+ .getDisabledSystemPkgLPr(pkg.packageName);
+ if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) {
+ for (int j=0; j<sysPs.pkg.libraryNames.size(); j++) {
+ if (name.equals(sysPs.pkg.libraryNames.get(j))) {
+ allowed = true;
+ allowed = true;
+ break;
+ }
+ }
+ }
+ } else {
+ allowed = true;
+ }
+ if (allowed) {
+ if (!mSharedLibraries.containsKey(name)) {
+ mSharedLibraries.put(name, new SharedLibraryEntry(null,
+ pkg.packageName));
+ } else if (!name.equals(pkg.packageName)) {
+ Slog.w(TAG, "Package " + pkg.packageName + " library "
+ + name + " already exists; skipping");
+ }
+ } else {
+ Slog.w(TAG, "Package " + pkg.packageName + " declares lib "
+ + name + " that is not declared on system image; skipping");
+ }
+ }
+ if ((scanMode&SCAN_BOOTING) == 0) {
+ // If we are not booting, we need to update any applications
+ // that are clients of our shared library. If we are booting,
+ // this will all be done once the scan is complete.
+ clientLibPkgs = updateAllSharedLibrariesLPw(pkg);
+ }
+ }
+ }
+ }
+
+ // We also need to dexopt any apps that are dependent on this library. Note that
+ // if these fail, we should abort the install since installing the library will
+ // result in some apps being broken.
+ if (clientLibPkgs != null) {
+ if ((scanMode&SCAN_NO_DEX) == 0) {
+ for (int i=0; i<clientLibPkgs.size(); i++) {
+ PackageParser.Package clientPkg = clientLibPkgs.get(i);
+ if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+ == DEX_OPT_FAILED) {
+ mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
+ return null;
+ }
+ }
+ }
+ }
+
// Request the ActivityManager to kill the process(only for existing packages)
// so that we do not end up in a confused state while the user is still using the older
// version of the application while the new one gets installed.
@@ -4170,6 +4540,15 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.uid);
}
+ // Also need to kill any apps that are dependent on the library.
+ if (clientLibPkgs != null) {
+ for (int i=0; i<clientLibPkgs.size(); i++) {
+ PackageParser.Package clientPkg = clientLibPkgs.get(i);
+ killApplication(clientPkg.applicationInfo.packageName,
+ clientPkg.applicationInfo.uid);
+ }
+ }
+
// writer
synchronized (mPackages) {
// We don't expect installation to fail beyond this point,
@@ -4575,7 +4954,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
}
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4611,7 +4990,7 @@ public class PackageManagerService extends IPackageManager.Stub {
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.receivers.get(i);
mReceivers.removeActivity(a, "receiver");
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4629,7 +5008,7 @@ public class PackageManagerService extends IPackageManager.Stub {
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.activities.get(i);
mActivities.removeActivity(a, "activity");
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4652,7 +5031,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if (bp != null && bp.perm == p) {
bp.perm = null;
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4671,7 +5050,7 @@ public class PackageManagerService extends IPackageManager.Stub {
for (i=0; i<N; i++) {
PackageParser.Instrumentation a = pkg.instrumentation.get(i);
mInstrumentation.remove(a.getComponentName());
- if (chatty) {
+ if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
@@ -4683,6 +5062,31 @@ public class PackageManagerService extends IPackageManager.Stub {
if (r != null) {
if (DEBUG_REMOVE) Log.d(TAG, " Instrumentation: " + r);
}
+
+ r = null;
+ if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // Only system apps can hold shared libraries.
+ if (pkg.libraryNames != null) {
+ for (i=0; i<pkg.libraryNames.size(); i++) {
+ String name = pkg.libraryNames.get(i);
+ SharedLibraryEntry cur = mSharedLibraries.get(name);
+ if (cur != null && cur.apk != null && cur.apk.equals(pkg.packageName)) {
+ mSharedLibraries.remove(name);
+ if (DEBUG_REMOVE && chatty) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append(name);
+ }
+ }
+ }
+ }
+ }
+ if (r != null) {
+ if (DEBUG_REMOVE) Log.d(TAG, " Libraries: " + r);
+ }
}
private static final boolean isPackageFilename(String name) {
@@ -4807,134 +5211,97 @@ public class PackageManagerService extends IPackageManager.Stub {
final int N = pkg.requestedPermissions.size();
for (int i=0; i<N; i++) {
final String name = pkg.requestedPermissions.get(i);
- //final boolean required = pkg.requestedPermssionsRequired.get(i);
+ final boolean required = pkg.requestedPermissionsRequired.get(i);
final BasePermission bp = mSettings.mPermissions.get(name);
if (DEBUG_INSTALL) {
if (gp != ps) {
Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
}
}
- if (bp != null && bp.packageSetting != null) {
- final String perm = bp.name;
- boolean allowed;
- boolean allowedSig = false;
- final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
- if (level == PermissionInfo.PROTECTION_NORMAL
- || level == PermissionInfo.PROTECTION_DANGEROUS) {
- allowed = true;
- } else if (bp.packageSetting == null) {
- // This permission is invalid; skip it.
- allowed = false;
- } else if (level == PermissionInfo.PROTECTION_SIGNATURE) {
- allowed = (compareSignatures(
- bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
- == PackageManager.SIGNATURE_MATCH)
- || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
- == PackageManager.SIGNATURE_MATCH);
- if (!allowed && (bp.protectionLevel
- & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) {
- if (isSystemApp(pkg)) {
- // For updated system applications, a system permission
- // is granted only if it had been defined by the original application.
- if (isUpdatedSystemApp(pkg)) {
- final PackageSetting sysPs = mSettings
- .getDisabledSystemPkgLPr(pkg.packageName);
- final GrantedPermissions origGp = sysPs.sharedUser != null
- ? sysPs.sharedUser : sysPs;
- if (origGp.grantedPermissions.contains(perm)) {
- allowed = true;
- } else {
- allowed = false;
- }
- } else {
- allowed = true;
- }
- }
- }
- if (!allowed && (bp.protectionLevel
- & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
- // For development permissions, a development permission
- // is granted only if it was already granted.
- if (origPermissions.contains(perm)) {
- allowed = true;
- } else {
- allowed = false;
- }
- }
- if (allowed) {
- allowedSig = true;
- }
- } else {
- allowed = false;
+
+ if (bp == null || bp.packageSetting == null) {
+ Slog.w(TAG, "Unknown permission " + name
+ + " in package " + pkg.packageName);
+ continue;
+ }
+
+ final String perm = bp.name;
+ boolean allowed;
+ boolean allowedSig = false;
+ final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+ if (level == PermissionInfo.PROTECTION_NORMAL
+ || level == PermissionInfo.PROTECTION_DANGEROUS) {
+ // We grant a normal or dangerous permission if any of the following
+ // are true:
+ // 1) The permission is required
+ // 2) The permission is optional, but was granted in the past
+ // 3) The permission is optional, but was requested by an
+ // app in /system (not /data)
+ //
+ // Otherwise, reject the permission.
+ allowed = (required || origPermissions.contains(perm)
+ || (isSystemApp(ps) && !isUpdatedSystemApp(ps)));
+ } else if (bp.packageSetting == null) {
+ // This permission is invalid; skip it.
+ allowed = false;
+ } else if (level == PermissionInfo.PROTECTION_SIGNATURE) {
+ allowed = grantSignaturePermission(perm, pkg, bp, origPermissions);
+ if (allowed) {
+ allowedSig = true;
}
- if (DEBUG_INSTALL) {
- if (gp != ps) {
- Log.i(TAG, "Package " + pkg.packageName + " granting " + perm);
+ } else {
+ allowed = false;
+ }
+ if (DEBUG_INSTALL) {
+ if (gp != ps) {
+ Log.i(TAG, "Package " + pkg.packageName + " granting " + perm);
+ }
+ }
+ if (allowed) {
+ if (!isSystemApp(ps) && ps.permissionsFixed) {
+ // If this is an existing, non-system package, then
+ // we can't add any new permissions to it.
+ if (!allowedSig && !gp.grantedPermissions.contains(perm)) {
+ // Except... if this is a permission that was added
+ // to the platform (note: need to only do this when
+ // updating the platform).
+ allowed = isNewPlatformPermissionForPackage(perm, pkg);
}
}
if (allowed) {
- if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0
- && ps.permissionsFixed) {
- // If this is an existing, non-system package, then
- // we can't add any new permissions to it.
- if (!allowedSig && !gp.grantedPermissions.contains(perm)) {
- allowed = false;
- // Except... if this is a permission that was added
- // to the platform (note: need to only do this when
- // updating the platform).
- final int NP = PackageParser.NEW_PERMISSIONS.length;
- for (int ip=0; ip<NP; ip++) {
- final PackageParser.NewPermissionInfo npi
- = PackageParser.NEW_PERMISSIONS[ip];
- if (npi.name.equals(perm)
- && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
- allowed = true;
- Log.i(TAG, "Auto-granting " + perm + " to old pkg "
- + pkg.packageName);
- break;
- }
- }
- }
- }
- if (allowed) {
- if (!gp.grantedPermissions.contains(perm)) {
- changedPermission = true;
- gp.grantedPermissions.add(perm);
- gp.gids = appendInts(gp.gids, bp.gids);
- } else if (!ps.haveGids) {
- gp.gids = appendInts(gp.gids, bp.gids);
- }
- } else {
- Slog.w(TAG, "Not granting permission " + perm
- + " to package " + pkg.packageName
- + " because it was previously installed without");
- }
- } else {
- if (gp.grantedPermissions.remove(perm)) {
+ if (!gp.grantedPermissions.contains(perm)) {
changedPermission = true;
- gp.gids = removeInts(gp.gids, bp.gids);
- Slog.i(TAG, "Un-granting permission " + perm
- + " from package " + pkg.packageName
- + " (protectionLevel=" + bp.protectionLevel
- + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
- + ")");
- } else {
- Slog.w(TAG, "Not granting permission " + perm
- + " to package " + pkg.packageName
- + " (protectionLevel=" + bp.protectionLevel
- + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
- + ")");
+ gp.grantedPermissions.add(perm);
+ gp.gids = appendInts(gp.gids, bp.gids);
+ } else if (!ps.haveGids) {
+ gp.gids = appendInts(gp.gids, bp.gids);
}
+ } else {
+ Slog.w(TAG, "Not granting permission " + perm
+ + " to package " + pkg.packageName
+ + " because it was previously installed without");
}
} else {
- Slog.w(TAG, "Unknown permission " + name
- + " in package " + pkg.packageName);
+ if (gp.grantedPermissions.remove(perm)) {
+ changedPermission = true;
+ gp.gids = removeInts(gp.gids, bp.gids);
+ Slog.i(TAG, "Un-granting permission " + perm
+ + " from package " + pkg.packageName
+ + " (protectionLevel=" + bp.protectionLevel
+ + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+ + ")");
+ } else {
+ Slog.w(TAG, "Not granting permission " + perm
+ + " to package " + pkg.packageName
+ + " (protectionLevel=" + bp.protectionLevel
+ + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+ + ")");
+ }
}
}
if ((changedPermission || replace) && !ps.permissionsFixed &&
- ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) ||
- ((ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)){
+ !isSystemApp(ps) || isUpdatedSystemApp(ps)){
// This is the first that we have heard about this package, so the
// permissions we have now selected are fixed until explicitly
// changed.
@@ -4942,8 +5309,78 @@ public class PackageManagerService extends IPackageManager.Stub {
}
ps.haveGids = true;
}
-
- private final class ActivityIntentResolver
+
+ private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
+ boolean allowed = false;
+ final int NP = PackageParser.NEW_PERMISSIONS.length;
+ for (int ip=0; ip<NP; ip++) {
+ final PackageParser.NewPermissionInfo npi
+ = PackageParser.NEW_PERMISSIONS[ip];
+ if (npi.name.equals(perm)
+ && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
+ allowed = true;
+ Log.i(TAG, "Auto-granting " + perm + " to old pkg "
+ + pkg.packageName);
+ break;
+ }
+ }
+ return allowed;
+ }
+
+ private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
+ BasePermission bp, HashSet<String> origPermissions) {
+ boolean allowed;
+ allowed = (compareSignatures(
+ bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
+ == PackageManager.SIGNATURE_MATCH)
+ || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
+ == PackageManager.SIGNATURE_MATCH);
+ if (!allowed && (bp.protectionLevel
+ & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) {
+ if (isSystemApp(pkg)) {
+ // For updated system applications, a system permission
+ // is granted only if it had been defined by the original application.
+ if (isUpdatedSystemApp(pkg)) {
+ final PackageSetting sysPs = mSettings
+ .getDisabledSystemPkgLPr(pkg.packageName);
+ final GrantedPermissions origGp = sysPs.sharedUser != null
+ ? sysPs.sharedUser : sysPs;
+ if (origGp.grantedPermissions.contains(perm)) {
+ allowed = true;
+ } else {
+ // The system apk may have been updated with an older
+ // version of the one on the data partition, but which
+ // granted a new system permission that it didn't have
+ // before. In this case we do want to allow the app to
+ // now get the new permission, because it is allowed by
+ // the system image.
+ allowed = false;
+ if (sysPs.pkg != null) {
+ for (int j=0;
+ j<sysPs.pkg.requestedPermissions.size(); j++) {
+ if (perm.equals(
+ sysPs.pkg.requestedPermissions.get(j))) {
+ allowed = true;
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ allowed = true;
+ }
+ }
+ }
+ if (!allowed && (bp.protectionLevel
+ & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+ // For development permissions, a development permission
+ // is granted only if it was already granted.
+ allowed = origPermissions.contains(perm);
+ }
+ return allowed;
+ }
+
+ final class ActivityIntentResolver
extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
boolean defaultOnly, int userId) {
@@ -5069,8 +5506,9 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
- protected String packageForFilter(PackageParser.ActivityIntentInfo info) {
- return info.activity.owner.packageName;
+ protected boolean isPackageForFilter(String packageName,
+ PackageParser.ActivityIntentInfo info) {
+ return packageName.equals(info.activity.owner.packageName);
}
@Override
@@ -5266,8 +5704,9 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
- protected String packageForFilter(PackageParser.ServiceIntentInfo info) {
- return info.service.owner.packageName;
+ protected boolean isPackageForFilter(String packageName,
+ PackageParser.ServiceIntentInfo info) {
+ return packageName.equals(info.service.owner.packageName);
}
@Override
@@ -5417,7 +5856,8 @@ public class PackageManagerService extends IPackageManager.Stub {
+ " " + intent.getExtras(), here);
}
am.broadcastIntent(null, intent, null, finishedReceiver,
- 0, null, null, null, finishedReceiver != null, false, id);
+ 0, null, null, null, android.app.AppOpsManager.OP_NONE,
+ finishedReceiver != null, false, id);
}
} catch (RemoteException ex) {
}
@@ -5540,6 +5980,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if ((event&REMOVE_EVENTS) != 0) {
if (ps != null) {
+ if (DEBUG_REMOVE) Slog.d(TAG, "Package disappeared: " + ps);
removePackageLI(ps, true);
removedPackage = ps.name;
removedAppId = ps.appId;
@@ -5548,6 +5989,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((event&ADD_EVENTS) != 0) {
if (p == null) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "New file appeared: " + fullPath);
p = scanPackageLI(fullPath,
(mIsRom ? PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR: 0) |
@@ -5628,6 +6070,14 @@ public class PackageManagerService extends IPackageManager.Stub {
null);
final int uid = Binder.getCallingUid();
+ if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
+ try {
+ observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED);
+ } catch (RemoteException re) {
+ }
+ return;
+ }
+
UserHandle user;
if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
@@ -5658,12 +6108,19 @@ public class PackageManagerService extends IPackageManager.Stub {
* @hide
*/
@Override
- public int installExistingPackage(String packageName) {
+ public int installExistingPackageAsUser(String packageName, int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
null);
PackageSetting pkgSetting;
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
+ if (UserHandle.getUserId(uid) != userId) {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "installExistingPackage for user " + userId);
+ }
+ if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
+ return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
+ }
long callingId = Binder.clearCallingIdentity();
try {
@@ -5687,6 +6144,24 @@ public class PackageManagerService extends IPackageManager.Stub {
if (sendAdded) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
packageName, extras, null, null, new int[] {userId});
+ try {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ final boolean isSystem =
+ isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting);
+ if (isSystem && am.isUserRunning(userId, false)) {
+ // The just-installed/enabled app is bundled on the system, so presumed
+ // to be able to run automatically without needing an explicit launch.
+ // Send it a BOOT_COMPLETED if it would ordinarily have gotten one.
+ Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED)
+ .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
+ .setPackage(packageName);
+ am.broadcastIntent(null, bcIntent, null, null, 0, null, null, null,
+ android.app.AppOpsManager.OP_NONE, false, false, userId);
+ }
+ } catch (RemoteException e) {
+ // shouldn't happen
+ Slog.w(TAG, "Unable to bootstrap installed package", e);
+ }
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -5694,7 +6169,16 @@ public class PackageManagerService extends IPackageManager.Stub {
return PackageManager.INSTALL_SUCCEEDED;
}
-
+
+ private boolean isUserRestricted(int userId, String restrictionKey) {
+ Bundle restrictions = sUserManager.getUserRestrictions(userId);
+ if (restrictions.getBoolean(restrictionKey, false)) {
+ Log.w(TAG, "User is restricted: " + restrictionKey);
+ return true;
+ }
+ return false;
+ }
+
@Override
public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
mContext.enforceCallingOrSelfPermission(
@@ -6085,7 +6569,7 @@ public class PackageManagerService extends IPackageManager.Stub {
final boolean startCopy() {
boolean res;
try {
- if (DEBUG_INSTALL) Slog.i(TAG, "startCopy");
+ if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
if (++mRetries > MAX_RETRIES) {
Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
@@ -6129,6 +6613,13 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
+ public String toString() {
+ return "MeasureParams{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + mStats.packageName + "}";
+ }
+
+ @Override
void handleStartCopy() throws RemoteException {
synchronized (mInstallLock) {
mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats.userHandle, mStats);
@@ -6217,6 +6708,13 @@ public class PackageManagerService extends IPackageManager.Stub {
this.encryptionParams = encryptionParams;
}
+ @Override
+ public String toString() {
+ return "InstallParams{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + mPackageURI + "}";
+ }
+
public ManifestDigest getManifestDigest() {
if (verificationParams == null) {
return null;
@@ -6630,6 +7128,13 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ @Override
+ public String toString() {
+ return "MoveParams{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
public void handleStartCopy() throws RemoteException {
mRet = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
// Check for storage space on target medium
@@ -7541,6 +8046,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Remember this for later, in case we need to rollback this install
String pkgName = pkg.packageName;
+ if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
synchronized(mPackages) {
if (mSettings.mRenamedPackages.containsKey(pkgName)) {
@@ -7573,6 +8079,7 @@ public class PackageManagerService extends IPackageManager.Stub {
} else {
updateSettingsLI(newPackage,
installerPackageName,
+ null, null,
res);
// delete the partially installed application. the data directory will have to be
// restored if it was already existing
@@ -7581,7 +8088,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// delete the package data and cache directories that it created in
// scanPackageLocked, unless those directories existed before we even tried to
// install.
- deletePackageLI(pkgName, UserHandle.ALL, false,
+ deletePackageLI(pkgName, UserHandle.ALL, false, null, null,
dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
res.removedInfo, true);
}
@@ -7594,34 +8101,49 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageParser.Package oldPackage;
String pkgName = pkg.packageName;
+ int[] allUsers;
+ boolean[] perUserInstalled;
+
// First find the old package info and check signatures
synchronized(mPackages) {
oldPackage = mPackages.get(pkgName);
+ if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage);
if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
Slog.w(TAG, "New package has a different signature: " + pkgName);
res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
return;
}
+
+ // In case of rollback, remember per-user/profile install state
+ PackageSetting ps = mSettings.mPackages.get(pkgName);
+ allUsers = sUserManager.getUserIds();
+ perUserInstalled = new boolean[allUsers.length];
+ for (int i = 0; i < allUsers.length; i++) {
+ perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false;
+ }
}
boolean sysPkg = (isSystemApp(oldPackage));
if (sysPkg) {
replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
- user, installerPackageName, res);
+ user, allUsers, perUserInstalled, installerPackageName, res);
} else {
replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
- user, installerPackageName, res);
+ user, allUsers, perUserInstalled, installerPackageName, res);
}
}
private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage,
PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
+ int[] allUsers, boolean[] perUserInstalled,
String installerPackageName, PackageInstalledInfo res) {
PackageParser.Package newPackage = null;
String pkgName = deletedPackage.packageName;
boolean deletedPkg = true;
boolean updatedSettings = false;
+ if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old="
+ + deletedPackage);
long origUpdateTime;
if (pkg.mExtras != null) {
origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime;
@@ -7630,7 +8152,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
// First delete the existing package while retaining the data directory
- if (!deletePackageLI(pkgName, null, true, PackageManager.DELETE_KEEP_DATA,
+ if (!deletePackageLI(pkgName, null, true, null, null, PackageManager.DELETE_KEEP_DATA,
res.removedInfo, true)) {
// If the existing package wasn't successfully deleted
res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
@@ -7648,6 +8170,7 @@ public class PackageManagerService extends IPackageManager.Stub {
} else {
updateSettingsLI(newPackage,
installerPackageName,
+ allUsers, perUserInstalled,
res);
updatedSettings = true;
}
@@ -7659,14 +8182,16 @@ public class PackageManagerService extends IPackageManager.Stub {
// scanPackageLocked, unless those directories existed before we even tried to
// install.
if(updatedSettings) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, rolling pack: " + pkgName);
deletePackageLI(
- pkgName, null, true,
+ pkgName, null, true, allUsers, perUserInstalled,
PackageManager.DELETE_KEEP_DATA,
res.removedInfo, true);
}
// Since we failed to install the new package we need to restore the old
// package that we deleted.
if(deletedPkg) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage);
File restoreFile = new File(deletedPackage.mPath);
// Parse old package
boolean oldOnSd = isExternal(deletedPackage);
@@ -7695,7 +8220,10 @@ public class PackageManagerService extends IPackageManager.Stub {
private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
+ int[] allUsers, boolean[] perUserInstalled,
String installerPackageName, PackageInstalledInfo res) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
+ + ", old=" + deletedPackage);
PackageParser.Package newPackage = null;
boolean updatedSettings = false;
parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
@@ -7755,7 +8283,7 @@ public class PackageManagerService extends IPackageManager.Stub {
newPkgSetting.firstInstallTime = oldPkgSetting.firstInstallTime;
newPkgSetting.lastUpdateTime = System.currentTimeMillis();
}
- updateSettingsLI(newPackage, installerPackageName, res);
+ updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
updatedSettings = true;
}
@@ -7801,8 +8329,9 @@ public class PackageManagerService extends IPackageManager.Stub {
return PackageManager.INSTALL_SUCCEEDED;
}
- private void updateSettingsLI(PackageParser.Package newPackage,
- String installerPackageName, PackageInstalledInfo res) {
+ private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
+ int[] allUsers, boolean[] perUserInstalled,
+ PackageInstalledInfo res) {
String pkgName = newPackage.packageName;
synchronized (mPackages) {
//write settings. the installStatus will be incomplete at this stage.
@@ -7818,12 +8347,42 @@ public class PackageManagerService extends IPackageManager.Stub {
return;
}
- Log.d(TAG, "New package installed in " + newPackage.mPath);
+ if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath);
synchronized (mPackages) {
updatePermissionsLPw(newPackage.packageName, newPackage,
UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
? UPDATE_PERMISSIONS_ALL : 0));
+ // For system-bundled packages, we assume that installing an upgraded version
+ // of the package implies that the user actually wants to run that new code,
+ // so we enable the package.
+ if (isSystemApp(newPackage)) {
+ // NB: implicit assumption that system package upgrades apply to all users
+ if (DEBUG_INSTALL) {
+ Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
+ }
+ PackageSetting ps = mSettings.mPackages.get(pkgName);
+ if (ps != null) {
+ if (res.origUsers != null) {
+ for (int userHandle : res.origUsers) {
+ ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
+ userHandle, installerPackageName);
+ }
+ }
+ // Also convey the prior install/uninstall state
+ if (allUsers != null && perUserInstalled != null) {
+ for (int i = 0; i < allUsers.length; i++) {
+ if (DEBUG_INSTALL) {
+ Slog.d(TAG, " user " + allUsers[i]
+ + " => " + perUserInstalled[i]);
+ }
+ ps.setInstalled(perUserInstalled[i], allUsers[i]);
+ }
+ // these install state changes will be persisted in the
+ // upcoming call to mSettings.writeLPr().
+ }
+ }
+ }
res.name = pkgName;
res.uid = newPackage.applicationInfo.uid;
res.pkg = newPackage;
@@ -7848,6 +8407,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Result object to be returned
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
+ if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
// Retrieve PackageSettings and parse package
int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
@@ -7909,14 +8469,18 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.setPackageName(oldName);
pkgName = pkg.packageName;
replace = true;
+ if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName="
+ + oldName + " pkgName=" + pkgName);
} else if (mPackages.containsKey(pkgName)) {
// This package, under its official name, already exists
// on the device; we should replace it.
replace = true;
+ if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName);
}
}
PackageSetting ps = mSettings.mPackages.get(pkgName);
if (ps != null) {
+ if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
if (ps.pkg != null && ps.pkg.applicationInfo != null) {
systemApp = (ps.pkg.applicationInfo.flags &
@@ -7984,6 +8548,10 @@ public class PackageManagerService extends IPackageManager.Stub {
return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ private static boolean isUpdatedSystemApp(PackageSetting ps) {
+ return (ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ }
+
private static boolean isUpdatedSystemApp(PackageParser.Package pkg) {
return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
}
@@ -8043,17 +8611,32 @@ public class PackageManagerService extends IPackageManager.Stub {
return tmpPackageFile;
}
- public void deletePackage(final String packageName,
- final IPackageDeleteObserver observer,
- final int flags) {
+ @Override
+ public void deletePackageAsUser(final String packageName,
+ final IPackageDeleteObserver observer,
+ final int userId, final int flags) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
- // Queue up an async operation since the package deletion may take a little while.
final int uid = Binder.getCallingUid();
+ if (UserHandle.getUserId(uid) != userId) {
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "deletePackage for user " + userId);
+ }
+ if (isUserRestricted(userId, UserManager.DISALLOW_UNINSTALL_APPS)) {
+ try {
+ observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_USER_RESTRICTED);
+ } catch (RemoteException re) {
+ }
+ return;
+ }
+
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId);
+ // Queue up an async operation since the package deletion may take a little while.
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
- final int returnCode = deletePackageX(packageName, uid, flags);
+ final int returnCode = deletePackageX(packageName, userId, flags);
if (observer != null) {
try {
observer.packageDeleted(packageName, returnCode);
@@ -8079,14 +8662,15 @@ public class PackageManagerService extends IPackageManager.Stub {
* persisting settings for later use
* sending a broadcast if necessary
*/
- private int deletePackageX(String packageName, int uid, int flags) {
+ private int deletePackageX(String packageName, int userId, int flags) {
final PackageRemovedInfo info = new PackageRemovedInfo();
final boolean res;
IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
try {
- if (dpm != null && dpm.packageHasActiveAdmins(packageName, UserHandle.getUserId(uid))) {
+ if (dpm != null && (dpm.packageHasActiveAdmins(packageName, userId)
+ || dpm.isDeviceOwner(packageName))) {
Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
}
@@ -8095,15 +8679,33 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean removedForAllUsers = false;
boolean systemUpdate = false;
+
+ // for the uninstall-updates case and restricted profiles, remember the per-
+ // userhandle installed state
+ int[] allUsers;
+ boolean[] perUserInstalled;
+ synchronized (mPackages) {
+ PackageSetting ps = mSettings.mPackages.get(packageName);
+ allUsers = sUserManager.getUserIds();
+ perUserInstalled = new boolean[allUsers.length];
+ for (int i = 0; i < allUsers.length; i++) {
+ perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false;
+ }
+ }
+
synchronized (mInstallLock) {
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
res = deletePackageLI(packageName,
(flags & PackageManager.DELETE_ALL_USERS) != 0
- ? UserHandle.ALL : new UserHandle(UserHandle.getUserId(uid)),
- true, flags | REMOVE_CHATTY, info, true);
+ ? UserHandle.ALL : new UserHandle(userId),
+ true, allUsers, perUserInstalled,
+ flags | REMOVE_CHATTY, info, true);
systemUpdate = info.isRemovedPackageSystemUpdate;
if (res && !systemUpdate && mPackages.get(packageName) == null) {
removedForAllUsers = true;
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate
+ + " removedForAllUsers=" + removedForAllUsers);
}
if (res) {
@@ -8176,9 +8778,11 @@ public class PackageManagerService extends IPackageManager.Stub {
* make sure this flag is set for partially installed apps. If not its meaningless to
* delete a partially installed application.
*/
- private void removePackageDataLI(PackageSetting ps, PackageRemovedInfo outInfo,
- int flags, boolean writeSettings) {
+ private void removePackageDataLI(PackageSetting ps,
+ int[] allUserHandles, boolean[] perUserInstalled,
+ PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
String packageName = ps.name;
+ if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
// Retrieve object to delete permissions for shared user later on
final PackageSetting deletedPs;
@@ -8212,6 +8816,20 @@ public class PackageManagerService extends IPackageManager.Stub {
}
clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL);
}
+ // make sure to preserve per-user disabled state if this removal was just
+ // a downgrade of a system app to the factory package
+ if (allUserHandles != null && perUserInstalled != null) {
+ if (DEBUG_REMOVE) {
+ Slog.d(TAG, "Propagating install state across downgrade");
+ }
+ for (int i = 0; i < allUserHandles.length; i++) {
+ if (DEBUG_REMOVE) {
+ Slog.d(TAG, " user " + allUserHandles[i]
+ + " => " + perUserInstalled[i]);
+ }
+ ps.setInstalled(perUserInstalled[i], allUserHandles[i]);
+ }
+ }
}
// can downgrade to reader
if (writeSettings) {
@@ -8219,13 +8837,21 @@ public class PackageManagerService extends IPackageManager.Stub {
mSettings.writeLPr();
}
}
+ if (outInfo != null) {
+ // A user ID was deleted here. Go through all users and remove it
+ // from KeyStore.
+ removeKeystoreDataIfNeeded(UserHandle.USER_ALL, outInfo.removedAppId);
+ }
}
/*
* Tries to delete system package.
*/
private boolean deleteSystemPackageLI(PackageSetting newPs,
+ int[] allUserHandles, boolean[] perUserInstalled,
int flags, PackageRemovedInfo outInfo, boolean writeSettings) {
+ final boolean applyUserRestrictions
+ = (allUserHandles != null) && (perUserInstalled != null);
PackageSetting disabledPs = null;
// Confirm if the system package has been updated
// An updated system app can be deleted. This will also have to restore
@@ -8234,11 +8860,21 @@ public class PackageManagerService extends IPackageManager.Stub {
synchronized (mPackages) {
disabledPs = mSettings.getDisabledSystemPkgLPr(newPs.name);
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "deleteSystemPackageLI: newPs=" + newPs
+ + " disabledPs=" + disabledPs);
if (disabledPs == null) {
Slog.w(TAG, "Attempt to delete unknown system package "+ newPs.name);
return false;
- } else {
- Log.i(TAG, "Deleting system pkg from data partition");
+ } else if (DEBUG_REMOVE) {
+ Slog.d(TAG, "Deleting system pkg from data partition");
+ }
+ if (DEBUG_REMOVE) {
+ if (applyUserRestrictions) {
+ Slog.d(TAG, "Remembering install states:");
+ for (int i = 0; i < allUserHandles.length; i++) {
+ Slog.d(TAG, " u=" + allUserHandles[i] + " inst=" + perUserInstalled[i]);
+ }
+ }
}
// Delete the updated package
outInfo.isRemovedPackageSystemUpdate = true;
@@ -8249,8 +8885,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// Preserve data by setting flag
flags |= PackageManager.DELETE_KEEP_DATA;
}
- boolean ret = deleteInstalledPackageLI(newPs, true, flags, outInfo,
- writeSettings);
+ boolean ret = deleteInstalledPackageLI(newPs, true, flags,
+ allUserHandles, perUserInstalled, outInfo, writeSettings);
if (!ret) {
return false;
}
@@ -8262,6 +8898,7 @@ public class PackageManagerService extends IPackageManager.Stub {
NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString);
}
// Install the system package
+ if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM,
SCAN_MONITOR | SCAN_NO_PATHS, 0, null);
@@ -8275,6 +8912,22 @@ public class PackageManagerService extends IPackageManager.Stub {
synchronized (mPackages) {
updatePermissionsLPw(newPkg.packageName, newPkg,
UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+ if (applyUserRestrictions) {
+ if (DEBUG_REMOVE) {
+ Slog.d(TAG, "Propagating install state across reinstall");
+ }
+ PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
+ for (int i = 0; i < allUserHandles.length; i++) {
+ if (DEBUG_REMOVE) {
+ Slog.d(TAG, " user " + allUserHandles[i]
+ + " => " + perUserInstalled[i]);
+ }
+ ps.setInstalled(perUserInstalled[i], allUserHandles[i]);
+ }
+ // Regardless of writeSettings we need to ensure that this restriction
+ // state propagation is persisted
+ mSettings.writeAllUsersPackageRestrictionsLPr();
+ }
// can downgrade to reader here
if (writeSettings) {
mSettings.writeLPr();
@@ -8284,14 +8937,15 @@ public class PackageManagerService extends IPackageManager.Stub {
}
private boolean deleteInstalledPackageLI(PackageSetting ps,
- boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo,
- boolean writeSettings) {
+ boolean deleteCodeAndResources, int flags,
+ int[] allUserHandles, boolean[] perUserInstalled,
+ PackageRemovedInfo outInfo, boolean writeSettings) {
if (outInfo != null) {
outInfo.uid = ps.appId;
}
// Delete package data from internal structures and also remove data if flag is set
- removePackageDataLI(ps, outInfo, flags, writeSettings);
+ removePackageDataLI(ps, allUserHandles, perUserInstalled, outInfo, flags, writeSettings);
// Delete application code and resources
if (deleteCodeAndResources && (outInfo != null)) {
@@ -8305,12 +8959,14 @@ public class PackageManagerService extends IPackageManager.Stub {
* This method handles package deletion in general
*/
private boolean deletePackageLI(String packageName, UserHandle user,
- boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo,
+ boolean deleteCodeAndResources, int[] allUserHandles, boolean[] perUserInstalled,
+ int flags, PackageRemovedInfo outInfo,
boolean writeSettings) {
if (packageName == null) {
Slog.w(TAG, "Attempt to delete null packageName.");
return false;
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
PackageSetting ps;
boolean dataOnly = false;
int removeUser = -1;
@@ -8321,28 +8977,44 @@ public class PackageManagerService extends IPackageManager.Stub {
Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
return false;
}
- if (!isSystemApp(ps) && user != null
+ if ((!isSystemApp(ps) || (flags&PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
&& user.getIdentifier() != UserHandle.USER_ALL) {
// The caller is asking that the package only be deleted for a single
// user. To do this, we just mark its uninstalled state and delete
- // its data.
+ // its data. If this is a system app, we only allow this to happen if
+ // they have set the special DELETE_SYSTEM_APP which requests different
+ // semantics than normal for uninstalling system apps.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Only deleting for single user");
ps.setUserState(user.getIdentifier(),
COMPONENT_ENABLED_STATE_DEFAULT,
false, //installed
true, //stopped
true, //notLaunched
- null, null);
- if (ps.isAnyInstalled(sUserManager.getUserIds())) {
- // Other user still have this package installed, so all
+ null, null, null);
+ if (!isSystemApp(ps)) {
+ if (ps.isAnyInstalled(sUserManager.getUserIds())) {
+ // Other user still have this package installed, so all
+ // we need to do is clear this user's data and save that
+ // it is uninstalled.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
+ removeUser = user.getIdentifier();
+ appId = ps.appId;
+ mSettings.writePackageRestrictionsLPr(removeUser);
+ } else {
+ // We need to set it back to 'installed' so the uninstall
+ // broadcasts will be sent correctly.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
+ ps.setInstalled(true, user.getIdentifier());
+ }
+ } else {
+ // This is a system app, so we assume that the
+ // other users still have this package installed, so all
// we need to do is clear this user's data and save that
// it is uninstalled.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
removeUser = user.getIdentifier();
appId = ps.appId;
mSettings.writePackageRestrictionsLPr(removeUser);
- } else {
- // We need to set it back to 'installed' so the uninstall
- // broadcasts will be sent correctly.
- ps.setInstalled(true, user.getIdentifier());
}
}
}
@@ -8350,33 +9022,38 @@ public class PackageManagerService extends IPackageManager.Stub {
if (removeUser >= 0) {
// From above, we determined that we are deleting this only
// for a single user. Continue the work here.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Updating install state for user: " + removeUser);
if (outInfo != null) {
outInfo.removedPackage = packageName;
outInfo.removedAppId = appId;
outInfo.removedUsers = new int[] {removeUser};
}
mInstaller.clearUserData(packageName, removeUser);
+ removeKeystoreDataIfNeeded(removeUser, appId);
schedulePackageCleaning(packageName, removeUser, false);
return true;
}
if (dataOnly) {
// Delete application data first
- removePackageDataLI(ps, outInfo, flags, writeSettings);
+ if (DEBUG_REMOVE) Slog.d(TAG, "Removing package data only");
+ removePackageDataLI(ps, null, null, outInfo, flags, writeSettings);
return true;
}
boolean ret = false;
if (isSystemApp(ps)) {
- Log.i(TAG, "Removing system package:" + ps.name);
+ if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package:" + ps.name);
// When an updated system application is deleted we delete the existing resources as well and
// fall back to existing code in system partition
- ret = deleteSystemPackageLI(ps, flags, outInfo, writeSettings);
+ ret = deleteSystemPackageLI(ps, allUserHandles, perUserInstalled,
+ flags, outInfo, writeSettings);
} else {
- Log.i(TAG, "Removing non-system package:" + ps.name);
+ if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package:" + ps.name);
// Kill application pre-emptively especially for apps on sd.
killApplication(packageName, ps.appId);
- ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags, outInfo,
- writeSettings);
+ ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags,
+ allUserHandles, perUserInstalled,
+ outInfo, writeSettings);
}
return ret;
}
@@ -8420,8 +9097,8 @@ public class PackageManagerService extends IPackageManager.Stub {
users = new int[] { userId };
}
final ClearStorageConnection conn = new ClearStorageConnection();
- if (mContext.bindService(
- containerIntent, conn, Context.BIND_AUTO_CREATE, UserHandle.USER_OWNER)) {
+ if (mContext.bindServiceAsUser(
+ containerIntent, conn, Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
try {
for (int curUser : users) {
long timeout = SystemClock.uptimeMillis() + 5000;
@@ -8507,29 +9184,34 @@ public class PackageManagerService extends IPackageManager.Stub {
}
PackageParser.Package p;
boolean dataOnly = false;
+ final int appId;
synchronized (mPackages) {
p = mPackages.get(packageName);
- if(p == null) {
+ if (p == null) {
dataOnly = true;
PackageSetting ps = mSettings.mPackages.get(packageName);
- if((ps == null) || (ps.pkg == null)) {
- Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
+ if ((ps == null) || (ps.pkg == null)) {
+ Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
return false;
}
p = ps.pkg;
}
- }
-
- if (!dataOnly) {
- //need to check this only for fully installed applications
- if (p == null) {
- Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
- return false;
+ if (!dataOnly) {
+ // need to check this only for fully installed applications
+ if (p == null) {
+ Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
+ return false;
+ }
+ final ApplicationInfo applicationInfo = p.applicationInfo;
+ if (applicationInfo == null) {
+ Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
+ return false;
+ }
}
- final ApplicationInfo applicationInfo = p.applicationInfo;
- if (applicationInfo == null) {
- Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
- return false;
+ if (p != null && p.applicationInfo != null) {
+ appId = p.applicationInfo.uid;
+ } else {
+ appId = -1;
}
}
int retCode = mInstaller.clearUserData(packageName, userId);
@@ -8538,9 +9220,33 @@ public class PackageManagerService extends IPackageManager.Stub {
+ packageName);
return false;
}
+ removeKeystoreDataIfNeeded(userId, appId);
return true;
}
+ /**
+ * Remove entries from the keystore daemon. Will only remove it if the
+ * {@code appId} is valid.
+ */
+ private static void removeKeystoreDataIfNeeded(int userId, int appId) {
+ if (appId < 0) {
+ return;
+ }
+
+ final KeyStore keyStore = KeyStore.getInstance();
+ if (keyStore != null) {
+ if (userId == UserHandle.USER_ALL) {
+ for (final int individual : sUserManager.getUserIds()) {
+ keyStore.clearUid(UserHandle.getUid(individual, appId));
+ }
+ } else {
+ keyStore.clearUid(UserHandle.getUid(userId, appId));
+ }
+ } else {
+ Slog.w(TAG, "Could not contact keystore to clear entries for app id " + appId);
+ }
+ }
+
public void deleteApplicationCacheFiles(final String packageName,
final IPackageDataObserver observer) {
mContext.enforceCallingOrSelfPermission(
@@ -8617,18 +9323,22 @@ public class PackageManagerService extends IPackageManager.Stub {
}
PackageParser.Package p;
boolean dataOnly = false;
+ String libDirPath = null;
String asecPath = null;
synchronized (mPackages) {
p = mPackages.get(packageName);
+ PackageSetting ps = mSettings.mPackages.get(packageName);
if(p == null) {
dataOnly = true;
- PackageSetting ps = mSettings.mPackages.get(packageName);
if((ps == null) || (ps.pkg == null)) {
Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
return false;
}
p = ps.pkg;
}
+ if (ps != null) {
+ libDirPath = ps.nativeLibraryPathString;
+ }
if (p != null && (isExternal(p) || isForwardLocked(p))) {
String secureContainerId = cidFromCodePath(p.applicationInfo.sourceDir);
if (secureContainerId != null) {
@@ -8647,8 +9357,8 @@ public class PackageManagerService extends IPackageManager.Stub {
publicSrcDir = applicationInfo.publicSourceDir;
}
}
- int res = mInstaller.getSizeInfo(packageName, userHandle, p.mPath, publicSrcDir,
- asecPath, pStats);
+ int res = mInstaller.getSizeInfo(packageName, userHandle, p.mPath, libDirPath,
+ publicSrcDir, asecPath, pStats);
if (res < 0) {
return false;
}
@@ -8806,8 +9516,10 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- if (clearPackagePreferredActivitiesLPw(packageName, UserHandle.getCallingUserId())) {
- scheduleWriteSettingsLocked();
+ int user = UserHandle.getCallingUserId();
+ if (clearPackagePreferredActivitiesLPw(packageName, user)) {
+ mSettings.writePackageRestrictionsLPr(user);
+ scheduleWriteSettingsLocked();
}
}
}
@@ -8825,7 +9537,8 @@ public class PackageManagerService extends IPackageManager.Stub {
Iterator<PreferredActivity> it = pir.filterIterator();
while (it.hasNext()) {
PreferredActivity pa = it.next();
- if (pa.mPref.mComponent.getPackageName().equals(packageName)) {
+ if (packageName == null ||
+ pa.mPref.mComponent.getPackageName().equals(packageName)) {
if (removed == null) {
removed = new ArrayList<PreferredActivity>();
}
@@ -8838,12 +9551,24 @@ public class PackageManagerService extends IPackageManager.Stub {
pir.removeFilter(pa);
}
changed = true;
- mSettings.writePackageRestrictionsLPr(thisUserId);
}
}
return changed;
}
+ public void resetPreferredActivities(int userId) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+ // writer
+ synchronized (mPackages) {
+ int user = UserHandle.getCallingUserId();
+ clearPackagePreferredActivitiesLPw(null, user);
+ mSettings.readDefaultPreferredAppsLPw(this, user);
+ mSettings.writePackageRestrictionsLPr(user);
+ scheduleWriteSettingsLocked();
+ }
+ }
+
public int getPreferredActivities(List<IntentFilter> outFilters,
List<ComponentName> outActivities, String packageName) {
@@ -8874,9 +9599,12 @@ public class PackageManagerService extends IPackageManager.Stub {
@Override
public void setApplicationEnabledSetting(String appPackageName,
- int newState, int flags, int userId) {
+ int newState, int flags, int userId, String callingPackage) {
if (!sUserManager.exists(userId)) return;
- setEnabledSetting(appPackageName, null, newState, flags, userId);
+ if (callingPackage == null) {
+ callingPackage = Integer.toString(Binder.getCallingUid());
+ }
+ setEnabledSetting(appPackageName, null, newState, flags, userId, callingPackage);
}
@Override
@@ -8884,21 +9612,22 @@ public class PackageManagerService extends IPackageManager.Stub {
int newState, int flags, int userId) {
if (!sUserManager.exists(userId)) return;
setEnabledSetting(componentName.getPackageName(),
- componentName.getClassName(), newState, flags, userId);
+ componentName.getClassName(), newState, flags, userId, null);
}
- private void setEnabledSetting(
- final String packageName, String className, int newState, final int flags, int userId) {
+ private void setEnabledSetting(final String packageName, String className, int newState,
+ final int flags, int userId, String callingPackage) {
if (!(newState == COMPONENT_ENABLED_STATE_DEFAULT
|| newState == COMPONENT_ENABLED_STATE_ENABLED
|| newState == COMPONENT_ENABLED_STATE_DISABLED
- || newState == COMPONENT_ENABLED_STATE_DISABLED_USER)) {
+ || newState == COMPONENT_ENABLED_STATE_DISABLED_USER
+ || newState == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
throw new IllegalArgumentException("Invalid new component state: "
+ newState);
}
PackageSetting pkgSetting;
final int uid = Binder.getCallingUid();
- final int permission = mContext.checkCallingPermission(
+ final int permission = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
enforceCrossUserPermission(uid, userId, false, "set enabled");
final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
@@ -8933,7 +9662,12 @@ public class PackageManagerService extends IPackageManager.Stub {
// Nothing to do
return;
}
- pkgSetting.setEnabled(newState, userId);
+ if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ // Don't care about who enables an app.
+ callingPackage = null;
+ }
+ pkgSetting.setEnabled(newState, userId, callingPackage);
// pkgSetting.pkg.mSetEnabled = newState;
} else {
// We're dealing with a component level state change
@@ -8970,8 +9704,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
mSettings.writePackageRestrictionsLPr(userId);
- packageUid = UserHandle.getUid(userId, pkgSetting.appId);
- components = mPendingBroadcasts.get(packageName);
+ components = mPendingBroadcasts.get(userId, packageName);
final boolean newPackage = components == null;
if (newPackage) {
components = new ArrayList<String>();
@@ -8983,10 +9716,10 @@ public class PackageManagerService extends IPackageManager.Stub {
sendNow = true;
// Purge entry from pending broadcast list if another one exists already
// since we are sending one right away.
- mPendingBroadcasts.remove(packageName);
+ mPendingBroadcasts.remove(userId, packageName);
} else {
if (newPackage) {
- mPendingBroadcasts.put(packageName, components);
+ mPendingBroadcasts.put(userId, packageName, components);
}
if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
// Schedule a message
@@ -8998,6 +9731,7 @@ public class PackageManagerService extends IPackageManager.Stub {
long callingId = Binder.clearCallingIdentity();
try {
if (sendNow) {
+ packageUid = UserHandle.getUid(userId, pkgSetting.appId);
sendPackageChangedBroadcast(packageName,
(flags&PackageManager.DONT_KILL_APP) != 0, components, packageUid);
}
@@ -9229,6 +9963,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
DumpState dumpState = new DumpState();
+ boolean fullPreferred = false;
String packageName = null;
@@ -9252,7 +9987,7 @@ public class PackageManagerService extends IPackageManager.Stub {
pw.println(" r[esolvers]: dump intent resolvers");
pw.println(" perm[issions]: dump permissions");
pw.println(" pref[erred]: print preferred package settings");
- pw.println(" preferred-xml: print preferred package settings as xml");
+ pw.println(" preferred-xml [--full]: print preferred package settings as xml");
pw.println(" prov[iders]: dump content providers");
pw.println(" p[ackages]: dump installed packages");
pw.println(" s[hared-users]: dump shared user IDs");
@@ -9286,6 +10021,10 @@ public class PackageManagerService extends IPackageManager.Stub {
dumpState.setDump(DumpState.DUMP_PREFERRED);
} else if ("preferred-xml".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_PREFERRED_XML);
+ if (opti < args.length && "--full".equals(args[opti])) {
+ fullPreferred = true;
+ opti++;
+ }
} else if ("p".equals(cmd) || "packages".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_PACKAGES);
} else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
@@ -9322,7 +10061,15 @@ public class PackageManagerService extends IPackageManager.Stub {
pw.print(" ");
pw.print(name);
pw.print(" -> ");
- pw.println(mSharedLibraries.get(name));
+ SharedLibraryEntry ent = mSharedLibraries.get(name);
+ if (ent.path != null) {
+ pw.print("(jar) ");
+ pw.print(ent.path);
+ } else {
+ pw.print("(apk) ");
+ pw.print(ent.apk);
+ }
+ pw.println();
}
}
@@ -9380,7 +10127,7 @@ public class PackageManagerService extends IPackageManager.Stub {
serializer.startDocument(null, true);
serializer.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
- mSettings.writePreferredActivitiesLPr(serializer, 0);
+ mSettings.writePreferredActivitiesLPr(serializer, 0, fullPreferred);
serializer.endDocument();
serializer.flush();
} catch (IllegalArgumentException e) {
@@ -9835,7 +10582,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Delete package internally
PackageRemovedInfo outInfo = new PackageRemovedInfo();
synchronized (mInstallLock) {
- boolean res = deletePackageLI(pkgName, null, false,
+ boolean res = deletePackageLI(pkgName, null, false, null, null,
PackageManager.DELETE_KEEP_DATA, outInfo, false);
if (res) {
pkgList.add(pkgName);
@@ -10120,8 +10867,9 @@ public class PackageManagerService extends IPackageManager.Stub {
/** Called by UserManagerService */
void cleanUpUserLILPw(int userHandle) {
- if (mDirtyUsers.remove(userHandle));
+ mDirtyUsers.remove(userHandle);
mSettings.removeUserLPr(userHandle);
+ mPendingBroadcasts.remove(userHandle);
if (mInstaller != null) {
// Technically, we shouldn't be doing this with the package lock
// held. However, this is very rare, and there is already so much
@@ -10133,7 +10881,7 @@ public class PackageManagerService extends IPackageManager.Stub {
/** Called by UserManagerService */
void createNewUserLILPw(int userHandle, File path) {
if (mInstaller != null) {
- mSettings.createNewUserLILPw(mInstaller, userHandle, path);
+ mSettings.createNewUserLILPw(this, mInstaller, userHandle, path);
}
}
@@ -10157,19 +10905,18 @@ public class PackageManagerService extends IPackageManager.Stub {
|| mSettings.mReadExternalStorageEnforced != enforced) {
mSettings.mReadExternalStorageEnforced = enforced;
mSettings.writeLPr();
-
- // kill any non-foreground processes so we restart them and
- // grant/revoke the GID.
- final IActivityManager am = ActivityManagerNative.getDefault();
- if (am != null) {
- final long token = Binder.clearCallingIdentity();
- try {
- am.killProcessesBelowForeground("setPermissionEnforcement");
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
+ }
+ }
+ // kill any non-foreground processes so we restart them and
+ // grant/revoke the GID.
+ final IActivityManager am = ActivityManagerNative.getDefault();
+ if (am != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ am.killProcessesBelowForeground("setPermissionEnforcement");
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
} else {
diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java
index ae1b213..e64ec6d 100644
--- a/services/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/java/com/android/server/pm/PackageSettingBase.java
@@ -189,14 +189,20 @@ class PackageSettingBase extends GrantedPermissions {
return DEFAULT_USER_STATE;
}
- void setEnabled(int state, int userId) {
- modifyUserState(userId).enabled = state;
+ void setEnabled(int state, int userId, String callingPackage) {
+ PackageUserState st = modifyUserState(userId);
+ st.enabled = state;
+ st.lastDisableAppCaller = callingPackage;
}
int getEnabled(int userId) {
return readUserState(userId).enabled;
}
+ String getLastDisabledAppCaller(int userId) {
+ return readUserState(userId).lastDisableAppCaller;
+ }
+
void setInstalled(boolean inst, int userId) {
modifyUserState(userId).installed = inst;
}
@@ -249,13 +255,14 @@ class PackageSettingBase extends GrantedPermissions {
}
void setUserState(int userId, int enabled, boolean installed, boolean stopped,
- boolean notLaunched, HashSet<String> enabledComponents,
+ boolean notLaunched, String lastDisableAppCaller, HashSet<String> enabledComponents,
HashSet<String> disabledComponents) {
PackageUserState state = modifyUserState(userId);
state.enabled = enabled;
state.installed = installed;
state.stopped = stopped;
state.notLaunched = notLaunched;
+ state.lastDisableAppCaller = lastDisableAppCaller;
state.enabledComponents = enabledComponents;
state.disabledComponents = disabledComponents;
}
diff --git a/services/java/com/android/server/pm/PreferredActivity.java b/services/java/com/android/server/pm/PreferredActivity.java
index dbf56ef..c655bb1 100644
--- a/services/java/com/android/server/pm/PreferredActivity.java
+++ b/services/java/com/android/server/pm/PreferredActivity.java
@@ -46,8 +46,8 @@ class PreferredActivity extends IntentFilter implements PreferredComponent.Callb
mPref = new PreferredComponent(this, parser);
}
- public void writeToXml(XmlSerializer serializer) throws IOException {
- mPref.writeToXml(serializer);
+ public void writeToXml(XmlSerializer serializer, boolean full) throws IOException {
+ mPref.writeToXml(serializer, full);
serializer.startTag(null, "filter");
super.writeToXml(serializer);
serializer.endTag(null, "filter");
diff --git a/services/java/com/android/server/pm/PreferredIntentResolver.java b/services/java/com/android/server/pm/PreferredIntentResolver.java
index 3f1e50c..7fe6a05 100644
--- a/services/java/com/android/server/pm/PreferredIntentResolver.java
+++ b/services/java/com/android/server/pm/PreferredIntentResolver.java
@@ -27,8 +27,8 @@ public class PreferredIntentResolver
return new PreferredActivity[size];
}
@Override
- protected String packageForFilter(PreferredActivity filter) {
- return filter.mPref.mComponent.getPackageName();
+ protected boolean isPackageForFilter(String packageName, PreferredActivity filter) {
+ return packageName.equals(filter.mPref.mComponent.getPackageName());
}
@Override
protected void dumpFilter(PrintWriter out, String prefix,
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index b744bc3..2e48074 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -18,14 +18,20 @@ package com.android.server.pm;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.PatternMatcher;
+import android.util.LogPrinter;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
-import com.android.server.IntentResolver;
import com.android.server.pm.PackageManagerService.DumpState;
import org.xmlpull.v1.XmlPullParser;
@@ -98,6 +104,7 @@ final class Settings {
private static final String ATTR_CODE = "code";
private static final String ATTR_NOT_LAUNCHED = "nl";
private static final String ATTR_ENABLED = "enabled";
+ private static final String ATTR_ENABLED_CALLER = "enabledCaller";
private static final String ATTR_STOPPED = "stopped";
private static final String ATTR_INSTALLED = "inst";
@@ -447,7 +454,7 @@ final class Settings {
installed,
true, // stopped,
true, // notLaunched
- null, null);
+ null, null, null);
writePackageRestrictionsLPr(user.id);
}
}
@@ -844,7 +851,7 @@ final class Settings {
true, // installed
false, // stopped
false, // notLaunched
- null, null);
+ null, null, null);
}
return;
}
@@ -889,6 +896,8 @@ final class Settings {
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
final int enabled = enabledStr == null
? COMPONENT_ENABLED_STATE_DEFAULT : Integer.parseInt(enabledStr);
+ final String enabledCaller = parser.getAttributeValue(null,
+ ATTR_ENABLED_CALLER);
final String installedStr = parser.getAttributeValue(null, ATTR_INSTALLED);
final boolean installed = installedStr == null
? true : Boolean.parseBoolean(installedStr);
@@ -919,7 +928,7 @@ final class Settings {
}
ps.setUserState(userId, enabled, installed, stopped, notLaunched,
- enabledComponents, disabledComponents);
+ enabledCaller, enabledComponents, disabledComponents);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
} else {
@@ -971,14 +980,14 @@ final class Settings {
return components;
}
- void writePreferredActivitiesLPr(XmlSerializer serializer, int userId)
+ void writePreferredActivitiesLPr(XmlSerializer serializer, int userId, boolean full)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, "preferred-activities");
PreferredIntentResolver pir = mPreferredActivities.get(userId);
if (pir != null) {
for (final PreferredActivity pa : pir.filterSet()) {
serializer.startTag(null, TAG_ITEM);
- pa.writeToXml(serializer);
+ pa.writeToXml(serializer, full);
serializer.endTag(null, TAG_ITEM);
}
}
@@ -1046,6 +1055,10 @@ final class Settings {
if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
serializer.attribute(null, ATTR_ENABLED,
Integer.toString(ustate.enabled));
+ if (ustate.lastDisableAppCaller != null) {
+ serializer.attribute(null, ATTR_ENABLED_CALLER,
+ ustate.lastDisableAppCaller);
+ }
}
if (ustate.enabledComponents != null
&& ustate.enabledComponents.size() > 0) {
@@ -1071,7 +1084,7 @@ final class Settings {
}
}
- writePreferredActivitiesLPr(serializer, userId);
+ writePreferredActivitiesLPr(serializer, userId, true);
serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
@@ -1559,7 +1572,8 @@ final class Settings {
}
}
- boolean readLPw(List<UserInfo> users, int sdkVersion, boolean onlyCore) {
+ boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,
+ boolean onlyCore) {
FileInputStream str = null;
if (mBackupSettingsFilename.exists()) {
try {
@@ -1589,9 +1603,6 @@ final class Settings {
mReadMessages.append("No settings file found\n");
PackageManagerService.reportSettingsProblem(Log.INFO,
"No settings file; creating initial state");
- if (!onlyCore) {
- readDefaultPreferredAppsLPw(0);
- }
mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion;
return false;
}
@@ -1773,7 +1784,7 @@ final class Settings {
return true;
}
- private void readDefaultPreferredAppsLPw(int userId) {
+ void readDefaultPreferredAppsLPw(PackageManagerService service, int userId) {
// Read preferred apps from .../etc/preferred-apps directory.
File preferredDir = new File(Environment.getRootDirectory(), "etc/preferred-apps");
if (!preferredDir.exists() || !preferredDir.isDirectory()) {
@@ -1795,6 +1806,7 @@ final class Settings {
continue;
}
+ if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Reading default preferred " + f);
FileInputStream str = null;
try {
str = new FileInputStream(f);
@@ -1816,7 +1828,7 @@ final class Settings {
+ " does not start with 'preferred-activities'");
continue;
}
- readPreferredActivitiesLPw(parser, userId);
+ readDefaultPreferredActivitiesLPw(service, parser, userId);
} catch (XmlPullParserException e) {
Slog.w(TAG, "Error reading apps file " + f, e);
} catch (IOException e) {
@@ -1832,6 +1844,112 @@ final class Settings {
}
}
+ private void readDefaultPreferredActivitiesLPw(PackageManagerService service,
+ XmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_ITEM)) {
+ PreferredActivity tmpPa = new PreferredActivity(parser);
+ if (tmpPa.mPref.getParseError() == null) {
+ // The initial preferences only specify the target activity
+ // component and intent-filter, not the set of matches. So we
+ // now need to query for the matches to build the correct
+ // preferred activity entry.
+ if (PackageManagerService.DEBUG_PREFERRED) {
+ Log.d(TAG, "Processing preferred:");
+ tmpPa.dump(new LogPrinter(Log.DEBUG, TAG), " ");
+ }
+ final ComponentName cn = tmpPa.mPref.mComponent;
+ Intent intent = new Intent();
+ int flags = 0;
+ intent.setAction(tmpPa.getAction(0));
+ for (int i=0; i<tmpPa.countCategories(); i++) {
+ String cat = tmpPa.getCategory(i);
+ if (cat.equals(Intent.CATEGORY_DEFAULT)) {
+ flags |= PackageManager.MATCH_DEFAULT_ONLY;
+ } else {
+ intent.addCategory(cat);
+ }
+ }
+ if (tmpPa.countDataSchemes() > 0) {
+ Uri.Builder builder = new Uri.Builder();
+ builder.scheme(tmpPa.getDataScheme(0));
+ if (tmpPa.countDataAuthorities() > 0) {
+ IntentFilter.AuthorityEntry auth = tmpPa.getDataAuthority(0);
+ if (auth.getHost() != null) {
+ builder.authority(auth.getHost());
+ }
+ }
+ if (tmpPa.countDataPaths() > 0) {
+ PatternMatcher path = tmpPa.getDataPath(0);
+ builder.path(path.getPath());
+ }
+ intent.setData(builder.build());
+ } else if (tmpPa.countDataTypes() > 0) {
+ intent.setType(tmpPa.getDataType(0));
+ }
+ List<ResolveInfo> ri = service.mActivities.queryIntent(intent,
+ intent.getType(), flags, 0);
+ if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Queried " + intent
+ + " results: " + ri);
+ int match = 0;
+ if (ri != null && ri.size() > 1) {
+ boolean haveAct = false;
+ boolean haveNonSys = false;
+ ComponentName[] set = new ComponentName[ri.size()];
+ for (int i=0; i<ri.size(); i++) {
+ ActivityInfo ai = ri.get(i).activityInfo;
+ set[i] = new ComponentName(ai.packageName, ai.name);
+ if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+ // If any of the matches are not system apps, then
+ // there is a third party app that is now an option...
+ // so don't set a default since we don't want to hide it.
+ if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result "
+ + ai.packageName + "/" + ai.name + ": non-system!");
+ haveNonSys = true;
+ break;
+ } else if (cn.getPackageName().equals(ai.packageName)
+ && cn.getClassName().equals(ai.name)) {
+ if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result "
+ + ai.packageName + "/" + ai.name + ": default!");
+ haveAct = true;
+ match = ri.get(i).match;
+ } else {
+ if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result "
+ + ai.packageName + "/" + ai.name + ": skipped");
+ }
+ }
+ if (haveAct && !haveNonSys) {
+ PreferredActivity pa = new PreferredActivity(tmpPa, match, set,
+ tmpPa.mPref.mComponent);
+ editPreferredActivitiesLPw(userId).addFilter(pa);
+ } else if (!haveNonSys) {
+ Slog.w(TAG, "No component found for default preferred activity "
+ + tmpPa.mPref.mComponent);
+ }
+ }
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: <preferred-activity> "
+ + tmpPa.mPref.getParseError() + " at "
+ + parser.getPositionDescription());
+ }
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Unknown element under <preferred-activities>: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
private int readInt(XmlPullParser parser, String ns, String name, int defValue) {
String v = parser.getAttributeValue(ns, name);
try {
@@ -2128,14 +2246,14 @@ final class Settings {
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
if (enabledStr != null) {
try {
- packageSetting.setEnabled(Integer.parseInt(enabledStr), 0 /* userId */);
+ packageSetting.setEnabled(Integer.parseInt(enabledStr), 0 /* userId */, null);
} catch (NumberFormatException e) {
if (enabledStr.equalsIgnoreCase("true")) {
- packageSetting.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 0);
+ packageSetting.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 0, null);
} else if (enabledStr.equalsIgnoreCase("false")) {
- packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0);
+ packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0, null);
} else if (enabledStr.equalsIgnoreCase("default")) {
- packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0);
+ packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: package " + name
@@ -2144,7 +2262,7 @@ final class Settings {
}
}
} else {
- packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0);
+ packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
}
final String installStatusStr = parser.getAttributeValue(null, "installStatus");
@@ -2331,7 +2449,8 @@ final class Settings {
}
}
- void createNewUserLILPw(Installer installer, int userHandle, File path) {
+ void createNewUserLILPw(PackageManagerService service, Installer installer,
+ int userHandle, File path) {
path.mkdir();
FileUtils.setPermissions(path.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
| FileUtils.S_IXOTH, -1, -1);
@@ -2340,10 +2459,9 @@ final class Settings {
ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle);
// Need to create a data directory for all apps under this user.
installer.createUserData(ps.name,
- UserHandle.getUid(userHandle, ps.appId), userHandle,
- ps.pkg.applicationInfo.seinfo);
+ UserHandle.getUid(userHandle, ps.appId), userHandle);
}
- readDefaultPreferredAppsLPw(userHandle);
+ readDefaultPreferredAppsLPw(service, userHandle);
writePackageRestrictionsLPr(userHandle);
}
@@ -2416,8 +2534,14 @@ final class Settings {
return false;
}
PackageUserState ustate = packageSettings.readUserState(userId);
+ if ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0) {
+ if (ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ return true;
+ }
+ }
if (ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED
|| ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_USER
+ || ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
|| (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled
&& ustate.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) {
return false;
@@ -2539,6 +2663,167 @@ final class Settings {
ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
};
+ void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf,
+ Date date, List<UserInfo> users) {
+ pw.print(prefix); pw.print("Package [");
+ pw.print(ps.realName != null ? ps.realName : ps.name);
+ pw.print("] (");
+ pw.print(Integer.toHexString(System.identityHashCode(ps)));
+ pw.println("):");
+
+ if (ps.realName != null) {
+ pw.print(prefix); pw.print(" compat name=");
+ pw.println(ps.name);
+ }
+
+ pw.print(prefix); pw.print(" userId="); pw.print(ps.appId);
+ pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
+ if (ps.sharedUser != null) {
+ pw.print(prefix); pw.print(" sharedUser="); pw.println(ps.sharedUser);
+ }
+ pw.print(prefix); pw.print(" pkg="); pw.println(ps.pkg);
+ pw.print(prefix); pw.print(" codePath="); pw.println(ps.codePathString);
+ pw.print(prefix); pw.print(" resourcePath="); pw.println(ps.resourcePathString);
+ pw.print(prefix); pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
+ pw.print(prefix); pw.print(" versionCode="); pw.print(ps.versionCode);
+ if (ps.pkg != null) {
+ pw.print(" targetSdk="); pw.print(ps.pkg.applicationInfo.targetSdkVersion);
+ }
+ pw.println();
+ if (ps.pkg != null) {
+ pw.print(prefix); pw.print(" versionName="); pw.println(ps.pkg.mVersionName);
+ pw.print(prefix); pw.print(" applicationInfo=");
+ pw.println(ps.pkg.applicationInfo.toString());
+ pw.print(prefix); pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags,
+ FLAG_DUMP_SPEC); pw.println();
+ pw.print(prefix); pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
+ if (ps.pkg.mOperationPending) {
+ pw.print(prefix); pw.println(" mOperationPending=true");
+ }
+ pw.print(prefix); pw.print(" supportsScreens=[");
+ boolean first = true;
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("small");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("medium");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("large");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("xlarge");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("resizeable");
+ }
+ if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+ if (!first)
+ pw.print(", ");
+ first = false;
+ pw.print("anyDensity");
+ }
+ pw.println("]");
+ if (ps.pkg.libraryNames != null && ps.pkg.libraryNames.size() > 0) {
+ pw.print(prefix); pw.println(" libraries:");
+ for (int i=0; i<ps.pkg.libraryNames.size(); i++) {
+ pw.print(prefix); pw.print(" "); pw.println(ps.pkg.libraryNames.get(i));
+ }
+ }
+ if (ps.pkg.usesLibraries != null && ps.pkg.usesLibraries.size() > 0) {
+ pw.print(prefix); pw.println(" usesLibraries:");
+ for (int i=0; i<ps.pkg.usesLibraries.size(); i++) {
+ pw.print(prefix); pw.print(" "); pw.println(ps.pkg.usesLibraries.get(i));
+ }
+ }
+ if (ps.pkg.usesOptionalLibraries != null
+ && ps.pkg.usesOptionalLibraries.size() > 0) {
+ pw.print(prefix); pw.println(" usesOptionalLibraries:");
+ for (int i=0; i<ps.pkg.usesOptionalLibraries.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.println(ps.pkg.usesOptionalLibraries.get(i));
+ }
+ }
+ if (ps.pkg.usesLibraryFiles != null
+ && ps.pkg.usesLibraryFiles.length > 0) {
+ pw.print(prefix); pw.println(" usesLibraryFiles:");
+ for (int i=0; i<ps.pkg.usesLibraryFiles.length; i++) {
+ pw.print(prefix); pw.print(" "); pw.println(ps.pkg.usesLibraryFiles[i]);
+ }
+ }
+ }
+ pw.print(prefix); pw.print(" timeStamp=");
+ date.setTime(ps.timeStamp);
+ pw.println(sdf.format(date));
+ pw.print(prefix); pw.print(" firstInstallTime=");
+ date.setTime(ps.firstInstallTime);
+ pw.println(sdf.format(date));
+ pw.print(prefix); pw.print(" lastUpdateTime=");
+ date.setTime(ps.lastUpdateTime);
+ pw.println(sdf.format(date));
+ if (ps.installerPackageName != null) {
+ pw.print(prefix); pw.print(" installerPackageName=");
+ pw.println(ps.installerPackageName);
+ }
+ pw.print(prefix); pw.print(" signatures="); pw.println(ps.signatures);
+ pw.print(prefix); pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed);
+ pw.print(" haveGids="); pw.print(ps.haveGids);
+ pw.print(" installStatus="); pw.println(ps.installStatus);
+ pw.print(prefix); pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
+ pw.println();
+ for (UserInfo user : users) {
+ pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": ");
+ pw.print(" installed=");
+ pw.print(ps.getInstalled(user.id));
+ pw.print(" stopped=");
+ pw.print(ps.getStopped(user.id));
+ pw.print(" notLaunched=");
+ pw.print(ps.getNotLaunched(user.id));
+ pw.print(" enabled=");
+ pw.println(ps.getEnabled(user.id));
+ String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
+ if (lastDisabledAppCaller != null) {
+ pw.print(prefix); pw.print(" lastDisabledCaller: ");
+ pw.println(lastDisabledAppCaller);
+ }
+ HashSet<String> cmp = ps.getDisabledComponents(user.id);
+ if (cmp != null && cmp.size() > 0) {
+ pw.print(prefix); pw.println(" disabledComponents:");
+ for (String s : cmp) {
+ pw.print(prefix); pw.print(" "); pw.println(s);
+ }
+ }
+ cmp = ps.getEnabledComponents(user.id);
+ if (cmp != null && cmp.size() > 0) {
+ pw.print(prefix); pw.println(" enabledComponents:");
+ for (String s : cmp) {
+ pw.print(prefix); pw.print(" "); pw.println(s);
+ }
+ }
+ }
+ if (ps.grantedPermissions.size() > 0) {
+ pw.print(prefix); pw.println(" grantedPermissions:");
+ for (String s : ps.grantedPermissions) {
+ pw.print(prefix); pw.print(" "); pw.println(s);
+ }
+ }
+ }
+
void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final Date date = new Date();
@@ -2560,123 +2845,7 @@ final class Settings {
pw.println("Packages:");
printedSomething = true;
}
- pw.print(" Package [");
- pw.print(ps.realName != null ? ps.realName : ps.name);
- pw.print("] (");
- pw.print(Integer.toHexString(System.identityHashCode(ps)));
- pw.println("):");
-
- if (ps.realName != null) {
- pw.print(" compat name=");
- pw.println(ps.name);
- }
-
- pw.print(" userId="); pw.print(ps.appId);
- pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
- pw.print(" sharedUser="); pw.println(ps.sharedUser);
- pw.print(" pkg="); pw.println(ps.pkg);
- pw.print(" codePath="); pw.println(ps.codePathString);
- pw.print(" resourcePath="); pw.println(ps.resourcePathString);
- pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
- pw.print(" versionCode="); pw.println(ps.versionCode);
- if (ps.pkg != null) {
- pw.print(" applicationInfo="); pw.println(ps.pkg.applicationInfo.toString());
- pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println();
- pw.print(" versionName="); pw.println(ps.pkg.mVersionName);
- pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
- pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion);
- if (ps.pkg.mOperationPending) {
- pw.println(" mOperationPending=true");
- }
- pw.print(" supportsScreens=[");
- boolean first = true;
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("small");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("medium");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("large");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("xlarge");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("resizeable");
- }
- if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
- if (!first)
- pw.print(", ");
- first = false;
- pw.print("anyDensity");
- }
- pw.println("]");
- }
- pw.print(" timeStamp=");
- date.setTime(ps.timeStamp);
- pw.println(sdf.format(date));
- pw.print(" firstInstallTime=");
- date.setTime(ps.firstInstallTime);
- pw.println(sdf.format(date));
- pw.print(" lastUpdateTime=");
- date.setTime(ps.lastUpdateTime);
- pw.println(sdf.format(date));
- if (ps.installerPackageName != null) {
- pw.print(" installerPackageName="); pw.println(ps.installerPackageName);
- }
- pw.print(" signatures="); pw.println(ps.signatures);
- pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed);
- pw.print(" haveGids="); pw.print(ps.haveGids);
- pw.print(" installStatus="); pw.println(ps.installStatus);
- pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
- pw.println();
- for (UserInfo user : users) {
- pw.print(" User "); pw.print(user.id); pw.print(": ");
- pw.print(" installed=");
- pw.print(ps.getInstalled(user.id));
- pw.print(" stopped=");
- pw.print(ps.getStopped(user.id));
- pw.print(" notLaunched=");
- pw.print(ps.getNotLaunched(user.id));
- pw.print(" enabled=");
- pw.println(ps.getEnabled(user.id));
- HashSet<String> cmp = ps.getDisabledComponents(user.id);
- if (cmp != null && cmp.size() > 0) {
- pw.println(" disabledComponents:");
- for (String s : cmp) {
- pw.print(" "); pw.println(s);
- }
- }
- cmp = ps.getEnabledComponents(user.id);
- if (cmp != null && cmp.size() > 0) {
- pw.println(" enabledComponents:");
- for (String s : cmp) {
- pw.print(" "); pw.println(s);
- }
- }
- }
- if (ps.grantedPermissions.size() > 0) {
- pw.println(" grantedPermissions:");
- for (String s : ps.grantedPermissions) {
- pw.print(" "); pw.println(s);
- }
- }
+ dumpPackageLPr(pw, " ", ps, sdf, date, users);
}
printedSomething = false;
@@ -2712,27 +2881,7 @@ final class Settings {
pw.println("Hidden system packages:");
printedSomething = true;
}
- pw.print(" Package [");
- pw.print(ps.realName != null ? ps.realName : ps.name);
- pw.print("] (");
- pw.print(Integer.toHexString(System.identityHashCode(ps)));
- pw.println("):");
- if (ps.realName != null) {
- pw.print(" compat name=");
- pw.println(ps.name);
- }
- if (ps.pkg != null && ps.pkg.applicationInfo != null) {
- pw.print(" applicationInfo=");
- pw.println(ps.pkg.applicationInfo.toString());
- }
- pw.print(" userId=");
- pw.println(ps.appId);
- pw.print(" sharedUser=");
- pw.println(ps.sharedUser);
- pw.print(" codePath=");
- pw.println(ps.codePathString);
- pw.print(" resourcePath=");
- pw.println(ps.resourcePathString);
+ dumpPackageLPr(pw, " ", ps, sdf, date, users);
}
}
}
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index dbfe34d..1323c93 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -25,11 +25,14 @@ import android.app.IStopUserCallback;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.RestrictionEntry;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
@@ -81,11 +84,23 @@ public class UserManagerService extends IUserManager.Stub {
private static final String ATTR_USER_VERSION = "version";
private static final String TAG_USERS = "users";
private static final String TAG_USER = "user";
+ private static final String TAG_RESTRICTIONS = "restrictions";
+ private static final String TAG_ENTRY = "entry";
+ private static final String TAG_VALUE = "value";
+ private static final String ATTR_KEY = "key";
+ private static final String ATTR_VALUE_TYPE = "type";
+ private static final String ATTR_MULTIPLE = "m";
+
+ private static final String ATTR_TYPE_STRING_ARRAY = "sa";
+ private static final String ATTR_TYPE_STRING = "s";
+ private static final String ATTR_TYPE_BOOLEAN = "b";
private static final String USER_INFO_DIR = "system" + File.separator + "users";
private static final String USER_LIST_FILENAME = "userlist.xml";
private static final String USER_PHOTO_FILENAME = "photo.png";
+ private static final String RESTRICTIONS_FILE_PREFIX = "res_";
+
private static final int MIN_USER_ID = 10;
private static final int USER_VERSION = 2;
@@ -104,6 +119,7 @@ public class UserManagerService extends IUserManager.Stub {
private final File mBaseUserPath;
private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
+ private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>();
/**
* Set of user IDs being actively removed. Removed IDs linger in this set
@@ -214,6 +230,13 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ @Override
+ public boolean isRestricted() {
+ synchronized (mPackagesLock) {
+ return getUserInfoLocked(UserHandle.getCallingUserId()).isRestricted();
+ }
+ }
+
/*
* Should be locked on mUsers before calling this.
*/
@@ -273,7 +296,7 @@ public class UserManagerService extends IUserManager.Stub {
Intent changedIntent = new Intent(Intent.ACTION_USER_INFO_CHANGED);
changedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
changedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(changedIntent, new UserHandle(userId));
+ mContext.sendBroadcastAsUser(changedIntent, UserHandle.ALL);
}
@Override
@@ -343,6 +366,26 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ @Override
+ public Bundle getUserRestrictions(int userId) {
+ // checkManageUsersPermission("getUserRestrictions");
+
+ synchronized (mPackagesLock) {
+ Bundle restrictions = mUserRestrictions.get(userId);
+ return restrictions != null ? restrictions : Bundle.EMPTY;
+ }
+ }
+
+ @Override
+ public void setUserRestrictions(Bundle restrictions, int userId) {
+ checkManageUsersPermission("setUserRestrictions");
+
+ synchronized (mPackagesLock) {
+ mUserRestrictions.get(userId).putAll(restrictions);
+ writeUserLocked(mUsers.get(userId));
+ }
+ }
+
/**
* Check if we've hit the limit of how many users can be created.
*/
@@ -454,7 +497,7 @@ public class UserManagerService extends IUserManager.Stub {
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));
+ UserInfo user = readUserLocked(Integer.parseInt(id));
if (user != null) {
mUsers.put(user.id, user);
@@ -519,11 +562,15 @@ public class UserManagerService extends IUserManager.Stub {
private void fallbackToSingleUserLocked() {
// Create the primary user
- UserInfo primary = new UserInfo(0,
+ UserInfo primary = new UserInfo(UserHandle.USER_OWNER,
mContext.getResources().getString(com.android.internal.R.string.owner_name), null,
UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED);
mUsers.put(0, primary);
mNextSerialNumber = MIN_USER_ID;
+
+ Bundle restrictions = new Bundle();
+ mUserRestrictions.append(UserHandle.USER_OWNER, restrictions);
+
updateUserIdsLocked();
writeUserListLocked();
@@ -568,6 +615,22 @@ public class UserManagerService extends IUserManager.Stub {
serializer.text(userInfo.name);
serializer.endTag(null, TAG_NAME);
+ Bundle restrictions = mUserRestrictions.get(userInfo.id);
+ if (restrictions != null) {
+ serializer.startTag(null, TAG_RESTRICTIONS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_INSTALL_APPS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
+ writeBoolean(serializer, restrictions,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_BLUETOOTH);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_USB_FILE_TRANSFER);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_CREDENTIALS);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_REMOVE_USER);
+ serializer.endTag(null, TAG_RESTRICTIONS);
+ }
serializer.endTag(null, TAG_USER);
serializer.endDocument();
@@ -620,7 +683,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private UserInfo readUser(int id) {
+ private UserInfo readUserLocked(int id) {
int flags = 0;
int serialNumber = id;
String name = null;
@@ -628,6 +691,7 @@ public class UserManagerService extends IUserManager.Stub {
long creationTime = 0L;
long lastLoggedInTime = 0L;
boolean partial = false;
+ Bundle restrictions = new Bundle();
FileInputStream fis = null;
try {
@@ -663,13 +727,30 @@ public class UserManagerService extends IUserManager.Stub {
partial = true;
}
- 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();
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tag = parser.getName();
+ if (TAG_NAME.equals(tag)) {
+ type = parser.next();
+ if (type == XmlPullParser.TEXT) {
+ name = parser.getText();
+ }
+ } else if (TAG_RESTRICTIONS.equals(tag)) {
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_INSTALL_APPS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
+ readBoolean(parser, restrictions,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_BLUETOOTH);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_USB_FILE_TRANSFER);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_CREDENTIALS);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_REMOVE_USER);
}
}
}
@@ -679,6 +760,7 @@ public class UserManagerService extends IUserManager.Stub {
userInfo.creationTime = creationTime;
userInfo.lastLoggedInTime = lastLoggedInTime;
userInfo.partial = partial;
+ mUserRestrictions.append(id, restrictions);
return userInfo;
} catch (IOException ioe) {
@@ -694,6 +776,22 @@ public class UserManagerService extends IUserManager.Stub {
return null;
}
+ private void readBoolean(XmlPullParser parser, Bundle restrictions,
+ String restrictionKey) {
+ String value = parser.getAttributeValue(null, restrictionKey);
+ if (value != null) {
+ restrictions.putBoolean(restrictionKey, Boolean.parseBoolean(value));
+ }
+ }
+
+ private void writeBoolean(XmlSerializer xml, Bundle restrictions, String restrictionKey)
+ throws IOException {
+ if (restrictions.containsKey(restrictionKey)) {
+ xml.attribute(null, restrictionKey,
+ Boolean.toString(restrictions.getBoolean(restrictionKey)));
+ }
+ }
+
private int readIntAttribute(XmlPullParser parser, String attr, int defaultValue) {
String valueString = parser.getAttributeValue(null, attr);
if (valueString == null) return defaultValue;
@@ -739,6 +837,8 @@ public class UserManagerService extends IUserManager.Stub {
userInfo.partial = false;
writeUserLocked(userInfo);
updateUserIdsLocked();
+ Bundle restrictions = new Bundle();
+ mUserRestrictions.append(userId, restrictions);
}
}
if (userInfo != null) {
@@ -870,6 +970,165 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
+ public Bundle getApplicationRestrictions(String packageName) {
+ return getApplicationRestrictionsForUser(packageName, UserHandle.getCallingUserId());
+ }
+
+ @Override
+ public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
+ if (UserHandle.getCallingUserId() != userId
+ || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
+ checkManageUsersPermission("Only system can get restrictions for other users/apps");
+ }
+ synchronized (mPackagesLock) {
+ // Read the restrictions from XML
+ return readApplicationRestrictionsLocked(packageName, userId);
+ }
+ }
+
+ @Override
+ public void setApplicationRestrictions(String packageName, Bundle restrictions,
+ int userId) {
+ if (UserHandle.getCallingUserId() != userId
+ || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
+ checkManageUsersPermission("Only system can set restrictions for other users/apps");
+ }
+ synchronized (mPackagesLock) {
+ // Write the restrictions to XML
+ writeApplicationRestrictionsLocked(packageName, restrictions, userId);
+ }
+ }
+
+ private int getUidForPackage(String packageName) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return mContext.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES).uid;
+ } catch (NameNotFoundException nnfe) {
+ return -1;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private Bundle readApplicationRestrictionsLocked(String packageName,
+ int userId) {
+ final Bundle restrictions = new Bundle();
+ final ArrayList<String> values = new ArrayList<String>();
+
+ FileInputStream fis = null;
+ try {
+ AtomicFile restrictionsFile =
+ new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
+ RESTRICTIONS_FILE_PREFIX + packageName + ".xml"));
+ fis = restrictionsFile.openRead();
+ 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(LOG_TAG, "Unable to read restrictions file "
+ + restrictionsFile.getBaseFile());
+ return restrictions;
+ }
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
+ String key = parser.getAttributeValue(null, ATTR_KEY);
+ String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE);
+ String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE);
+ if (multiple != null) {
+ int count = Integer.parseInt(multiple);
+ while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG
+ && parser.getName().equals(TAG_VALUE)) {
+ values.add(parser.nextText().trim());
+ count--;
+ }
+ }
+ String [] valueStrings = new String[values.size()];
+ values.toArray(valueStrings);
+ restrictions.putStringArray(key, valueStrings);
+ } else if (ATTR_TYPE_BOOLEAN.equals(valType)) {
+ restrictions.putBoolean(key, Boolean.parseBoolean(
+ parser.nextText().trim()));
+ } else {
+ String value = parser.nextText().trim();
+ restrictions.putString(key, value);
+ }
+ }
+ }
+
+ } catch (IOException ioe) {
+ } catch (XmlPullParserException pe) {
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ return restrictions;
+ }
+
+ private void writeApplicationRestrictionsLocked(String packageName,
+ Bundle restrictions, int userId) {
+ FileOutputStream fos = null;
+ AtomicFile restrictionsFile = new AtomicFile(
+ new File(Environment.getUserSystemDirectory(userId),
+ RESTRICTIONS_FILE_PREFIX + packageName + ".xml"));
+ try {
+ fos = restrictionsFile.startWrite();
+ 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_RESTRICTIONS);
+
+ for (String key : restrictions.keySet()) {
+ Object value = restrictions.get(key);
+ serializer.startTag(null, TAG_ENTRY);
+ serializer.attribute(null, ATTR_KEY, key);
+
+ if (value instanceof Boolean) {
+ serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN);
+ serializer.text(value.toString());
+ } else if (value == null || value instanceof String) {
+ serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING);
+ serializer.text(value != null ? (String) value : "");
+ } else {
+ serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY);
+ String[] values = (String[]) value;
+ serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length));
+ for (String choice : values) {
+ serializer.startTag(null, TAG_VALUE);
+ serializer.text(choice != null ? choice : "");
+ serializer.endTag(null, TAG_VALUE);
+ }
+ }
+ serializer.endTag(null, TAG_ENTRY);
+ }
+
+ serializer.endTag(null, TAG_RESTRICTIONS);
+
+ serializer.endDocument();
+ restrictionsFile.finishWrite(fos);
+ } catch (Exception e) {
+ restrictionsFile.failWrite(fos);
+ Slog.e(LOG_TAG, "Error writing application restrictions list");
+ }
+ }
+
+ @Override
public int getUserSerialNumber(int userHandle) {
synchronized (mPackagesLock) {
if (!exists(userHandle)) return -1;
diff --git a/services/java/com/android/server/power/ElectronBeam.java b/services/java/com/android/server/power/ElectronBeam.java
index 8e19e11..379e704 100644
--- a/services/java/com/android/server/power/ElectronBeam.java
+++ b/services/java/com/android/server/power/ElectronBeam.java
@@ -16,30 +16,31 @@
package com.android.server.power;
-import com.android.server.display.DisplayManagerService;
-import com.android.server.display.DisplayTransactionListener;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
-import android.graphics.Bitmap;
import android.graphics.PixelFormat;
+import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES10;
-import android.opengl.GLUtils;
+import android.opengl.GLES11Ext;
import android.os.Looper;
import android.util.FloatMath;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
-import java.io.PrintWriter;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
+import com.android.server.display.DisplayManagerService;
+import com.android.server.display.DisplayTransactionListener;
/**
* Bzzzoooop! *crackle*
@@ -80,6 +81,7 @@ final class ElectronBeam {
private int mDisplayWidth; // real width, not rotated
private int mDisplayHeight; // real height, not rotated
private SurfaceSession mSurfaceSession;
+ private SurfaceControl mSurfaceControl;
private Surface mSurface;
private NaturalSurfaceLayout mSurfaceLayout;
private EGLDisplay mEglDisplay;
@@ -92,6 +94,7 @@ final class ElectronBeam {
// Texture names. We only use one texture, which contains the screenshot.
private final int[] mTexNames = new int[1];
private boolean mTexNamesGenerated;
+ private float mTexMatrix[] = new float[16];
// Vertex and corresponding texture coordinates.
// We have 4 2D vertices, so 8 elements. The vertices form a quad.
@@ -113,6 +116,7 @@ final class ElectronBeam {
*/
public static final int MODE_FADE = 2;
+
public ElectronBeam(DisplayManagerService displayManager) {
mDisplayManager = displayManager;
}
@@ -262,19 +266,23 @@ final class ElectronBeam {
GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer);
GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
+ // set-up texturing
+ GLES10.glDisable(GLES10.GL_TEXTURE_2D);
+ GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+
// bind texture and set blending for drawing planes
- GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, mTexNames[0]);
+ GLES10.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexNames[0]);
GLES10.glTexEnvx(GLES10.GL_TEXTURE_ENV, GLES10.GL_TEXTURE_ENV_MODE,
mMode == MODE_WARM_UP ? GLES10.GL_MODULATE : GLES10.GL_REPLACE);
- GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
+ GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR);
- GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
+ GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_LINEAR);
- GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
+ GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES10.GL_TEXTURE_WRAP_S, GLES10.GL_CLAMP_TO_EDGE);
- GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
+ GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES10.GL_TEXTURE_WRAP_T, GLES10.GL_CLAMP_TO_EDGE);
- GLES10.glEnable(GLES10.GL_TEXTURE_2D);
+ GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, mTexCoordBuffer);
GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
@@ -294,7 +302,7 @@ final class ElectronBeam {
GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
// clean up after drawing planes
- GLES10.glDisable(GLES10.GL_TEXTURE_2D);
+ GLES10.glDisable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
GLES10.glColorMask(true, true, true, true);
@@ -369,81 +377,50 @@ final class ElectronBeam {
}
private boolean captureScreenshotTextureAndSetViewport() {
- // TODO: Use a SurfaceTexture to avoid the extra texture upload.
- Bitmap bitmap = Surface.screenshot(mDisplayWidth, mDisplayHeight,
- 0, ELECTRON_BEAM_LAYER - 1);
- if (bitmap == null) {
- Slog.e(TAG, "Could not take a screenshot!");
+ if (!attachEglContext()) {
return false;
}
try {
- if (!attachEglContext()) {
- return false;
- }
- try {
- if (!mTexNamesGenerated) {
- GLES10.glGenTextures(1, mTexNames, 0);
- if (checkGlErrors("glGenTextures")) {
- return false;
- }
- mTexNamesGenerated = true;
- }
-
- GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, mTexNames[0]);
- if (checkGlErrors("glBindTexture")) {
+ if (!mTexNamesGenerated) {
+ GLES10.glGenTextures(1, mTexNames, 0);
+ if (checkGlErrors("glGenTextures")) {
return false;
}
+ mTexNamesGenerated = true;
+ }
- float u = 1.0f;
- float v = 1.0f;
- GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0);
- if (checkGlErrors("glTexImage2D, first try", false)) {
- // Try a power of two size texture instead.
- int tw = nextPowerOfTwo(mDisplayWidth);
- int th = nextPowerOfTwo(mDisplayHeight);
- int format = GLUtils.getInternalFormat(bitmap);
- GLES10.glTexImage2D(GLES10.GL_TEXTURE_2D, 0,
- format, tw, th, 0,
- format, GLES10.GL_UNSIGNED_BYTE, null);
- if (checkGlErrors("glTexImage2D, second try")) {
- return false;
- }
-
- GLUtils.texSubImage2D(GLES10.GL_TEXTURE_2D, 0, 0, 0, bitmap);
- if (checkGlErrors("glTexSubImage2D")) {
- return false;
- }
-
- u = (float)mDisplayWidth / tw;
- v = (float)mDisplayHeight / th;
- }
-
- // Set up texture coordinates for a quad.
- // We might need to change this if the texture ends up being
- // a different size from the display for some reason.
- mTexCoordBuffer.put(0, 0f);
- mTexCoordBuffer.put(1, v);
- mTexCoordBuffer.put(2, 0f);
- mTexCoordBuffer.put(3, 0f);
- mTexCoordBuffer.put(4, u);
- mTexCoordBuffer.put(5, 0f);
- mTexCoordBuffer.put(6, u);
- mTexCoordBuffer.put(7, v);
-
- // Set up our viewport.
- GLES10.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
- GLES10.glMatrixMode(GLES10.GL_PROJECTION);
- GLES10.glLoadIdentity();
- GLES10.glOrthof(0, mDisplayWidth, 0, mDisplayHeight, 0, 1);
- GLES10.glMatrixMode(GLES10.GL_MODELVIEW);
- GLES10.glLoadIdentity();
- GLES10.glMatrixMode(GLES10.GL_TEXTURE);
- GLES10.glLoadIdentity();
+ final SurfaceTexture st = new SurfaceTexture(mTexNames[0]);
+ final Surface s = new Surface(st);
+ try {
+ SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
+ SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), s);
} finally {
- detachEglContext();
+ s.release();
}
+
+ st.updateTexImage();
+ st.getTransformMatrix(mTexMatrix);
+
+ // Set up texture coordinates for a quad.
+ // We might need to change this if the texture ends up being
+ // a different size from the display for some reason.
+ mTexCoordBuffer.put(0, 0f); mTexCoordBuffer.put(1, 0f);
+ mTexCoordBuffer.put(2, 0f); mTexCoordBuffer.put(3, 1f);
+ mTexCoordBuffer.put(4, 1f); mTexCoordBuffer.put(5, 1f);
+ mTexCoordBuffer.put(6, 1f); mTexCoordBuffer.put(7, 0f);
+
+ // Set up our viewport.
+ GLES10.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
+ GLES10.glMatrixMode(GLES10.GL_PROJECTION);
+ GLES10.glLoadIdentity();
+ GLES10.glOrthof(0, mDisplayWidth, 0, mDisplayHeight, 0, 1);
+ GLES10.glMatrixMode(GLES10.GL_MODELVIEW);
+ GLES10.glLoadIdentity();
+ GLES10.glMatrixMode(GLES10.GL_TEXTURE);
+ GLES10.glLoadIdentity();
+ GLES10.glLoadMatrixf(mTexMatrix, 0);
} finally {
- bitmap.recycle();
+ detachEglContext();
}
return true;
}
@@ -525,32 +502,34 @@ final class ElectronBeam {
mSurfaceSession = new SurfaceSession();
}
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
- if (mSurface == null) {
+ if (mSurfaceControl == null) {
try {
int flags;
if (mMode == MODE_FADE) {
- flags = Surface.FX_SURFACE_DIM | Surface.HIDDEN;
+ flags = SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN;
} else {
- flags = Surface.OPAQUE | Surface.HIDDEN;
+ flags = SurfaceControl.OPAQUE | SurfaceControl.HIDDEN;
}
- mSurface = new Surface(mSurfaceSession,
+ mSurfaceControl = new SurfaceControl(mSurfaceSession,
"ElectronBeam", mDisplayWidth, mDisplayHeight,
PixelFormat.OPAQUE, flags);
- } catch (Surface.OutOfResourcesException ex) {
+ } catch (SurfaceControl.OutOfResourcesException ex) {
Slog.e(TAG, "Unable to create surface.", ex);
return false;
}
}
- mSurface.setLayerStack(mDisplayLayerStack);
- mSurface.setSize(mDisplayWidth, mDisplayHeight);
-
- mSurfaceLayout = new NaturalSurfaceLayout(mDisplayManager, mSurface);
+ mSurfaceControl.setLayerStack(mDisplayLayerStack);
+ mSurfaceControl.setSize(mDisplayWidth, mDisplayHeight);
+ mSurface = new Surface();
+ mSurface.copyFrom(mSurfaceControl);
+
+ mSurfaceLayout = new NaturalSurfaceLayout(mDisplayManager, mSurfaceControl);
mSurfaceLayout.onDisplayTransaction();
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
}
return true;
}
@@ -560,6 +539,7 @@ final class ElectronBeam {
int[] eglSurfaceAttribList = new int[] {
EGL14.EGL_NONE
};
+ // turn our SurfaceControl into a Surface
mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface,
eglSurfaceAttribList, 0);
if (mEglSurface == null) {
@@ -580,16 +560,17 @@ final class ElectronBeam {
}
private void destroySurface() {
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
mSurfaceLayout.dispose();
mSurfaceLayout = null;
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
- mSurface.destroy();
+ mSurfaceControl.destroy();
+ mSurface.release();
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
}
- mSurface = null;
+ mSurfaceControl = null;
mSurfaceVisible = false;
mSurfaceAlpha = 0f;
}
@@ -597,13 +578,13 @@ final class ElectronBeam {
private boolean showSurface(float alpha) {
if (!mSurfaceVisible || mSurfaceAlpha != alpha) {
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
- mSurface.setLayer(ELECTRON_BEAM_LAYER);
- mSurface.setAlpha(alpha);
- mSurface.show();
+ mSurfaceControl.setLayer(ELECTRON_BEAM_LAYER);
+ mSurfaceControl.setAlpha(alpha);
+ mSurfaceControl.show();
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
}
mSurfaceVisible = true;
mSurfaceAlpha = alpha;
@@ -657,10 +638,6 @@ final class ElectronBeam {
return 1.0f / (1.0f + FloatMath.exp(-x * s));
}
- private static int nextPowerOfTwo(int value) {
- return 1 << (32 - Integer.numberOfLeadingZeros(value));
- }
-
private static FloatBuffer createNativeFloatBuffer(int size) {
ByteBuffer bb = ByteBuffer.allocateDirect(size * 4);
bb.order(ByteOrder.nativeOrder());
@@ -708,17 +685,17 @@ final class ElectronBeam {
*/
private static final class NaturalSurfaceLayout implements DisplayTransactionListener {
private final DisplayManagerService mDisplayManager;
- private Surface mSurface;
+ private SurfaceControl mSurfaceControl;
- public NaturalSurfaceLayout(DisplayManagerService displayManager, Surface surface) {
+ public NaturalSurfaceLayout(DisplayManagerService displayManager, SurfaceControl surfaceControl) {
mDisplayManager = displayManager;
- mSurface = surface;
+ mSurfaceControl = surfaceControl;
mDisplayManager.registerDisplayTransactionListener(this);
}
public void dispose() {
synchronized (this) {
- mSurface = null;
+ mSurfaceControl = null;
}
mDisplayManager.unregisterDisplayTransactionListener(this);
}
@@ -726,27 +703,27 @@ final class ElectronBeam {
@Override
public void onDisplayTransaction() {
synchronized (this) {
- if (mSurface == null) {
+ if (mSurfaceControl == null) {
return;
}
DisplayInfo displayInfo = mDisplayManager.getDisplayInfo(Display.DEFAULT_DISPLAY);
switch (displayInfo.rotation) {
case Surface.ROTATION_0:
- mSurface.setPosition(0, 0);
- mSurface.setMatrix(1, 0, 0, 1);
+ mSurfaceControl.setPosition(0, 0);
+ mSurfaceControl.setMatrix(1, 0, 0, 1);
break;
case Surface.ROTATION_90:
- mSurface.setPosition(0, displayInfo.logicalHeight);
- mSurface.setMatrix(0, -1, 1, 0);
+ mSurfaceControl.setPosition(0, displayInfo.logicalHeight);
+ mSurfaceControl.setMatrix(0, -1, 1, 0);
break;
case Surface.ROTATION_180:
- mSurface.setPosition(displayInfo.logicalWidth, displayInfo.logicalHeight);
- mSurface.setMatrix(-1, 0, 0, -1);
+ mSurfaceControl.setPosition(displayInfo.logicalWidth, displayInfo.logicalHeight);
+ mSurfaceControl.setMatrix(-1, 0, 0, -1);
break;
case Surface.ROTATION_270:
- mSurface.setPosition(displayInfo.logicalWidth, 0);
- mSurface.setMatrix(0, 1, -1, 0);
+ mSurfaceControl.setPosition(displayInfo.logicalWidth, 0);
+ mSurfaceControl.setMatrix(0, 1, -1, 0);
break;
}
}
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java
index 546f22e..1203e02 100644
--- a/services/java/com/android/server/power/PowerManagerService.java
+++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -236,11 +236,20 @@ public final class PowerManagerService extends IPowerManager.Stub
// is actually on or actually off or whatever was requested.
private boolean mDisplayReady;
- // True if holding a wake-lock to block suspend of the CPU.
+ // The suspend blocker used to keep the CPU alive when an application has acquired
+ // a wake lock.
+ private final SuspendBlocker mWakeLockSuspendBlocker;
+
+ // True if the wake lock suspend blocker has been acquired.
private boolean mHoldingWakeLockSuspendBlocker;
- // The suspend blocker used to keep the CPU alive when wake locks have been acquired.
- private final SuspendBlocker mWakeLockSuspendBlocker;
+ // The suspend blocker used to keep the CPU alive when the display is on, the
+ // display is getting ready or there is user activity (in which case the display
+ // must be on).
+ private final SuspendBlocker mDisplaySuspendBlocker;
+
+ // True if the display suspend blocker has been acquired.
+ private boolean mHoldingDisplaySuspendBlocker;
// The screen on blocker used to keep the screen from turning on while the lock
// screen is coming up.
@@ -366,11 +375,13 @@ public final class PowerManagerService extends IPowerManager.Stub
public PowerManagerService() {
synchronized (mLock) {
- mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService");
- mWakeLockSuspendBlocker.acquire();
+ mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
+ mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display");
+ mDisplaySuspendBlocker.acquire();
+ mHoldingDisplaySuspendBlocker = true;
+
mScreenOnBlocker = new ScreenOnBlockerImpl();
mDisplayBlanker = new DisplayBlankerImpl();
- mHoldingWakeLockSuspendBlocker = true;
mWakefulness = WAKEFULNESS_AWAKE;
}
@@ -421,7 +432,7 @@ public final class PowerManagerService extends IPowerManager.Stub
mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting();
mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting();
- SensorManager sensorManager = new SystemSensorManager(mHandler.getLooper());
+ SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
// The notifier runs on the system server's main looper so as not to interfere
// with the animations and other critical functions of the power manager.
@@ -656,17 +667,21 @@ public final class PowerManagerService extends IPowerManager.Stub
private void releaseWakeLockInternal(IBinder lock, int flags) {
synchronized (mLock) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock)
- + ", flags=0x" + Integer.toHexString(flags));
- }
-
int index = findWakeLockIndexLocked(lock);
if (index < 0) {
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock)
+ + " [not found], flags=0x" + Integer.toHexString(flags));
+ }
return;
}
WakeLock wakeLock = mWakeLocks.get(index);
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock)
+ + " [" + wakeLock.mTag + "], flags=0x" + Integer.toHexString(flags));
+ }
+
mWakeLocks.remove(index);
notifyWakeLockReleasedLocked(wakeLock);
wakeLock.mLock.unlinkToDeath(wakeLock, 0);
@@ -684,7 +699,8 @@ public final class PowerManagerService extends IPowerManager.Stub
private void handleWakeLockDeath(WakeLock wakeLock) {
synchronized (mLock) {
if (DEBUG_SPEW) {
- Slog.d(TAG, "handleWakeLockDeath: lock=" + Objects.hashCode(wakeLock.mLock));
+ Slog.d(TAG, "handleWakeLockDeath: lock=" + Objects.hashCode(wakeLock.mLock)
+ + " [" + wakeLock.mTag + "]");
}
int index = mWakeLocks.indexOf(wakeLock);
@@ -736,10 +752,19 @@ public final class PowerManagerService extends IPowerManager.Stub
synchronized (mLock) {
int index = findWakeLockIndexLocked(lock);
if (index < 0) {
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "updateWakeLockWorkSourceInternal: lock=" + Objects.hashCode(lock)
+ + " [not found], ws=" + ws);
+ }
throw new IllegalArgumentException("Wake lock not active");
}
WakeLock wakeLock = mWakeLocks.get(index);
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "updateWakeLockWorkSourceInternal: lock=" + Objects.hashCode(lock)
+ + " [" + wakeLock.mTag + "], ws=" + ws);
+ }
+
if (!wakeLock.hasSameWorkSource(ws)) {
notifyWakeLockReleasedLocked(wakeLock);
wakeLock.updateWorkSource(ws);
@@ -1708,29 +1733,30 @@ public final class PowerManagerService extends IPowerManager.Stub
* This function must have no other side-effects.
*/
private void updateSuspendBlockerLocked() {
- boolean wantCpu = isCpuNeededLocked();
- if (wantCpu != mHoldingWakeLockSuspendBlocker) {
- mHoldingWakeLockSuspendBlocker = wantCpu;
- if (wantCpu) {
- if (DEBUG) {
- Slog.d(TAG, "updateSuspendBlockerLocked: Acquiring suspend blocker.");
- }
- mWakeLockSuspendBlocker.acquire();
- } else {
- if (DEBUG) {
- Slog.d(TAG, "updateSuspendBlockerLocked: Releasing suspend blocker.");
- }
- mWakeLockSuspendBlocker.release();
- }
+ final boolean needWakeLockSuspendBlocker = (mWakeLockSummary != 0);
+ final boolean needDisplaySuspendBlocker = (mUserActivitySummary != 0
+ || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF
+ || !mDisplayReady || !mBootCompleted);
+
+ // First acquire suspend blockers if needed.
+ if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) {
+ mWakeLockSuspendBlocker.acquire();
+ mHoldingWakeLockSuspendBlocker = true;
+ }
+ if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) {
+ mDisplaySuspendBlocker.acquire();
+ mHoldingDisplaySuspendBlocker = true;
}
- }
- private boolean isCpuNeededLocked() {
- return !mBootCompleted
- || mWakeLockSummary != 0
- || mUserActivitySummary != 0
- || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF
- || !mDisplayReady;
+ // Then release suspend blockers if needed.
+ if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
+ mWakeLockSuspendBlocker.release();
+ mHoldingWakeLockSuspendBlocker = false;
+ }
+ if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
+ mDisplaySuspendBlocker.release();
+ mHoldingDisplaySuspendBlocker = false;
+ }
}
@Override // Binder call
@@ -2201,6 +2227,7 @@ public final class PowerManagerService extends IPowerManager.Stub
+ TimeUtils.formatUptime(mLastUserActivityTimeNoChangeLights));
pw.println(" mDisplayReady=" + mDisplayReady);
pw.println(" mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker);
+ pw.println(" mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker);
pw.println();
pw.println("Settings and Configuration:");
@@ -2501,6 +2528,9 @@ public final class PowerManagerService extends IPowerManager.Stub
synchronized (this) {
mReferenceCount += 1;
if (mReferenceCount == 1) {
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "Acquiring suspend blocker \"" + mName + "\".");
+ }
nativeAcquireSuspendBlocker(mName);
}
}
@@ -2511,6 +2541,9 @@ public final class PowerManagerService extends IPowerManager.Stub
synchronized (this) {
mReferenceCount -= 1;
if (mReferenceCount == 0) {
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "Releasing suspend blocker \"" + mName + "\".");
+ }
nativeReleaseSuspendBlocker(mName);
} else if (mReferenceCount < 0) {
Log.wtf(TAG, "Suspend blocker \"" + mName
diff --git a/services/java/com/android/server/search/SearchManagerService.java b/services/java/com/android/server/search/SearchManagerService.java
new file mode 100644
index 0000000..b5d81d1
--- /dev/null
+++ b/services/java/com/android/server/search/SearchManagerService.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2007 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.search;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.ISearchManager;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.os.Binder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * The search manager service handles the search UI, and maintains a registry of searchable
+ * activities.
+ */
+public class SearchManagerService extends ISearchManager.Stub {
+
+ // general debugging support
+ private static final String TAG = "SearchManagerService";
+
+ // Context that the service is running in.
+ private final Context mContext;
+
+ // This field is initialized lazily in getSearchables(), and then never modified.
+ private final SparseArray<Searchables> mSearchables = new SparseArray<Searchables>();
+
+ /**
+ * Initializes the Search Manager service in the provided system context.
+ * Only one instance of this object should be created!
+ *
+ * @param context to use for accessing DB, window manager, etc.
+ */
+ public SearchManagerService(Context context) {
+ mContext = context;
+ mContext.registerReceiver(new BootCompletedReceiver(),
+ new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+ mContext.registerReceiver(new UserReceiver(),
+ new IntentFilter(Intent.ACTION_USER_REMOVED));
+ new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
+ }
+
+ private Searchables getSearchables(int userId) {
+ long origId = Binder.clearCallingIdentity();
+ try {
+ boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
+ .getUserInfo(userId) != null;
+ if (!userExists) return null;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ synchronized (mSearchables) {
+ Searchables searchables = mSearchables.get(userId);
+
+ if (searchables == null) {
+ //Log.i(TAG, "Building list of searchable activities for userId=" + userId);
+ searchables = new Searchables(mContext, userId);
+ searchables.buildSearchableList();
+ mSearchables.append(userId, searchables);
+ }
+ return searchables;
+ }
+ }
+
+ private void onUserRemoved(int userId) {
+ if (userId != UserHandle.USER_OWNER) {
+ synchronized (mSearchables) {
+ mSearchables.remove(userId);
+ }
+ }
+ }
+
+ /**
+ * Creates the initial searchables list after boot.
+ */
+ private final class BootCompletedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ new Thread() {
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ mContext.unregisterReceiver(BootCompletedReceiver.this);
+ getSearchables(0);
+ }
+ }.start();
+ }
+ }
+
+ private final class UserReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_OWNER));
+ }
+ }
+
+ /**
+ * Refreshes the "searchables" list when packages are added/removed.
+ */
+ class MyPackageMonitor extends PackageMonitor {
+
+ @Override
+ public void onSomePackagesChanged() {
+ updateSearchables();
+ }
+
+ @Override
+ public void onPackageModified(String pkg) {
+ updateSearchables();
+ }
+
+ private void updateSearchables() {
+ final int changingUserId = getChangingUserId();
+ synchronized (mSearchables) {
+ // Update list of searchable activities
+ for (int i = 0; i < mSearchables.size(); i++) {
+ if (changingUserId == mSearchables.keyAt(i)) {
+ getSearchables(mSearchables.keyAt(i)).buildSearchableList();
+ break;
+ }
+ }
+ }
+ // Inform all listeners that the list of searchables has been updated.
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId));
+ }
+ }
+
+ class GlobalSearchProviderObserver extends ContentObserver {
+ private final ContentResolver mResolver;
+
+ public GlobalSearchProviderObserver(ContentResolver resolver) {
+ super(null);
+ mResolver = resolver;
+ mResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY),
+ false /* notifyDescendants */,
+ this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mSearchables) {
+ for (int i = 0; i < mSearchables.size(); i++) {
+ getSearchables(mSearchables.keyAt(i)).buildSearchableList();
+ }
+ }
+ Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ }
+
+ //
+ // Searchable activities API
+ //
+
+ /**
+ * Returns the SearchableInfo for a given activity.
+ *
+ * @param launchActivity The activity from which we're launching this search.
+ * @return Returns a SearchableInfo record describing the parameters of the search,
+ * or null if no searchable metadata was available.
+ */
+ public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
+ if (launchActivity == null) {
+ Log.e(TAG, "getSearchableInfo(), activity == null");
+ return null;
+ }
+ return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity);
+ }
+
+ /**
+ * Returns a list of the searchable activities that can be included in global search.
+ */
+ public List<SearchableInfo> getSearchablesInGlobalSearch() {
+ return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList();
+ }
+
+ public List<ResolveInfo> getGlobalSearchActivities() {
+ return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities();
+ }
+
+ /**
+ * Gets the name of the global search activity.
+ */
+ public ComponentName getGlobalSearchActivity() {
+ return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
+ }
+
+ /**
+ * Gets the name of the web search activity.
+ */
+ public ComponentName getWebSearchActivity() {
+ return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
+ }
+
+ @Override
+ public ComponentName getAssistIntent(int userHandle) {
+ try {
+ userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userHandle, true, false, "getAssistIntent", null);
+ IPackageManager pm = AppGlobals.getPackageManager();
+ Intent assistIntent = new Intent(Intent.ACTION_ASSIST);
+ ResolveInfo info =
+ pm.resolveIntent(assistIntent,
+ assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.MATCH_DEFAULT_ONLY, userHandle);
+ if (info != null) {
+ return new ComponentName(
+ info.activityInfo.applicationInfo.packageName,
+ info.activityInfo.name);
+ }
+ } catch (RemoteException re) {
+ // Local call
+ Log.e(TAG, "RemoteException in getAssistIntent: " + re);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in getAssistIntent: " + e);
+ }
+ return null;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ synchronized (mSearchables) {
+ for (int i = 0; i < mSearchables.size(); i++) {
+ ipw.print("\nUser: "); ipw.println(mSearchables.keyAt(i));
+ ipw.increaseIndent();
+ mSearchables.valueAt(i).dump(fd, ipw, args);
+ ipw.decreaseIndent();
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/search/Searchables.java b/services/java/com/android/server/search/Searchables.java
new file mode 100644
index 0000000..0ffbb7d
--- /dev/null
+++ b/services/java/com/android/server/search/Searchables.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2009 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.search;
+
+import android.app.AppGlobals;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class maintains the information about all searchable activities.
+ * This is a hidden class.
+ */
+public class Searchables {
+
+ private static final String LOG_TAG = "Searchables";
+
+ // static strings used for XML lookups, etc.
+ // TODO how should these be documented for the developer, in a more structured way than
+ // the current long wordy javadoc in SearchManager.java ?
+ private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
+ private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
+
+ private Context mContext;
+
+ private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
+ private ArrayList<SearchableInfo> mSearchablesList = null;
+ private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
+ // Contains all installed activities that handle the global search
+ // intent.
+ private List<ResolveInfo> mGlobalSearchActivities;
+ private ComponentName mCurrentGlobalSearchActivity = null;
+ private ComponentName mWebSearchActivity = null;
+
+ public static String GOOGLE_SEARCH_COMPONENT_NAME =
+ "com.android.googlesearch/.GoogleSearch";
+ public static String ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME =
+ "com.google.android.providers.enhancedgooglesearch/.Launcher";
+
+ // Cache the package manager instance
+ final private IPackageManager mPm;
+ // User for which this Searchables caches information
+ private int mUserId;
+
+ /**
+ *
+ * @param context Context to use for looking up activities etc.
+ */
+ public Searchables (Context context, int userId) {
+ mContext = context;
+ mUserId = userId;
+ mPm = AppGlobals.getPackageManager();
+ }
+
+ /**
+ * Look up, or construct, based on the activity.
+ *
+ * The activities fall into three cases, based on meta-data found in
+ * the manifest entry:
+ * <ol>
+ * <li>The activity itself implements search. This is indicated by the
+ * presence of a "android.app.searchable" meta-data attribute.
+ * The value is a reference to an XML file containing search information.</li>
+ * <li>A related activity implements search. This is indicated by the
+ * presence of a "android.app.default_searchable" meta-data attribute.
+ * The value is a string naming the activity implementing search. In this
+ * case the factory will "redirect" and return the searchable data.</li>
+ * <li>No searchability data is provided. We return null here and other
+ * code will insert the "default" (e.g. contacts) search.
+ *
+ * TODO: cache the result in the map, and check the map first.
+ * TODO: it might make sense to implement the searchable reference as
+ * an application meta-data entry. This way we don't have to pepper each
+ * and every activity.
+ * TODO: can we skip the constructor step if it's a non-searchable?
+ * TODO: does it make sense to plug the default into a slot here for
+ * automatic return? Probably not, but it's one way to do it.
+ *
+ * @param activity The name of the current activity, or null if the
+ * activity does not define any explicit searchable metadata.
+ */
+ public SearchableInfo getSearchableInfo(ComponentName activity) {
+ // Step 1. Is the result already hashed? (case 1)
+ SearchableInfo result;
+ synchronized (this) {
+ result = mSearchablesMap.get(activity);
+ if (result != null) return result;
+ }
+
+ // Step 2. See if the current activity references a searchable.
+ // Note: Conceptually, this could be a while(true) loop, but there's
+ // no point in implementing reference chaining here and risking a loop.
+ // References must point directly to searchable activities.
+
+ ActivityInfo ai = null;
+ try {
+ ai = mPm.getActivityInfo(activity, PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error getting activity info " + re);
+ return null;
+ }
+ String refActivityName = null;
+
+ // First look for activity-specific reference
+ Bundle md = ai.metaData;
+ if (md != null) {
+ refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
+ }
+ // If not found, try for app-wide reference
+ if (refActivityName == null) {
+ md = ai.applicationInfo.metaData;
+ if (md != null) {
+ refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
+ }
+ }
+
+ // Irrespective of source, if a reference was found, follow it.
+ if (refActivityName != null)
+ {
+ // This value is deprecated, return null
+ if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
+ return null;
+ }
+ String pkg = activity.getPackageName();
+ ComponentName referredActivity;
+ if (refActivityName.charAt(0) == '.') {
+ referredActivity = new ComponentName(pkg, pkg + refActivityName);
+ } else {
+ referredActivity = new ComponentName(pkg, refActivityName);
+ }
+
+ // Now try the referred activity, and if found, cache
+ // it against the original name so we can skip the check
+ synchronized (this) {
+ result = mSearchablesMap.get(referredActivity);
+ if (result != null) {
+ mSearchablesMap.put(activity, result);
+ return result;
+ }
+ }
+ }
+
+ // Step 3. None found. Return null.
+ return null;
+
+ }
+
+ /**
+ * Builds an entire list (suitable for display) of
+ * activities that are searchable, by iterating the entire set of
+ * ACTION_SEARCH & ACTION_WEB_SEARCH intents.
+ *
+ * Also clears the hash of all activities -> searches which will
+ * refill as the user clicks "search".
+ *
+ * This should only be done at startup and again if we know that the
+ * list has changed.
+ *
+ * TODO: every activity that provides a ACTION_SEARCH intent should
+ * also provide searchability meta-data. There are a bunch of checks here
+ * that, if data is not found, silently skip to the next activity. This
+ * won't help a developer trying to figure out why their activity isn't
+ * showing up in the list, but an exception here is too rough. I would
+ * like to find a better notification mechanism.
+ *
+ * TODO: sort the list somehow? UI choice.
+ */
+ public void buildSearchableList() {
+ // These will become the new values at the end of the method
+ HashMap<ComponentName, SearchableInfo> newSearchablesMap
+ = new HashMap<ComponentName, SearchableInfo>();
+ ArrayList<SearchableInfo> newSearchablesList
+ = new ArrayList<SearchableInfo>();
+ ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
+ = new ArrayList<SearchableInfo>();
+
+ // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
+ List<ResolveInfo> searchList;
+ final Intent intent = new Intent(Intent.ACTION_SEARCH);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA);
+
+ List<ResolveInfo> webSearchInfoList;
+ final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
+ webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
+
+ // analyze each one, generate a Searchables record, and record
+ if (searchList != null || webSearchInfoList != null) {
+ int search_count = (searchList == null ? 0 : searchList.size());
+ int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size());
+ int count = search_count + web_search_count;
+ for (int ii = 0; ii < count; ii++) {
+ // for each component, try to find metadata
+ ResolveInfo info = (ii < search_count)
+ ? searchList.get(ii)
+ : webSearchInfoList.get(ii - search_count);
+ ActivityInfo ai = info.activityInfo;
+ // Check first to avoid duplicate entries.
+ if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
+ SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai,
+ mUserId);
+ if (searchable != null) {
+ newSearchablesList.add(searchable);
+ newSearchablesMap.put(searchable.getSearchActivity(), searchable);
+ if (searchable.shouldIncludeInGlobalSearch()) {
+ newSearchablesInGlobalSearchList.add(searchable);
+ }
+ }
+ }
+ }
+ }
+
+ List<ResolveInfo> newGlobalSearchActivities = findGlobalSearchActivities();
+
+ // Find the global search activity
+ ComponentName newGlobalSearchActivity = findGlobalSearchActivity(
+ newGlobalSearchActivities);
+
+ // Find the web search activity
+ ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);
+
+ // Store a consistent set of new values
+ synchronized (this) {
+ mSearchablesMap = newSearchablesMap;
+ mSearchablesList = newSearchablesList;
+ mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
+ mGlobalSearchActivities = newGlobalSearchActivities;
+ mCurrentGlobalSearchActivity = newGlobalSearchActivity;
+ mWebSearchActivity = newWebSearchActivity;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Returns a sorted list of installed search providers as per
+ * the following heuristics:
+ *
+ * (a) System apps are given priority over non system apps.
+ * (b) Among system apps and non system apps, the relative ordering
+ * is defined by their declared priority.
+ */
+ private List<ResolveInfo> findGlobalSearchActivities() {
+ // Step 1 : Query the package manager for a list
+ // of activities that can handle the GLOBAL_SEARCH intent.
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
+ List<ResolveInfo> activities =
+ queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (activities != null && !activities.isEmpty()) {
+ // Step 2: Rank matching activities according to our heuristics.
+ Collections.sort(activities, GLOBAL_SEARCH_RANKER);
+ }
+
+ return activities;
+ }
+
+ /**
+ * Finds the global search activity.
+ */
+ private ComponentName findGlobalSearchActivity(List<ResolveInfo> installed) {
+ // Fetch the global search provider from the system settings,
+ // and if it's still installed, return it.
+ final String searchProviderSetting = getGlobalSearchProviderSetting();
+ if (!TextUtils.isEmpty(searchProviderSetting)) {
+ final ComponentName globalSearchComponent = ComponentName.unflattenFromString(
+ searchProviderSetting);
+ if (globalSearchComponent != null && isInstalled(globalSearchComponent)) {
+ return globalSearchComponent;
+ }
+ }
+
+ return getDefaultGlobalSearchProvider(installed);
+ }
+
+ /**
+ * Checks whether the global search provider with a given
+ * component name is installed on the system or not. This deals with
+ * cases such as the removal of an installed provider.
+ */
+ private boolean isInstalled(ComponentName globalSearch) {
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
+ intent.setComponent(globalSearch);
+
+ List<ResolveInfo> activities = queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (activities != null && !activities.isEmpty()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static final Comparator<ResolveInfo> GLOBAL_SEARCH_RANKER =
+ new Comparator<ResolveInfo>() {
+ @Override
+ public int compare(ResolveInfo lhs, ResolveInfo rhs) {
+ if (lhs == rhs) {
+ return 0;
+ }
+ boolean lhsSystem = isSystemApp(lhs);
+ boolean rhsSystem = isSystemApp(rhs);
+
+ if (lhsSystem && !rhsSystem) {
+ return -1;
+ } else if (rhsSystem && !lhsSystem) {
+ return 1;
+ } else {
+ // Either both system engines, or both non system
+ // engines.
+ //
+ // Note, this isn't a typo. Higher priority numbers imply
+ // higher priority, but are "lower" in the sort order.
+ return rhs.priority - lhs.priority;
+ }
+ }
+ };
+
+ /**
+ * @return true iff. the resolve info corresponds to a system application.
+ */
+ private static final boolean isSystemApp(ResolveInfo res) {
+ return (res.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ /**
+ * Returns the highest ranked search provider as per the
+ * ranking defined in {@link #getGlobalSearchActivities()}.
+ */
+ private ComponentName getDefaultGlobalSearchProvider(List<ResolveInfo> providerList) {
+ if (providerList != null && !providerList.isEmpty()) {
+ ActivityInfo ai = providerList.get(0).activityInfo;
+ return new ComponentName(ai.packageName, ai.name);
+ }
+
+ Log.w(LOG_TAG, "No global search activity found");
+ return null;
+ }
+
+ private String getGlobalSearchProviderSetting() {
+ return Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY);
+ }
+
+ /**
+ * Finds the web search activity.
+ *
+ * Only looks in the package of the global search activity.
+ */
+ private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) {
+ if (globalSearchActivity == null) {
+ return null;
+ }
+ Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
+ intent.setPackage(globalSearchActivity.getPackageName());
+ List<ResolveInfo> activities =
+ queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+
+ if (activities != null && !activities.isEmpty()) {
+ ActivityInfo ai = activities.get(0).activityInfo;
+ // TODO: do some sanity checks here?
+ return new ComponentName(ai.packageName, ai.name);
+ }
+ Log.w(LOG_TAG, "No web search activity found");
+ return null;
+ }
+
+ private List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+ List<ResolveInfo> activities = null;
+ try {
+ activities =
+ mPm.queryIntentActivities(intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ flags, mUserId);
+ } catch (RemoteException re) {
+ // Local call
+ }
+ return activities;
+ }
+
+ /**
+ * Returns the list of searchable activities.
+ */
+ public synchronized ArrayList<SearchableInfo> getSearchablesList() {
+ ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
+ return result;
+ }
+
+ /**
+ * Returns a list of the searchable activities that can be included in global search.
+ */
+ public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() {
+ return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList);
+ }
+
+ /**
+ * Returns a list of activities that handle the global search intent.
+ */
+ public synchronized ArrayList<ResolveInfo> getGlobalSearchActivities() {
+ return new ArrayList<ResolveInfo>(mGlobalSearchActivities);
+ }
+
+ /**
+ * Gets the name of the global search activity.
+ */
+ public synchronized ComponentName getGlobalSearchActivity() {
+ return mCurrentGlobalSearchActivity;
+ }
+
+ /**
+ * Gets the name of the web search activity.
+ */
+ public synchronized ComponentName getWebSearchActivity() {
+ return mWebSearchActivity;
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Searchable authorities:");
+ synchronized (this) {
+ if (mSearchablesList != null) {
+ for (SearchableInfo info: mSearchablesList) {
+ pw.print(" "); pw.println(info.getSuggestAuthority());
+ }
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 4480151..9601e9a 100644
--- a/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -56,9 +56,9 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
private static final String UPDATE_CERTIFICATE_KEY = "config_update_certificate";
- private final File updateDir;
- private final File updateContent;
- private final File updateVersion;
+ protected final File updateDir;
+ protected final File updateContent;
+ protected final File updateVersion;
public ConfigUpdateInstallReceiver(String updateDir, String updateContentPath,
String updateMetadataPath, String updateVersionPath) {
@@ -77,7 +77,7 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
// get the certificate from Settings.Secure
X509Certificate cert = getCert(context.getContentResolver());
// get the content path from the extras
- String altContent = getAltContent(intent);
+ byte[] altContent = getAltContent(intent);
// get the version from the extras
int altVersion = getVersionFromIntent(intent);
// get the previous value from the extras
@@ -102,6 +102,7 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
Slog.i(TAG, "Found new update, installing...");
install(altContent, altVersion);
Slog.i(TAG, "Installation successful");
+ postInstall(context, intent);
}
} catch (Exception e) {
Slog.e(TAG, "Could not update content!", e);
@@ -172,28 +173,26 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
}
}
- private String getAltContent(Intent i) throws IOException {
- String contents = IoUtils.readFileAsString(getContentFromIntent(i));
- return contents.trim();
+ private byte[] getAltContent(Intent i) throws IOException {
+ return IoUtils.readFileAsByteArray(getContentFromIntent(i));
}
- private String getCurrentContent() {
+ private byte[] getCurrentContent() {
try {
- return IoUtils.readFileAsString(updateContent.getCanonicalPath()).trim();
+ return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath());
} catch (IOException e) {
Slog.i(TAG, "Failed to read current content, assuming first update!");
return null;
}
}
- private static String getCurrentHash(String content) {
+ private static String getCurrentHash(byte[] content) {
if (content == null) {
return "0";
}
try {
MessageDigest dgst = MessageDigest.getInstance("SHA512");
- byte[] encoded = content.getBytes();
- byte[] fingerprint = dgst.digest(encoded);
+ byte[] fingerprint = dgst.digest(content);
return IntegralToString.bytesToHexString(fingerprint, false);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
@@ -213,22 +212,20 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
return current.equals(required);
}
- private boolean verifySignature(String content, int version, String requiredPrevious,
+ private boolean verifySignature(byte[] content, int version, String requiredPrevious,
String signature, X509Certificate cert) throws Exception {
Signature signer = Signature.getInstance("SHA512withRSA");
signer.initVerify(cert);
- signer.update(content.getBytes());
+ signer.update(content);
signer.update(Long.toString(version).getBytes());
signer.update(requiredPrevious.getBytes());
return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT));
}
- private void writeUpdate(File dir, File file, String content) throws IOException {
+ protected void writeUpdate(File dir, File file, byte[] content) throws IOException {
FileOutputStream out = null;
File tmp = null;
try {
- // create the temporary file
- tmp = File.createTempFile("journal", "", dir);
// create the parents for the destination file
File parent = file.getParentFile();
parent.mkdirs();
@@ -236,11 +233,13 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
if (!parent.exists()) {
throw new IOException("Failed to create directory " + parent.getCanonicalPath());
}
+ // create the temporary file
+ tmp = File.createTempFile("journal", "", dir);
// mark tmp -rw-r--r--
tmp.setReadable(true, false);
// write to it
out = new FileOutputStream(tmp);
- out.write(content.getBytes());
+ out.write(content);
// sync to disk
out.getFD().sync();
// atomic rename
@@ -255,8 +254,11 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
}
}
- private void install(String content, int version) throws IOException {
+ protected void install(byte[] content, int version) throws IOException {
writeUpdate(updateDir, updateContent, content);
- writeUpdate(updateDir, updateVersion, Long.toString(version));
+ writeUpdate(updateDir, updateVersion, Long.toString(version).getBytes());
+ }
+
+ protected void postInstall(Context context, Intent intent) {
}
}
diff --git a/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java b/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java
new file mode 100644
index 0000000..9185903
--- /dev/null
+++ b/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 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.updates;
+
+import com.android.server.firewall.IntentFirewall;
+
+public class IntentFirewallInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ public IntentFirewallInstallReceiver() {
+ super(IntentFirewall.getRulesFile().getParent(), IntentFirewall.getRulesFile().getName(),
+ "metadata/", "version");
+ }
+}
diff --git a/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java b/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
new file mode 100644
index 0000000..5dd30f1
--- /dev/null
+++ b/services/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2013 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.updates;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.FileUtils;
+import android.os.SELinux;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.Base64;
+import android.util.Slog;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
+public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ private static final String TAG = "SELinuxPolicyInstallReceiver";
+
+ private static final String sepolicyPath = "sepolicy";
+ private static final String fileContextsPath = "file_contexts";
+ private static final String propertyContextsPath = "property_contexts";
+ private static final String seappContextsPath = "seapp_contexts";
+
+ public SELinuxPolicyInstallReceiver() {
+ super("/data/security/bundle", "sepolicy_bundle", "metadata/", "version");
+ }
+
+ private void backupContexts(File contexts) {
+ new File(contexts, seappContextsPath).renameTo(
+ new File(contexts, seappContextsPath + "_backup"));
+
+ new File(contexts, propertyContextsPath).renameTo(
+ new File(contexts, propertyContextsPath + "_backup"));
+
+ new File(contexts, fileContextsPath).renameTo(
+ new File(contexts, fileContextsPath + "_backup"));
+
+ new File(contexts, sepolicyPath).renameTo(
+ new File(contexts, sepolicyPath + "_backup"));
+ }
+
+ private void copyUpdate(File contexts) {
+ new File(updateDir, seappContextsPath).renameTo(new File(contexts, seappContextsPath));
+ new File(updateDir, propertyContextsPath).renameTo(new File(contexts, propertyContextsPath));
+ new File(updateDir, fileContextsPath).renameTo(new File(contexts, fileContextsPath));
+ new File(updateDir, sepolicyPath).renameTo(new File(contexts, sepolicyPath));
+ }
+
+ private int readInt(BufferedInputStream reader) throws IOException {
+ int value = 0;
+ for (int i=0; i < 4; i++) {
+ value = (value << 8) | reader.read();
+ }
+ return value;
+ }
+
+ private int[] readChunkLengths(BufferedInputStream bundle) throws IOException {
+ int[] chunks = new int[4];
+ chunks[0] = readInt(bundle);
+ chunks[1] = readInt(bundle);
+ chunks[2] = readInt(bundle);
+ chunks[3] = readInt(bundle);
+ return chunks;
+ }
+
+ private void installFile(File destination, BufferedInputStream stream, int length)
+ throws IOException {
+ byte[] chunk = new byte[length];
+ stream.read(chunk, 0, length);
+ writeUpdate(updateDir, destination, Base64.decode(chunk, Base64.DEFAULT));
+ }
+
+ private void unpackBundle() throws IOException {
+ BufferedInputStream stream = new BufferedInputStream(new FileInputStream(updateContent));
+ try {
+ int[] chunkLengths = readChunkLengths(stream);
+ installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[0]);
+ installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[1]);
+ installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[2]);
+ installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[3]);
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ }
+
+ private void applyUpdate() throws IOException, ErrnoException {
+ Slog.i(TAG, "Applying SELinux policy");
+ File contexts = new File(updateDir.getParentFile(), "contexts");
+ File current = new File(updateDir.getParentFile(), "current");
+ File update = new File(updateDir.getParentFile(), "update");
+ File tmp = new File(updateDir.getParentFile(), "tmp");
+ if (current.exists()) {
+ Libcore.os.symlink(updateDir.getPath(), update.getPath());
+ Libcore.os.rename(update.getPath(), current.getPath());
+ } else {
+ Libcore.os.symlink(updateDir.getPath(), current.getPath());
+ }
+ contexts.mkdirs();
+ backupContexts(contexts);
+ copyUpdate(contexts);
+ Libcore.os.symlink(contexts.getPath(), tmp.getPath());
+ Libcore.os.rename(tmp.getPath(), current.getPath());
+ SystemProperties.set("selinux.reload_policy", "1");
+ }
+
+ private void setEnforcingMode(Context context) {
+ String mode = Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.SELINUX_STATUS);
+ if ("1".equals(mode)) {
+ Slog.i(TAG, "Setting enforcing mode");
+ SystemProperties.set("persist.selinux.enforcing", mode);
+ } else if ("0".equals(mode)) {
+ Slog.i(TAG, "Tried to set permissive mode, ignoring");
+ } else {
+ Slog.e(TAG, "Got invalid enforcing mode: " + mode);
+ }
+ }
+
+ @Override
+ protected void postInstall(Context context, Intent intent) {
+ try {
+ unpackBundle();
+ applyUpdate();
+ setEnforcingMode(context);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "SELinux policy update malformed: ", e);
+ } catch (IOException e) {
+ Slog.e(TAG, "Could not update selinux policy: ", e);
+ } catch (ErrnoException e) {
+ Slog.e(TAG, "Could not update selinux policy: ", e);
+ }
+ }
+}
diff --git a/services/java/com/android/server/updates/TZInfoInstallReceiver.java b/services/java/com/android/server/updates/TZInfoInstallReceiver.java
new file mode 100644
index 0000000..83adbdb
--- /dev/null
+++ b/services/java/com/android/server/updates/TZInfoInstallReceiver.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.updates;
+
+import android.util.Base64;
+import android.util.Slog;
+
+import java.io.IOException;
+
+public class TZInfoInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ public TZInfoInstallReceiver() {
+ super("/data/misc/zoneinfo/", "tzdata", "metadata/", "version");
+ }
+
+ @Override
+ protected void install(byte[] encodedContent, int version) throws IOException {
+ super.install(Base64.decode(encodedContent, Base64.DEFAULT), version);
+ }
+}
diff --git a/services/java/com/android/server/usb/UsbDebuggingManager.java b/services/java/com/android/server/usb/UsbDebuggingManager.java
index 1bb3a2c..93d3114 100644
--- a/services/java/com/android/server/usb/UsbDebuggingManager.java
+++ b/services/java/com/android/server/usb/UsbDebuggingManager.java
@@ -151,6 +151,7 @@ public class UsbDebuggingManager implements Runnable {
private static final int MESSAGE_ADB_ALLOW = 3;
private static final int MESSAGE_ADB_DENY = 4;
private static final int MESSAGE_ADB_CONFIRM = 5;
+ private static final int MESSAGE_ADB_CLEAR = 6;
public UsbDebuggingHandler(Looper looper) {
super(looper);
@@ -214,6 +215,10 @@ public class UsbDebuggingManager implements Runnable {
showConfirmationDialog(key, mFingerprints);
break;
}
+
+ case MESSAGE_ADB_CLEAR:
+ deleteKeyFile();
+ break;
}
}
}
@@ -257,17 +262,25 @@ public class UsbDebuggingManager implements Runnable {
}
}
- private void writeKey(String key) {
+ private File getUserKeyFile() {
File dataDir = Environment.getDataDirectory();
File adbDir = new File(dataDir, ADB_DIRECTORY);
if (!adbDir.exists()) {
Slog.e(TAG, "ADB data directory does not exist");
- return;
+ return null;
}
+ return new File(adbDir, ADB_KEYS_FILE);
+ }
+
+ private void writeKey(String key) {
try {
- File keyFile = new File(adbDir, ADB_KEYS_FILE);
+ File keyFile = getUserKeyFile();
+
+ if (keyFile == null) {
+ return;
+ }
if (!keyFile.exists()) {
keyFile.createNewFile();
@@ -286,6 +299,12 @@ public class UsbDebuggingManager implements Runnable {
}
}
+ private void deleteKeyFile() {
+ File keyFile = getUserKeyFile();
+ if (keyFile != null) {
+ keyFile.delete();
+ }
+ }
public void setAdbEnabled(boolean enabled) {
mHandler.sendEmptyMessage(enabled ? UsbDebuggingHandler.MESSAGE_ADB_ENABLED
@@ -303,6 +322,9 @@ public class UsbDebuggingManager implements Runnable {
mHandler.sendEmptyMessage(UsbDebuggingHandler.MESSAGE_ADB_DENY);
}
+ public void clearUsbDebuggingKeys() {
+ mHandler.sendEmptyMessage(UsbDebuggingHandler.MESSAGE_ADB_CLEAR);
+ }
public void dump(FileDescriptor fd, PrintWriter pw) {
pw.println(" USB Debugging State:");
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index c7c2c62..87aa8cce 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -169,7 +169,9 @@ public class UsbDeviceManager {
startAccessoryMode();
}
- if ("1".equals(SystemProperties.get("ro.adb.secure"))) {
+ boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false);
+ boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt"));
+ if (secureAdbEnabled && !dataEncrypted) {
mDebuggingManager = new UsbDebuggingManager(context);
}
}
@@ -862,6 +864,15 @@ public class UsbDeviceManager {
}
}
+ public void clearUsbDebuggingKeys() {
+ if (mDebuggingManager != null) {
+ mDebuggingManager.clearUsbDebuggingKeys();
+ } else {
+ throw new RuntimeException("Cannot clear Usb Debugging keys, "
+ + "UsbDebuggingManager not enabled");
+ }
+ }
+
public void dump(FileDescriptor fd, PrintWriter pw) {
if (mHandler != null) {
mHandler.dump(fd, pw);
diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java
index 3918d15..36669b1 100644
--- a/services/java/com/android/server/usb/UsbService.java
+++ b/services/java/com/android/server/usb/UsbService.java
@@ -255,6 +255,12 @@ public class UsbService extends IUsbManager.Stub {
}
@Override
+ public void clearUsbDebuggingKeys() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ mDeviceManager.clearUsbDebuggingKeys();
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
diff --git a/services/java/com/android/server/usb/UsbSettingsManager.java b/services/java/com/android/server/usb/UsbSettingsManager.java
index 4b2bbfe..9b5b312 100644
--- a/services/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/java/com/android/server/usb/UsbSettingsManager.java
@@ -34,6 +34,7 @@ import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Binder;
import android.os.Environment;
+import android.os.Process;
import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.Log;
@@ -364,8 +365,9 @@ class UsbSettingsManager {
}
@Override
- public void onPackageChanged(String packageName, int uid, String[] components) {
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
handlePackageUpdate(packageName);
+ return false;
}
@Override
@@ -852,21 +854,29 @@ class UsbSettingsManager {
public boolean hasPermission(UsbDevice device) {
synchronized (mLock) {
+ int uid = Binder.getCallingUid();
+ if (uid == Process.SYSTEM_UID) {
+ return true;
+ }
SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
if (uidList == null) {
return false;
}
- return uidList.get(Binder.getCallingUid());
+ return uidList.get(uid);
}
}
public boolean hasPermission(UsbAccessory accessory) {
synchronized (mLock) {
+ int uid = Binder.getCallingUid();
+ if (uid == Process.SYSTEM_UID) {
+ return true;
+ }
SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
if (uidList == null) {
return false;
}
- return uidList.get(Binder.getCallingUid());
+ return uidList.get(uid);
}
}
diff --git a/services/java/com/android/server/wifi/README.txt b/services/java/com/android/server/wifi/README.txt
new file mode 100644
index 0000000..39e1475
--- /dev/null
+++ b/services/java/com/android/server/wifi/README.txt
@@ -0,0 +1,19 @@
+WifiService: Implements the IWifiManager 3rd party API. The API and the device state information (screen on/off, battery state, sleep policy) go as input into the WifiController which tracks high level states as to whether STA or AP mode is operational and controls the WifiStateMachine to handle bringup and shut down.
+
+WifiController: Acts as a controller to the WifiStateMachine based on various inputs (API and device state). Runs on the same thread created in WifiService.
+
+WifiSettingsStore: Tracks the various settings (wifi toggle, airplane toggle, tethering toggle, scan mode toggle) and provides API to figure if wifi should be turned on or off.
+
+WifiTrafficPoller: Polls traffic on wifi and notifies apps listening on it.
+
+WifiNotificationController: Controls whether the open network notification is displayed or not based on the scan results.
+
+WifiStateMachine: Tracks the various states on STA and AP connectivity and handles bring up and shut down.
+
+
+Feature description:
+
+Scan-only mode with Wi-Fi turned off:
+ - Setup wizard opts user into allowing scanning for improved location. We show no further dialogs in setup wizard since the user has just opted into the feature. This is the reason WifiService listens to DEVICE_PROVISIONED setting.
+ - Once the user has his device provisioned, turning off Wi-Fi from settings or from a third party app will show up a dialog reminding the user that scan mode will be on even though Wi-Fi is being turned off. The user has the choice to turn this notification off.
+ - In the scan mode, the device continues to allow scanning from any app with Wi-Fi turned off. This is done by disabling all networks and allowing only scans to be passed.
diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java
new file mode 100644
index 0000000..87b4394
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiController.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2013 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.wifi;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
+import android.net.wifi.WifiStateMachine;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.wifi.WifiService.LockList;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+class WifiController extends StateMachine {
+ private static final String TAG = "WifiController";
+ private static final boolean DBG = false;
+ private Context mContext;
+ private boolean mScreenOff;
+ private boolean mDeviceIdle;
+ private int mPluggedType;
+ private int mStayAwakeConditions;
+ private long mIdleMillis;
+ private int mSleepPolicy;
+
+ private AlarmManager mAlarmManager;
+ private PendingIntent mIdleIntent;
+ private static final int IDLE_REQUEST = 0;
+
+ /**
+ * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
+ * Settings.Global value is not present. This timeout value is chosen as
+ * the approximate point at which the battery drain caused by Wi-Fi
+ * being enabled but not active exceeds the battery drain caused by
+ * re-establishing a connection to the mobile data network.
+ */
+ private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
+
+ /**
+ * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}. This is the default value if a
+ * Settings.Global value is not present. This is the minimum time after wifi is disabled
+ * we'll act on an enable. Enable requests received before this delay will be deferred.
+ */
+ private static final long DEFAULT_REENABLE_DELAY_MS = 500;
+
+ // finding that delayed messages can sometimes be delivered earlier than expected
+ // probably rounding errors.. add a margin to prevent problems
+ private static final long DEFER_MARGIN_MS = 5;
+
+ NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
+
+ private static final String ACTION_DEVICE_IDLE =
+ "com.android.server.WifiManager.action.DEVICE_IDLE";
+
+ /* References to values tracked in WifiService */
+ final WifiStateMachine mWifiStateMachine;
+ final WifiSettingsStore mSettingsStore;
+ final LockList mLocks;
+
+ /**
+ * Temporary for computing UIDS that are responsible for starting WIFI.
+ * Protected by mWifiStateTracker lock.
+ */
+ private final WorkSource mTmpWorkSource = new WorkSource();
+
+ private long mReEnableDelayMillis;
+
+ private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
+
+ static final int CMD_EMERGENCY_MODE_CHANGED = BASE + 1;
+ static final int CMD_SCREEN_ON = BASE + 2;
+ static final int CMD_SCREEN_OFF = BASE + 3;
+ static final int CMD_BATTERY_CHANGED = BASE + 4;
+ static final int CMD_DEVICE_IDLE = BASE + 5;
+ static final int CMD_LOCKS_CHANGED = BASE + 6;
+ static final int CMD_SCAN_ALWAYS_MODE_CHANGED = BASE + 7;
+ static final int CMD_WIFI_TOGGLED = BASE + 8;
+ static final int CMD_AIRPLANE_TOGGLED = BASE + 9;
+ static final int CMD_SET_AP = BASE + 10;
+ static final int CMD_DEFERRED_TOGGLE = BASE + 11;
+
+ private DefaultState mDefaultState = new DefaultState();
+ private StaEnabledState mStaEnabledState = new StaEnabledState();
+ private ApStaDisabledState mApStaDisabledState = new ApStaDisabledState();
+ private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
+ private ApEnabledState mApEnabledState = new ApEnabledState();
+ private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
+ private DeviceInactiveState mDeviceInactiveState = new DeviceInactiveState();
+ private ScanOnlyLockHeldState mScanOnlyLockHeldState = new ScanOnlyLockHeldState();
+ private FullLockHeldState mFullLockHeldState = new FullLockHeldState();
+ private FullHighPerfLockHeldState mFullHighPerfLockHeldState = new FullHighPerfLockHeldState();
+ private NoLockHeldState mNoLockHeldState = new NoLockHeldState();
+ private EcmState mEcmState = new EcmState();
+
+ WifiController(Context context, WifiService service, Looper looper) {
+ super(TAG, looper);
+ mContext = context;
+ mWifiStateMachine = service.mWifiStateMachine;
+ mSettingsStore = service.mSettingsStore;
+ mLocks = service.mLocks;
+
+ mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
+ mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
+
+ addState(mDefaultState);
+ addState(mApStaDisabledState, mDefaultState);
+ addState(mStaEnabledState, mDefaultState);
+ addState(mDeviceActiveState, mStaEnabledState);
+ addState(mDeviceInactiveState, mStaEnabledState);
+ addState(mScanOnlyLockHeldState, mDeviceInactiveState);
+ addState(mFullLockHeldState, mDeviceInactiveState);
+ addState(mFullHighPerfLockHeldState, mDeviceInactiveState);
+ addState(mNoLockHeldState, mDeviceInactiveState);
+ addState(mStaDisabledWithScanState, mDefaultState);
+ addState(mApEnabledState, mDefaultState);
+ addState(mEcmState, mDefaultState);
+ if (mSettingsStore.isScanAlwaysAvailable()) {
+ setInitialState(mStaDisabledWithScanState);
+ } else {
+ setInitialState(mApStaDisabledState);
+ }
+ setLogRecSize(100);
+ setLogOnlyTransitions(false);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DEVICE_IDLE);
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ACTION_DEVICE_IDLE)) {
+ sendMessage(CMD_DEVICE_IDLE);
+ } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ }
+ }
+ },
+ new IntentFilter(filter));
+
+ initializeAndRegisterForSettingsChange(looper);
+ }
+
+ private void initializeAndRegisterForSettingsChange(Looper looper) {
+ Handler handler = new Handler(looper);
+ readStayAwakeConditions();
+ registerForStayAwakeModeChange(handler);
+ readWifiIdleTime();
+ registerForWifiIdleTimeChange(handler);
+ readWifiSleepPolicy();
+ registerForWifiSleepPolicyChange(handler);
+ readWifiReEnableDelay();
+ }
+
+ private void readStayAwakeConditions() {
+ mStayAwakeConditions = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+ }
+
+ private void readWifiIdleTime() {
+ mIdleMillis = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
+ }
+
+ private void readWifiSleepPolicy() {
+ mSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_SLEEP_POLICY,
+ Settings.Global.WIFI_SLEEP_POLICY_NEVER);
+ }
+
+ private void readWifiReEnableDelay() {
+ mReEnableDelayMillis = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS);
+ }
+
+ /**
+ * Observes settings changes to scan always mode.
+ */
+ private void registerForStayAwakeModeChange(Handler handler) {
+ ContentObserver contentObserver = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ readStayAwakeConditions();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
+ false, contentObserver);
+ }
+
+ /**
+ * Observes settings changes to scan always mode.
+ */
+ private void registerForWifiIdleTimeChange(Handler handler) {
+ ContentObserver contentObserver = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ readWifiIdleTime();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS),
+ false, contentObserver);
+ }
+
+ /**
+ * Observes changes to wifi sleep policy
+ */
+ private void registerForWifiSleepPolicyChange(Handler handler) {
+ ContentObserver contentObserver = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ readWifiSleepPolicy();
+ }
+ };
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY),
+ false, contentObserver);
+ }
+
+ /**
+ * Determines whether the Wi-Fi chipset should stay awake or be put to
+ * sleep. Looks at the setting for the sleep policy and the current
+ * conditions.
+ *
+ * @see #shouldDeviceStayAwake(int)
+ */
+ private boolean shouldWifiStayAwake(int pluggedType) {
+ if (mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
+ // Never sleep
+ return true;
+ } else if ((mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
+ (pluggedType != 0)) {
+ // Never sleep while plugged, and we're plugged
+ return true;
+ } else {
+ // Default
+ return shouldDeviceStayAwake(pluggedType);
+ }
+ }
+
+ /**
+ * Determine whether the bit value corresponding to {@code pluggedType} is set in
+ * the bit string mStayAwakeConditions. This determines whether the device should
+ * stay awake based on the current plugged type.
+ *
+ * @param pluggedType the type of plug (USB, AC, or none) for which the check is
+ * being made
+ * @return {@code true} if {@code pluggedType} indicates that the device is
+ * supposed to stay awake, {@code false} otherwise.
+ */
+ private boolean shouldDeviceStayAwake(int pluggedType) {
+ return (mStayAwakeConditions & pluggedType) != 0;
+ }
+
+ private void updateBatteryWorkSource() {
+ mTmpWorkSource.clear();
+ if (mDeviceIdle) {
+ mLocks.updateWorkSource(mTmpWorkSource);
+ }
+ mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
+ }
+
+ class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_SCREEN_ON:
+ mAlarmManager.cancel(mIdleIntent);
+ mScreenOff = false;
+ mDeviceIdle = false;
+ updateBatteryWorkSource();
+ break;
+ case CMD_SCREEN_OFF:
+ mScreenOff = true;
+ /*
+ * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+ * AND the "stay on while plugged in" setting doesn't match the
+ * current power conditions (i.e, not plugged in, plugged in to USB,
+ * or plugged in to AC).
+ */
+ if (!shouldWifiStayAwake(mPluggedType)) {
+ //Delayed shutdown if wifi is connected
+ if (mNetworkInfo.getDetailedState() ==
+ NetworkInfo.DetailedState.CONNECTED) {
+ if (DBG) Slog.d(TAG, "set idle timer: " + mIdleMillis + " ms");
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + mIdleMillis, mIdleIntent);
+ } else {
+ sendMessage(CMD_DEVICE_IDLE);
+ }
+ }
+ break;
+ case CMD_DEVICE_IDLE:
+ mDeviceIdle = true;
+ updateBatteryWorkSource();
+ break;
+ case CMD_BATTERY_CHANGED:
+ /*
+ * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+ * AND we are transitioning from a state in which the device was supposed
+ * to stay awake to a state in which it is not supposed to stay awake.
+ * If "stay awake" state is not changing, we do nothing, to avoid resetting
+ * the already-set timer.
+ */
+ int pluggedType = msg.arg1;
+ if (DBG) Slog.d(TAG, "battery changed pluggedType: " + pluggedType);
+ if (mScreenOff && shouldWifiStayAwake(mPluggedType) &&
+ !shouldWifiStayAwake(pluggedType)) {
+ long triggerTime = System.currentTimeMillis() + mIdleMillis;
+ if (DBG) Slog.d(TAG, "set idle timer for " + mIdleMillis + "ms");
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
+ }
+
+ mPluggedType = pluggedType;
+ break;
+ case CMD_SET_AP:
+ case CMD_SCAN_ALWAYS_MODE_CHANGED:
+ case CMD_LOCKS_CHANGED:
+ case CMD_WIFI_TOGGLED:
+ case CMD_AIRPLANE_TOGGLED:
+ case CMD_EMERGENCY_MODE_CHANGED:
+ break;
+ case CMD_DEFERRED_TOGGLE:
+ log("DEFERRED_TOGGLE ignored due to state change");
+ break;
+ default:
+ throw new RuntimeException("WifiController.handleMessage " + msg.what);
+ }
+ return HANDLED;
+ }
+
+ }
+
+ class ApStaDisabledState extends State {
+ private int mDeferredEnableSerialNumber = 0;
+ private boolean mHaveDeferredEnable = false;
+ private long mDisabledTimestamp;
+
+ @Override
+ public void enter() {
+ mWifiStateMachine.setSupplicantRunning(false);
+ // Supplicant can't restart right away, so not the time we switched off
+ mDisabledTimestamp = SystemClock.elapsedRealtime();
+ mDeferredEnableSerialNumber++;
+ mHaveDeferredEnable = false;
+ }
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_WIFI_TOGGLED:
+ case CMD_AIRPLANE_TOGGLED:
+ if (mSettingsStore.isWifiToggleEnabled()) {
+ if (doDeferEnable(msg)) {
+ if (mHaveDeferredEnable) {
+ // have 2 toggles now, inc serial number an ignore both
+ mDeferredEnableSerialNumber++;
+ }
+ mHaveDeferredEnable = !mHaveDeferredEnable;
+ break;
+ }
+ if (mDeviceIdle == false) {
+ transitionTo(mDeviceActiveState);
+ } else {
+ checkLocksAndTransitionWhenDeviceIdle();
+ }
+ }
+ break;
+ case CMD_SCAN_ALWAYS_MODE_CHANGED:
+ if (mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mStaDisabledWithScanState);
+ }
+ break;
+ case CMD_SET_AP:
+ if (msg.arg1 == 1) {
+ mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
+ true);
+ transitionTo(mApEnabledState);
+ }
+ break;
+ case CMD_DEFERRED_TOGGLE:
+ if (msg.arg1 != mDeferredEnableSerialNumber) {
+ log("DEFERRED_TOGGLE ignored due to serial mismatch");
+ break;
+ }
+ log("DEFERRED_TOGGLE handled");
+ sendMessage((Message)(msg.obj));
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ private boolean doDeferEnable(Message msg) {
+ long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
+ if (delaySoFar >= mReEnableDelayMillis) {
+ return false;
+ }
+
+ log("WifiController msg " + msg + " deferred for " +
+ (mReEnableDelayMillis - delaySoFar) + "ms");
+
+ // need to defer this action.
+ Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
+ deferredMsg.obj = Message.obtain(msg);
+ deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
+ sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
+ return true;
+ }
+
+ }
+
+ class StaEnabledState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setSupplicantRunning(true);
+ }
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_WIFI_TOGGLED:
+ if (! mSettingsStore.isWifiToggleEnabled()) {
+ if (mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mStaDisabledWithScanState);
+ } else {
+ transitionTo(mApStaDisabledState);
+ }
+ }
+ break;
+ case CMD_AIRPLANE_TOGGLED:
+ /* When wi-fi is turned off due to airplane,
+ * disable entirely (including scan)
+ */
+ if (! mSettingsStore.isWifiToggleEnabled()) {
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ case CMD_EMERGENCY_MODE_CHANGED:
+ if (msg.arg1 == 1) {
+ transitionTo(mEcmState);
+ break;
+ }
+ default:
+ return NOT_HANDLED;
+
+ }
+ return HANDLED;
+ }
+ }
+
+ class StaDisabledWithScanState extends State {
+ private int mDeferredEnableSerialNumber = 0;
+ private boolean mHaveDeferredEnable = false;
+ private long mDisabledTimestamp;
+
+ @Override
+ public void enter() {
+ mWifiStateMachine.setSupplicantRunning(true);
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ // Supplicant can't restart right away, so not the time we switched off
+ mDisabledTimestamp = SystemClock.elapsedRealtime();
+ mDeferredEnableSerialNumber++;
+ mHaveDeferredEnable = false;
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_WIFI_TOGGLED:
+ if (mSettingsStore.isWifiToggleEnabled()) {
+ if (doDeferEnable(msg)) {
+ if (mHaveDeferredEnable) {
+ // have 2 toggles now, inc serial number and ignore both
+ mDeferredEnableSerialNumber++;
+ }
+ mHaveDeferredEnable = !mHaveDeferredEnable;
+ break;
+ }
+ if (mDeviceIdle == false) {
+ transitionTo(mDeviceActiveState);
+ } else {
+ checkLocksAndTransitionWhenDeviceIdle();
+ }
+ }
+ break;
+ case CMD_AIRPLANE_TOGGLED:
+ if (mSettingsStore.isAirplaneModeOn() &&
+ ! mSettingsStore.isWifiToggleEnabled()) {
+ transitionTo(mApStaDisabledState);
+ }
+ case CMD_SCAN_ALWAYS_MODE_CHANGED:
+ if (! mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ case CMD_SET_AP:
+ // Before starting tethering, turn off supplicant for scan mode
+ if (msg.arg1 == 1) {
+ deferMessage(msg);
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ case CMD_DEFERRED_TOGGLE:
+ if (msg.arg1 != mDeferredEnableSerialNumber) {
+ log("DEFERRED_TOGGLE ignored due to serial mismatch");
+ break;
+ }
+ logd("DEFERRED_TOGGLE handled");
+ sendMessage((Message)(msg.obj));
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ private boolean doDeferEnable(Message msg) {
+ long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
+ if (delaySoFar >= mReEnableDelayMillis) {
+ return false;
+ }
+
+ log("WifiController msg " + msg + " deferred for " +
+ (mReEnableDelayMillis - delaySoFar) + "ms");
+
+ // need to defer this action.
+ Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
+ deferredMsg.obj = Message.obtain(msg);
+ deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
+ sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
+ return true;
+ }
+
+ }
+
+ class ApEnabledState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_AIRPLANE_TOGGLED:
+ if (mSettingsStore.isAirplaneModeOn()) {
+ mWifiStateMachine.setHostApRunning(null, false);
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ case CMD_SET_AP:
+ if (msg.arg1 == 0) {
+ mWifiStateMachine.setHostApRunning(null, false);
+ transitionTo(mApStaDisabledState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class EcmState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setSupplicantRunning(false);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what == CMD_EMERGENCY_MODE_CHANGED && msg.arg1 == 0) {
+ if (mSettingsStore.isWifiToggleEnabled()) {
+ if (mDeviceIdle == false) {
+ transitionTo(mDeviceActiveState);
+ } else {
+ checkLocksAndTransitionWhenDeviceIdle();
+ }
+ } else if (mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mStaDisabledWithScanState);
+ } else {
+ transitionTo(mApStaDisabledState);
+ }
+ return HANDLED;
+ } else {
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ /* Parent: StaEnabledState */
+ class DeviceActiveState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.setHighPerfModeEnabled(false);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what == CMD_DEVICE_IDLE) {
+ checkLocksAndTransitionWhenDeviceIdle();
+ // We let default state handle the rest of work
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /* Parent: StaEnabledState */
+ class DeviceInactiveState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_LOCKS_CHANGED:
+ checkLocksAndTransitionWhenDeviceIdle();
+ updateBatteryWorkSource();
+ return HANDLED;
+ case CMD_SCREEN_ON:
+ transitionTo(mDeviceActiveState);
+ // More work in default state
+ return NOT_HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a scan only lock. */
+ class ScanOnlyLockHeldState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ }
+ }
+
+ /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a full lock. */
+ class FullLockHeldState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.setHighPerfModeEnabled(false);
+ }
+ }
+
+ /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a high perf lock. */
+ class FullHighPerfLockHeldState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.setHighPerfModeEnabled(true);
+ }
+ }
+
+ /* Parent: DeviceInactiveState. Device is inactive and no app is holding a wifi lock. */
+ class NoLockHeldState extends State {
+ @Override
+ public void enter() {
+ mWifiStateMachine.setDriverStart(false);
+ }
+ }
+
+ private void checkLocksAndTransitionWhenDeviceIdle() {
+ if (mLocks.hasLocks()) {
+ switch (mLocks.getStrongestLockMode()) {
+ case WIFI_MODE_FULL:
+ transitionTo(mFullLockHeldState);
+ break;
+ case WIFI_MODE_FULL_HIGH_PERF:
+ transitionTo(mFullHighPerfLockHeldState);
+ break;
+ case WIFI_MODE_SCAN_ONLY:
+ transitionTo(mScanOnlyLockHeldState);
+ break;
+ default:
+ loge("Illegal lock " + mLocks.getStrongestLockMode());
+ }
+ } else {
+ if (mSettingsStore.isScanAlwaysAvailable()) {
+ transitionTo(mScanOnlyLockHeldState);
+ } else {
+ transitionTo(mNoLockHeldState);
+ }
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+
+ pw.println("mScreenOff " + mScreenOff);
+ pw.println("mDeviceIdle " + mDeviceIdle);
+ pw.println("mPluggedType " + mPluggedType);
+ pw.println("mIdleMillis " + mIdleMillis);
+ pw.println("mSleepPolicy " + mSleepPolicy);
+ }
+}
diff --git a/services/java/com/android/server/wifi/WifiNotificationController.java b/services/java/com/android/server/wifi/WifiNotificationController.java
new file mode 100644
index 0000000..a9206e0
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiNotificationController.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2013 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.wifi;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.TaskStackBuilder;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.NetworkInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiStateMachine;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/* Takes care of handling the "open wi-fi network available" notification @hide */
+final class WifiNotificationController {
+ /**
+ * The icon to show in the 'available networks' notification. This will also
+ * be the ID of the Notification given to the NotificationManager.
+ */
+ private static final int ICON_NETWORKS_AVAILABLE =
+ com.android.internal.R.drawable.stat_notify_wifi_in_range;
+ /**
+ * When a notification is shown, we wait this amount before possibly showing it again.
+ */
+ private final long NOTIFICATION_REPEAT_DELAY_MS;
+ /**
+ * Whether the user has set the setting to show the 'available networks' notification.
+ */
+ private boolean mNotificationEnabled;
+ /**
+ * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
+ */
+ private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
+ /**
+ * The {@link System#currentTimeMillis()} must be at least this value for us
+ * to show the notification again.
+ */
+ private long mNotificationRepeatTime;
+ /**
+ * The Notification object given to the NotificationManager.
+ */
+ private Notification mNotification;
+ /**
+ * Whether the notification is being shown, as set by us. That is, if the
+ * user cancels the notification, we will not receive the callback so this
+ * will still be true. We only guarantee if this is false, then the
+ * notification is not showing.
+ */
+ private boolean mNotificationShown;
+ /**
+ * The number of continuous scans that must occur before consider the
+ * supplicant in a scanning state. This allows supplicant to associate with
+ * remembered networks that are in the scan results.
+ */
+ private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
+ /**
+ * The number of scans since the last network state change. When this
+ * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
+ * supplicant to actually be scanning. When the network state changes to
+ * something other than scanning, we reset this to 0.
+ */
+ private int mNumScansSinceNetworkStateChange;
+
+ private final Context mContext;
+ private final WifiStateMachine mWifiStateMachine;
+ private NetworkInfo mNetworkInfo;
+ private volatile int mWifiState;
+
+ WifiNotificationController(Context context, WifiStateMachine wsm) {
+ mContext = context;
+ mWifiStateMachine = wsm;
+ mWifiState = WifiManager.WIFI_STATE_UNKNOWN;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN);
+ resetNotification();
+ } else if (intent.getAction().equals(
+ WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ // reset & clear notification on a network connect & disconnect
+ switch(mNetworkInfo.getDetailedState()) {
+ case CONNECTED:
+ case DISCONNECTED:
+ case CAPTIVE_PORTAL_CHECK:
+ resetNotification();
+ break;
+ }
+ } else if (intent.getAction().equals(
+ WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ checkAndSetNotification(mNetworkInfo,
+ mWifiStateMachine.syncGetScanResultsList());
+ }
+ }
+ }, filter);
+
+ // Setting is in seconds
+ NOTIFICATION_REPEAT_DELAY_MS = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
+ mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
+ mNotificationEnabledSettingObserver.register();
+ }
+
+ private synchronized void checkAndSetNotification(NetworkInfo networkInfo,
+ List<ScanResult> scanResults) {
+ // TODO: unregister broadcast so we do not have to check here
+ // If we shouldn't place a notification on available networks, then
+ // don't bother doing any of the following
+ if (!mNotificationEnabled) return;
+ if (networkInfo == null) return;
+ if (mWifiState != WifiManager.WIFI_STATE_ENABLED) return;
+
+ NetworkInfo.State state = networkInfo.getState();
+ if ((state == NetworkInfo.State.DISCONNECTED)
+ || (state == NetworkInfo.State.UNKNOWN)) {
+ if (scanResults != null) {
+ int numOpenNetworks = 0;
+ for (int i = scanResults.size() - 1; i >= 0; i--) {
+ ScanResult scanResult = scanResults.get(i);
+
+ //A capability of [ESS] represents an open access point
+ //that is available for an STA to connect
+ if (scanResult.capabilities != null &&
+ scanResult.capabilities.equals("[ESS]")) {
+ numOpenNetworks++;
+ }
+ }
+
+ if (numOpenNetworks > 0) {
+ if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
+ /*
+ * We've scanned continuously at least
+ * NUM_SCANS_BEFORE_NOTIFICATION times. The user
+ * probably does not have a remembered network in range,
+ * since otherwise supplicant would have tried to
+ * associate and thus resetting this counter.
+ */
+ setNotificationVisible(true, numOpenNetworks, false, 0);
+ }
+ return;
+ }
+ }
+ }
+
+ // No open networks in range, remove the notification
+ setNotificationVisible(false, 0, false, 0);
+ }
+
+ /**
+ * Clears variables related to tracking whether a notification has been
+ * shown recently and clears the current notification.
+ */
+ private synchronized void resetNotification() {
+ mNotificationRepeatTime = 0;
+ mNumScansSinceNetworkStateChange = 0;
+ setNotificationVisible(false, 0, false, 0);
+ }
+
+ /**
+ * Display or don't display a notification that there are open Wi-Fi networks.
+ * @param visible {@code true} if notification should be visible, {@code false} otherwise
+ * @param numNetworks the number networks seen
+ * @param force {@code true} to force notification to be shown/not-shown,
+ * even if it is already shown/not-shown.
+ * @param delay time in milliseconds after which the notification should be made
+ * visible or invisible.
+ */
+ private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
+ int delay) {
+
+ // Since we use auto cancel on the notification, when the
+ // mNetworksAvailableNotificationShown is true, the notification may
+ // have actually been canceled. However, when it is false we know
+ // for sure that it is not being shown (it will not be shown any other
+ // place than here)
+
+ // If it should be hidden and it is already hidden, then noop
+ if (!visible && !mNotificationShown && !force) {
+ return;
+ }
+
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ Message message;
+ if (visible) {
+
+ // Not enough time has passed to show the notification again
+ if (System.currentTimeMillis() < mNotificationRepeatTime) {
+ return;
+ }
+
+ if (mNotification == null) {
+ // Cache the Notification object.
+ mNotification = new Notification();
+ mNotification.when = 0;
+ mNotification.icon = ICON_NETWORKS_AVAILABLE;
+ mNotification.flags = Notification.FLAG_AUTO_CANCEL;
+ mNotification.contentIntent = TaskStackBuilder.create(mContext)
+ .addNextIntentWithParentStack(
+ new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK))
+ .getPendingIntent(0, 0, null, UserHandle.CURRENT);
+ }
+
+ CharSequence title = mContext.getResources().getQuantityText(
+ com.android.internal.R.plurals.wifi_available, numNetworks);
+ CharSequence details = mContext.getResources().getQuantityText(
+ com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
+ mNotification.tickerText = title;
+ mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
+
+ mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
+
+ notificationManager.notifyAsUser(null, ICON_NETWORKS_AVAILABLE, mNotification,
+ UserHandle.ALL);
+ } else {
+ notificationManager.cancelAsUser(null, ICON_NETWORKS_AVAILABLE, UserHandle.ALL);
+ }
+
+ mNotificationShown = visible;
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mNotificationEnabled " + mNotificationEnabled);
+ pw.println("mNotificationRepeatTime " + mNotificationRepeatTime);
+ pw.println("mNotificationShown " + mNotificationShown);
+ pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange);
+ }
+
+ private class NotificationEnabledSettingObserver extends ContentObserver {
+ public NotificationEnabledSettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void register() {
+ ContentResolver cr = mContext.getContentResolver();
+ cr.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
+ synchronized (WifiNotificationController.this) {
+ mNotificationEnabled = getValue();
+ }
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+
+ synchronized (WifiNotificationController.this) {
+ mNotificationEnabled = getValue();
+ resetNotification();
+ }
+ }
+
+ private boolean getValue() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+ }
+ }
+
+}
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index dfcc72b..a70978e 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -14,17 +14,12 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.wifi;
import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.TaskStackBuilder;
+import android.app.AppOpsManager;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,21 +27,16 @@ import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.wifi.IWifiManager;
import android.net.wifi.ScanResult;
-import android.net.wifi.SupplicantState;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiStateMachine;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiWatchdogStateMachine;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WpsInfo;
-import android.net.wifi.WpsResult;
-import android.net.ConnectivityManager;
import android.net.DhcpInfo;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.State;
-import android.net.NetworkInfo.DetailedState;
-import android.net.TrafficStats;
+import android.net.DhcpResults;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.Messenger;
@@ -55,57 +45,50 @@ import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Message;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
-import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.Inet4Address;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
+import com.android.internal.R;
import com.android.internal.app.IBatteryStats;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.AsyncChannel;
import com.android.server.am.BatteryStatsService;
-import com.android.internal.R;
-
+import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
+import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_LOCKS_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_ON;
+import static com.android.server.wifi.WifiController.CMD_SET_AP;
+import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
/**
* WifiService handles remote WiFi operation requests by implementing
* the IWifiManager interface.
*
* @hide
*/
-//TODO: Clean up multiple locks and implement WifiService
-// as a SM to track soft AP/client/adhoc bring up based
-// on device idle state, airplane mode and boot.
-
-public class WifiService extends IWifiManager.Stub {
+public final class WifiService extends IWifiManager.Stub {
private static final String TAG = "WifiService";
private static final boolean DBG = false;
- private final WifiStateMachine mWifiStateMachine;
+ final WifiStateMachine mWifiStateMachine;
- private Context mContext;
+ private final Context mContext;
- private AlarmManager mAlarmManager;
- private PendingIntent mIdleIntent;
- private static final int IDLE_REQUEST = 0;
- private boolean mScreenOff;
- private boolean mDeviceIdle;
- private boolean mEmergencyCallbackMode = false;
- private int mPluggedType;
-
- private final LockList mLocks = new LockList();
+ final LockList mLocks = new LockList();
// some wifi lock statistics
private int mFullHighPerfLocksAcquired;
private int mFullHighPerfLocksReleased;
@@ -120,104 +103,16 @@ public class WifiService extends IWifiManager.Stub {
private int mMulticastDisabled;
private final IBatteryStats mBatteryStats;
+ private final AppOpsManager mAppOps;
- private boolean mEnableTrafficStatsPoll = false;
- private int mTrafficStatsPollToken = 0;
- private long mTxPkts;
- private long mRxPkts;
- /* Tracks last reported data activity */
- private int mDataActivity;
private String mInterfaceName;
- /**
- * Interval in milliseconds between polling for traffic
- * statistics
- */
- private static final int POLL_TRAFFIC_STATS_INTERVAL_MSECS = 1000;
-
- /**
- * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
- * Settings.Global value is not present. This timeout value is chosen as
- * the approximate point at which the battery drain caused by Wi-Fi
- * being enabled but not active exceeds the battery drain caused by
- * re-establishing a connection to the mobile data network.
- */
- private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
-
- private static final String ACTION_DEVICE_IDLE =
- "com.android.server.WifiManager.action.DEVICE_IDLE";
-
- private static final int WIFI_DISABLED = 0;
- private static final int WIFI_ENABLED = 1;
- /* Wifi enabled while in airplane mode */
- private static final int WIFI_ENABLED_AIRPLANE_OVERRIDE = 2;
- /* Wifi disabled due to airplane mode on */
- private static final int WIFI_DISABLED_AIRPLANE_ON = 3;
-
- /* Persisted state that tracks the wifi & airplane interaction from settings */
- private AtomicInteger mPersistWifiState = new AtomicInteger(WIFI_DISABLED);
- /* Tracks current airplane mode state */
- private AtomicBoolean mAirplaneModeOn = new AtomicBoolean(false);
- /* Tracks whether wifi is enabled from WifiStateMachine's perspective */
- private boolean mWifiEnabled;
-
- /* The work source (UID) that triggered the current WIFI scan, synchronized
- * on this */
- private WorkSource mScanWorkSource;
-
- private boolean mIsReceiverRegistered = false;
-
-
- NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
-
- // Variables relating to the 'available networks' notification
- /**
- * The icon to show in the 'available networks' notification. This will also
- * be the ID of the Notification given to the NotificationManager.
- */
- private static final int ICON_NETWORKS_AVAILABLE =
- com.android.internal.R.drawable.stat_notify_wifi_in_range;
- /**
- * When a notification is shown, we wait this amount before possibly showing it again.
- */
- private final long NOTIFICATION_REPEAT_DELAY_MS;
- /**
- * Whether the user has set the setting to show the 'available networks' notification.
- */
- private boolean mNotificationEnabled;
- /**
- * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
- */
- private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
- /**
- * The {@link System#currentTimeMillis()} must be at least this value for us
- * to show the notification again.
- */
- private long mNotificationRepeatTime;
- /**
- * The Notification object given to the NotificationManager.
- */
- private Notification mNotification;
- /**
- * Whether the notification is being shown, as set by us. That is, if the
- * user cancels the notification, we will not receive the callback so this
- * will still be true. We only guarantee if this is false, then the
- * notification is not showing.
- */
- private boolean mNotificationShown;
- /**
- * The number of continuous scans that must occur before consider the
- * supplicant in a scanning state. This allows supplicant to associate with
- * remembered networks that are in the scan results.
- */
- private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
- /**
- * The number of scans since the last network state change. When this
- * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
- * supplicant to actually be scanning. When the network state changes to
- * something other than scanning, we reset this to 0.
- */
- private int mNumScansSinceNetworkStateChange;
+ /* Tracks the open wi-fi network notification */
+ private WifiNotificationController mNotificationController;
+ /* Polls traffic stats and notifies clients */
+ private WifiTrafficPoller mTrafficPoller;
+ /* Tracks the persisted states for wi-fi & airplane mode */
+ final WifiSettingsStore mSettingsStore;
/**
* Asynchronous channel to WifiStateMachine
@@ -225,16 +120,11 @@ public class WifiService extends IWifiManager.Stub {
private AsyncChannel mWifiStateMachineChannel;
/**
- * Clients receiving asynchronous messages
- */
- private List<AsyncChannel> mClients = new ArrayList<AsyncChannel>();
-
- /**
* Handles client connections
*/
- private class AsyncServiceHandler extends Handler {
+ private class ClientHandler extends Handler {
- AsyncServiceHandler(android.os.Looper looper) {
+ ClientHandler(android.os.Looper looper) {
super(looper);
}
@@ -244,7 +134,9 @@ public class WifiService extends IWifiManager.Stub {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
- mClients.add((AsyncChannel) msg.obj);
+ // We track the clients by the Messenger
+ // since it is expected to be always available
+ mTrafficPoller.addClient(msg.replyTo);
} else {
Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
}
@@ -256,7 +148,7 @@ public class WifiService extends IWifiManager.Stub {
} else {
if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
}
- mClients.remove((AsyncChannel) msg.obj);
+ mTrafficPoller.removeClient(msg.replyTo);
break;
}
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
@@ -264,60 +156,25 @@ public class WifiService extends IWifiManager.Stub {
ac.connect(mContext, this, msg.replyTo);
break;
}
- case WifiManager.ENABLE_TRAFFIC_STATS_POLL: {
- mEnableTrafficStatsPoll = (msg.arg1 == 1);
- mTrafficStatsPollToken++;
- if (mEnableTrafficStatsPoll) {
- notifyOnDataActivity();
- sendMessageDelayed(Message.obtain(this, WifiManager.TRAFFIC_STATS_POLL,
- mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
- }
- break;
- }
- case WifiManager.TRAFFIC_STATS_POLL: {
- if (msg.arg1 == mTrafficStatsPollToken) {
- notifyOnDataActivity();
- sendMessageDelayed(Message.obtain(this, WifiManager.TRAFFIC_STATS_POLL,
- mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
- }
- break;
- }
- case WifiManager.CONNECT_NETWORK: {
- mWifiStateMachine.sendMessage(Message.obtain(msg));
- break;
- }
- case WifiManager.SAVE_NETWORK: {
- mWifiStateMachine.sendMessage(Message.obtain(msg));
- break;
- }
- case WifiManager.FORGET_NETWORK: {
- mWifiStateMachine.sendMessage(Message.obtain(msg));
- break;
- }
- case WifiManager.START_WPS: {
- mWifiStateMachine.sendMessage(Message.obtain(msg));
- break;
- }
- case WifiManager.CANCEL_WPS: {
- mWifiStateMachine.sendMessage(Message.obtain(msg));
- break;
- }
- case WifiManager.DISABLE_NETWORK: {
- mWifiStateMachine.sendMessage(Message.obtain(msg));
- break;
- }
+ /* Client commands are forwarded to state machine */
+ case WifiManager.CONNECT_NETWORK:
+ case WifiManager.SAVE_NETWORK:
+ case WifiManager.FORGET_NETWORK:
+ case WifiManager.START_WPS:
+ case WifiManager.CANCEL_WPS:
+ case WifiManager.DISABLE_NETWORK:
case WifiManager.RSSI_PKTCNT_FETCH: {
mWifiStateMachine.sendMessage(Message.obtain(msg));
break;
}
default: {
- Slog.d(TAG, "WifiServicehandler.handleMessage ignoring msg=" + msg);
+ Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
break;
}
}
}
}
- private AsyncServiceHandler mAsyncServiceHandler;
+ private ClientHandler mClientHandler;
/**
* Handles interaction with WifiStateMachine
@@ -359,14 +216,9 @@ public class WifiService extends IWifiManager.Stub {
}
WifiStateMachineHandler mWifiStateMachineHandler;
- /**
- * Temporary for computing UIDS that are responsible for starting WIFI.
- * Protected by mWifiStateTracker lock.
- */
- private final WorkSource mTmpWorkSource = new WorkSource();
private WifiWatchdogStateMachine mWifiWatchdogStateMachine;
- WifiService(Context context) {
+ public WifiService(Context context) {
mContext = context;
mInterfaceName = SystemProperties.get("wifi.interface", "wlan0");
@@ -374,109 +226,38 @@ public class WifiService extends IWifiManager.Stub {
mWifiStateMachine = new WifiStateMachine(mContext, mInterfaceName);
mWifiStateMachine.enableRssiPolling(true);
mBatteryStats = BatteryStatsService.getService();
+ mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
- mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
- Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
- mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
+ mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine);
+ mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
+ mSettingsStore = new WifiSettingsStore(mContext);
- mContext.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mAirplaneModeOn.set(isAirplaneModeOn());
- handleAirplaneModeToggled(mAirplaneModeOn.get());
- updateWifiState();
- }
- },
- new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ HandlerThread wifiThread = new HandlerThread("WifiService");
+ wifiThread.start();
+ mClientHandler = new ClientHandler(wifiThread.getLooper());
+ mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
+ mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
+ mWifiController.start();
+ registerForScanModeChange();
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
- WifiManager.WIFI_STATE_DISABLED);
-
- mWifiEnabled = (wifiState == WifiManager.WIFI_STATE_ENABLED);
-
- // reset & clear notification on any wifi state change
- resetNotification();
- } else if (intent.getAction().equals(
- WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
- WifiManager.EXTRA_NETWORK_INFO);
- // reset & clear notification on a network connect & disconnect
- switch(mNetworkInfo.getDetailedState()) {
- case CONNECTED:
- case DISCONNECTED:
- case CAPTIVE_PORTAL_CHECK:
- evaluateTrafficStatsPolling();
- resetNotification();
- break;
- }
- } else if (intent.getAction().equals(
- WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
- noteScanEnd();
- checkAndSetNotification();
+ if (mSettingsStore.handleAirplaneModeToggled()) {
+ mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED);
}
}
- }, filter);
-
- HandlerThread wifiThread = new HandlerThread("WifiService");
- wifiThread.start();
- mAsyncServiceHandler = new AsyncServiceHandler(wifiThread.getLooper());
- mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
-
- // Setting is in seconds
- NOTIFICATION_REPEAT_DELAY_MS = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
- mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
- mNotificationEnabledSettingObserver.register();
- }
-
- /** Tell battery stats about a new WIFI scan */
- private void noteScanStart() {
- WorkSource scanWorkSource = null;
- synchronized (WifiService.this) {
- if (mScanWorkSource != null) {
- // Scan already in progress, don't add this one to battery stats
- return;
- }
- scanWorkSource = new WorkSource(Binder.getCallingUid());
- mScanWorkSource = scanWorkSource;
- }
+ },
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
- long id = Binder.clearCallingIdentity();
- try {
- mBatteryStats.noteWifiScanStartedFromSource(scanWorkSource);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- } finally {
- Binder.restoreCallingIdentity(id);
- }
+ // Adding optimizations of only receiving broadcasts when wifi is enabled
+ // can result in race conditions when apps toggle wifi in the background
+ // without active user involvement. Always receive broadcasts.
+ registerForBroadcasts();
}
- /** Tell battery stats that the current WIFI scan has completed */
- private void noteScanEnd() {
- WorkSource scanWorkSource = null;
- synchronized (WifiService.this) {
- scanWorkSource = mScanWorkSource;
- mScanWorkSource = null;
- }
- if (scanWorkSource != null) {
- try {
- mBatteryStats.noteWifiScanStoppedFromSource(scanWorkSource);
- } catch (RemoteException e) {
- Log.w(TAG, e);
- }
- }
- }
+ private WifiController mWifiController;
/**
* Check if Wi-Fi needs to be enabled and start
@@ -485,10 +266,8 @@ public class WifiService extends IWifiManager.Stub {
* This function is used only at boot time
*/
public void checkAndStartWifi() {
- mAirplaneModeOn.set(isAirplaneModeOn());
- mPersistWifiState.set(getPersistedWifiState());
- /* Start if Wi-Fi should be enabled or the saved state indicates Wi-Fi was on */
- boolean wifiEnabled = shouldWifiBeEnabled() || testAndClearWifiSavedState();
+ /* Check if wi-fi needs to be enabled */
+ boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
Slog.i(TAG, "WifiService starting up with Wi-Fi " +
(wifiEnabled ? "enabled" : "disabled"));
@@ -501,75 +280,6 @@ public class WifiService extends IWifiManager.Stub {
}
- private boolean testAndClearWifiSavedState() {
- final ContentResolver cr = mContext.getContentResolver();
- int wifiSavedState = 0;
- try {
- wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE);
- if(wifiSavedState == 1)
- Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
- } catch (Settings.SettingNotFoundException e) {
- ;
- }
- return (wifiSavedState == 1);
- }
-
- private int getPersistedWifiState() {
- final ContentResolver cr = mContext.getContentResolver();
- try {
- return Settings.Global.getInt(cr, Settings.Global.WIFI_ON);
- } catch (Settings.SettingNotFoundException e) {
- Settings.Global.putInt(cr, Settings.Global.WIFI_ON, WIFI_DISABLED);
- return WIFI_DISABLED;
- }
- }
-
- private boolean shouldWifiBeEnabled() {
- if (mAirplaneModeOn.get()) {
- return mPersistWifiState.get() == WIFI_ENABLED_AIRPLANE_OVERRIDE;
- } else {
- return mPersistWifiState.get() != WIFI_DISABLED;
- }
- }
-
- private void handleWifiToggled(boolean wifiEnabled) {
- boolean airplaneEnabled = mAirplaneModeOn.get() && isAirplaneToggleable();
- if (wifiEnabled) {
- if (airplaneEnabled) {
- persistWifiState(WIFI_ENABLED_AIRPLANE_OVERRIDE);
- } else {
- persistWifiState(WIFI_ENABLED);
- }
- } else {
- // When wifi state is disabled, we do not care
- // if airplane mode is on or not. The scenario of
- // wifi being disabled due to airplane mode being turned on
- // is handled handleAirplaneModeToggled()
- persistWifiState(WIFI_DISABLED);
- }
- }
-
- private void handleAirplaneModeToggled(boolean airplaneEnabled) {
- if (airplaneEnabled) {
- // Wifi disabled due to airplane on
- if (mWifiEnabled) {
- persistWifiState(WIFI_DISABLED_AIRPLANE_ON);
- }
- } else {
- /* On airplane mode disable, restore wifi state if necessary */
- if (testAndClearWifiSavedState() ||
- mPersistWifiState.get() == WIFI_ENABLED_AIRPLANE_OVERRIDE) {
- persistWifiState(WIFI_ENABLED);
- }
- }
- }
-
- private void persistWifiState(int state) {
- final ContentResolver cr = mContext.getContentResolver();
- mPersistWifiState.set(state);
- Settings.Global.putInt(cr, Settings.Global.WIFI_ON, state);
- }
-
/**
* see {@link android.net.wifi.WifiManager#pingSupplicant()}
* @return {@code true} if the operation succeeds, {@code false} otherwise
@@ -587,10 +297,9 @@ public class WifiService extends IWifiManager.Stub {
/**
* see {@link android.net.wifi.WifiManager#startScan()}
*/
- public void startScan(boolean forceActive) {
+ public void startScan() {
enforceChangePermission();
- mWifiStateMachine.startScan(forceActive);
- noteScanStart();
+ mWifiStateMachine.startScan(Binder.getCallingUid());
}
private void enforceAccessPermission() {
@@ -630,33 +339,22 @@ public class WifiService extends IWifiManager.Stub {
Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
}
- if (enable) {
- reportStartWorkSource();
- }
- mWifiStateMachine.setWifiEnabled(enable);
-
/*
- * Caller might not have WRITE_SECURE_SETTINGS,
- * only CHANGE_WIFI_STATE is enforced
- */
+ * Caller might not have WRITE_SECURE_SETTINGS,
+ * only CHANGE_WIFI_STATE is enforced
+ */
long ident = Binder.clearCallingIdentity();
try {
- handleWifiToggled(enable);
+ if (! mSettingsStore.handleWifiToggled(enable)) {
+ // Nothing to do if wifi cannot be toggled
+ return true;
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
- if (enable) {
- if (!mIsReceiverRegistered) {
- registerForBroadcasts();
- mIsReceiverRegistered = true;
- }
- } else if (mIsReceiverRegistered) {
- mContext.unregisterReceiver(mReceiver);
- mIsReceiverRegistered = false;
- }
-
+ mWifiController.sendMessage(CMD_WIFI_TOGGLED);
return true;
}
@@ -681,7 +379,7 @@ public class WifiService extends IWifiManager.Stub {
*/
public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
enforceChangePermission();
- mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled);
+ mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
}
/**
@@ -718,6 +416,16 @@ public class WifiService extends IWifiManager.Stub {
}
/**
+ * @param enable {@code true} to enable, {@code false} to disable.
+ * @return {@code true} if the enable/disable operation was
+ * started or is already in the queue.
+ */
+ public boolean isScanAlwaysAvailable() {
+ enforceAccessPermission();
+ return mSettingsStore.isScanAlwaysAvailable();
+ }
+
+ /**
* see {@link android.net.wifi.WifiManager#disconnect()}
*/
public void disconnect() {
@@ -838,10 +546,15 @@ public class WifiService extends IWifiManager.Stub {
* a list of {@link ScanResult} objects.
* @return the list of results
*/
- public List<ScanResult> getScanResults() {
+ public List<ScanResult> getScanResults(String callingPackage) {
enforceAccessPermission();
int userId = UserHandle.getCallingUserId();
+ int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
+ if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+ != AppOpsManager.MODE_ALLOWED) {
+ return new ArrayList<ScanResult>();
+ }
try {
int currentUser = ActivityManager.getCurrentUser();
if (userId != currentUser) {
@@ -884,7 +597,12 @@ public class WifiService extends IWifiManager.Stub {
Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
" with persist set to " + persist);
enforceChangePermission();
- mWifiStateMachine.setCountryCode(countryCode, persist);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mWifiStateMachine.setCountryCode(countryCode, persist);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
/**
@@ -901,7 +619,12 @@ public class WifiService extends IWifiManager.Stub {
if (!isDualBandSupported()) return;
Slog.i(TAG, "WifiService trying to set frequency band to " + band +
" with persist set to " + persist);
- mWifiStateMachine.setFrequencyBand(band, persist);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mWifiStateMachine.setFrequencyBand(band, persist);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@@ -923,10 +646,53 @@ public class WifiService extends IWifiManager.Stub {
* Return the DHCP-assigned addresses from the last successful DHCP request,
* if any.
* @return the DHCP information
+ * @deprecated
*/
public DhcpInfo getDhcpInfo() {
enforceAccessPermission();
- return mWifiStateMachine.syncGetDhcpInfo();
+ DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
+ if (dhcpResults.linkProperties == null) return null;
+
+ DhcpInfo info = new DhcpInfo();
+ for (LinkAddress la : dhcpResults.linkProperties.getLinkAddresses()) {
+ InetAddress addr = la.getAddress();
+ if (addr instanceof Inet4Address) {
+ info.ipAddress = NetworkUtils.inetAddressToInt((Inet4Address)addr);
+ break;
+ }
+ }
+ for (RouteInfo r : dhcpResults.linkProperties.getRoutes()) {
+ if (r.isDefaultRoute()) {
+ InetAddress gateway = r.getGateway();
+ if (gateway instanceof Inet4Address) {
+ info.gateway = NetworkUtils.inetAddressToInt((Inet4Address)gateway);
+ }
+ } else if (r.hasGateway() == false) {
+ LinkAddress dest = r.getDestination();
+ if (dest.getAddress() instanceof Inet4Address) {
+ info.netmask = NetworkUtils.prefixLengthToNetmaskInt(
+ dest.getNetworkPrefixLength());
+ }
+ }
+ }
+ int dnsFound = 0;
+ for (InetAddress dns : dhcpResults.linkProperties.getDnses()) {
+ if (dns instanceof Inet4Address) {
+ if (dnsFound == 0) {
+ info.dns1 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
+ } else {
+ info.dns2 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
+ }
+ if (++dnsFound > 1) break;
+ }
+ }
+ InetAddress serverAddress = dhcpResults.serverAddress;
+ if (serverAddress instanceof Inet4Address) {
+ info.serverAddress = NetworkUtils.inetAddressToInt((Inet4Address)serverAddress);
+ }
+ info.leaseDuration = dhcpResults.leaseDuration;
+
+ return info;
}
/**
@@ -940,7 +706,7 @@ public class WifiService extends IWifiManager.Stub {
* of WifiLock & device idle status unless wifi enabled status is toggled
*/
- mWifiStateMachine.setDriverStart(true, mEmergencyCallbackMode);
+ mWifiStateMachine.setDriverStart(true);
mWifiStateMachine.reconnectCommand();
}
@@ -959,7 +725,7 @@ public class WifiService extends IWifiManager.Stub {
* TODO: if a stop is issued, wifi is brought up only by startWifi
* unless wifi enabled status is toggled
*/
- mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode);
+ mWifiStateMachine.setDriverStart(false);
}
/**
@@ -989,7 +755,7 @@ public class WifiService extends IWifiManager.Stub {
public Messenger getWifiServiceMessenger() {
enforceAccessPermission();
enforceChangePermission();
- return new Messenger(mAsyncServiceHandler);
+ return new Messenger(mClientHandler);
}
/** Get a reference to WifiStateMachine handler for AsyncChannel communication */
@@ -1011,177 +777,39 @@ public class WifiService extends IWifiManager.Stub {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
-
- long idleMillis =
- Settings.Global.getLong(mContext.getContentResolver(),
- Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
- int stayAwakeConditions =
- Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
if (action.equals(Intent.ACTION_SCREEN_ON)) {
- if (DBG) {
- Slog.d(TAG, "ACTION_SCREEN_ON");
- }
- mAlarmManager.cancel(mIdleIntent);
- mScreenOff = false;
- evaluateTrafficStatsPolling();
- setDeviceIdleAndUpdateWifi(false);
+ mWifiController.sendMessage(CMD_SCREEN_ON);
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
- if (DBG) {
- Slog.d(TAG, "ACTION_SCREEN_OFF");
- }
- mScreenOff = true;
- evaluateTrafficStatsPolling();
- /*
- * Set a timer to put Wi-Fi to sleep, but only if the screen is off
- * AND the "stay on while plugged in" setting doesn't match the
- * current power conditions (i.e, not plugged in, plugged in to USB,
- * or plugged in to AC).
- */
- if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) {
- //Delayed shutdown if wifi is connected
- if (mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) {
- if (DBG) Slog.d(TAG, "setting ACTION_DEVICE_IDLE: " + idleMillis + " ms");
- mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
- + idleMillis, mIdleIntent);
- } else {
- setDeviceIdleAndUpdateWifi(true);
- }
- }
- } else if (action.equals(ACTION_DEVICE_IDLE)) {
- setDeviceIdleAndUpdateWifi(true);
+ mWifiController.sendMessage(CMD_SCREEN_OFF);
} else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
- /*
- * Set a timer to put Wi-Fi to sleep, but only if the screen is off
- * AND we are transitioning from a state in which the device was supposed
- * to stay awake to a state in which it is not supposed to stay awake.
- * If "stay awake" state is not changing, we do nothing, to avoid resetting
- * the already-set timer.
- */
int pluggedType = intent.getIntExtra("plugged", 0);
- if (DBG) {
- Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType);
- }
- if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) &&
- !shouldWifiStayAwake(stayAwakeConditions, pluggedType)) {
- long triggerTime = System.currentTimeMillis() + idleMillis;
- if (DBG) {
- Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms");
- }
- mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
- }
-
- mPluggedType = pluggedType;
+ mWifiController.sendMessage(CMD_BATTERY_CHANGED, pluggedType, 0, null);
} else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
BluetoothAdapter.STATE_DISCONNECTED);
mWifiStateMachine.sendBluetoothAdapterStateChange(state);
} else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
- mEmergencyCallbackMode = intent.getBooleanExtra("phoneinECMState", false);
- updateWifiState();
- }
- }
-
- /**
- * Determines whether the Wi-Fi chipset should stay awake or be put to
- * sleep. Looks at the setting for the sleep policy and the current
- * conditions.
- *
- * @see #shouldDeviceStayAwake(int, int)
- */
- private boolean shouldWifiStayAwake(int stayAwakeConditions, int pluggedType) {
- //Never sleep as long as the user has not changed the settings
- int wifiSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.WIFI_SLEEP_POLICY,
- Settings.Global.WIFI_SLEEP_POLICY_NEVER);
-
- if (wifiSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
- // Never sleep
- return true;
- } else if ((wifiSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
- (pluggedType != 0)) {
- // Never sleep while plugged, and we're plugged
- return true;
- } else {
- // Default
- return shouldDeviceStayAwake(stayAwakeConditions, pluggedType);
+ boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false);
+ mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0);
}
}
-
- /**
- * Determine whether the bit value corresponding to {@code pluggedType} is set in
- * the bit string {@code stayAwakeConditions}. Because a {@code pluggedType} value
- * of {@code 0} isn't really a plugged type, but rather an indication that the
- * device isn't plugged in at all, there is no bit value corresponding to a
- * {@code pluggedType} value of {@code 0}. That is why we shift by
- * {@code pluggedType - 1} instead of by {@code pluggedType}.
- * @param stayAwakeConditions a bit string specifying which "plugged types" should
- * keep the device (and hence Wi-Fi) awake.
- * @param pluggedType the type of plug (USB, AC, or none) for which the check is
- * being made
- * @return {@code true} if {@code pluggedType} indicates that the device is
- * supposed to stay awake, {@code false} otherwise.
- */
- private boolean shouldDeviceStayAwake(int stayAwakeConditions, int pluggedType) {
- return (stayAwakeConditions & pluggedType) != 0;
- }
};
- private void setDeviceIdleAndUpdateWifi(boolean deviceIdle) {
- mDeviceIdle = deviceIdle;
- reportStartWorkSource();
- updateWifiState();
- }
-
- private synchronized void reportStartWorkSource() {
- mTmpWorkSource.clear();
- if (mDeviceIdle) {
- for (int i=0; i<mLocks.mList.size(); i++) {
- mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource);
+ /**
+ * Observes settings changes to scan always mode.
+ */
+ private void registerForScanModeChange() {
+ ContentObserver contentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mSettingsStore.handleWifiScanAlwaysAvailableToggled();
+ mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
}
- }
- mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
- }
-
- private void updateWifiState() {
- boolean lockHeld = mLocks.hasLocks();
- int strongestLockMode = WifiManager.WIFI_MODE_FULL;
- boolean wifiShouldBeStarted;
-
- if (mEmergencyCallbackMode) {
- wifiShouldBeStarted = false;
- } else {
- wifiShouldBeStarted = !mDeviceIdle || lockHeld;
- }
-
- if (lockHeld) {
- strongestLockMode = mLocks.getStrongestLockMode();
- }
- /* If device is not idle, lockmode cannot be scan only */
- if (!mDeviceIdle && strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY) {
- strongestLockMode = WifiManager.WIFI_MODE_FULL;
- }
-
- /* Disable tethering when airplane mode is enabled */
- if (mAirplaneModeOn.get()) {
- mWifiStateMachine.setWifiApEnabled(null, false);
- }
+ };
- if (shouldWifiBeEnabled()) {
- if (wifiShouldBeStarted) {
- reportStartWorkSource();
- mWifiStateMachine.setWifiEnabled(true);
- mWifiStateMachine.setScanOnlyMode(
- strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
- mWifiStateMachine.setDriverStart(true, mEmergencyCallbackMode);
- mWifiStateMachine.setHighPerfModeEnabled(strongestLockMode
- == WifiManager.WIFI_MODE_FULL_HIGH_PERF);
- } else {
- mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode);
- }
- } else {
- mWifiStateMachine.setWifiEnabled(false);
- }
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
+ false, contentObserver);
}
private void registerForBroadcasts() {
@@ -1189,36 +817,12 @@ public class WifiService extends IWifiManager.Stub {
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
- intentFilter.addAction(ACTION_DEVICE_IDLE);
+ intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
mContext.registerReceiver(mReceiver, intentFilter);
}
- private boolean isAirplaneSensitive() {
- String airplaneModeRadios = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_RADIOS);
- return airplaneModeRadios == null
- || airplaneModeRadios.contains(Settings.Global.RADIO_WIFI);
- }
-
- private boolean isAirplaneToggleable() {
- String toggleableRadios = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
- return toggleableRadios != null
- && toggleableRadios.contains(Settings.Global.RADIO_WIFI);
- }
-
- /**
- * Returns true if Wi-Fi is sensitive to airplane mode, and airplane mode is
- * currently on.
- * @return {@code true} if airplane mode is on.
- */
- private boolean isAirplaneModeOn() {
- return isAirplaneSensitive() && Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -1232,11 +836,13 @@ public class WifiService extends IWifiManager.Stub {
pw.println("Stay-awake conditions: " +
Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
- pw.println();
+ pw.println("mMulticastEnabled " + mMulticastEnabled);
+ pw.println("mMulticastDisabled " + mMulticastDisabled);
+ mWifiController.dump(fd, pw, args);
+ mSettingsStore.dump(fd, pw, args);
+ mNotificationController.dump(fd, pw, args);
+ mTrafficPoller.dump(fd, pw, args);
- pw.println("Internal state:");
- pw.println(mWifiStateMachine);
- pw.println();
pw.println("Latest scan results:");
List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
if (scanResults != null && scanResults.size() != 0) {
@@ -1261,11 +867,10 @@ public class WifiService extends IWifiManager.Stub {
pw.println("Locks held:");
mLocks.dump(pw);
+ mWifiWatchdogStateMachine.dump(fd, pw, args);
pw.println();
- pw.println("WifiWatchdogStateMachine dump");
- mWifiWatchdogStateMachine.dump(pw);
- pw.println("WifiStateMachine dump");
mWifiStateMachine.dump(fd, pw, args);
+ pw.println();
}
private class WifiLock extends DeathRecipient {
@@ -1284,18 +889,18 @@ public class WifiService extends IWifiManager.Stub {
}
}
- private class LockList {
+ class LockList {
private List<WifiLock> mList;
private LockList() {
mList = new ArrayList<WifiLock>();
}
- private synchronized boolean hasLocks() {
+ synchronized boolean hasLocks() {
return !mList.isEmpty();
}
- private synchronized int getStrongestLockMode() {
+ synchronized int getStrongestLockMode() {
if (mList.isEmpty()) {
return WifiManager.WIFI_MODE_FULL;
}
@@ -1311,6 +916,12 @@ public class WifiService extends IWifiManager.Stub {
return WifiManager.WIFI_MODE_SCAN_ONLY;
}
+ synchronized void updateWorkSource(WorkSource ws) {
+ for (int i = 0; i < mLocks.mList.size(); i++) {
+ ws.add(mLocks.mList.get(i).mWorkSource);
+ }
+ }
+
private void addLock(WifiLock lock) {
if (findLockByBinder(lock.mBinder) < 0) {
mList.add(lock);
@@ -1330,9 +941,10 @@ public class WifiService extends IWifiManager.Stub {
private int findLockByBinder(IBinder binder) {
int size = mList.size();
- for (int i = size - 1; i >= 0; i--)
+ for (int i = size - 1; i >= 0; i--) {
if (mList.get(i).mBinder == binder)
return i;
+ }
return -1;
}
@@ -1416,12 +1028,7 @@ public class WifiService extends IWifiManager.Stub {
++mScanLocksAcquired;
break;
}
-
- // Be aggressive about adding new locks into the accounted state...
- // we want to over-report rather than under-report.
- reportStartWorkSource();
-
- updateWifiState();
+ mWifiController.sendMessage(CMD_LOCKS_CHANGED);
return true;
} catch (RemoteException e) {
return false;
@@ -1488,11 +1095,8 @@ public class WifiService extends IWifiManager.Stub {
++mScanLocksReleased;
break;
}
+ mWifiController.sendMessage(CMD_LOCKS_CHANGED);
}
-
- // TODO - should this only happen if you hadLock?
- updateWifiState();
-
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1629,194 +1233,4 @@ public class WifiService extends IWifiManager.Stub {
return (mMulticasters.size() > 0);
}
}
-
- /**
- * Evaluate if traffic stats polling is needed based on
- * connection and screen on status
- */
- private void evaluateTrafficStatsPolling() {
- Message msg;
- if (mNetworkInfo.getDetailedState() == DetailedState.CONNECTED && !mScreenOff) {
- msg = Message.obtain(mAsyncServiceHandler,
- WifiManager.ENABLE_TRAFFIC_STATS_POLL, 1, 0);
- } else {
- msg = Message.obtain(mAsyncServiceHandler,
- WifiManager.ENABLE_TRAFFIC_STATS_POLL, 0, 0);
- }
- msg.sendToTarget();
- }
-
- private void notifyOnDataActivity() {
- long sent, received;
- long preTxPkts = mTxPkts, preRxPkts = mRxPkts;
- int dataActivity = WifiManager.DATA_ACTIVITY_NONE;
-
- mTxPkts = TrafficStats.getTxPackets(mInterfaceName);
- mRxPkts = TrafficStats.getRxPackets(mInterfaceName);
-
- if (preTxPkts > 0 || preRxPkts > 0) {
- sent = mTxPkts - preTxPkts;
- received = mRxPkts - preRxPkts;
- if (sent > 0) {
- dataActivity |= WifiManager.DATA_ACTIVITY_OUT;
- }
- if (received > 0) {
- dataActivity |= WifiManager.DATA_ACTIVITY_IN;
- }
-
- if (dataActivity != mDataActivity && !mScreenOff) {
- mDataActivity = dataActivity;
- for (AsyncChannel client : mClients) {
- client.sendMessage(WifiManager.DATA_ACTIVITY_NOTIFICATION, mDataActivity);
- }
- }
- }
- }
-
-
- private void checkAndSetNotification() {
- // If we shouldn't place a notification on available networks, then
- // don't bother doing any of the following
- if (!mNotificationEnabled) return;
-
- State state = mNetworkInfo.getState();
- if ((state == NetworkInfo.State.DISCONNECTED)
- || (state == NetworkInfo.State.UNKNOWN)) {
- // Look for an open network
- List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
- if (scanResults != null) {
- int numOpenNetworks = 0;
- for (int i = scanResults.size() - 1; i >= 0; i--) {
- ScanResult scanResult = scanResults.get(i);
-
- //A capability of [ESS] represents an open access point
- //that is available for an STA to connect
- if (scanResult.capabilities != null &&
- scanResult.capabilities.equals("[ESS]")) {
- numOpenNetworks++;
- }
- }
-
- if (numOpenNetworks > 0) {
- if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
- /*
- * We've scanned continuously at least
- * NUM_SCANS_BEFORE_NOTIFICATION times. The user
- * probably does not have a remembered network in range,
- * since otherwise supplicant would have tried to
- * associate and thus resetting this counter.
- */
- setNotificationVisible(true, numOpenNetworks, false, 0);
- }
- return;
- }
- }
- }
-
- // No open networks in range, remove the notification
- setNotificationVisible(false, 0, false, 0);
- }
-
- /**
- * Clears variables related to tracking whether a notification has been
- * shown recently and clears the current notification.
- */
- private void resetNotification() {
- mNotificationRepeatTime = 0;
- mNumScansSinceNetworkStateChange = 0;
- setNotificationVisible(false, 0, false, 0);
- }
-
- /**
- * Display or don't display a notification that there are open Wi-Fi networks.
- * @param visible {@code true} if notification should be visible, {@code false} otherwise
- * @param numNetworks the number networks seen
- * @param force {@code true} to force notification to be shown/not-shown,
- * even if it is already shown/not-shown.
- * @param delay time in milliseconds after which the notification should be made
- * visible or invisible.
- */
- private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
- int delay) {
-
- // Since we use auto cancel on the notification, when the
- // mNetworksAvailableNotificationShown is true, the notification may
- // have actually been canceled. However, when it is false we know
- // for sure that it is not being shown (it will not be shown any other
- // place than here)
-
- // If it should be hidden and it is already hidden, then noop
- if (!visible && !mNotificationShown && !force) {
- return;
- }
-
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
-
- Message message;
- if (visible) {
-
- // Not enough time has passed to show the notification again
- if (System.currentTimeMillis() < mNotificationRepeatTime) {
- return;
- }
-
- if (mNotification == null) {
- // Cache the Notification object.
- mNotification = new Notification();
- mNotification.when = 0;
- mNotification.icon = ICON_NETWORKS_AVAILABLE;
- mNotification.flags = Notification.FLAG_AUTO_CANCEL;
- mNotification.contentIntent = TaskStackBuilder.create(mContext)
- .addNextIntentWithParentStack(
- new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK))
- .getPendingIntent(0, 0, null, UserHandle.CURRENT);
- }
-
- CharSequence title = mContext.getResources().getQuantityText(
- com.android.internal.R.plurals.wifi_available, numNetworks);
- CharSequence details = mContext.getResources().getQuantityText(
- com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
- mNotification.tickerText = title;
- mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
-
- mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
-
- notificationManager.notifyAsUser(null, ICON_NETWORKS_AVAILABLE, mNotification,
- UserHandle.ALL);
- } else {
- notificationManager.cancelAsUser(null, ICON_NETWORKS_AVAILABLE, UserHandle.ALL);
- }
-
- mNotificationShown = visible;
- }
-
- private class NotificationEnabledSettingObserver extends ContentObserver {
-
- public NotificationEnabledSettingObserver(Handler handler) {
- super(handler);
- }
-
- public void register() {
- ContentResolver cr = mContext.getContentResolver();
- cr.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
- mNotificationEnabled = getValue();
- }
-
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
-
- mNotificationEnabled = getValue();
- resetNotification();
- }
-
- private boolean getValue() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
- }
- }
-
-
}
diff --git a/services/java/com/android/server/wifi/WifiSettingsStore.java b/services/java/com/android/server/wifi/WifiSettingsStore.java
new file mode 100644
index 0000000..3ff8061
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiSettingsStore.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2013 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.wifi;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/* Tracks persisted settings for Wi-Fi and airplane mode interaction */
+final class WifiSettingsStore {
+ /* Values tracked in Settings.Global.WIFI_ON */
+ private static final int WIFI_DISABLED = 0;
+ private static final int WIFI_ENABLED = 1;
+ /* Wifi enabled while in airplane mode */
+ private static final int WIFI_ENABLED_AIRPLANE_OVERRIDE = 2;
+ /* Wifi disabled due to airplane mode on */
+ private static final int WIFI_DISABLED_AIRPLANE_ON = 3;
+
+ /* Persisted state that tracks the wifi & airplane interaction from settings */
+ private int mPersistWifiState = WIFI_DISABLED;
+ /* Tracks current airplane mode state */
+ private boolean mAirplaneModeOn = false;
+
+ /* Tracks the setting of scan being available even when wi-fi is turned off
+ */
+ private boolean mScanAlwaysAvailable;
+
+ private final Context mContext;
+
+ /* Tracks if we have checked the saved wi-fi state after boot */
+ private boolean mCheckSavedStateAtBoot = false;
+
+ WifiSettingsStore(Context context) {
+ mContext = context;
+ mAirplaneModeOn = getPersistedAirplaneModeOn();
+ mPersistWifiState = getPersistedWifiState();
+ mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
+ }
+
+ synchronized boolean isWifiToggleEnabled() {
+ if (!mCheckSavedStateAtBoot) {
+ mCheckSavedStateAtBoot = true;
+ if (testAndClearWifiSavedState()) return true;
+ }
+
+ if (mAirplaneModeOn) {
+ return mPersistWifiState == WIFI_ENABLED_AIRPLANE_OVERRIDE;
+ } else {
+ return mPersistWifiState != WIFI_DISABLED;
+ }
+ }
+
+ /**
+ * Returns true if airplane mode is currently on.
+ * @return {@code true} if airplane mode is on.
+ */
+ synchronized boolean isAirplaneModeOn() {
+ return mAirplaneModeOn;
+ }
+
+ synchronized boolean isScanAlwaysAvailable() {
+ return mScanAlwaysAvailable;
+ }
+
+ synchronized boolean handleWifiToggled(boolean wifiEnabled) {
+ // Can Wi-Fi be toggled in airplane mode ?
+ if (mAirplaneModeOn && !isAirplaneToggleable()) {
+ return false;
+ }
+
+ if (wifiEnabled) {
+ if (mAirplaneModeOn) {
+ persistWifiState(WIFI_ENABLED_AIRPLANE_OVERRIDE);
+ } else {
+ persistWifiState(WIFI_ENABLED);
+ }
+ } else {
+ // When wifi state is disabled, we do not care
+ // if airplane mode is on or not. The scenario of
+ // wifi being disabled due to airplane mode being turned on
+ // is handled handleAirplaneModeToggled()
+ persistWifiState(WIFI_DISABLED);
+ }
+ return true;
+ }
+
+ synchronized boolean handleAirplaneModeToggled() {
+ // Is Wi-Fi sensitive to airplane mode changes ?
+ if (!isAirplaneSensitive()) {
+ return false;
+ }
+
+ mAirplaneModeOn = getPersistedAirplaneModeOn();
+ if (mAirplaneModeOn) {
+ // Wifi disabled due to airplane on
+ if (mPersistWifiState == WIFI_ENABLED) {
+ persistWifiState(WIFI_DISABLED_AIRPLANE_ON);
+ }
+ } else {
+ /* On airplane mode disable, restore wifi state if necessary */
+ if (testAndClearWifiSavedState() ||
+ mPersistWifiState == WIFI_ENABLED_AIRPLANE_OVERRIDE) {
+ persistWifiState(WIFI_ENABLED);
+ }
+ }
+ return true;
+ }
+
+ synchronized void handleWifiScanAlwaysAvailableToggled() {
+ mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mPersistWifiState " + mPersistWifiState);
+ pw.println("mAirplaneModeOn " + mAirplaneModeOn);
+ }
+
+ private void persistWifiState(int state) {
+ final ContentResolver cr = mContext.getContentResolver();
+ mPersistWifiState = state;
+ Settings.Global.putInt(cr, Settings.Global.WIFI_ON, state);
+ }
+
+ /* Does Wi-Fi need to be disabled when airplane mode is on ? */
+ private boolean isAirplaneSensitive() {
+ String airplaneModeRadios = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_RADIOS);
+ return airplaneModeRadios == null
+ || airplaneModeRadios.contains(Settings.Global.RADIO_WIFI);
+ }
+
+ /* Is Wi-Fi allowed to be re-enabled while airplane mode is on ? */
+ private boolean isAirplaneToggleable() {
+ String toggleableRadios = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+ return toggleableRadios != null
+ && toggleableRadios.contains(Settings.Global.RADIO_WIFI);
+ }
+
+ /* After a reboot, we restore wi-fi to be on if it was turned off temporarily for tethering.
+ * The settings app tracks the saved state, but the framework has to check it at boot to
+ * make sure the wi-fi is turned on in case it was turned off for the purpose of tethering.
+ *
+ * Note that this is not part of the regular WIFI_ON setting because this only needs to
+ * be controlled through the settings app and not the Wi-Fi public API.
+ */
+ private boolean testAndClearWifiSavedState() {
+ final ContentResolver cr = mContext.getContentResolver();
+ int wifiSavedState = 0;
+ try {
+ wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE);
+ if(wifiSavedState == 1)
+ Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
+ } catch (Settings.SettingNotFoundException e) {
+ ;
+ }
+ return (wifiSavedState == 1);
+ }
+
+ private int getPersistedWifiState() {
+ final ContentResolver cr = mContext.getContentResolver();
+ try {
+ return Settings.Global.getInt(cr, Settings.Global.WIFI_ON);
+ } catch (Settings.SettingNotFoundException e) {
+ Settings.Global.putInt(cr, Settings.Global.WIFI_ON, WIFI_DISABLED);
+ return WIFI_DISABLED;
+ }
+ }
+
+ private boolean getPersistedAirplaneModeOn() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
+
+ private boolean getPersistedScanAlwaysAvailable() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
+ 0) == 1;
+ }
+}
diff --git a/services/java/com/android/server/wifi/WifiTrafficPoller.java b/services/java/com/android/server/wifi/WifiTrafficPoller.java
new file mode 100644
index 0000000..b498550
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiTrafficPoller.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2013 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.wifi;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import static android.net.NetworkInfo.DetailedState.CONNECTED;
+import android.net.TrafficStats;
+import android.net.wifi.WifiManager;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.os.Handler;
+import android.os.Message;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.android.internal.util.AsyncChannel;
+
+/* Polls for traffic stats and notifies the clients */
+final class WifiTrafficPoller {
+ /**
+ * Interval in milliseconds between polling for traffic
+ * statistics
+ */
+ private static final int POLL_TRAFFIC_STATS_INTERVAL_MSECS = 1000;
+
+ private static final int ENABLE_TRAFFIC_STATS_POLL = 1;
+ private static final int TRAFFIC_STATS_POLL = 2;
+ private static final int ADD_CLIENT = 3;
+ private static final int REMOVE_CLIENT = 4;
+
+ private boolean mEnableTrafficStatsPoll = false;
+ private int mTrafficStatsPollToken = 0;
+ private long mTxPkts;
+ private long mRxPkts;
+ /* Tracks last reported data activity */
+ private int mDataActivity;
+
+ private final List<Messenger> mClients = new ArrayList<Messenger>();
+ // err on the side of updating at boot since screen on broadcast may be missed
+ // the first time
+ private AtomicBoolean mScreenOn = new AtomicBoolean(true);
+ private final TrafficHandler mTrafficHandler;
+ private NetworkInfo mNetworkInfo;
+ private final String mInterface;
+
+ WifiTrafficPoller(Context context, String iface) {
+ mInterface = iface;
+ mTrafficHandler = new TrafficHandler();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+
+ context.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(
+ WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ mScreenOn.set(false);
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+ mScreenOn.set(true);
+ }
+ evaluateTrafficStatsPolling();
+ }
+ }, filter);
+ }
+
+ void addClient(Messenger client) {
+ Message.obtain(mTrafficHandler, ADD_CLIENT, client).sendToTarget();
+ }
+
+ void removeClient(Messenger client) {
+ Message.obtain(mTrafficHandler, REMOVE_CLIENT, client).sendToTarget();
+ }
+
+
+ private class TrafficHandler extends Handler {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case ENABLE_TRAFFIC_STATS_POLL:
+ mEnableTrafficStatsPoll = (msg.arg1 == 1);
+ mTrafficStatsPollToken++;
+ if (mEnableTrafficStatsPoll) {
+ notifyOnDataActivity();
+ sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
+ mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
+ }
+ break;
+ case TRAFFIC_STATS_POLL:
+ if (msg.arg1 == mTrafficStatsPollToken) {
+ notifyOnDataActivity();
+ sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
+ mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
+ }
+ break;
+ case ADD_CLIENT:
+ mClients.add((Messenger) msg.obj);
+ break;
+ case REMOVE_CLIENT:
+ mClients.remove(msg.obj);
+ break;
+ }
+
+ }
+ }
+
+ private void evaluateTrafficStatsPolling() {
+ Message msg;
+ if (mNetworkInfo == null) return;
+ if (mNetworkInfo.getDetailedState() == CONNECTED && mScreenOn.get()) {
+ msg = Message.obtain(mTrafficHandler,
+ ENABLE_TRAFFIC_STATS_POLL, 1, 0);
+ } else {
+ msg = Message.obtain(mTrafficHandler,
+ ENABLE_TRAFFIC_STATS_POLL, 0, 0);
+ }
+ msg.sendToTarget();
+ }
+
+ private void notifyOnDataActivity() {
+ long sent, received;
+ long preTxPkts = mTxPkts, preRxPkts = mRxPkts;
+ int dataActivity = WifiManager.DATA_ACTIVITY_NONE;
+
+ mTxPkts = TrafficStats.getTxPackets(mInterface);
+ mRxPkts = TrafficStats.getRxPackets(mInterface);
+
+ if (preTxPkts > 0 || preRxPkts > 0) {
+ sent = mTxPkts - preTxPkts;
+ received = mRxPkts - preRxPkts;
+ if (sent > 0) {
+ dataActivity |= WifiManager.DATA_ACTIVITY_OUT;
+ }
+ if (received > 0) {
+ dataActivity |= WifiManager.DATA_ACTIVITY_IN;
+ }
+
+ if (dataActivity != mDataActivity && mScreenOn.get()) {
+ mDataActivity = dataActivity;
+ for (Messenger client : mClients) {
+ Message msg = Message.obtain();
+ msg.what = WifiManager.DATA_ACTIVITY_NOTIFICATION;
+ msg.arg1 = mDataActivity;
+ try {
+ client.send(msg);
+ } catch (RemoteException e) {
+ // Failed to reach, skip
+ // Client removal is handled in WifiService
+ }
+ }
+ }
+ }
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mEnableTrafficStatsPoll " + mEnableTrafficStatsPoll);
+ pw.println("mTrafficStatsPollToken " + mTrafficStatsPollToken);
+ pw.println("mTxPkts " + mTxPkts);
+ pw.println("mRxPkts " + mRxPkts);
+ pw.println("mDataActivity " + mDataActivity);
+ }
+
+}
diff --git a/services/java/com/android/server/wm/AppTransition.java b/services/java/com/android/server/wm/AppTransition.java
new file mode 100644
index 0000000..cd3daaa
--- /dev/null
+++ b/services/java/com/android/server/wm/AppTransition.java
@@ -0,0 +1,767 @@
+/*
+ * 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.wm;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.ScaleAnimation;
+
+import com.android.internal.util.DumpUtils.Dump;
+import com.android.server.AttributeCache;
+import com.android.server.wm.WindowManagerService.H;
+
+import java.io.PrintWriter;
+
+import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
+
+// State management of app transitions. When we are preparing for a
+// transition, mNextAppTransition will be the kind of transition to
+// perform or TRANSIT_NONE if we are not waiting. If we are waiting,
+// mOpeningApps and mClosingApps are the lists of tokens that will be
+// made visible or hidden at the next transition.
+public class AppTransition implements Dump {
+ private static final String TAG = "AppTransition";
+ private static final boolean DEBUG_APP_TRANSITIONS =
+ WindowManagerService.DEBUG_APP_TRANSITIONS;
+ private static final boolean DEBUG_ANIM = WindowManagerService.DEBUG_ANIM;
+
+ /** Bit mask that is set for all enter transition. */
+ public static final int TRANSIT_ENTER_MASK = 0x1000;
+
+ /** Bit mask that is set for all exit transitions. */
+ public static final int TRANSIT_EXIT_MASK = 0x2000;
+
+ /** Not set up for a transition. */
+ public static final int TRANSIT_UNSET = -1;
+ /** No animation for transition. */
+ public static final int TRANSIT_NONE = 0;
+ /** A window in a new activity is being opened on top of an existing one in the same task. */
+ public static final int TRANSIT_ACTIVITY_OPEN = 6 | TRANSIT_ENTER_MASK;
+ /** The window in the top-most activity is being closed to reveal the
+ * previous activity in the same task. */
+ public static final int TRANSIT_ACTIVITY_CLOSE = 7 | TRANSIT_EXIT_MASK;
+ /** A window in a new task is being opened on top of an existing one
+ * in another activity's task. */
+ public static final int TRANSIT_TASK_OPEN = 8 | TRANSIT_ENTER_MASK;
+ /** A window in the top-most activity is being closed to reveal the
+ * previous activity in a different task. */
+ public static final int TRANSIT_TASK_CLOSE = 9 | TRANSIT_EXIT_MASK;
+ /** A window in an existing task is being displayed on top of an existing one
+ * in another activity's task. */
+ public static final int TRANSIT_TASK_TO_FRONT = 10 | TRANSIT_ENTER_MASK;
+ /** A window in an existing task is being put below all other tasks. */
+ public static final int TRANSIT_TASK_TO_BACK = 11 | TRANSIT_EXIT_MASK;
+ /** A window in a new activity that doesn't have a wallpaper is being opened on top of one that
+ * does, effectively closing the wallpaper. */
+ public static final int TRANSIT_WALLPAPER_CLOSE = 12 | TRANSIT_EXIT_MASK;
+ /** A window in a new activity that does have a wallpaper is being opened on one that didn't,
+ * effectively opening the wallpaper. */
+ public static final int TRANSIT_WALLPAPER_OPEN = 13 | TRANSIT_ENTER_MASK;
+ /** A window in a new activity is being opened on top of an existing one, and both are on top
+ * of the wallpaper. */
+ public static final int TRANSIT_WALLPAPER_INTRA_OPEN = 14 | TRANSIT_ENTER_MASK;
+ /** The window in the top-most activity is being closed to reveal the previous activity, and
+ * both are on top of the wallpaper. */
+ public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15 | TRANSIT_EXIT_MASK;
+
+ /** Fraction of animation at which the recents thumbnail becomes completely transparent */
+ private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f;
+
+ private static final long DEFAULT_APP_TRANSITION_DURATION = 250;
+
+ private final Context mContext;
+ private final Handler mH;
+
+ private int mNextAppTransition = TRANSIT_UNSET;
+
+ private static final int NEXT_TRANSIT_TYPE_NONE = 0;
+ private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1;
+ private static final int NEXT_TRANSIT_TYPE_SCALE_UP = 2;
+ private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP = 3;
+ private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN = 4;
+ private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
+
+ private String mNextAppTransitionPackage;
+ private Bitmap mNextAppTransitionThumbnail;
+ // Used for thumbnail transitions. True if we're scaling up, false if scaling down
+ private boolean mNextAppTransitionScaleUp;
+ private IRemoteCallback mNextAppTransitionCallback;
+ private int mNextAppTransitionEnter;
+ private int mNextAppTransitionExit;
+ private int mNextAppTransitionStartX;
+ private int mNextAppTransitionStartY;
+ private int mNextAppTransitionStartWidth;
+ private int mNextAppTransitionStartHeight;
+
+ private final static int APP_STATE_IDLE = 0;
+ private final static int APP_STATE_READY = 1;
+ private final static int APP_STATE_RUNNING = 2;
+ private final static int APP_STATE_TIMEOUT = 3;
+ private int mAppTransitionState = APP_STATE_IDLE;
+
+ private final int mConfigShortAnimTime;
+ private final Interpolator mDecelerateInterpolator;
+ private final Interpolator mThumbnailFadeoutInterpolator;
+
+ private int mCurrentUserId = 0;
+
+ AppTransition(Context context, Handler h) {
+ mContext = context;
+ mH = h;
+ mConfigShortAnimTime = context.getResources().getInteger(
+ com.android.internal.R.integer.config_shortAnimTime);
+ mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.decelerate_cubic);
+ mThumbnailFadeoutInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float input) {
+ // Linear response for first fraction, then complete after that.
+ if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
+ return input / RECENTS_THUMBNAIL_FADEOUT_FRACTION;
+ }
+ return 1.0f;
+ }
+ };
+ }
+
+ boolean isTransitionSet() {
+ return mNextAppTransition != TRANSIT_UNSET;
+ }
+
+ boolean isTransitionNone() {
+ return mNextAppTransition == TRANSIT_NONE;
+ }
+
+ boolean isTransitionEqual(int transit) {
+ return mNextAppTransition == transit;
+ }
+
+ int getAppTransition() {
+ return mNextAppTransition;
+ }
+
+ void setAppTransition(int transit) {
+ mNextAppTransition = transit;
+ }
+
+ boolean isReady() {
+ return mAppTransitionState == APP_STATE_READY
+ || mAppTransitionState == APP_STATE_TIMEOUT;
+ }
+
+ void setReady() {
+ mAppTransitionState = APP_STATE_READY;
+ }
+
+ boolean isRunning() {
+ return mAppTransitionState == APP_STATE_RUNNING;
+ }
+
+ void setIdle() {
+ mAppTransitionState = APP_STATE_IDLE;
+ }
+
+ boolean isTimeout() {
+ return mAppTransitionState == APP_STATE_TIMEOUT;
+ }
+
+ void setTimeout() {
+ mAppTransitionState = APP_STATE_TIMEOUT;
+ }
+
+ Bitmap getNextAppTransitionThumbnail() {
+ return mNextAppTransitionThumbnail;
+ }
+
+ void getStartingPoint(Point outPoint) {
+ outPoint.x = mNextAppTransitionStartX;
+ outPoint.y = mNextAppTransitionStartY;
+ }
+
+ void prepare() {
+ if (!isRunning()) {
+ mAppTransitionState = APP_STATE_IDLE;
+ }
+ }
+
+ void goodToGo() {
+ mNextAppTransition = TRANSIT_UNSET;
+ mAppTransitionState = APP_STATE_RUNNING;
+ }
+
+ void clear() {
+ mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
+ mNextAppTransitionPackage = null;
+ mNextAppTransitionThumbnail = null;
+ }
+
+ void freeze() {
+ setAppTransition(AppTransition.TRANSIT_UNSET);
+ clear();
+ setReady();
+ }
+
+ private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) {
+ if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg="
+ + (lp != null ? lp.packageName : null)
+ + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
+ if (lp != null && lp.windowAnimations != 0) {
+ // If this is a system resource, don't try to load it from the
+ // application resources. It is nice to avoid loading application
+ // resources if we can.
+ String packageName = lp.packageName != null ? lp.packageName : "android";
+ int resId = lp.windowAnimations;
+ if ((resId&0xFF000000) == 0x01000000) {
+ packageName = "android";
+ }
+ if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
+ + packageName);
+ return AttributeCache.instance().get(packageName, resId,
+ com.android.internal.R.styleable.WindowAnimation, mCurrentUserId);
+ }
+ return null;
+ }
+
+ private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
+ if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package="
+ + packageName + " resId=0x" + Integer.toHexString(resId));
+ if (packageName != null) {
+ if ((resId&0xFF000000) == 0x01000000) {
+ packageName = "android";
+ }
+ if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
+ + packageName);
+ return AttributeCache.instance().get(packageName, resId,
+ com.android.internal.R.styleable.WindowAnimation, mCurrentUserId);
+ }
+ return null;
+ }
+
+ Animation loadAnimation(WindowManager.LayoutParams lp, int animAttr) {
+ int anim = 0;
+ Context context = mContext;
+ if (animAttr >= 0) {
+ AttributeCache.Entry ent = getCachedAnimations(lp);
+ if (ent != null) {
+ context = ent.context;
+ anim = ent.array.getResourceId(animAttr, 0);
+ }
+ }
+ if (anim != 0) {
+ return AnimationUtils.loadAnimation(context, anim);
+ }
+ return null;
+ }
+
+ private Animation loadAnimation(String packageName, int resId) {
+ int anim = 0;
+ Context context = mContext;
+ if (resId >= 0) {
+ AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
+ if (ent != null) {
+ context = ent.context;
+ anim = resId;
+ }
+ }
+ if (anim != 0) {
+ return AnimationUtils.loadAnimation(context, anim);
+ }
+ return null;
+ }
+
+ /**
+ * Compute the pivot point for an animation that is scaling from a small
+ * rect on screen to a larger rect. The pivot point varies depending on
+ * the distance between the inner and outer edges on both sides. This
+ * function computes the pivot point for one dimension.
+ * @param startPos Offset from left/top edge of outer rectangle to
+ * left/top edge of inner rectangle.
+ * @param finalScale The scaling factor between the size of the outer
+ * and inner rectangles.
+ */
+ private static float computePivot(int startPos, float finalScale) {
+ final float denom = finalScale-1;
+ if (Math.abs(denom) < .0001f) {
+ return startPos;
+ }
+ return -startPos / denom;
+ }
+
+ private Animation createScaleUpAnimationLocked(int transit, boolean enter,
+ int appWidth, int appHeight) {
+ Animation a = null;
+ if (enter) {
+ // Entering app zooms out from the center of the initial rect.
+ float scaleW = mNextAppTransitionStartWidth / (float) appWidth;
+ float scaleH = mNextAppTransitionStartHeight / (float) appHeight;
+ Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
+ computePivot(mNextAppTransitionStartX, scaleW),
+ computePivot(mNextAppTransitionStartY, scaleH));
+ scale.setInterpolator(mDecelerateInterpolator);
+
+ Animation alpha = new AlphaAnimation(0, 1);
+ alpha.setInterpolator(mThumbnailFadeoutInterpolator);
+
+ AnimationSet set = new AnimationSet(false);
+ set.addAnimation(scale);
+ set.addAnimation(alpha);
+ set.setDetachWallpaper(true);
+ a = set;
+ } else if (transit == TRANSIT_WALLPAPER_INTRA_OPEN ||
+ transit == TRANSIT_WALLPAPER_INTRA_CLOSE) {
+ // If we are on top of the wallpaper, we need an animation that
+ // correctly handles the wallpaper staying static behind all of
+ // the animated elements. To do this, will just have the existing
+ // element fade out.
+ a = new AlphaAnimation(1, 0);
+ a.setDetachWallpaper(true);
+ } else {
+ // For normal animations, the exiting element just holds in place.
+ a = new AlphaAnimation(1, 1);
+ }
+
+ // Pick the desired duration. If this is an inter-activity transition,
+ // it is the standard duration for that. Otherwise we use the longer
+ // task transition duration.
+ final long duration;
+ switch (transit) {
+ case TRANSIT_ACTIVITY_OPEN:
+ case TRANSIT_ACTIVITY_CLOSE:
+ duration = mConfigShortAnimTime;
+ break;
+ default:
+ duration = DEFAULT_APP_TRANSITION_DURATION;
+ break;
+ }
+ a.setDuration(duration);
+ a.setFillAfter(true);
+ a.setInterpolator(mDecelerateInterpolator);
+ a.initialize(appWidth, appHeight, appWidth, appHeight);
+ return a;
+ }
+
+ Animation createThumbnailAnimationLocked(int transit, boolean enter, boolean thumb,
+ int appWidth, int appHeight) {
+ Animation a;
+ final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+ final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+ final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+ final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+ if (thumb) {
+ // Animation for zooming thumbnail from its initial size to
+ // filling the screen.
+ if (mNextAppTransitionScaleUp) {
+ float scaleW = appWidth / thumbWidth;
+ float scaleH = appHeight / thumbHeight;
+ Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
+ computePivot(mNextAppTransitionStartX, 1 / scaleW),
+ computePivot(mNextAppTransitionStartY, 1 / scaleH));
+ scale.setInterpolator(mDecelerateInterpolator);
+
+ Animation alpha = new AlphaAnimation(1, 0);
+ alpha.setInterpolator(mThumbnailFadeoutInterpolator);
+
+ // This AnimationSet uses the Interpolators assigned above.
+ AnimationSet set = new AnimationSet(false);
+ set.addAnimation(scale);
+ set.addAnimation(alpha);
+ a = set;
+ } else {
+ float scaleW = appWidth / thumbWidth;
+ float scaleH = appHeight / thumbHeight;
+ a = new ScaleAnimation(scaleW, 1, scaleH, 1,
+ computePivot(mNextAppTransitionStartX, 1 / scaleW),
+ computePivot(mNextAppTransitionStartY, 1 / scaleH));
+ }
+ } else if (enter) {
+ // Entering app zooms out from the center of the thumbnail.
+ if (mNextAppTransitionScaleUp) {
+ float scaleW = thumbWidth / appWidth;
+ float scaleH = thumbHeight / appHeight;
+ a = new ScaleAnimation(scaleW, 1, scaleH, 1,
+ computePivot(mNextAppTransitionStartX, scaleW),
+ computePivot(mNextAppTransitionStartY, scaleH));
+ } else {
+ // noop animation
+ a = new AlphaAnimation(1, 1);
+ }
+ } else {
+ // Exiting app
+ if (mNextAppTransitionScaleUp) {
+ if (transit == TRANSIT_WALLPAPER_INTRA_OPEN) {
+ // Fade out while bringing up selected activity. This keeps the
+ // current activity from showing through a launching wallpaper
+ // activity.
+ a = new AlphaAnimation(1, 0);
+ } else {
+ // noop animation
+ a = new AlphaAnimation(1, 1);
+ }
+ } else {
+ float scaleW = thumbWidth / appWidth;
+ float scaleH = thumbHeight / appHeight;
+ Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
+ computePivot(mNextAppTransitionStartX, scaleW),
+ computePivot(mNextAppTransitionStartY, scaleH));
+
+ Animation alpha = new AlphaAnimation(1, 0);
+
+ AnimationSet set = new AnimationSet(true);
+ set.addAnimation(scale);
+ set.addAnimation(alpha);
+ set.setZAdjustment(Animation.ZORDER_TOP);
+ a = set;
+ }
+ }
+
+ // Pick the desired duration. If this is an inter-activity transition,
+ // it is the standard duration for that. Otherwise we use the longer
+ // task transition duration.
+ final long duration;
+ switch (transit) {
+ case TRANSIT_ACTIVITY_OPEN:
+ case TRANSIT_ACTIVITY_CLOSE:
+ duration = mConfigShortAnimTime;
+ break;
+ default:
+ duration = DEFAULT_APP_TRANSITION_DURATION;
+ break;
+ }
+ a.setDuration(duration);
+ a.setFillAfter(true);
+ a.setInterpolator(mDecelerateInterpolator);
+ a.initialize(appWidth, appHeight, appWidth, appHeight);
+ return a;
+ }
+
+
+ Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+ int appWidth, int appHeight) {
+ Animation a;
+ if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
+ a = loadAnimation(mNextAppTransitionPackage, enter ?
+ mNextAppTransitionEnter : mNextAppTransitionExit);
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "applyAnimation:"
+ + " anim=" + a + " nextAppTransition=ANIM_CUSTOM"
+ + " transit=" + transit + " isEntrance=" + enter
+ + " Callers=" + Debug.getCallers(3));
+ } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
+ a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight);
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "applyAnimation:"
+ + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
+ + " transit=" + transit + " isEntrance=" + enter
+ + " Callers=" + Debug.getCallers(3));
+ } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
+ mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
+ mNextAppTransitionScaleUp =
+ (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
+ a = createThumbnailAnimationLocked(transit, enter, false, appWidth, appHeight);
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ String animName = mNextAppTransitionScaleUp ?
+ "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
+ Slog.v(TAG, "applyAnimation:"
+ + " anim=" + a + " nextAppTransition=" + animName
+ + " transit=" + transit + " isEntrance=" + enter
+ + " Callers=" + Debug.getCallers(3));
+ }
+ } else {
+ int animAttr = 0;
+ switch (transit) {
+ case TRANSIT_ACTIVITY_OPEN:
+ animAttr = enter
+ ? WindowAnimation_activityOpenEnterAnimation
+ : WindowAnimation_activityOpenExitAnimation;
+ break;
+ case TRANSIT_ACTIVITY_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_activityCloseEnterAnimation
+ : WindowAnimation_activityCloseExitAnimation;
+ break;
+ case TRANSIT_TASK_OPEN:
+ animAttr = enter
+ ? WindowAnimation_taskOpenEnterAnimation
+ : WindowAnimation_taskOpenExitAnimation;
+ break;
+ case TRANSIT_TASK_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_taskCloseEnterAnimation
+ : WindowAnimation_taskCloseExitAnimation;
+ break;
+ case TRANSIT_TASK_TO_FRONT:
+ animAttr = enter
+ ? WindowAnimation_taskToFrontEnterAnimation
+ : WindowAnimation_taskToFrontExitAnimation;
+ break;
+ case TRANSIT_TASK_TO_BACK:
+ animAttr = enter
+ ? WindowAnimation_taskToBackEnterAnimation
+ : WindowAnimation_taskToBackExitAnimation;
+ break;
+ case TRANSIT_WALLPAPER_OPEN:
+ animAttr = enter
+ ? WindowAnimation_wallpaperOpenEnterAnimation
+ : WindowAnimation_wallpaperOpenExitAnimation;
+ break;
+ case TRANSIT_WALLPAPER_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_wallpaperCloseEnterAnimation
+ : WindowAnimation_wallpaperCloseExitAnimation;
+ break;
+ case TRANSIT_WALLPAPER_INTRA_OPEN:
+ animAttr = enter
+ ? WindowAnimation_wallpaperIntraOpenEnterAnimation
+ : WindowAnimation_wallpaperIntraOpenExitAnimation;
+ break;
+ case TRANSIT_WALLPAPER_INTRA_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_wallpaperIntraCloseEnterAnimation
+ : WindowAnimation_wallpaperIntraCloseExitAnimation;
+ break;
+ }
+ a = animAttr != 0 ? loadAnimation(lp, animAttr) : null;
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "applyAnimation:"
+ + " anim=" + a
+ + " animAttr=0x" + Integer.toHexString(animAttr)
+ + " transit=" + transit + " isEntrance=" + enter
+ + " Callers=" + Debug.getCallers(3));
+ }
+ return a;
+ }
+
+ void postAnimationCallback() {
+ if (mNextAppTransitionCallback != null) {
+ mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, mNextAppTransitionCallback));
+ mNextAppTransitionCallback = null;
+ }
+ }
+
+ void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
+ IRemoteCallback startedCallback) {
+ if (isTransitionSet()) {
+ mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
+ mNextAppTransitionPackage = packageName;
+ mNextAppTransitionThumbnail = null;
+ mNextAppTransitionEnter = enterAnim;
+ mNextAppTransitionExit = exitAnim;
+ postAnimationCallback();
+ mNextAppTransitionCallback = startedCallback;
+ } else {
+ postAnimationCallback();
+ }
+ }
+
+ void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+ int startHeight) {
+ if (isTransitionSet()) {
+ mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
+ mNextAppTransitionPackage = null;
+ mNextAppTransitionThumbnail = null;
+ mNextAppTransitionStartX = startX;
+ mNextAppTransitionStartY = startY;
+ mNextAppTransitionStartWidth = startWidth;
+ mNextAppTransitionStartHeight = startHeight;
+ postAnimationCallback();
+ mNextAppTransitionCallback = null;
+ }
+ }
+
+ void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY,
+ IRemoteCallback startedCallback, boolean scaleUp) {
+ if (isTransitionSet()) {
+ mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
+ : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
+ mNextAppTransitionPackage = null;
+ mNextAppTransitionThumbnail = srcThumb;
+ mNextAppTransitionScaleUp = scaleUp;
+ mNextAppTransitionStartX = startX;
+ mNextAppTransitionStartY = startY;
+ postAnimationCallback();
+ mNextAppTransitionCallback = startedCallback;
+ } else {
+ postAnimationCallback();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "mNextAppTransition=0x" + Integer.toHexString(mNextAppTransition);
+ }
+
+ /**
+ * Returns the human readable name of a window transition.
+ *
+ * @param transition The window transition.
+ * @return The transition symbolic name.
+ */
+ public static String appTransitionToString(int transition) {
+ switch (transition) {
+ case TRANSIT_UNSET: {
+ return "TRANSIT_UNSET";
+ }
+ case TRANSIT_NONE: {
+ return "TRANSIT_NONE";
+ }
+ case TRANSIT_EXIT_MASK: {
+ return "TRANSIT_EXIT_MASK";
+ }
+ case TRANSIT_ACTIVITY_OPEN: {
+ return "TRANSIT_ACTIVITY_OPEN";
+ }
+ case TRANSIT_ACTIVITY_CLOSE: {
+ return "TRANSIT_ACTIVITY_CLOSE";
+ }
+ case TRANSIT_TASK_OPEN: {
+ return "TRANSIT_TASK_OPEN";
+ }
+ case TRANSIT_TASK_CLOSE: {
+ return "TRANSIT_TASK_CLOSE";
+ }
+ case TRANSIT_TASK_TO_FRONT: {
+ return "TRANSIT_TASK_TO_FRONT";
+ }
+ case TRANSIT_TASK_TO_BACK: {
+ return "TRANSIT_TASK_TO_BACK";
+ }
+ case TRANSIT_WALLPAPER_CLOSE: {
+ return "TRANSIT_WALLPAPER_CLOSE";
+ }
+ case TRANSIT_WALLPAPER_OPEN: {
+ return "TRANSIT_WALLPAPER_OPEN";
+ }
+ case TRANSIT_WALLPAPER_INTRA_OPEN: {
+ return "TRANSIT_WALLPAPER_INTRA_OPEN";
+ }
+ case TRANSIT_WALLPAPER_INTRA_CLOSE: {
+ return "TRANSIT_WALLPAPER_INTRA_CLOSE";
+ }
+ default: {
+ return "<UNKNOWN>";
+ }
+ }
+ }
+
+ private String appStateToString() {
+ switch (mAppTransitionState) {
+ case APP_STATE_IDLE:
+ return "APP_STATE_IDLE";
+ case APP_STATE_READY:
+ return "APP_STATE_READY";
+ case APP_STATE_RUNNING:
+ return "APP_STATE_RUNNING";
+ case APP_STATE_TIMEOUT:
+ return "APP_STATE_TIMEOUT";
+ default:
+ return "unknown state=" + mAppTransitionState;
+ }
+ }
+
+ private String transitTypeToString() {
+ switch (mNextAppTransitionType) {
+ case NEXT_TRANSIT_TYPE_NONE:
+ return "NEXT_TRANSIT_TYPE_NONE";
+ case NEXT_TRANSIT_TYPE_CUSTOM:
+ return "NEXT_TRANSIT_TYPE_CUSTOM";
+ case NEXT_TRANSIT_TYPE_SCALE_UP:
+ return "NEXT_TRANSIT_TYPE_SCALE_UP";
+ case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
+ return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP";
+ case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
+ return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN";
+ default:
+ return "unknown type=" + mNextAppTransitionType;
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.print(" " + this);
+ pw.print(" mAppTransitionState="); pw.println(appStateToString());
+ if (mNextAppTransitionType != NEXT_TRANSIT_TYPE_NONE) {
+ pw.print(" mNextAppTransitionType="); pw.println(transitTypeToString());
+ }
+ switch (mNextAppTransitionType) {
+ case NEXT_TRANSIT_TYPE_CUSTOM:
+ pw.print(" mNextAppTransitionPackage=");
+ pw.println(mNextAppTransitionPackage);
+ pw.print(" mNextAppTransitionEnter=0x");
+ pw.print(Integer.toHexString(mNextAppTransitionEnter));
+ pw.print(" mNextAppTransitionExit=0x");
+ pw.println(Integer.toHexString(mNextAppTransitionExit));
+ break;
+ case NEXT_TRANSIT_TYPE_SCALE_UP:
+ pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX);
+ pw.print(" mNextAppTransitionStartY=");
+ pw.println(mNextAppTransitionStartY);
+ pw.print(" mNextAppTransitionStartWidth=");
+ pw.print(mNextAppTransitionStartWidth);
+ pw.print(" mNextAppTransitionStartHeight=");
+ pw.println(mNextAppTransitionStartHeight);
+ break;
+ case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
+ case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
+ pw.print(" mNextAppTransitionThumbnail=");
+ pw.print(mNextAppTransitionThumbnail);
+ pw.print(" mNextAppTransitionStartX=");
+ pw.print(mNextAppTransitionStartX);
+ pw.print(" mNextAppTransitionStartY=");
+ pw.println(mNextAppTransitionStartY);
+ pw.print(" mNextAppTransitionScaleUp="); pw.println(mNextAppTransitionScaleUp);
+ break;
+ }
+ if (mNextAppTransitionCallback != null) {
+ pw.print(" mNextAppTransitionCallback=");
+ pw.println(mNextAppTransitionCallback);
+ }
+ }
+
+ public void setCurrentUser(int newUserId) {
+ mCurrentUserId = newUserId;
+ }
+}
diff --git a/services/java/com/android/server/wm/AppWindowAnimator.java b/services/java/com/android/server/wm/AppWindowAnimator.java
index ca94d04..6293dc6 100644
--- a/services/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/java/com/android/server/wm/AppWindowAnimator.java
@@ -4,8 +4,10 @@ package com.android.server.wm;
import android.graphics.Matrix;
import android.util.Slog;
+import android.util.TimeUtils;
import android.view.Display;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
import android.view.animation.Transformation;
@@ -22,7 +24,6 @@ public class AppWindowAnimator {
boolean animating;
Animation animation;
- boolean animInitialized;
boolean hasTransformation;
final Transformation transformation = new Transformation();
@@ -30,6 +31,11 @@ public class AppWindowAnimator {
// Protect with mAnimator.
boolean freezingScreen;
+ /**
+ * How long we last kept the screen frozen.
+ */
+ int lastFreezeDuration;
+
// Offset to the window of all layers in the token, for use by
// AppWindowToken animations.
int animLayerAdjustment;
@@ -39,7 +45,7 @@ public class AppWindowAnimator {
boolean allDrawn;
// Special surface for thumbnail animation.
- Surface thumbnail;
+ SurfaceControl thumbnail;
int thumbnailTransactionSeq;
int thumbnailX;
int thumbnailY;
@@ -58,12 +64,15 @@ public class AppWindowAnimator {
mAnimator = atoken.mAnimator;
}
- public void setAnimation(Animation anim, boolean initialized) {
- if (WindowManagerService.localLOGV) Slog.v(
- TAG, "Setting animation in " + mAppToken + ": " + anim);
+ public void setAnimation(Animation anim, int width, int height) {
+ if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken
+ + ": " + anim + " wxh=" + width + "x" + height
+ + " isVisible=" + mAppToken.isVisible());
animation = anim;
animating = false;
- animInitialized = initialized;
+ if (!anim.isInitialized()) {
+ anim.initialize(width, height, width, height);
+ }
anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
anim.scaleCurrentDuration(mService.mTransitionAnimationScale);
int zorder = anim.getZAdjustment();
@@ -80,26 +89,29 @@ public class AppWindowAnimator {
}
// Start out animation gone if window is gone, or visible if window is visible.
transformation.clear();
- transformation.setAlpha(mAppToken.reportedVisible ? 1 : 0);
+ transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
hasTransformation = true;
}
public void setDummyAnimation() {
- if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken);
+ if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken
+ + " isVisible=" + mAppToken.isVisible());
animation = sDummyAnimation;
- animInitialized = false;
hasTransformation = true;
transformation.clear();
- transformation.setAlpha(mAppToken.reportedVisible ? 1 : 0);
+ transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
}
public void clearAnimation() {
if (animation != null) {
animation = null;
animating = true;
- animInitialized = false;
}
clearThumbnail();
+ if (mAppToken.deferClearAllDrawn) {
+ mAppToken.allDrawn = false;
+ mAppToken.deferClearAllDrawn = false;
+ }
}
public void clearThumbnail() {
@@ -125,7 +137,7 @@ public class AppWindowAnimator {
if (w == mService.mInputMethodTarget && !mService.mInputMethodTargetWaitingAnim) {
mService.setInputMethodAnimLayerAdjustment(adj);
}
- if (w == mAnimator.mWallpaperTarget && mAnimator.mLowerWallpaperTarget == null) {
+ if (w == mService.mWallpaperTarget && mService.mLowerWallpaperTarget == null) {
mService.setWallpaperAnimLayerAdjustmentLocked(adj);
}
}
@@ -185,7 +197,7 @@ public class AppWindowAnimator {
}
// This must be called while inside a transaction.
- boolean stepAnimationLocked(long currentTime, int dw, int dh) {
+ boolean stepAnimationLocked(long currentTime) {
if (mService.okToDisplay()) {
// We will run animations as long as the display isn't frozen.
@@ -202,12 +214,8 @@ public class AppWindowAnimator {
if (!animating) {
if (WindowManagerService.DEBUG_ANIM) Slog.v(
TAG, "Starting animation in " + mAppToken +
- " @ " + currentTime + ": dw=" + dw + " dh=" + dh
- + " scale=" + mService.mTransitionAnimationScale
+ " @ " + currentTime + " scale=" + mService.mTransitionAnimationScale
+ " allDrawn=" + mAppToken.allDrawn + " animating=" + animating);
- if (!animInitialized) {
- animation.initialize(dw, dh, dw, dh);
- }
animation.setStartTime(currentTime);
animating = true;
if (thumbnail != null) {
@@ -285,9 +293,12 @@ public class AppWindowAnimator {
pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
pw.print(" allDrawn="); pw.print(allDrawn);
pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment);
+ if (lastFreezeDuration != 0) {
+ pw.print(prefix); pw.print("lastFreezeDuration=");
+ TimeUtils.formatDuration(lastFreezeDuration, pw); pw.println();
+ }
if (animating || animation != null) {
- pw.print(prefix); pw.print("animating="); pw.print(animating);
- pw.print(" animInitialized="); pw.println(animInitialized);
+ pw.print(prefix); pw.print("animating="); pw.println(animating);
pw.print(prefix); pw.print("animation="); pw.println(animation);
}
if (hasTransformation) {
diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java
index 2802ad7..fbb5013 100644
--- a/services/java/com/android/server/wm/AppWindowToken.java
+++ b/services/java/com/android/server/wm/AppWindowToken.java
@@ -30,21 +30,18 @@ import android.view.View;
import android.view.WindowManager;
import java.io.PrintWriter;
-import java.util.ArrayList;
/**
* Version of WindowToken that is specifically for a particular application (or
* really activity) that is displaying windows.
*/
class AppWindowToken extends WindowToken {
- // The user who owns this app window token.
- final int userId;
// Non-null only for application tokens.
final IApplicationToken appToken;
// All of the windows and child windows that are included in this
// application token. Note this list is NOT sorted!
- final ArrayList<WindowState> allAppWindows = new ArrayList<WindowState>();
+ final WindowList allAppWindows = new WindowList();
final AppWindowAnimator mAppAnimator;
final WindowAnimator mAnimator;
@@ -66,6 +63,9 @@ class AppWindowToken extends WindowToken {
int numDrawnWindows;
boolean inPendingTransaction;
boolean allDrawn;
+ // Set to true when this app creates a surface while in the middle of an animation. In that
+ // case do not clear allDrawn until the animation completes.
+ boolean deferClearAllDrawn;
// Is this token going to be hidden in a little while? If so, it
// won't be taken into account for setting the screen orientation.
@@ -100,10 +100,9 @@ class AppWindowToken extends WindowToken {
// Input application handle used by the input dispatcher.
final InputApplicationHandle mInputApplicationHandle;
- AppWindowToken(WindowManagerService _service, int _userId, IApplicationToken _token) {
+ AppWindowToken(WindowManagerService _service, IApplicationToken _token) {
super(_service, _token.asBinder(),
WindowManager.LayoutParams.TYPE_APPLICATION, true);
- userId = _userId;
appWindowToken = this;
appToken = _token;
mInputApplicationHandle = new InputApplicationHandle(this);
@@ -154,7 +153,7 @@ class AppWindowToken extends WindowToken {
+ win.isDrawnLw()
+ ", isAnimating=" + win.mWinAnimator.isAnimating());
if (!win.isDrawnLw()) {
- Slog.v(WindowManagerService.TAG, "Not displayed: s=" + win.mWinAnimator.mSurface
+ Slog.v(WindowManagerService.TAG, "Not displayed: s=" + win.mWinAnimator.mSurfaceControl
+ " pv=" + win.mPolicyVisibility
+ " mDrawState=" + win.mWinAnimator.mDrawState
+ " ah=" + win.mAttachedHidden
@@ -224,12 +223,26 @@ class AppWindowToken extends WindowToken {
return null;
}
+ boolean isVisible() {
+ final int N = allAppWindows.size();
+ for (int i=0; i<N; i++) {
+ WindowState win = allAppWindows.get(i);
+ if (!win.mAppFreezing
+ && (win.mViewVisibility == View.VISIBLE ||
+ (win.mWinAnimator.isAnimating() &&
+ !service.mAppTransition.isTransitionSet()))
+ && !win.mDestroying && win.isDrawnLw()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
void dump(PrintWriter pw, String prefix) {
super.dump(pw, prefix);
if (appToken != null) {
- pw.print(prefix); pw.print("app=true");
- pw.print(" userId="); pw.println(userId);
+ pw.print(prefix); pw.println("app=true");
}
if (allAppWindows.size() > 0) {
pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows);
diff --git a/services/java/com/android/server/wm/BlackFrame.java b/services/java/com/android/server/wm/BlackFrame.java
index 5b77b20..774b165 100644
--- a/services/java/com/android/server/wm/BlackFrame.java
+++ b/services/java/com/android/server/wm/BlackFrame.java
@@ -23,6 +23,7 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.util.Slog;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
/**
@@ -33,23 +34,25 @@ public class BlackFrame {
final int left;
final int top;
final int layer;
- final Surface surface;
+ final SurfaceControl surface;
BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack)
- throws Surface.OutOfResourcesException {
+ throws SurfaceControl.OutOfResourcesException {
left = l;
top = t;
this.layer = layer;
int w = r-l;
int h = b-t;
+
if (WindowManagerService.DEBUG_SURFACE_TRACE) {
surface = new WindowStateAnimator.SurfaceTrace(session, "BlackSurface("
+ l + ", " + t + ")",
- w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM | Surface.HIDDEN);
+ w, h, PixelFormat.OPAQUE, SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
} else {
- surface = new Surface(session, "BlackSurface",
- w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM | Surface.HIDDEN);
+ surface = new SurfaceControl(session, "BlackSurface",
+ w, h, PixelFormat.OPAQUE, SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
}
+
surface.setAlpha(1);
surface.setLayerStack(layerStack);
surface.setLayer(layer);
@@ -104,7 +107,7 @@ public class BlackFrame {
}
public BlackFrame(SurfaceSession session, Rect outer, Rect inner,
- int layer, final int layerStack) throws Surface.OutOfResourcesException {
+ int layer, final int layerStack) throws SurfaceControl.OutOfResourcesException {
boolean success = false;
mOuterRect = new Rect(outer);
diff --git a/services/java/com/android/server/wm/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java
deleted file mode 100644
index 5874202..0000000
--- a/services/java/com/android/server/wm/DimAnimator.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * 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.wm;
-
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.util.Slog;
-import android.util.TypedValue;
-import android.view.Surface;
-import android.view.SurfaceSession;
-
-import java.io.PrintWriter;
-
-/**
- * DimAnimator class that controls the dim animation. This holds the surface and
- * all state used for dim animation.
- */
-class DimAnimator {
- static final String TAG = "DimAnimator";
-
- Surface mDimSurface;
- boolean mDimShown = false;
- float mDimCurrentAlpha;
- float mDimTargetAlpha;
- float mDimDeltaPerMs;
- long mLastDimAnimTime;
-
- int mLastDimWidth, mLastDimHeight;
-
- DimAnimator (SurfaceSession session, final int layerStack) {
- try {
- if (WindowManagerService.DEBUG_SURFACE_TRACE) {
- mDimSurface = new WindowStateAnimator.SurfaceTrace(session,
- "DimAnimator",
- 16, 16, PixelFormat.OPAQUE,
- Surface.FX_SURFACE_DIM | Surface.HIDDEN);
- } else {
- mDimSurface = new Surface(session, "DimAnimator",
- 16, 16, PixelFormat.OPAQUE,
- Surface.FX_SURFACE_DIM | Surface.HIDDEN);
- }
- if (WindowManagerService.SHOW_TRANSACTIONS ||
- WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
- " DIM " + mDimSurface + ": CREATE");
- mDimSurface.setLayerStack(layerStack);
- mDimSurface.setAlpha(0.0f);
- mDimSurface.show();
- } catch (Exception e) {
- Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e);
- }
- }
-
- /**
- * Set's the dim surface's layer and update dim parameters that will be used in
- * {@link #updateSurface} after all windows are examined.
- */
- void updateParameters(final Resources res, final Parameters params, final long currentTime) {
- if (mDimSurface == null) {
- Slog.e(TAG, "updateParameters: no Surface");
- return;
- }
-
- // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose a
- // corner.
- final int dw = (int) (params.mDimWidth * 1.5);
- final int dh = (int) (params.mDimHeight * 1.5);
- final WindowStateAnimator winAnimator = params.mDimWinAnimator;
- final float target = params.mDimTarget;
- if (!mDimShown) {
- if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
- " DIM " + mDimSurface + ": SHOW pos=(0,0) (" + dw + "x" + dh + ")");
- mDimShown = true;
- try {
- mLastDimWidth = dw;
- mLastDimHeight = dh;
- // back off position so mDimXXX/4 is before and mDimXXX/4 is after
- mDimSurface.setPosition(-1 * dw / 6, -1 * dh /6);
- mDimSurface.setSize(dw, dh);
- mDimSurface.show();
- } catch (RuntimeException e) {
- Slog.w(WindowManagerService.TAG, "Failure showing dim surface", e);
- }
- } else if (mLastDimWidth != dw || mLastDimHeight != dh) {
- mLastDimWidth = dw;
- mLastDimHeight = dh;
- mDimSurface.setSize(dw, dh);
- // back off position so mDimXXX/4 is before and mDimXXX/4 is after
- mDimSurface.setPosition(-1 * dw / 6, -1 * dh /6);
- }
-
- mDimSurface.setLayer(winAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM);
-
- if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM "
- + mDimSurface + ": layer=" + (winAnimator.mAnimLayer-1) + " target=" + target);
- if (mDimTargetAlpha != target) {
- // If the desired dim level has changed, then
- // start an animation to it.
- mLastDimAnimTime = currentTime;
- long duration = (winAnimator.mAnimating && winAnimator.mAnimation != null)
- ? winAnimator.mAnimation.computeDurationHint()
- : WindowManagerService.DEFAULT_DIM_DURATION;
- if (target > mDimTargetAlpha) {
- TypedValue tv = new TypedValue();
- res.getValue(com.android.internal.R.fraction.config_dimBehindFadeDuration,
- tv, true);
- if (tv.type == TypedValue.TYPE_FRACTION) {
- duration = (long)tv.getFraction(duration, duration);
- } else if (tv.type >= TypedValue.TYPE_FIRST_INT
- && tv.type <= TypedValue.TYPE_LAST_INT) {
- duration = tv.data;
- }
- }
- if (duration < 1) {
- // Don't divide by zero
- duration = 1;
- }
- mDimTargetAlpha = target;
- mDimDeltaPerMs = (mDimTargetAlpha-mDimCurrentAlpha) / duration;
- }
- }
-
- /**
- * Updating the surface's alpha. Returns true if the animation continues, or returns
- * false when the animation is finished and the dim surface is hidden.
- */
- boolean updateSurface(boolean dimming, long currentTime, boolean displayFrozen) {
- if (mDimSurface == null) {
- Slog.e(TAG, "updateSurface: no Surface");
- return false;
- }
-
- if (!dimming) {
- if (mDimTargetAlpha != 0) {
- mLastDimAnimTime = currentTime;
- mDimTargetAlpha = 0;
- mDimDeltaPerMs = (-mDimCurrentAlpha) / WindowManagerService.DEFAULT_DIM_DURATION;
- }
- }
-
- boolean animating = mLastDimAnimTime != 0;
- if (animating) {
- mDimCurrentAlpha += mDimDeltaPerMs
- * (currentTime-mLastDimAnimTime);
- if (displayFrozen) {
- // If the display is frozen, there is no reason to animate.
- animating = false;
- } else if (mDimDeltaPerMs > 0) {
- if (mDimCurrentAlpha > mDimTargetAlpha) {
- animating = false;
- }
- } else if (mDimDeltaPerMs < 0) {
- if (mDimCurrentAlpha < mDimTargetAlpha) {
- animating = false;
- }
- } else {
- animating = false;
- }
-
- // Do we need to continue animating?
- if (animating) {
- if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM "
- + mDimSurface + ": alpha=" + mDimCurrentAlpha);
- mLastDimAnimTime = currentTime;
- mDimSurface.setAlpha(mDimCurrentAlpha);
- } else {
- mDimCurrentAlpha = mDimTargetAlpha;
- mLastDimAnimTime = 0;
- if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM "
- + mDimSurface + ": final alpha=" + mDimCurrentAlpha);
- mDimSurface.setAlpha(mDimCurrentAlpha);
- if (!dimming) {
- if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface
- + ": HIDE");
- try {
- mDimSurface.hide();
- } catch (RuntimeException e) {
- Slog.w(WindowManagerService.TAG, "Illegal argument exception hiding dim surface");
- }
- mDimShown = false;
- }
- }
- }
- return animating;
- }
-
- public void kill() {
- if (mDimSurface != null) {
- mDimSurface.destroy();
- mDimSurface = null;
- }
- }
-
- public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix);
- pw.print("mDimSurface="); pw.print(mDimSurface);
- pw.print(" "); pw.print(mLastDimWidth); pw.print(" x ");
- pw.println(mLastDimHeight);
- pw.print(prefix);
- pw.print("mDimShown="); pw.print(mDimShown);
- pw.print(" current="); pw.print(mDimCurrentAlpha);
- pw.print(" target="); pw.print(mDimTargetAlpha);
- pw.print(" delta="); pw.print(mDimDeltaPerMs);
- pw.print(" lastAnimTime="); pw.println(mLastDimAnimTime);
- }
-
- static class Parameters {
- final WindowStateAnimator mDimWinAnimator;
- final int mDimWidth;
- final int mDimHeight;
- final float mDimTarget;
- Parameters(final WindowStateAnimator dimWinAnimator, final int dimWidth,
- final int dimHeight, final float dimTarget) {
- mDimWinAnimator = dimWinAnimator;
- mDimWidth = dimWidth;
- mDimHeight = dimHeight;
- mDimTarget = dimTarget;
- }
-
- Parameters(Parameters o) {
- mDimWinAnimator = o.mDimWinAnimator;
- mDimWidth = o.mDimWidth;
- mDimHeight = o.mDimHeight;
- mDimTarget = o.mDimTarget;
- }
-
- public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix);
- pw.print("mDimWinAnimator="); pw.print(mDimWinAnimator.mWin.mAttrs.getTitle());
- pw.print(" "); pw.print(mDimWidth); pw.print(" x ");
- pw.print(mDimHeight);
- pw.print(" mDimTarget="); pw.println(mDimTarget);
- }
- }
-}
diff --git a/services/java/com/android/server/wm/DimLayer.java b/services/java/com/android/server/wm/DimLayer.java
new file mode 100644
index 0000000..9bd36ba
--- /dev/null
+++ b/services/java/com/android/server/wm/DimLayer.java
@@ -0,0 +1,269 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.server.wm;
+
+import android.graphics.PixelFormat;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+
+public class DimLayer {
+ private static final String TAG = "DimLayer";
+ private static final boolean DEBUG = false;
+
+ /** Reference to the owner of this object. */
+ final DisplayContent mDisplayContent;
+
+ /** Actual surface that dims */
+ SurfaceControl mDimSurface;
+
+ /** Last value passed to mDimSurface.setAlpha() */
+ float mAlpha = 0;
+
+ /** Last value passed to mDimSurface.setLayer() */
+ int mLayer = -1;
+
+ /** Last values passed to mDimSurface.setSize() */
+ int mLastDimWidth, mLastDimHeight;
+
+ /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */
+ private boolean mShowing = false;
+
+ /** Value of mAlpha when beginning transition to mTargetAlpha */
+ float mStartAlpha = 0;
+
+ /** Final value of mAlpha following transition */
+ float mTargetAlpha = 0;
+
+ /** Time in units of SystemClock.uptimeMillis() at which the current transition started */
+ long mStartTime;
+
+ /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */
+ long mDuration;
+
+ DimLayer(WindowManagerService service, int displayId) {
+ if (DEBUG) Slog.v(TAG, "Ctor: displayId=" + displayId);
+ mDisplayContent = service.getDisplayContentLocked(displayId);
+ SurfaceControl.openTransaction();
+ try {
+ if (WindowManagerService.DEBUG_SURFACE_TRACE) {
+ mDimSurface = new WindowStateAnimator.SurfaceTrace(service.mFxSession,
+ "DimSurface",
+ 16, 16, PixelFormat.OPAQUE,
+ SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+ } else {
+ mDimSurface = new SurfaceControl(service.mFxSession, TAG,
+ 16, 16, PixelFormat.OPAQUE,
+ SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+ }
+ if (WindowManagerService.SHOW_TRANSACTIONS ||
+ WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(TAG,
+ " DIM " + mDimSurface + ": CREATE");
+ mDimSurface.setLayerStack(displayId);
+ } catch (Exception e) {
+ Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+
+ /** Return true if dim layer is showing */
+ boolean isDimming() {
+ return mTargetAlpha != 0;
+ }
+
+ /** Return true if in a transition period */
+ boolean isAnimating() {
+ return mTargetAlpha != mAlpha;
+ }
+
+ float getTargetAlpha() {
+ return mTargetAlpha;
+ }
+
+ void setLayer(int layer) {
+ if (mLayer != layer) {
+ mLayer = layer;
+ mDimSurface.setLayer(layer);
+ }
+ }
+
+ int getLayer() {
+ return mLayer;
+ }
+
+ private void setAlpha(float alpha) {
+ if (mAlpha != alpha) {
+ if (DEBUG) Slog.v(TAG, "setAlpha alpha=" + alpha);
+ try {
+ mDimSurface.setAlpha(alpha);
+ if (alpha == 0 && mShowing) {
+ if (DEBUG) Slog.v(TAG, "setAlpha hiding");
+ mDimSurface.hide();
+ mShowing = false;
+ } else if (alpha > 0 && !mShowing) {
+ if (DEBUG) Slog.v(TAG, "setAlpha showing");
+ mDimSurface.show();
+ mShowing = true;
+ }
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure setting alpha immediately", e);
+ }
+ mAlpha = alpha;
+ }
+ }
+
+ /**
+ * @param duration The time to test.
+ * @return True if the duration would lead to an earlier end to the current animation.
+ */
+ private boolean durationEndsEarlier(long duration) {
+ return SystemClock.uptimeMillis() + duration < mStartTime + mDuration;
+ }
+
+ /** Jump to the end of the animation.
+ * NOTE: Must be called with Surface transaction open. */
+ void show() {
+ if (isAnimating()) {
+ if (DEBUG) Slog.v(TAG, "show: immediate");
+ show(mLayer, mTargetAlpha, 0);
+ }
+ }
+
+ /**
+ * Begin an animation to a new dim value.
+ * NOTE: Must be called with Surface transaction open.
+ *
+ * @param layer The layer to set the surface to.
+ * @param alpha The dim value to end at.
+ * @param duration How long to take to get there in milliseconds.
+ */
+ void show(int layer, float alpha, long duration) {
+ if (DEBUG) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha
+ + " duration=" + duration);
+ if (mDimSurface == null) {
+ Slog.e(TAG, "show: no Surface");
+ // Make sure isAnimating() returns false.
+ mTargetAlpha = mAlpha = 0;
+ return;
+ }
+
+ // Set surface size to screen size.
+ final DisplayInfo info = mDisplayContent.getDisplayInfo();
+ // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose a
+ // corner.
+ final int dw = (int) (info.logicalWidth * 1.5);
+ final int dh = (int) (info.logicalHeight * 1.5);
+ // back off position so 1/4 of Surface is before and 1/4 is after.
+ final float xPos = -1 * dw / 6;
+ final float yPos = -1 * dh / 6;
+
+ if (mLastDimWidth != dw || mLastDimHeight != dh || mLayer != layer) {
+ try {
+ mDimSurface.setPosition(xPos, yPos);
+ mDimSurface.setSize(dw, dh);
+ mDimSurface.setLayer(layer);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure setting size or layer", e);
+ }
+ mLastDimWidth = dw;
+ mLastDimHeight = dh;
+ mLayer = layer;
+ }
+
+ long curTime = SystemClock.uptimeMillis();
+ final boolean animating = isAnimating();
+ if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration)))
+ || (!animating && mAlpha != alpha)) {
+ if (duration <= 0) {
+ // No animation required, just set values.
+ setAlpha(alpha);
+ } else {
+ // Start or continue animation with new parameters.
+ mStartAlpha = mAlpha;
+ mStartTime = curTime;
+ mDuration = duration;
+ }
+ }
+ if (DEBUG) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime=" + mStartTime);
+ mTargetAlpha = alpha;
+ }
+
+ /** Immediate hide.
+ * NOTE: Must be called with Surface transaction open. */
+ void hide() {
+ if (mShowing) {
+ if (DEBUG) Slog.v(TAG, "hide: immediate");
+ hide(0);
+ }
+ }
+
+ /**
+ * Gradually fade to transparent.
+ * NOTE: Must be called with Surface transaction open.
+ *
+ * @param duration Time to fade in milliseconds.
+ */
+ void hide(long duration) {
+ if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) {
+ if (DEBUG) Slog.v(TAG, "hide: duration=" + duration);
+ show(mLayer, 0, duration);
+ }
+ }
+
+ /**
+ * Advance the dimming per the last #show(int, float, long) call.
+ * NOTE: Must be called with Surface transaction open.
+ *
+ * @return True if animation is still required after this step.
+ */
+ boolean stepAnimation() {
+ if (mDimSurface == null) {
+ Slog.e(TAG, "stepAnimation: null Surface");
+ // Ensure that isAnimating() returns false;
+ mTargetAlpha = mAlpha = 0;
+ return false;
+ }
+
+ if (isAnimating()) {
+ final long curTime = SystemClock.uptimeMillis();
+ final float alphaDelta = mTargetAlpha - mStartAlpha;
+ float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration;
+ if (alphaDelta > 0 && alpha > mTargetAlpha ||
+ alphaDelta < 0 && alpha < mTargetAlpha) {
+ // Don't exceed limits.
+ alpha = mTargetAlpha;
+ }
+ if (DEBUG) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha);
+ setAlpha(alpha);
+ }
+
+ return isAnimating();
+ }
+
+ /** Cleanup */
+ void destroySurface() {
+ if (DEBUG) Slog.v(TAG, "destroySurface.");
+ if (mDimSurface != null) {
+ mDimSurface.destroy();
+ mDimSurface = null;
+ }
+ }
+
+ public void printTo(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mDimSurface="); pw.println(mDimSurface);
+ pw.print(prefix); pw.print(" mLayer="); pw.print(mLayer);
+ pw.print(" mAlpha="); pw.println(mAlpha);
+ pw.print(prefix); pw.print("mLastDimWidth="); pw.print(mLastDimWidth);
+ pw.print(" mLastDimWidth="); pw.println(mLastDimWidth);
+ pw.print(prefix); pw.print("Last animation: mStartTime="); pw.print(mStartTime);
+ pw.print(" mDuration="); pw.print(mDuration);
+ pw.print(" curTime="); pw.println(SystemClock.uptimeMillis());
+ pw.print(" mStartAlpha="); pw.println(mStartAlpha);
+ pw.print(" mTargetAlpha="); pw.print(mTargetAlpha);
+ }
+}
diff --git a/services/java/com/android/server/wm/DimSurface.java b/services/java/com/android/server/wm/DimSurface.java
deleted file mode 100644
index 4225868..0000000
--- a/services/java/com/android/server/wm/DimSurface.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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.wm;
-
-import android.graphics.PixelFormat;
-import android.util.Slog;
-import android.view.Surface;
-import android.view.SurfaceSession;
-
-import java.io.PrintWriter;
-
-class DimSurface {
- static final String TAG = "DimSurface";
-
- Surface mDimSurface;
- boolean mDimShown = false;
- int mDimColor = 0;
- int mLayer = -1;
- int mLastDimWidth, mLastDimHeight;
-
- DimSurface(SurfaceSession session, final int layerStack) {
- try {
- if (WindowManagerService.DEBUG_SURFACE_TRACE) {
- mDimSurface = new WindowStateAnimator.SurfaceTrace(session,
- "DimSurface",
- 16, 16, PixelFormat.OPAQUE,
- Surface.FX_SURFACE_DIM | Surface.HIDDEN);
- } else {
- mDimSurface = new Surface(session, "DimSurface",
- 16, 16, PixelFormat.OPAQUE,
- Surface.FX_SURFACE_DIM | Surface.HIDDEN);
- }
- if (WindowManagerService.SHOW_TRANSACTIONS ||
- WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
- " DIM " + mDimSurface + ": CREATE");
- mDimSurface.setLayerStack(layerStack);
- mDimSurface.setAlpha(0.0f);
- mDimSurface.show();
- } catch (Exception e) {
- Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e);
- }
- }
-
- /**
- * Show the dim surface.
- */
- void show(int dw, int dh, int layer, int color) {
- if (mDimSurface == null) {
- Slog.e(TAG, "show: no Surface");
- return;
- }
-
- if (!mDimShown) {
- if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": SHOW pos=(0,0) (" +
- dw + "x" + dh + " layer=" + layer + ")");
- mDimShown = true;
- try {
- mLastDimWidth = dw;
- mLastDimHeight = dh;
- mDimSurface.setPosition(0, 0);
- mDimSurface.setSize(dw, dh);
- mDimSurface.setLayer(layer);
- mDimSurface.show();
- } catch (RuntimeException e) {
- Slog.w(WindowManagerService.TAG, "Failure showing dim surface", e);
- }
- } else if (mLastDimWidth != dw || mLastDimHeight != dh || mDimColor != color
- || mLayer != layer) {
- if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": pos=(0,0) (" +
- dw + "x" + dh + " layer=" + layer + ")");
- mLastDimWidth = dw;
- mLastDimHeight = dh;
- mLayer = layer;
- mDimColor = color;
- mDimSurface.setSize(dw, dh);
- mDimSurface.setLayer(layer);
- mDimSurface.setAlpha(((color>>24)&0xff)/255.0f);
- }
- }
-
- void hide() {
- if (mDimSurface == null) {
- Slog.e(TAG, "hide: no Surface");
- return;
- }
-
- if (mDimShown) {
- mDimShown = false;
- try {
- if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " HIDE " + mDimSurface);
- mDimSurface.hide();
- } catch (RuntimeException e) {
- Slog.w(WindowManagerService.TAG, "Illegal argument exception hiding dim surface");
- }
- }
- }
-
- void kill() {
- if (mDimSurface != null) {
- mDimSurface.destroy();
- mDimSurface = null;
- }
- }
-
- public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mDimSurface="); pw.println(mDimSurface);
- pw.print(prefix); pw.print("mDimShown="); pw.print(mDimShown);
- pw.print(" mLayer="); pw.print(mLayer);
- pw.print(" mDimColor=0x"); pw.println(Integer.toHexString(mDimColor));
- pw.print(prefix); pw.print("mLastDimWidth="); pw.print(mLastDimWidth);
- pw.print(" mLastDimWidth="); pw.println(mLastDimWidth);
- }
-}
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index 68cdbfc..59e4b0e 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -16,10 +16,8 @@
package com.android.server.wm;
-import android.os.RemoteCallbackList;
import android.view.Display;
import android.view.DisplayInfo;
-import android.view.IDisplayContentChangeListener;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -43,12 +41,6 @@ class DisplayContent {
* from mDisplayWindows; */
private WindowList mWindows = new WindowList();
- // Specification for magnifying the display content.
- MagnificationSpec mMagnificationSpec;
-
- // Callback for observing content changes on a display.
- RemoteCallbackList<IDisplayContentChangeListener> mDisplayContentChangeListeners;
-
// This protects the following display size properties, so that
// getDisplaySize() doesn't need to acquire the global lock. This is
// needed because the window manager sometimes needs to use ActivityThread
@@ -128,9 +120,6 @@ class DisplayContent {
pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
pw.print(subPrefix); pw.print("layoutNeeded="); pw.print(layoutNeeded);
- if (mMagnificationSpec != null) {
- pw.print(" mMagnificationSpec="); pw.print(mMagnificationSpec);
- }
pw.println();
}
}
diff --git a/services/java/com/android/server/wm/DisplayMagnifier.java b/services/java/com/android/server/wm/DisplayMagnifier.java
new file mode 100644
index 0000000..0f51028
--- /dev/null
+++ b/services/java/com/android/server/wm/DisplayMagnifier.java
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2012 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.wm;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.Service;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Pools.SimplePool;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.IMagnificationCallbacks;
+import android.view.MagnificationSpec;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * This class is a part of the window manager and encapsulates the
+ * functionality related to display magnification.
+ */
+final class DisplayMagnifier {
+ private static final String LOG_TAG = DisplayMagnifier.class.getSimpleName();
+
+ private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
+ private static final boolean DEBUG_ROTATION = false;
+ private static final boolean DEBUG_LAYERS = false;
+ private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
+ private static final boolean DEBUG_VIEWPORT_WINDOW = false;
+
+ private final Rect mTempRect1 = new Rect();
+ private final Rect mTempRect2 = new Rect();
+
+ private final Region mTempRegion1 = new Region();
+ private final Region mTempRegion2 = new Region();
+ private final Region mTempRegion3 = new Region();
+ private final Region mTempRegion4 = new Region();
+
+ private final Context mContext;
+ private final WindowManagerService mWindowManagerService;
+ private final MagnifiedViewport mMagnifedViewport;
+ private final Handler mHandler;
+
+ private final IMagnificationCallbacks mCallbacks;
+
+ private final long mLongAnimationDuration;
+
+ public DisplayMagnifier(WindowManagerService windowManagerService,
+ IMagnificationCallbacks callbacks) {
+ mContext = windowManagerService.mContext;
+ mWindowManagerService = windowManagerService;
+ mCallbacks = callbacks;
+ mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+ mMagnifedViewport = new MagnifiedViewport();
+ mLongAnimationDuration = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+ }
+
+ public void setMagnificationSpecLocked(MagnificationSpec spec) {
+ mMagnifedViewport.updateMagnificationSpecLocked(spec);
+ mMagnifedViewport.recomputeBoundsLocked();
+ mWindowManagerService.scheduleAnimationLocked();
+ }
+
+ public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) {
+ if (DEBUG_RECTANGLE_REQUESTED) {
+ Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
+ }
+ if (!mMagnifedViewport.isMagnifyingLocked()) {
+ return;
+ }
+ Rect magnifiedRegionBounds = mTempRect2;
+ mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
+ if (magnifiedRegionBounds.contains(rectangle)) {
+ return;
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = rectangle.left;
+ args.argi2 = rectangle.top;
+ args.argi3 = rectangle.right;
+ args.argi4 = rectangle.bottom;
+ mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
+ args).sendToTarget();
+ }
+
+ public void onWindowLayersChangedLocked() {
+ if (DEBUG_LAYERS) {
+ Slog.i(LOG_TAG, "Layers changed.");
+ }
+ mMagnifedViewport.recomputeBoundsLocked();
+ mWindowManagerService.scheduleAnimationLocked();
+ }
+
+ public void onRotationChangedLocked(DisplayContent displayContent, int rotation) {
+ if (DEBUG_ROTATION) {
+ Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation)
+ + " displayId: " + displayContent.getDisplayId());
+ }
+ mMagnifedViewport.onRotationChangedLocked();
+ mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
+ }
+
+ public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
+ if (DEBUG_WINDOW_TRANSITIONS) {
+ Slog.i(LOG_TAG, "Window transition: "
+ + AppTransition.appTransitionToString(transition)
+ + " displayId: " + windowState.getDisplayId());
+ }
+ final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+ if (magnifying) {
+ switch (transition) {
+ case AppTransition.TRANSIT_ACTIVITY_OPEN:
+ case AppTransition.TRANSIT_TASK_OPEN:
+ case AppTransition.TRANSIT_TASK_TO_FRONT:
+ case AppTransition.TRANSIT_WALLPAPER_OPEN:
+ case AppTransition.TRANSIT_WALLPAPER_CLOSE:
+ case AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN: {
+ mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
+ }
+ }
+ }
+ }
+
+ public void onWindowTransitionLocked(WindowState windowState, int transition) {
+ if (DEBUG_WINDOW_TRANSITIONS) {
+ Slog.i(LOG_TAG, "Window transition: "
+ + AppTransition.appTransitionToString(transition)
+ + " displayId: " + windowState.getDisplayId());
+ }
+ final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+ final int type = windowState.mAttrs.type;
+ switch (transition) {
+ case WindowManagerPolicy.TRANSIT_ENTER:
+ case WindowManagerPolicy.TRANSIT_SHOW: {
+ if (!magnifying) {
+ break;
+ }
+ switch (type) {
+ case WindowManager.LayoutParams.TYPE_APPLICATION:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
+ case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
+ case WindowManager.LayoutParams.TYPE_PHONE:
+ case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
+ case WindowManager.LayoutParams.TYPE_TOAST:
+ case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
+ case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
+ case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
+ case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
+ case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
+ case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
+ case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
+ case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
+ Rect magnifiedRegionBounds = mTempRect2;
+ mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
+ magnifiedRegionBounds);
+ Rect touchableRegionBounds = mTempRect1;
+ windowState.getTouchableRegion(mTempRegion1);
+ mTempRegion1.getBounds(touchableRegionBounds);
+ if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
+ try {
+ mCallbacks.onRectangleOnScreenRequested(
+ touchableRegionBounds.left,
+ touchableRegionBounds.top,
+ touchableRegionBounds.right,
+ touchableRegionBounds.bottom);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+ } break;
+ } break;
+ }
+ }
+ }
+
+ public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
+ MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
+ if (spec != null && !spec.isNop()) {
+ WindowManagerPolicy policy = mWindowManagerService.mPolicy;
+ final int windowType = windowState.mAttrs.type;
+ if (!policy.isTopLevelWindow(windowType) && windowState.mAttachedWindow != null
+ && !policy.canMagnifyWindow(windowType)) {
+ return null;
+ }
+ if (!policy.canMagnifyWindow(windowState.mAttrs.type)) {
+ return null;
+ }
+ }
+ return spec;
+ }
+
+ public void destroyLocked() {
+ mMagnifedViewport.destroyWindow();
+ }
+
+ /** NOTE: This has to be called within a surface transaction. */
+ public void drawMagnifiedRegionBorderIfNeededLocked() {
+ mMagnifedViewport.drawWindowIfNeededLocked();
+ }
+
+ private final class MagnifiedViewport {
+
+ private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5;
+
+ private final SparseArray<WindowStateInfo> mTempWindowStateInfos =
+ new SparseArray<WindowStateInfo>();
+
+ private final float[] mTempFloats = new float[9];
+
+ private final RectF mTempRectF = new RectF();
+
+ private final Point mTempPoint = new Point();
+
+ private final Matrix mTempMatrix = new Matrix();
+
+ private final Region mMagnifiedBounds = new Region();
+ private final Region mOldMagnifiedBounds = new Region();
+
+ private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain();
+
+ private final WindowManager mWindowManager;
+
+ private final int mBorderWidth;
+ private final int mHalfBorderWidth;
+
+ private final ViewportWindow mWindow;
+
+ private boolean mFullRedrawNeeded;
+
+ public MagnifiedViewport() {
+ mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
+ mBorderWidth = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP,
+ mContext.getResources().getDisplayMetrics());
+ mHalfBorderWidth = (int) (mBorderWidth + 0.5) / 2;
+ mWindow = new ViewportWindow(mContext);
+ recomputeBoundsLocked();
+ }
+
+ public void updateMagnificationSpecLocked(MagnificationSpec spec) {
+ if (spec != null) {
+ mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
+ } else {
+ mMagnificationSpec.clear();
+ }
+ // If this message is pending we are in a rotation animation and do not want
+ // to show the border. We will do so when the pending message is handled.
+ if (!mHandler.hasMessages(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
+ setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true);
+ }
+ }
+
+ public void recomputeBoundsLocked() {
+ mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ final int screenWidth = mTempPoint.x;
+ final int screenHeight = mTempPoint.y;
+
+ Region magnifiedBounds = mMagnifiedBounds;
+ magnifiedBounds.set(0, 0, 0, 0);
+
+ Region availableBounds = mTempRegion1;
+ availableBounds.set(0, 0, screenWidth, screenHeight);
+
+ Region nonMagnifiedBounds = mTempRegion4;
+ nonMagnifiedBounds.set(0, 0, 0, 0);
+
+ SparseArray<WindowStateInfo> visibleWindows = mTempWindowStateInfos;
+ visibleWindows.clear();
+ getWindowsOnScreenLocked(visibleWindows);
+
+ final int visibleWindowCount = visibleWindows.size();
+ for (int i = visibleWindowCount - 1; i >= 0; i--) {
+ WindowStateInfo info = visibleWindows.valueAt(i);
+ if (info.mWindowState.mAttrs.type == WindowManager
+ .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
+ continue;
+ }
+
+ Region windowBounds = mTempRegion2;
+ Matrix matrix = mTempMatrix;
+ populateTransformationMatrix(info.mWindowState, matrix);
+ RectF windowFrame = mTempRectF;
+
+ if (mWindowManagerService.mPolicy.canMagnifyWindow(info.mWindowState.mAttrs.type)) {
+ windowFrame.set(info.mWindowState.mFrame);
+ windowFrame.offset(-windowFrame.left, -windowFrame.top);
+ matrix.mapRect(windowFrame);
+ windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+ (int) windowFrame.right, (int) windowFrame.bottom);
+ magnifiedBounds.op(windowBounds, Region.Op.UNION);
+ magnifiedBounds.op(availableBounds, Region.Op.INTERSECT);
+ } else {
+ windowFrame.set(info.mTouchableRegion);
+ windowFrame.offset(-info.mWindowState.mFrame.left,
+ -info.mWindowState.mFrame.top);
+ matrix.mapRect(windowFrame);
+ windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+ (int) windowFrame.right, (int) windowFrame.bottom);
+ nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
+ windowBounds.op(magnifiedBounds, Region.Op.DIFFERENCE);
+ availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
+ }
+
+ Region accountedBounds = mTempRegion2;
+ accountedBounds.set(magnifiedBounds);
+ accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
+ accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
+
+ if (accountedBounds.isRect()) {
+ Rect accountedFrame = mTempRect1;
+ accountedBounds.getBounds(accountedFrame);
+ if (accountedFrame.width() == screenWidth
+ && accountedFrame.height() == screenHeight) {
+ break;
+ }
+ }
+ }
+
+ for (int i = visibleWindowCount - 1; i >= 0; i--) {
+ WindowStateInfo info = visibleWindows.valueAt(i);
+ info.recycle();
+ visibleWindows.removeAt(i);
+ }
+
+ magnifiedBounds.op(mHalfBorderWidth, mHalfBorderWidth,
+ screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth,
+ Region.Op.INTERSECT);
+
+ if (!mOldMagnifiedBounds.equals(magnifiedBounds)) {
+ Region bounds = Region.obtain();
+ bounds.set(magnifiedBounds);
+ mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED,
+ bounds).sendToTarget();
+
+ mWindow.setBounds(magnifiedBounds);
+ Rect dirtyRect = mTempRect1;
+ if (mFullRedrawNeeded) {
+ mFullRedrawNeeded = false;
+ dirtyRect.set(mHalfBorderWidth, mHalfBorderWidth,
+ screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth);
+ mWindow.invalidate(dirtyRect);
+ } else {
+ Region dirtyRegion = mTempRegion3;
+ dirtyRegion.set(magnifiedBounds);
+ dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
+ dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
+ dirtyRegion.getBounds(dirtyRect);
+ mWindow.invalidate(dirtyRect);
+ }
+
+ mOldMagnifiedBounds.set(magnifiedBounds);
+ }
+ }
+
+ private void populateTransformationMatrix(WindowState windowState, Matrix outMatrix) {
+ mTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx;
+ mTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx;
+ mTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDsDy;
+ mTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDtDy;
+ mTempFloats[Matrix.MTRANS_X] = windowState.mShownFrame.left;
+ mTempFloats[Matrix.MTRANS_Y] = windowState.mShownFrame.top;
+ mTempFloats[Matrix.MPERSP_0] = 0;
+ mTempFloats[Matrix.MPERSP_1] = 0;
+ mTempFloats[Matrix.MPERSP_2] = 1;
+ outMatrix.setValues(mTempFloats);
+ }
+
+ private void getWindowsOnScreenLocked(SparseArray<WindowStateInfo> outWindowStates) {
+ DisplayContent displayContent = mWindowManagerService.getDefaultDisplayContentLocked();
+ WindowList windowList = displayContent.getWindowList();
+ final int windowCount = windowList.size();
+ for (int i = 0; i < windowCount; i++) {
+ WindowState windowState = windowList.get(i);
+ if ((windowState.isOnScreen() || windowState.mAttrs.type == WindowManager
+ .LayoutParams.TYPE_UNIVERSE_BACKGROUND)
+ && !windowState.mWinAnimator.mEnterAnimationPending) {
+ outWindowStates.put(windowState.mLayer, WindowStateInfo.obtain(windowState));
+ }
+ }
+ }
+
+ public void onRotationChangedLocked() {
+ // If we are magnifying, hide the magnified border window immediately so
+ // the user does not see strange artifacts during rotation. The screenshot
+ // used for rotation has already the border. After the rotation is complete
+ // we will show the border.
+ if (isMagnifyingLocked()) {
+ setMagnifiedRegionBorderShownLocked(false, false);
+ final long delay = (long) (mLongAnimationDuration
+ * mWindowManagerService.mWindowAnimationScale);
+ Message message = mHandler.obtainMessage(
+ MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
+ mHandler.sendMessageDelayed(message, delay);
+ }
+ recomputeBoundsLocked();
+ mWindow.updateSize();
+ }
+
+ public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
+ if (shown) {
+ mFullRedrawNeeded = true;
+ mOldMagnifiedBounds.set(0, 0, 0, 0);
+ }
+ mWindow.setShown(shown, animate);
+ }
+
+ public void getMagnifiedFrameInContentCoordsLocked(Rect rect) {
+ MagnificationSpec spec = mMagnificationSpec;
+ mMagnifiedBounds.getBounds(rect);
+ rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
+ rect.scale(1.0f / spec.scale);
+ }
+
+ public boolean isMagnifyingLocked() {
+ return mMagnificationSpec.scale > 1.0f;
+ }
+
+ public MagnificationSpec getMagnificationSpecLocked() {
+ return mMagnificationSpec;
+ }
+
+ /** NOTE: This has to be called within a surface transaction. */
+ public void drawWindowIfNeededLocked() {
+ recomputeBoundsLocked();
+ mWindow.drawIfNeeded();
+ }
+
+ public void destroyWindow() {
+ mWindow.releaseSurface();
+ }
+
+ private final class ViewportWindow {
+ private static final String SURFACE_TITLE = "Magnification Overlay";
+
+ private static final String PROPERTY_NAME_ALPHA = "alpha";
+
+ private static final int MIN_ALPHA = 0;
+ private static final int MAX_ALPHA = 255;
+
+ private final Region mBounds = new Region();
+ private final Rect mDirtyRect = new Rect();
+ private final Paint mPaint = new Paint();
+
+ private final ValueAnimator mShowHideFrameAnimator;
+ private final SurfaceControl mSurfaceControl;
+ private final Surface mSurface = new Surface();
+
+ private boolean mShown;
+ private int mAlpha;
+
+ private boolean mInvalidated;
+
+ public ViewportWindow(Context context) {
+ SurfaceControl surfaceControl = null;
+ try {
+ mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession, SURFACE_TITLE,
+ mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+ } catch (SurfaceControl.OutOfResourcesException oore) {
+ /* ignore */
+ }
+ mSurfaceControl = surfaceControl;
+ mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay().getLayerStack());
+ mSurfaceControl.setLayer(mWindowManagerService.mPolicy.windowTypeToLayerLw(
+ WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY)
+ * WindowManagerService.TYPE_LAYER_MULTIPLIER);
+ mSurfaceControl.setPosition(0, 0);
+ mSurface.copyFrom(mSurfaceControl);
+
+ TypedValue typedValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
+ typedValue, true);
+ final int borderColor = context.getResources().getColor(typedValue.resourceId);
+
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(mBorderWidth);
+ mPaint.setColor(borderColor);
+
+ Interpolator interpolator = new DecelerateInterpolator(2.5f);
+ final long longAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+
+ mShowHideFrameAnimator = ObjectAnimator.ofInt(this, PROPERTY_NAME_ALPHA,
+ MIN_ALPHA, MAX_ALPHA);
+ mShowHideFrameAnimator.setInterpolator(interpolator);
+ mShowHideFrameAnimator.setDuration(longAnimationDuration);
+ mInvalidated = true;
+ }
+
+ public void setShown(boolean shown, boolean animate) {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (mShown == shown) {
+ return;
+ }
+ mShown = shown;
+ if (animate) {
+ if (mShowHideFrameAnimator.isRunning()) {
+ mShowHideFrameAnimator.reverse();
+ } else {
+ if (shown) {
+ mShowHideFrameAnimator.start();
+ } else {
+ mShowHideFrameAnimator.reverse();
+ }
+ }
+ } else {
+ mShowHideFrameAnimator.cancel();
+ if (shown) {
+ setAlpha(MAX_ALPHA);
+ } else {
+ setAlpha(MIN_ALPHA);
+ }
+ }
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ // Called reflectively from an animator.
+ public int getAlpha() {
+ synchronized (mWindowManagerService.mWindowMap) {
+ return mAlpha;
+ }
+ }
+
+ public void setAlpha(int alpha) {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (mAlpha == alpha) {
+ return;
+ }
+ mAlpha = alpha;
+ invalidate(null);
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
+ }
+ }
+ }
+
+ public void setBounds(Region bounds) {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (mBounds.equals(bounds)) {
+ return;
+ }
+ mBounds.set(bounds);
+ invalidate(mDirtyRect);
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
+ }
+ }
+ }
+
+ public void updateSize() {
+ synchronized (mWindowManagerService.mWindowMap) {
+ mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y);
+ invalidate(mDirtyRect);
+ }
+ }
+
+ public void invalidate(Rect dirtyRect) {
+ if (dirtyRect != null) {
+ mDirtyRect.set(dirtyRect);
+ } else {
+ mDirtyRect.setEmpty();
+ }
+ mInvalidated = true;
+ mWindowManagerService.scheduleAnimationLocked();
+ }
+
+ /** NOTE: This has to be called within a surface transaction. */
+ public void drawIfNeeded() {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (!mInvalidated) {
+ return;
+ }
+ mInvalidated = false;
+ Canvas canvas = null;
+ try {
+ // Empty dirty rectangle means unspecified.
+ if (mDirtyRect.isEmpty()) {
+ mBounds.getBounds(mDirtyRect);
+ }
+ mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth);
+ canvas = mSurface.lockCanvas(mDirtyRect);
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
+ }
+ } catch (IllegalArgumentException iae) {
+ /* ignore */
+ } catch (OutOfResourcesException oore) {
+ /* ignore */
+ }
+ if (canvas == null) {
+ return;
+ }
+ if (DEBUG_VIEWPORT_WINDOW) {
+ Slog.i(LOG_TAG, "Bounds: " + mBounds);
+ }
+ canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+ mPaint.setAlpha(mAlpha);
+ Path path = mBounds.getBoundaryPath();
+ canvas.drawPath(path, mPaint);
+
+ mSurface.unlockCanvasAndPost(canvas);
+
+ if (mAlpha > 0) {
+ mSurfaceControl.show();
+ } else {
+ mSurfaceControl.hide();
+ }
+ }
+ }
+
+ public void releaseSurface() {
+ mSurfaceControl.release();
+ mSurface.release();
+ }
+ }
+ }
+
+ private static final class WindowStateInfo {
+ private static final int MAX_POOL_SIZE = 30;
+
+ private static final SimplePool<WindowStateInfo> sPool =
+ new SimplePool<WindowStateInfo>(MAX_POOL_SIZE);
+
+ private static final Region mTempRegion = new Region();
+
+ public WindowState mWindowState;
+ public final Rect mTouchableRegion = new Rect();
+
+ public static WindowStateInfo obtain(WindowState windowState) {
+ WindowStateInfo info = sPool.acquire();
+ if (info == null) {
+ info = new WindowStateInfo();
+ }
+ info.mWindowState = windowState;
+ windowState.getTouchableRegion(mTempRegion);
+ mTempRegion.getBounds(info.mTouchableRegion);
+ return info;
+ }
+
+ public void recycle() {
+ mWindowState = null;
+ mTouchableRegion.setEmpty();
+ sPool.release(this);
+ }
+ }
+
+ private class MyHandler extends Handler {
+ public static final int MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED = 1;
+ public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+ public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
+ public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
+ public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
+
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: {
+ Region bounds = (Region) message.obj;
+ try {
+ mCallbacks.onMagnifedBoundsChanged(bounds);
+ } catch (RemoteException re) {
+ /* ignore */
+ } finally {
+ bounds.recycle();
+ }
+ } break;
+ case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ final int left = args.argi1;
+ final int top = args.argi2;
+ final int right = args.argi3;
+ final int bottom = args.argi4;
+ try {
+ mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom);
+ } catch (RemoteException re) {
+ /* ignore */
+ } finally {
+ args.recycle();
+ }
+ } break;
+ case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
+ try {
+ mCallbacks.onUserContextChanged();
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ } break;
+ case MESSAGE_NOTIFY_ROTATION_CHANGED: {
+ final int rotation = message.arg1;
+ try {
+ mCallbacks.onRotationChanged(rotation);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ } break;
+ case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
+ synchronized (mWindowManagerService.mWindowMap) {
+ if (mMagnifedViewport.isMagnifyingLocked()) {
+ mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
+ mWindowManagerService.scheduleAnimationLocked();
+ }
+ }
+ } break;
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/wm/DisplaySettings.java b/services/java/com/android/server/wm/DisplaySettings.java
new file mode 100644
index 0000000..34d1a64
--- /dev/null
+++ b/services/java/com/android/server/wm/DisplaySettings.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2013 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.wm;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * Current persistent settings about a display
+ */
+public class DisplaySettings {
+ private static final String TAG = WindowManagerService.TAG;
+
+ private final Context mContext;
+ private final AtomicFile mFile;
+ private final HashMap<String, Entry> mEntries = new HashMap<String, Entry>();
+
+ public static class Entry {
+ public final String name;
+ public int overscanLeft;
+ public int overscanTop;
+ public int overscanRight;
+ public int overscanBottom;
+
+ public Entry(String _name) {
+ name = _name;
+ }
+ }
+
+ public DisplaySettings(Context context) {
+ mContext = context;
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ mFile = new AtomicFile(new File(systemDir, "display_settings.xml"));
+ }
+
+ public void getOverscanLocked(String name, Rect outRect) {
+ Entry entry = mEntries.get(name);
+ if (entry != null) {
+ outRect.left = entry.overscanLeft;
+ outRect.top = entry.overscanTop;
+ outRect.right = entry.overscanRight;
+ outRect.bottom = entry.overscanBottom;
+ } else {
+ outRect.set(0, 0, 0, 0);
+ }
+ }
+
+ public void setOverscanLocked(String name, int left, int top, int right, int bottom) {
+ if (left == 0 && top == 0 && right == 0 && bottom == 0) {
+ // Right now all we are storing is overscan; if there is no overscan,
+ // we have no need for the entry.
+ mEntries.remove(name);
+ return;
+ }
+ Entry entry = mEntries.get(name);
+ if (entry == null) {
+ entry = new Entry(name);
+ mEntries.put(name, entry);
+ }
+ entry.overscanLeft = left;
+ entry.overscanTop = top;
+ entry.overscanRight = right;
+ entry.overscanBottom = bottom;
+ }
+
+ public void readSettingsLocked() {
+ FileInputStream stream;
+ try {
+ stream = mFile.openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "No existing display settings " + mFile.getBaseFile()
+ + "; starting empty");
+ return;
+ }
+ boolean success = false;
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("display")) {
+ readDisplay(parser);
+ } else {
+ Slog.w(TAG, "Unknown element under <display-settings>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ success = true;
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } finally {
+ if (!success) {
+ mEntries.clear();
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ private int getIntAttribute(XmlPullParser parser, String name) {
+ try {
+ String str = parser.getAttributeValue(null, name);
+ return str != null ? Integer.parseInt(str) : 0;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ private void readDisplay(XmlPullParser parser) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ String name = parser.getAttributeValue(null, "name");
+ if (name != null) {
+ Entry entry = new Entry(name);
+ entry.overscanLeft = getIntAttribute(parser, "overscanLeft");
+ entry.overscanTop = getIntAttribute(parser, "overscanTop");
+ entry.overscanRight = getIntAttribute(parser, "overscanRight");
+ entry.overscanBottom = getIntAttribute(parser, "overscanBottom");
+ mEntries.put(name, entry);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+
+ public void writeSettingsLocked() {
+ FileOutputStream stream;
+ try {
+ stream = mFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write display settings: " + e);
+ return;
+ }
+
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, "display-settings");
+
+ for (Entry entry : mEntries.values()) {
+ out.startTag(null, "display");
+ out.attribute(null, "name", entry.name);
+ if (entry.overscanLeft != 0) {
+ out.attribute(null, "overscanLeft", Integer.toString(entry.overscanLeft));
+ }
+ if (entry.overscanTop != 0) {
+ out.attribute(null, "overscanTop", Integer.toString(entry.overscanTop));
+ }
+ if (entry.overscanRight != 0) {
+ out.attribute(null, "overscanRight", Integer.toString(entry.overscanRight));
+ }
+ if (entry.overscanBottom != 0) {
+ out.attribute(null, "overscanBottom", Integer.toString(entry.overscanBottom));
+ }
+ out.endTag(null, "display");
+ }
+
+ out.endTag(null, "display-settings");
+ out.endDocument();
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write display settings, restoring backup.", e);
+ mFile.failWrite(stream);
+ }
+ }
+}
diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java
index 72fc180..745b886 100644
--- a/services/java/com/android/server/wm/DragState.java
+++ b/services/java/com/android/server/wm/DragState.java
@@ -34,6 +34,7 @@ import android.view.Display;
import android.view.DragEvent;
import android.view.InputChannel;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -45,7 +46,7 @@ import java.util.ArrayList;
class DragState {
final WindowManagerService mService;
IBinder mToken;
- Surface mSurface;
+ SurfaceControl mSurfaceControl;
int mFlags;
IBinder mLocalWin;
ClipData mData;
@@ -64,21 +65,21 @@ class DragState {
private final Region mTmpRegion = new Region();
- DragState(WindowManagerService service, IBinder token, Surface surface,
+ DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
int flags, IBinder localWin) {
mService = service;
mToken = token;
- mSurface = surface;
+ mSurfaceControl = surface;
mFlags = flags;
mLocalWin = localWin;
mNotifiedWindows = new ArrayList<WindowState>();
}
void reset() {
- if (mSurface != null) {
- mSurface.destroy();
+ if (mSurfaceControl != null) {
+ mSurfaceControl.destroy();
}
- mSurface = null;
+ mSurfaceControl = null;
mFlags = 0;
mLocalWin = null;
mToken = null;
@@ -293,14 +294,14 @@ class DragState {
// Move the surface to the given touch
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
WindowManagerService.TAG, ">>> OPEN TRANSACTION notifyMoveLw");
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
- mSurface.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
+ mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DRAG "
- + mSurface + ": pos=(" +
+ + mSurfaceControl + ": pos=(" +
(int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
WindowManagerService.TAG, "<<< CLOSE TRANSACTION notifyMoveLw");
}
diff --git a/services/java/com/android/server/wm/MagnificationSpec.java b/services/java/com/android/server/wm/MagnificationSpec.java
deleted file mode 100644
index 31aae66..0000000
--- a/services/java/com/android/server/wm/MagnificationSpec.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2012 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.wm;
-
-public class MagnificationSpec {
- public float mScale = 1.0f;
- public float mOffsetX;
- public float mOffsetY;
-
- public void initialize(float scale, float offsetX, float offsetY) {
- mScale = scale;
- mOffsetX = offsetX;
- mOffsetY = offsetY;
- }
-
- public boolean isNop() {
- return mScale == 1.0f && mOffsetX == 0 && mOffsetY == 0;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("<scale:");
- builder.append(mScale);
- builder.append(",offsetX:");
- builder.append(mOffsetX);
- builder.append(",offsetY:");
- builder.append(mOffsetY);
- builder.append(">");
- return builder.toString();
- }
-}
diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java
index cfcf841..b2fbec1 100644
--- a/services/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -27,6 +27,7 @@ import android.graphics.Rect;
import android.util.Slog;
import android.view.Display;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -43,12 +44,11 @@ class ScreenRotationAnimation {
final Context mContext;
final Display mDisplay;
- Surface mSurface;
+ SurfaceControl mSurfaceControl;
BlackFrame mCustomBlackFrame;
BlackFrame mExitingBlackFrame;
BlackFrame mEnteringBlackFrame;
int mWidth, mHeight;
- int mExitAnimId, mEnterAnimId;
int mOriginalRotation;
int mOriginalWidth, mOriginalHeight;
@@ -127,7 +127,7 @@ class ScreenRotationAnimation {
long mHalfwayPoint;
public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mSurface="); pw.print(mSurface);
+ pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl);
pw.print(" mWidth="); pw.print(mWidth);
pw.print(" mHeight="); pw.println(mHeight);
if (USE_CUSTOM_BLACK_FRAME) {
@@ -189,12 +189,9 @@ class ScreenRotationAnimation {
}
public ScreenRotationAnimation(Context context, Display display, SurfaceSession session,
- boolean inTransaction, int originalWidth, int originalHeight, int originalRotation,
- int exitAnim, int enterAnim) {
+ boolean inTransaction, int originalWidth, int originalHeight, int originalRotation) {
mContext = context;
mDisplay = display;
- mExitAnimId = exitAnim;
- mEnterAnimId = enterAnim;
// Screenshot does NOT include rotation!
if (originalRotation == Surface.ROTATION_90
@@ -213,41 +210,43 @@ class ScreenRotationAnimation {
if (!inTransaction) {
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
">>> OPEN TRANSACTION ScreenRotationAnimation");
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
}
try {
try {
if (WindowManagerService.DEBUG_SURFACE_TRACE) {
- mSurface = new SurfaceTrace(session, "FreezeSurface",
+ mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface",
mWidth, mHeight,
- PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN);
+ PixelFormat.OPAQUE, SurfaceControl.HIDDEN);
} else {
- mSurface = new Surface(session, "FreezeSurface",
+ mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
mWidth, mHeight,
- PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN);
- }
- if (!mSurface.isValid()) {
- // Screenshot failed, punt.
- mSurface = null;
- return;
+ PixelFormat.OPAQUE, SurfaceControl.HIDDEN);
}
- mSurface.setLayerStack(mDisplay.getLayerStack());
- mSurface.setLayer(FREEZE_LAYER + 1);
- mSurface.setAlpha(0);
- mSurface.show();
- } catch (Surface.OutOfResourcesException e) {
+ // capture a screenshot into the surface we just created
+ Surface sur = new Surface();
+ sur.copyFrom(mSurfaceControl);
+ // FIXME: we should use the proper display
+ SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
+ SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
+ mSurfaceControl.setLayerStack(mDisplay.getLayerStack());
+ mSurfaceControl.setLayer(FREEZE_LAYER + 1);
+ mSurfaceControl.setAlpha(0);
+ mSurfaceControl.show();
+ sur.destroy();
+ } catch (SurfaceControl.OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate freeze surface", e);
}
if (WindowManagerService.SHOW_TRANSACTIONS ||
WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
- " FREEZE " + mSurface + ": CREATE");
+ " FREEZE " + mSurfaceControl + ": CREATE");
setRotationInTransaction(originalRotation);
} finally {
if (!inTransaction) {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
"<<< CLOSE TRANSACTION ScreenRotationAnimation");
}
@@ -255,7 +254,7 @@ class ScreenRotationAnimation {
}
boolean hasScreenshot() {
- return mSurface != null;
+ return mSurfaceControl != null;
}
static int deltaRotation(int oldRotation, int newRotation) {
@@ -265,14 +264,13 @@ class ScreenRotationAnimation {
}
private void setSnapshotTransformInTransaction(Matrix matrix, float alpha) {
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
matrix.getValues(mTmpFloats);
- mSurface.setPosition(mTmpFloats[Matrix.MTRANS_X],
- mTmpFloats[Matrix.MTRANS_Y]);
- mSurface.setMatrix(
+ mSurfaceControl.setPosition(mTmpFloats[Matrix.MTRANS_X], mTmpFloats[Matrix.MTRANS_Y]);
+ mSurfaceControl.setMatrix(
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
- mSurface.setAlpha(alpha);
+ mSurfaceControl.setAlpha(alpha);
if (DEBUG_TRANSFORMS) {
float[] srcPnts = new float[] { 0, 0, mWidth, mHeight };
float[] dstPnts = new float[4];
@@ -326,7 +324,7 @@ class ScreenRotationAnimation {
setRotationInTransaction(rotation);
if (TWO_PHASE_ANIMATION) {
return startAnimation(session, maxAnimationDuration, animationScale,
- finalWidth, finalHeight, false);
+ finalWidth, finalHeight, false, 0, 0);
}
// Don't start animation yet.
@@ -337,8 +335,9 @@ class ScreenRotationAnimation {
* Returns true if animating.
*/
private boolean startAnimation(SurfaceSession session, long maxAnimationDuration,
- float animationScale, int finalWidth, int finalHeight, boolean dismissing) {
- if (mSurface == null) {
+ float animationScale, int finalWidth, int finalHeight, boolean dismissing,
+ int exitAnim, int enterAnim) {
+ if (mSurfaceControl == null) {
// Can't do animation.
return false;
}
@@ -380,10 +379,10 @@ class ScreenRotationAnimation {
+ " origWidth=" + mOriginalWidth + " origHeight=" + mOriginalHeight);
final boolean customAnim;
- if (mExitAnimId != 0 && mEnterAnimId != 0) {
+ if (exitAnim != 0 && enterAnim != 0) {
customAnim = true;
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, mExitAnimId);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, mEnterAnimId);
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
} else {
customAnim = false;
switch (delta) {
@@ -497,7 +496,7 @@ class ScreenRotationAnimation {
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
WindowManagerService.TAG,
">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
// Compute the transformation matrix that must be applied
// the the black frame to make it stay in the initial position
@@ -514,10 +513,10 @@ class ScreenRotationAnimation {
mCustomBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 3,
layerStack);
mCustomBlackFrame.setMatrix(mFrameInitialMatrix);
- } catch (Surface.OutOfResourcesException e) {
+ } catch (SurfaceControl.OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
WindowManagerService.TAG,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -528,7 +527,7 @@ class ScreenRotationAnimation {
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
WindowManagerService.TAG,
">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
// Compute the transformation matrix that must be applied
// the the black frame to make it stay in the initial position
@@ -544,10 +543,10 @@ class ScreenRotationAnimation {
mExitingBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 2,
layerStack);
mExitingBlackFrame.setMatrix(mFrameInitialMatrix);
- } catch (Surface.OutOfResourcesException e) {
+ } catch (SurfaceControl.OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
WindowManagerService.TAG,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -558,7 +557,7 @@ class ScreenRotationAnimation {
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
WindowManagerService.TAG,
">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
@@ -566,10 +565,10 @@ class ScreenRotationAnimation {
Rect inner = new Rect(0, 0, finalWidth, finalHeight);
mEnteringBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER,
layerStack);
- } catch (Surface.OutOfResourcesException e) {
+ } catch (SurfaceControl.OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
WindowManagerService.TAG,
"<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -583,15 +582,15 @@ class ScreenRotationAnimation {
* Returns true if animating.
*/
public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
- float animationScale, int finalWidth, int finalHeight) {
+ float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
if (DEBUG_STATE) Slog.v(TAG, "Dismiss!");
- if (mSurface == null) {
+ if (mSurfaceControl == null) {
// Can't do animation.
return false;
}
if (!mStarted) {
startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight,
- true);
+ true, exitAnim, enterAnim);
}
if (!mStarted) {
return false;
@@ -603,12 +602,12 @@ class ScreenRotationAnimation {
public void kill() {
if (DEBUG_STATE) Slog.v(TAG, "Kill!");
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
if (WindowManagerService.SHOW_TRANSACTIONS ||
WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
- " FREEZE " + mSurface + ": DESTROY");
- mSurface.destroy();
- mSurface = null;
+ " FREEZE " + mSurfaceControl + ": DESTROY");
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
}
if (mCustomBlackFrame != null) {
mCustomBlackFrame.kill();
@@ -866,10 +865,10 @@ class ScreenRotationAnimation {
return;
}
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
if (!mMoreStartExit && !mMoreFinishExit && !mMoreRotateExit) {
if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, hiding screenshot surface");
- mSurface.hide();
+ mSurfaceControl.hide();
}
}
diff --git a/services/java/com/android/server/wm/Session.java b/services/java/com/android/server/wm/Session.java
index 3b4c1ab..1d95c44 100644
--- a/services/java/com/android/server/wm/Session.java
+++ b/services/java/com/android/server/wm/Session.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.view.IWindowId;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
@@ -40,6 +41,7 @@ import android.view.IWindow;
import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
@@ -181,13 +183,13 @@ final class Session extends IWindowSession.Stub
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags,
- int flags, Rect outFrame, Rect outContentInsets,
+ int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
int res = mService.relayoutWindow(this, window, seq, attrs,
requestedWidth, requestedHeight, viewFlags, flags,
- outFrame, outContentInsets, outVisibleInsets,
+ outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
outConfig, outSurface);
if (false) Slog.d(WindowManagerService.TAG, "<<<<<< EXITING relayout to "
+ Binder.getCallingPid());
@@ -313,19 +315,19 @@ final class Session extends IWindowSession.Stub
mService.mDragState.mThumbOffsetY = thumbCenterY;
// Make the surface visible at the proper location
- final Surface surface = mService.mDragState.mSurface;
+ final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl;
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
WindowManagerService.TAG, ">>> OPEN TRANSACTION performDrag");
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
- surface.setPosition(touchX - thumbCenterX,
+ surfaceControl.setPosition(touchX - thumbCenterX,
touchY - thumbCenterY);
- surface.setAlpha(.7071f);
- surface.setLayer(mService.mDragState.getDragLayerLw());
- surface.setLayerStack(display.getLayerStack());
- surface.show();
+ surfaceControl.setAlpha(.7071f);
+ surfaceControl.setLayer(mService.mDragState.getDragLayerLw());
+ surfaceControl.setLayerStack(display.getLayerStack());
+ surfaceControl.show();
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
WindowManagerService.TAG, "<<< CLOSE TRANSACTION performDrag");
}
@@ -446,6 +448,10 @@ final class Session extends IWindowSession.Stub
}
}
+ public IWindowId getWindowId(IBinder window) {
+ return mService.getWindowId(window);
+ }
+
void windowAddedLocked() {
if (mSurfaceSession == null) {
if (WindowManagerService.localLOGV) Slog.v(
diff --git a/services/java/com/android/server/wm/StrictModeFlash.java b/services/java/com/android/server/wm/StrictModeFlash.java
index 90bbd08..31628e3 100644
--- a/services/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/java/com/android/server/wm/StrictModeFlash.java
@@ -24,29 +24,32 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.view.Display;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
class StrictModeFlash {
private static final String TAG = "StrictModeFlash";
- Surface mSurface;
- int mLastDW;
- int mLastDH;
- boolean mDrawNeeded;
- final int mThickness = 20;
+ private final SurfaceControl mSurfaceControl;
+ private final Surface mSurface = new Surface();
+ private int mLastDW;
+ private int mLastDH;
+ private boolean mDrawNeeded;
+ private final int mThickness = 20;
public StrictModeFlash(Display display, SurfaceSession session) {
+ SurfaceControl ctrl = null;
try {
- mSurface = new Surface(session, "StrictModeFlash",
- 1, 1, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
- } catch (Surface.OutOfResourcesException e) {
- return;
+ ctrl = new SurfaceControl(session, "StrictModeFlash",
+ 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+ ctrl.setLayerStack(display.getLayerStack());
+ ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary.
+ ctrl.setPosition(0, 0);
+ ctrl.show();
+ mSurface.copyFrom(ctrl);
+ } catch (SurfaceControl.OutOfResourcesException e) {
}
-
- mSurface.setLayerStack(display.getLayerStack());
- mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary.
- mSurface.setPosition(0, 0);
- mSurface.show();
+ mSurfaceControl = ctrl;
mDrawNeeded = true;
}
@@ -88,14 +91,14 @@ class StrictModeFlash {
// Note: caller responsible for being inside
// Surface.openTransaction() / closeTransaction()
public void setVisibility(boolean on) {
- if (mSurface == null) {
+ if (mSurfaceControl == null) {
return;
}
drawIfNeeded();
if (on) {
- mSurface.show();
+ mSurfaceControl.show();
} else {
- mSurface.hide();
+ mSurfaceControl.hide();
}
}
@@ -105,7 +108,7 @@ class StrictModeFlash {
}
mLastDW = dw;
mLastDH = dh;
- mSurface.setSize(dw, dh);
+ mSurfaceControl.setSize(dw, dh);
mDrawNeeded = true;
}
diff --git a/services/java/com/android/server/wm/Watermark.java b/services/java/com/android/server/wm/Watermark.java
index ac152c9..fedd314 100644
--- a/services/java/com/android/server/wm/Watermark.java
+++ b/services/java/com/android/server/wm/Watermark.java
@@ -28,6 +28,7 @@ import android.util.Log;
import android.util.TypedValue;
import android.view.Display;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.Surface.OutOfResourcesException;
@@ -35,21 +36,20 @@ import android.view.Surface.OutOfResourcesException;
* Displays a watermark on top of the window manager's windows.
*/
class Watermark {
- final Display mDisplay;
- final String[] mTokens;
- final String mText;
- final Paint mTextPaint;
- final int mTextWidth;
- final int mTextHeight;
- final int mTextAscent;
- final int mTextDescent;
- final int mDeltaX;
- final int mDeltaY;
-
- Surface mSurface;
- int mLastDW;
- int mLastDH;
- boolean mDrawNeeded;
+ private final Display mDisplay;
+ private final String[] mTokens;
+ private final String mText;
+ private final Paint mTextPaint;
+ private final int mTextWidth;
+ private final int mTextHeight;
+ private final int mDeltaX;
+ private final int mDeltaY;
+
+ private final SurfaceControl mSurfaceControl;
+ private final Surface mSurface = new Surface();
+ private int mLastDW;
+ private int mLastDH;
+ private boolean mDrawNeeded;
Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) {
if (false) {
@@ -90,8 +90,6 @@ class Watermark {
FontMetricsInt fm = mTextPaint.getFontMetricsInt();
mTextWidth = (int)mTextPaint.measureText(mText);
- mTextAscent = fm.ascent;
- mTextDescent = fm.descent;
mTextHeight = fm.descent - fm.ascent;
mDeltaX = WindowManagerService.getPropertyInt(tokens, 2,
@@ -112,22 +110,25 @@ class Watermark {
mTextPaint.setColor(color);
mTextPaint.setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor);
+ SurfaceControl ctrl = null;
try {
- mSurface = new Surface(session, "WatermarkSurface",
- 1, 1, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
- mSurface.setLayerStack(mDisplay.getLayerStack());
- mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100);
- mSurface.setPosition(0, 0);
- mSurface.show();
- } catch (OutOfResourcesException e) {
+ ctrl = new SurfaceControl(session, "WatermarkSurface",
+ 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+ ctrl.setLayerStack(mDisplay.getLayerStack());
+ ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100);
+ ctrl.setPosition(0, 0);
+ ctrl.show();
+ mSurface.copyFrom(ctrl);
+ } catch (SurfaceControl.OutOfResourcesException e) {
}
+ mSurfaceControl = ctrl;
}
void positionSurface(int dw, int dh) {
if (mLastDW != dw || mLastDH != dh) {
mLastDW = dw;
mLastDH = dh;
- mSurface.setSize(dw, dh);
+ mSurfaceControl.setSize(dw, dh);
mDrawNeeded = true;
}
}
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index 54914c2..054a075 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -9,8 +9,7 @@ import static com.android.server.wm.WindowManagerService.LayoutFields.SET_UPDATE
import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_MAY_CHANGE;
import static com.android.server.wm.WindowManagerService.LayoutFields.SET_FORCE_HIDING_CHANGED;
import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE;
-
-import static com.android.server.wm.WindowManagerService.H.UPDATE_ANIM_PARAMETERS;
+import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_ACTION_PENDING;
import android.content.Context;
import android.os.Debug;
@@ -20,14 +19,15 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
+import android.util.TypedValue;
import android.view.Display;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
-import com.android.server.wm.WindowManagerService.AppWindowAnimParams;
+import com.android.server.wm.WindowManagerService.DisplayContentsIterator;
import com.android.server.wm.WindowManagerService.LayoutFields;
-import com.android.server.wm.WindowManagerService.LayoutToAnimatorParams;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -39,6 +39,10 @@ import java.util.ArrayList;
public class WindowAnimator {
private static final String TAG = "WindowAnimator";
+ /** Amount of time in milliseconds to animate the dim surface from one value to another,
+ * when no window animation is driving it. */
+ static final int DEFAULT_DIM_DURATION = 200;
+
final WindowManagerService mService;
final Context mContext;
final WindowManagerPolicy mPolicy;
@@ -49,16 +53,6 @@ public class WindowAnimator {
int mAdjResult;
- // Layout changes for individual Displays. Indexed by displayId.
- SparseIntArray mPendingLayoutChanges = new SparseIntArray();
-
- // TODO: Assign these from each iteration through DisplayContent. Only valid between loops.
- /** Overall window dimensions */
- int mDw, mDh;
-
- /** Interior window dimensions */
- int mInnerDw, mInnerDh;
-
/** Time of current animation step. Reset on each iteration */
long mCurrentTime;
@@ -66,42 +60,21 @@ public class WindowAnimator {
* is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
private int mAnimTransactionSequence;
- // Window currently running an animation that has requested it be detached
- // from the wallpaper. This means we need to ensure the wallpaper is
- // visible behind it in case it animates in a way that would allow it to be
- // seen. If multiple windows satisfy this, use the lowest window.
+ /** Window currently running an animation that has requested it be detached
+ * from the wallpaper. This means we need to ensure the wallpaper is
+ * visible behind it in case it animates in a way that would allow it to be
+ * seen. If multiple windows satisfy this, use the lowest window. */
WindowState mWindowDetachedWallpaper = null;
WindowStateAnimator mUniverseBackground = null;
int mAboveUniverseLayer = 0;
int mBulkUpdateParams = 0;
+ Object mLastWindowFreezeSource;
SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators =
new SparseArray<WindowAnimator.DisplayContentsAnimator>();
- static final int WALLPAPER_ACTION_PENDING = 1;
- int mPendingActions;
-
- WindowState mWallpaperTarget = null;
- AppWindowAnimator mWpAppAnimator = null;
- WindowState mLowerWallpaperTarget = null;
- WindowState mUpperWallpaperTarget = null;
-
- ArrayList<AppWindowAnimator> mAppAnimators = new ArrayList<AppWindowAnimator>();
-
- ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>();
-
- /** Parameters being passed from this into mService. */
- static class AnimatorToLayoutParams {
- boolean mUpdateQueued;
- int mBulkUpdateParams;
- SparseIntArray mPendingLayoutChanges;
- WindowState mWindowDetachedWallpaper;
- }
- /** Do not modify unless holding mService.mWindowMap or this and mAnimToLayout in that order */
- final AnimatorToLayoutParams mAnimToLayout = new AnimatorToLayoutParams();
-
boolean mInitialized = false;
// forceHiding states.
@@ -129,13 +102,9 @@ public class WindowAnimator {
mAnimationRunnable = new Runnable() {
@Override
public void run() {
- // TODO(cmautner): When full isolation is achieved for animation, the first lock
- // goes away and only the WindowAnimator.this remains.
- synchronized(mService.mWindowMap) {
- synchronized(WindowAnimator.this) {
- copyLayoutToAnimParamsLocked();
- animateLocked();
- }
+ synchronized (mService.mWindowMap) {
+ mService.mAnimationScheduled = false;
+ animateLocked();
}
}
};
@@ -153,7 +122,7 @@ public class WindowAnimator {
final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
if (displayAnimator != null) {
if (displayAnimator.mWindowAnimationBackgroundSurface != null) {
- displayAnimator.mWindowAnimationBackgroundSurface.kill();
+ displayAnimator.mWindowAnimationBackgroundSurface.destroySurface();
displayAnimator.mWindowAnimationBackgroundSurface = null;
}
if (displayAnimator.mScreenRotationAnimation != null) {
@@ -161,7 +130,7 @@ public class WindowAnimator {
displayAnimator.mScreenRotationAnimation = null;
}
if (displayAnimator.mDimAnimator != null) {
- displayAnimator.mDimAnimator.kill();
+ displayAnimator.mDimAnimator.destroySurface();
displayAnimator.mDimAnimator = null;
}
}
@@ -169,117 +138,17 @@ public class WindowAnimator {
mDisplayContentsAnimators.delete(displayId);
}
- /** Locked on mAnimToLayout */
- void updateAnimToLayoutLocked() {
- final AnimatorToLayoutParams animToLayout = mAnimToLayout;
- synchronized (animToLayout) {
- animToLayout.mBulkUpdateParams = mBulkUpdateParams;
- animToLayout.mPendingLayoutChanges = mPendingLayoutChanges.clone();
- animToLayout.mWindowDetachedWallpaper = mWindowDetachedWallpaper;
-
- if (!animToLayout.mUpdateQueued) {
- animToLayout.mUpdateQueued = true;
- mService.mH.sendMessage(mService.mH.obtainMessage(UPDATE_ANIM_PARAMETERS));
- }
- }
- }
-
- /** Copy all WindowManagerService params into local params here. Locked on 'this'. */
- private void copyLayoutToAnimParamsLocked() {
- final LayoutToAnimatorParams layoutToAnim = mService.mLayoutToAnim;
- synchronized(layoutToAnim) {
- layoutToAnim.mAnimationScheduled = false;
-
- if (!layoutToAnim.mParamsModified) {
- return;
- }
- layoutToAnim.mParamsModified = false;
-
- if ((layoutToAnim.mChanges & LayoutToAnimatorParams.WALLPAPER_TOKENS_CHANGED) != 0) {
- layoutToAnim.mChanges &= ~LayoutToAnimatorParams.WALLPAPER_TOKENS_CHANGED;
- mWallpaperTokens = new ArrayList<WindowToken>(layoutToAnim.mWallpaperTokens);
- }
-
- if (WindowManagerService.DEBUG_WALLPAPER_LIGHT) {
- if (mWallpaperTarget != layoutToAnim.mWallpaperTarget
- || mLowerWallpaperTarget != layoutToAnim.mLowerWallpaperTarget
- || mUpperWallpaperTarget != layoutToAnim.mUpperWallpaperTarget) {
- Slog.d(TAG, "Pulling anim wallpaper: target=" + layoutToAnim.mWallpaperTarget
- + " lower=" + layoutToAnim.mLowerWallpaperTarget + " upper="
- + layoutToAnim.mUpperWallpaperTarget);
- }
- }
- mWallpaperTarget = layoutToAnim.mWallpaperTarget;
- mWpAppAnimator = mWallpaperTarget == null
- ? null : mWallpaperTarget.mAppToken == null
- ? null : mWallpaperTarget.mAppToken.mAppAnimator;
- mLowerWallpaperTarget = layoutToAnim.mLowerWallpaperTarget;
- mUpperWallpaperTarget = layoutToAnim.mUpperWallpaperTarget;
-
- // Set the new DimAnimator params.
- final int numDisplays = mDisplayContentsAnimators.size();
- for (int i = 0; i < numDisplays; i++) {
- final int displayId = mDisplayContentsAnimators.keyAt(i);
- DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
-
- displayAnimator.mWinAnimators.clear();
- final WinAnimatorList winAnimators = layoutToAnim.mWinAnimatorLists.get(displayId);
- if (winAnimators != null) {
- displayAnimator.mWinAnimators.addAll(winAnimators);
- }
-
- DimAnimator.Parameters dimParams = layoutToAnim.mDimParams.get(displayId);
- if (dimParams == null) {
- displayAnimator.mDimParams = null;
- } else {
- final WindowStateAnimator newWinAnimator = dimParams.mDimWinAnimator;
-
- // Only set dim params on the highest dimmed layer.
- final WindowStateAnimator existingDimWinAnimator =
- displayAnimator.mDimParams == null ?
- null : displayAnimator.mDimParams.mDimWinAnimator;
- // Don't turn on for an unshown surface, or for any layer but the highest
- // dimmed layer.
- if (newWinAnimator.mSurfaceShown && (existingDimWinAnimator == null
- || !existingDimWinAnimator.mSurfaceShown
- || existingDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) {
- displayAnimator.mDimParams = new DimAnimator.Parameters(dimParams);
- }
- }
- }
-
- mAppAnimators.clear();
- final int N = layoutToAnim.mAppWindowAnimParams.size();
- for (int i = 0; i < N; i++) {
- final AppWindowAnimParams params = layoutToAnim.mAppWindowAnimParams.get(i);
- AppWindowAnimator appAnimator = params.mAppAnimator;
- appAnimator.mAllAppWinAnimators.clear();
- appAnimator.mAllAppWinAnimators.addAll(params.mWinAnimators);
- mAppAnimators.add(appAnimator);
- }
- }
+ AppWindowAnimator getWallpaperAppAnimator() {
+ return mService.mWallpaperTarget == null
+ ? null : mService.mWallpaperTarget.mAppToken == null
+ ? null : mService.mWallpaperTarget.mAppToken.mAppAnimator;
}
- void hideWallpapersLocked(final WindowState w, boolean fromAnimator) {
- // There is an issue where this function can be called either from
- // the animation or the layout side of the window manager. The problem
- // is that if it is called from the layout side, we may not yet have
- // propagated the current layout wallpaper state over into the animation
- // state. If that is the case, we can do bad things like hide the
- // wallpaper when we had just made it shown because the animation side
- // doesn't yet see that there is now a wallpaper target. As a temporary
- // work-around, we tell the function here which side of the window manager
- // is calling so it can use the right state.
- if (fromAnimator) {
- hideWallpapersLocked(w, mWallpaperTarget, mLowerWallpaperTarget, mWallpaperTokens);
- } else {
- hideWallpapersLocked(w, mService.mWallpaperTarget,
- mService.mLowerWallpaperTarget, mService.mWallpaperTokens);
- }
- }
+ void hideWallpapersLocked(final WindowState w) {
+ final WindowState wallpaperTarget = mService.mWallpaperTarget;
+ final WindowState lowerWallpaperTarget = mService.mLowerWallpaperTarget;
+ final ArrayList<WindowToken> wallpaperTokens = mService.mWallpaperTokens;
- void hideWallpapersLocked(final WindowState w, final WindowState wallpaperTarget,
- final WindowState lowerWallpaperTarget, final ArrayList<WindowToken> wallpaperTokens) {
if ((wallpaperTarget == w && lowerWallpaperTarget == null) || wallpaperTarget == null) {
final int numTokens = wallpaperTokens.size();
for (int i = numTokens - 1; i >= 0; i--) {
@@ -306,12 +175,13 @@ public class WindowAnimator {
private void updateAppWindowsLocked() {
int i;
- final int NAT = mAppAnimators.size();
+ final ArrayList<AppWindowToken> appTokens = mService.mAnimatingAppTokens;
+ final int NAT = appTokens.size();
for (i=0; i<NAT; i++) {
- final AppWindowAnimator appAnimator = mAppAnimators.get(i);
+ final AppWindowAnimator appAnimator = appTokens.get(i).mAppAnimator;
final boolean wasAnimating = appAnimator.animation != null
&& appAnimator.animation != AppWindowAnimator.sDummyAnimation;
- if (appAnimator.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) {
+ if (appAnimator.stepAnimationLocked(mCurrentTime)) {
mAnimating = true;
} else if (wasAnimating) {
// stopped animating, do one more pass through the layout
@@ -327,7 +197,7 @@ public class WindowAnimator {
final AppWindowAnimator appAnimator = mService.mExitingAppTokens.get(i).mAppAnimator;
final boolean wasAnimating = appAnimator.animation != null
&& appAnimator.animation != AppWindowAnimator.sDummyAnimation;
- if (appAnimator.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) {
+ if (appAnimator.stepAnimationLocked(mCurrentTime)) {
mAnimating = true;
} else if (wasAnimating) {
// stopped animating, do one more pass through the layout
@@ -342,18 +212,17 @@ public class WindowAnimator {
private void updateWindowsLocked(final int displayId) {
++mAnimTransactionSequence;
- final WinAnimatorList winAnimatorList =
- getDisplayContentsAnimatorLocked(displayId).mWinAnimators;
+ final WindowList windows = mService.getWindowListLocked(displayId);
ArrayList<WindowStateAnimator> unForceHiding = null;
boolean wallpaperInUnForceHiding = false;
mForceHiding = KEYGUARD_NOT_SHOWN;
- for (int i = winAnimatorList.size() - 1; i >= 0; i--) {
- WindowStateAnimator winAnimator = winAnimatorList.get(i);
- WindowState win = winAnimator.mWin;
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ WindowState win = windows.get(i);
+ WindowStateAnimator winAnimator = win.mWinAnimator;
final int flags = winAnimator.mAttrFlags;
- if (winAnimator.mSurface != null) {
+ if (winAnimator.mSurfaceControl != null) {
final boolean wasAnimating = winAnimator.mWasAnimating;
final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);
@@ -362,13 +231,13 @@ public class WindowAnimator {
", nowAnimating=" + nowAnimating);
}
- if (wasAnimating && !winAnimator.mAnimating && mWallpaperTarget == win) {
+ if (wasAnimating && !winAnimator.mAnimating && mService.mWallpaperTarget == win) {
mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 2",
- mPendingLayoutChanges.get(Display.DEFAULT_DISPLAY));
+ getPendingLayoutChanges(Display.DEFAULT_DISPLAY));
}
}
@@ -382,7 +251,7 @@ public class WindowAnimator {
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 3",
- mPendingLayoutChanges.get(displayId));
+ getPendingLayoutChanges(displayId));
}
mService.mFocusMayChange = true;
}
@@ -445,7 +314,7 @@ public class WindowAnimator {
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 4",
- mPendingLayoutChanges.get(Display.DEFAULT_DISPLAY));
+ getPendingLayoutChanges(Display.DEFAULT_DISPLAY));
}
}
}
@@ -455,11 +324,11 @@ public class WindowAnimator {
if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) {
if (atoken == null || atoken.allDrawn) {
if (winAnimator.performShowLocked()) {
- mPendingLayoutChanges.put(displayId,
+ setPendingLayoutChanges(displayId,
WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 5",
- mPendingLayoutChanges.get(displayId));
+ getPendingLayoutChanges(displayId));
}
}
}
@@ -493,21 +362,19 @@ public class WindowAnimator {
private void updateWallpaperLocked(int displayId) {
final DisplayContentsAnimator displayAnimator =
getDisplayContentsAnimatorLocked(displayId);
- final WinAnimatorList winAnimatorList = displayAnimator.mWinAnimators;
+ final WindowList windows = mService.getWindowListLocked(displayId);
WindowStateAnimator windowAnimationBackground = null;
int windowAnimationBackgroundColor = 0;
WindowState detachedWallpaper = null;
- final DimSurface windowAnimationBackgroundSurface =
- displayAnimator.mWindowAnimationBackgroundSurface;
- for (int i = winAnimatorList.size() - 1; i >= 0; i--) {
- WindowStateAnimator winAnimator = winAnimatorList.get(i);
- if (winAnimator.mSurface == null) {
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ final WindowState win = windows.get(i);
+ WindowStateAnimator winAnimator = win.mWinAnimator;
+ if (winAnimator.mSurfaceControl == null) {
continue;
}
final int flags = winAnimator.mAttrFlags;
- final WindowState win = winAnimator.mWin;
// If this window is animating, make a note that we have
// an animating window and take care of a request to run
@@ -566,11 +433,11 @@ public class WindowAnimator {
// don't cause the wallpaper to suddenly disappear.
int animLayer = windowAnimationBackground.mAnimLayer;
WindowState win = windowAnimationBackground.mWin;
- if (mWallpaperTarget == win
- || mLowerWallpaperTarget == win || mUpperWallpaperTarget == win) {
- final int N = winAnimatorList.size();
+ if (mService.mWallpaperTarget == win || mService.mLowerWallpaperTarget == win
+ || mService.mUpperWallpaperTarget == win) {
+ final int N = windows.size();
for (int i = 0; i < N; i++) {
- WindowStateAnimator winAnimator = winAnimatorList.get(i);
+ WindowStateAnimator winAnimator = windows.get(i).mWinAnimator;
if (winAnimator.mIsWallpaper) {
animLayer = winAnimator.mAnimLayer;
break;
@@ -578,25 +445,24 @@ public class WindowAnimator {
}
}
- if (windowAnimationBackgroundSurface != null) {
- windowAnimationBackgroundSurface.show(mDw, mDh,
- animLayer - WindowManagerService.LAYER_OFFSET_DIM,
- windowAnimationBackgroundColor);
- }
+ displayAnimator.mWindowAnimationBackgroundSurface.show(
+ animLayer - WindowManagerService.LAYER_OFFSET_DIM,
+ ((windowAnimationBackgroundColor >> 24) & 0xff) / 255f, 0);
} else {
- if (windowAnimationBackgroundSurface != null) {
- windowAnimationBackgroundSurface.hide();
- }
+ displayAnimator.mWindowAnimationBackgroundSurface.hide();
}
}
+ /** See if any windows have been drawn, so they (and others associated with them) can now be
+ * shown. */
private void testTokenMayBeDrawnLocked() {
// See if any windows have been drawn, so they (and others
// associated with them) can now be shown.
- final int NT = mAppAnimators.size();
+ final ArrayList<AppWindowToken> appTokens = mService.mAnimatingAppTokens;
+ final int NT = appTokens.size();
for (int i=0; i<NT; i++) {
- AppWindowAnimator appAnimator = mAppAnimators.get(i);
- AppWindowToken wtoken = appAnimator.mAppToken;
+ AppWindowToken wtoken = appTokens.get(i);
+ AppWindowAnimator appAnimator = wtoken.mAppAnimator;
final boolean allDrawn = wtoken.allDrawn;
if (allDrawn != appAnimator.allDrawn) {
appAnimator.allDrawn = allDrawn;
@@ -618,7 +484,7 @@ public class WindowAnimator {
setAppLayoutChanges(appAnimator,
WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM,
"testTokenMayBeDrawnLocked");
-
+
// We can now show all of the drawn windows!
if (!mService.mOpeningApps.contains(wtoken)) {
mAnimating |= appAnimator.showAllWindowsLocked();
@@ -634,14 +500,24 @@ public class WindowAnimator {
updateWallpaperLocked(displayId);
}
- // TODO(cmautner): Change the following comment when no longer locked on mWindowMap */
- /** Locked on mService.mWindowMap and this. */
+ private long getDimBehindFadeDuration(long duration) {
+ TypedValue tv = new TypedValue();
+ mContext.getResources().getValue(
+ com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
+ if (tv.type == TypedValue.TYPE_FRACTION) {
+ duration = (long)tv.getFraction(duration, duration);
+ } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
+ duration = tv.data;
+ }
+ return duration;
+ }
+
+ /** Locked on mService.mWindowMap. */
private void animateLocked() {
if (!mInitialized) {
return;
}
- mPendingLayoutChanges.clear();
mCurrentTime = SystemClock.uptimeMillis();
mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
boolean wasAnimating = mAnimating;
@@ -652,8 +528,8 @@ public class WindowAnimator {
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
TAG, ">>> OPEN TRANSACTION animateLocked");
- Surface.openTransaction();
- Surface.setAnimationTransaction();
+ SurfaceControl.openTransaction();
+ SurfaceControl.setAnimationTransaction();
try {
updateAppWindowsLocked();
@@ -678,10 +554,10 @@ public class WindowAnimator {
// associated with exiting/removed apps
performAnimationsLocked(displayId);
- final WinAnimatorList winAnimatorList = displayAnimator.mWinAnimators;
- final int N = winAnimatorList.size();
+ final WindowList windows = mService.getWindowListLocked(displayId);
+ final int N = windows.size();
for (int j = 0; j < N; j++) {
- winAnimatorList.get(j).prepareSurfaceLocked(true);
+ windows.get(j).mWinAnimator.prepareSurfaceLocked(true);
}
}
@@ -697,14 +573,45 @@ public class WindowAnimator {
screenRotationAnimation.updateSurfacesInTransaction();
}
- final DimAnimator.Parameters dimParams = displayAnimator.mDimParams;
- final DimAnimator dimAnimator = displayAnimator.mDimAnimator;
- if (dimAnimator != null && dimParams != null) {
- dimAnimator.updateParameters(mContext.getResources(), dimParams, mCurrentTime);
+ final DimLayer dimAnimator = displayAnimator.mDimAnimator;
+ final WindowStateAnimator winAnimator = displayAnimator.mDimWinAnimator;
+ final int dimLayer;
+ final float dimAmount;
+ if (winAnimator == null) {
+ dimLayer = dimAnimator.getLayer();
+ dimAmount = 0;
+ } else {
+ dimLayer = winAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM;
+ dimAmount = winAnimator.mWin.mAttrs.dimAmount;
+ }
+ final float targetAlpha = dimAnimator.getTargetAlpha();
+ if (targetAlpha != dimAmount) {
+ if (winAnimator == null) {
+ dimAnimator.hide(DEFAULT_DIM_DURATION);
+ } else {
+ long duration = (winAnimator.mAnimating && winAnimator.mAnimation != null)
+ ? winAnimator.mAnimation.computeDurationHint()
+ : DEFAULT_DIM_DURATION;
+ if (targetAlpha > dimAmount) {
+ duration = getDimBehindFadeDuration(duration);
+ }
+ dimAnimator.show(dimLayer, dimAmount, duration);
+ }
+ } else if (dimAnimator.getLayer() != dimLayer) {
+ dimAnimator.setLayer(dimLayer);
}
- if (dimAnimator != null && dimAnimator.mDimShown) {
- mAnimating |= dimAnimator.updateSurface(isDimmingLocked(displayId),
- mCurrentTime, !mService.okToDisplay());
+ if (dimAnimator.isAnimating()) {
+ if (!mService.okToDisplay()) {
+ // Jump to the end of the animation.
+ dimAnimator.show();
+ } else {
+ mAnimating |= dimAnimator.stepAnimation();
+ }
+ }
+
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) {
+ mService.mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
}
}
@@ -714,26 +621,35 @@ public class WindowAnimator {
} catch (RuntimeException e) {
Log.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
TAG, "<<< CLOSE TRANSACTION animateLocked");
}
- for (int i = mPendingLayoutChanges.size() - 1; i >= 0; i--) {
- if ((mPendingLayoutChanges.valueAt(i)
- & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
- mPendingActions |= WALLPAPER_ACTION_PENDING;
+ boolean hasPendingLayoutChanges = false;
+ DisplayContentsIterator iterator = mService.new DisplayContentsIterator();
+ while (iterator.hasNext()) {
+ final DisplayContent displayContent = iterator.next();
+ final int pendingChanges = getPendingLayoutChanges(displayContent.getDisplayId());
+ if ((pendingChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+ mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING;
+ }
+ if (pendingChanges != 0) {
+ hasPendingLayoutChanges = true;
}
}
- if (mBulkUpdateParams != 0 || mPendingLayoutChanges.size() > 0) {
- updateAnimToLayoutLocked();
+ boolean doRequest = false;
+ if (mBulkUpdateParams != 0) {
+ doRequest = mService.copyAnimToLayoutParamsLocked();
+ }
+
+ if (hasPendingLayoutChanges || doRequest) {
+ mService.requestTraversalLocked();
}
if (mAnimating) {
- synchronized (mService.mLayoutToAnim) {
- mService.scheduleAnimationLocked();
- }
+ mService.scheduleAnimationLocked();
} else if (wasAnimating) {
mService.requestTraversalLocked();
}
@@ -741,7 +657,7 @@ public class WindowAnimator {
Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
+ " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+ " mPendingLayoutChanges(DEFAULT_DISPLAY)="
- + Integer.toHexString(mPendingLayoutChanges.get(Display.DEFAULT_DISPLAY)));
+ + Integer.toHexString(getPendingLayoutChanges(Display.DEFAULT_DISPLAY)));
}
}
@@ -750,22 +666,19 @@ public class WindowAnimator {
mCurrentFocus = currentFocus;
}
- void setDisplayDimensions(final int curWidth, final int curHeight,
- final int appWidth, final int appHeight) {
- mDw = curWidth;
- mDh = curHeight;
- mInnerDw = appWidth;
- mInnerDh = appHeight;
- }
-
boolean isDimmingLocked(int displayId) {
- return getDisplayContentsAnimatorLocked(displayId).mDimParams != null;
+ return getDisplayContentsAnimatorLocked(displayId).mDimAnimator.isDimming();
}
boolean isDimmingLocked(final WindowStateAnimator winAnimator) {
- DimAnimator.Parameters dimParams =
- getDisplayContentsAnimatorLocked(winAnimator.mWin.getDisplayId()).mDimParams;
- return dimParams != null && dimParams.mDimWinAnimator == winAnimator;
+ final int displayId = winAnimator.mWin.getDisplayId();
+ DisplayContentsAnimator displayAnimator =
+ getDisplayContentsAnimatorLocked(displayId);
+ if (displayAnimator != null) {
+ return displayAnimator.mDimWinAnimator == winAnimator
+ && displayAnimator.mDimAnimator.isDimming();
+ }
+ return false;
}
static String bulkUpdateParamsToString(int bulkUpdateParams) {
@@ -792,73 +705,30 @@ public class WindowAnimator {
final String subPrefix = " " + prefix;
final String subSubPrefix = " " + subPrefix;
- boolean needSep = false;
- if (mAppAnimators.size() > 0) {
- needSep = true;
- pw.println(" App Animators:");
- for (int i=mAppAnimators.size()-1; i>=0; i--) {
- AppWindowAnimator anim = mAppAnimators.get(i);
- pw.print(prefix); pw.print("App Animator #"); pw.print(i);
- pw.print(' '); pw.print(anim);
- if (dumpAll) {
- pw.println(':');
- anim.dump(pw, subPrefix, dumpAll);
- } else {
- pw.println();
- }
- }
- }
- if (mWallpaperTokens.size() > 0) {
- if (needSep) {
- pw.println();
- }
- needSep = true;
- pw.print(prefix); pw.println("Wallpaper tokens:");
- for (int i=mWallpaperTokens.size()-1; i>=0; i--) {
- WindowToken token = mWallpaperTokens.get(i);
- pw.print(prefix); pw.print("Wallpaper #"); pw.print(i);
- pw.print(' '); pw.print(token);
- if (dumpAll) {
- pw.println(':');
- token.dump(pw, subPrefix);
- } else {
- pw.println();
- }
- }
- }
-
- if (needSep) {
- pw.println();
- }
for (int i = 0; i < mDisplayContentsAnimators.size(); i++) {
pw.print(prefix); pw.print("DisplayContentsAnimator #");
pw.print(mDisplayContentsAnimators.keyAt(i));
pw.println(":");
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
- for (int j=0; j<displayAnimator.mWinAnimators.size(); j++) {
- WindowStateAnimator wanim = displayAnimator.mWinAnimators.get(j);
+ final WindowList windows =
+ mService.getWindowListLocked(mDisplayContentsAnimators.keyAt(i));
+ final int N = windows.size();
+ for (int j = 0; j < N; j++) {
+ WindowStateAnimator wanim = windows.get(j).mWinAnimator;
pw.print(subPrefix); pw.print("Window #"); pw.print(j);
pw.print(": "); pw.println(wanim);
}
if (displayAnimator.mWindowAnimationBackgroundSurface != null) {
- if (dumpAll || displayAnimator.mWindowAnimationBackgroundSurface.mDimShown) {
+ if (dumpAll || displayAnimator.mWindowAnimationBackgroundSurface.isDimming()) {
pw.print(subPrefix); pw.println("mWindowAnimationBackgroundSurface:");
displayAnimator.mWindowAnimationBackgroundSurface.printTo(subSubPrefix, pw);
}
}
- if (displayAnimator.mDimAnimator != null) {
- if (dumpAll || displayAnimator.mDimAnimator.mDimShown) {
- pw.print(subPrefix); pw.println("mDimAnimator:");
- displayAnimator.mDimAnimator.printTo(subSubPrefix, pw);
- }
- } else if (dumpAll) {
- pw.print(subPrefix); pw.println("no DimAnimator ");
- }
- if (displayAnimator.mDimParams != null) {
- pw.print(subPrefix); pw.println("mDimParams:");
- displayAnimator.mDimParams.printTo(subSubPrefix, pw);
- } else if (dumpAll) {
- pw.print(subPrefix); pw.println("no DimParams ");
+ if (dumpAll || displayAnimator.mDimAnimator.isDimming()) {
+ pw.print(subPrefix); pw.println("mDimAnimator:");
+ displayAnimator.mDimAnimator.printTo(subSubPrefix, pw);
+ pw.print(subPrefix); pw.print("mDimWinAnimator=");
+ pw.println(displayAnimator.mDimWinAnimator);
}
if (displayAnimator.mScreenRotationAnimation != null) {
pw.print(subPrefix); pw.println("mScreenRotationAnimation:");
@@ -876,58 +746,40 @@ public class WindowAnimator {
pw.print(" mForceHiding="); pw.println(forceHidingToString());
pw.print(prefix); pw.print("mCurrentTime=");
pw.println(TimeUtils.formatUptime(mCurrentTime));
- pw.print(prefix); pw.print("mDw=");
- pw.print(mDw); pw.print(" mDh="); pw.print(mDh);
- pw.print(" mInnerDw="); pw.print(mInnerDw);
- pw.print(" mInnerDh="); pw.println(mInnerDh);
}
if (mBulkUpdateParams != 0) {
pw.print(prefix); pw.print("mBulkUpdateParams=0x");
pw.print(Integer.toHexString(mBulkUpdateParams));
pw.println(bulkUpdateParamsToString(mBulkUpdateParams));
}
- if (mPendingActions != 0) {
- pw.print(prefix); pw.print("mPendingActions=0x");
- pw.println(Integer.toHexString(mPendingActions));
- }
if (mWindowDetachedWallpaper != null) {
pw.print(prefix); pw.print("mWindowDetachedWallpaper=");
pw.println(mWindowDetachedWallpaper);
}
- pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget);
- pw.print(prefix); pw.print("mWpAppAnimator="); pw.println(mWpAppAnimator);
- if (mLowerWallpaperTarget != null || mUpperWallpaperTarget != null) {
- pw.print(prefix); pw.print("mLowerWallpaperTarget=");
- pw.println(mLowerWallpaperTarget);
- pw.print(prefix); pw.print("mUpperWallpaperTarget=");
- pw.println(mUpperWallpaperTarget);
- }
if (mUniverseBackground != null) {
pw.print(prefix); pw.print("mUniverseBackground="); pw.print(mUniverseBackground);
pw.print(" mAboveUniverseLayer="); pw.println(mAboveUniverseLayer);
}
}
- void clearPendingActions() {
- synchronized (this) {
- mPendingActions = 0;
- }
+ int getPendingLayoutChanges(final int displayId) {
+ return mService.getDisplayContentLocked(displayId).pendingLayoutChanges;
}
void setPendingLayoutChanges(final int displayId, final int changes) {
- mPendingLayoutChanges.put(displayId, mPendingLayoutChanges.get(displayId) | changes);
+ mService.getDisplayContentLocked(displayId).pendingLayoutChanges |= changes;
}
void setAppLayoutChanges(final AppWindowAnimator appAnimator, final int changes, String s) {
// Used to track which displays layout changes have been done.
SparseIntArray displays = new SparseIntArray();
- for (int i = appAnimator.mAllAppWinAnimators.size() - 1; i >= 0; i--) {
- WindowStateAnimator winAnimator = appAnimator.mAllAppWinAnimators.get(i);
- final int displayId = winAnimator.mWin.mDisplayContent.getDisplayId();
+ WindowList windows = appAnimator.mAppToken.allAppWindows;
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ final int displayId = windows.get(i).getDisplayId();
if (displays.indexOfKey(displayId) < 0) {
setPendingLayoutChanges(displayId, changes);
if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
- mService.debugLayoutRepeats(s, mPendingLayoutChanges.get(displayId));
+ mService.debugLayoutRepeats(s, getPendingLayoutChanges(displayId));
}
// Keep from processing this display again.
displays.put(displayId, changes);
@@ -935,6 +787,22 @@ public class WindowAnimator {
}
}
+ void setDimWinAnimatorLocked(int displayId, WindowStateAnimator newWinAnimator) {
+ DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
+ if (newWinAnimator == null) {
+ displayAnimator.mDimWinAnimator = null;
+ } else {
+ // Only set dim params on the highest dimmed layer.
+ final WindowStateAnimator existingDimWinAnimator = displayAnimator.mDimWinAnimator;
+ // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
+ if (newWinAnimator.mSurfaceShown && (existingDimWinAnimator == null
+ || !existingDimWinAnimator.mSurfaceShown
+ || existingDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) {
+ displayAnimator.mDimWinAnimator = newWinAnimator;
+ }
+ }
+ }
+
private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
if (displayAnimator == null) {
@@ -953,16 +821,14 @@ public class WindowAnimator {
}
private class DisplayContentsAnimator {
- WinAnimatorList mWinAnimators = new WinAnimatorList();
- DimAnimator mDimAnimator = null;
- DimAnimator.Parameters mDimParams = null;
- DimSurface mWindowAnimationBackgroundSurface = null;
+ DimLayer mDimAnimator = null;
+ WindowStateAnimator mDimWinAnimator = null;
+ DimLayer mWindowAnimationBackgroundSurface = null;
ScreenRotationAnimation mScreenRotationAnimation = null;
public DisplayContentsAnimator(int displayId) {
- mDimAnimator = new DimAnimator(mService.mFxSession, displayId);
- mWindowAnimationBackgroundSurface =
- new DimSurface(mService.mFxSession, displayId);
+ mDimAnimator = new DimLayer(mService, displayId);
+ mWindowAnimationBackgroundSurface = new DimLayer(mService, displayId);
}
}
}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 194c750..f2c1bbd 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -40,6 +40,11 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+
+import android.app.AppOpsManager;
+import android.util.TimeUtils;
+import android.view.IWindowId;
import com.android.internal.app.IBatteryStats;
import com.android.internal.policy.PolicyManager;
import com.android.internal.policy.impl.PhoneWindowManager;
@@ -58,7 +63,6 @@ import com.android.server.power.ShutdownThread;
import android.Manifest;
import android.app.ActivityManagerNative;
-import android.app.ActivityOptions;
import android.app.IActivityManager;
import android.app.StatusBarManager;
import android.app.admin.DevicePolicyManager;
@@ -73,6 +77,7 @@ import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Point;
@@ -92,7 +97,6 @@ import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
@@ -115,8 +119,8 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IApplicationToken;
-import android.view.IDisplayContentChangeListener;
import android.view.IInputFilter;
+import android.view.IMagnificationCallbacks;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindow;
@@ -127,24 +131,20 @@ import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.KeyEvent;
+import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewTreeObserver;
-import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerPolicy.FakeWindow;
-import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.ScaleAnimation;
import android.view.animation.Transformation;
import java.io.BufferedWriter;
@@ -255,11 +255,6 @@ public class WindowManagerService extends IWindowManager.Stub
*/
static final int MAX_ANIMATION_DURATION = 10*1000;
- /** Amount of time (in milliseconds) to animate the dim surface from one
- * value to another, when no window animation is driving it.
- */
- static final int DEFAULT_DIM_DURATION = 200;
-
/** Amount of time (in milliseconds) to animate the fade-in-out transition for
* compatible windows.
*/
@@ -268,9 +263,6 @@ public class WindowManagerService extends IWindowManager.Stub
/** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
- /** Fraction of animation at which the recents thumbnail becomes completely transparent */
- static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f;
-
/**
* If true, the window manager will do its own custom freezing and general
* management of the screen during rotation.
@@ -292,12 +284,12 @@ public class WindowManagerService extends IWindowManager.Stub
private static final String SYSTEM_SECURE = "ro.secure";
private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
+ private static final int MAX_SCREENSHOT_RETRIES = 3;
+
final private KeyguardDisableHandler mKeyguardDisableHandler;
private final boolean mHeadless;
- private static final float THUMBNAIL_ANIMATION_DECELERATE_FACTOR = 1.5f;
-
final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -326,6 +318,10 @@ public class WindowManagerService extends IWindowManager.Stub
final IBatteryStats mBatteryStats;
+ final AppOpsManager mAppOps;
+
+ final DisplaySettings mDisplaySettings;
+
/**
* All currently active sessions with clients.
*/
@@ -334,6 +330,7 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Mapping from an IWindow IBinder to the server's Window object.
* This is also used as the lock for all of our state.
+ * NOTE: Never call into methods that lock ActivityManagerService while holding this object.
*/
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<IBinder, WindowState>();
@@ -435,6 +432,8 @@ public class WindowManagerService extends IWindowManager.Stub
IInputMethodManager mInputMethodManager;
+ DisplayMagnifier mDisplayMagnifier;
+
final SurfaceSession mFxSession;
Watermark mWatermark;
StrictModeFlash mStrictModeFlash;
@@ -466,6 +465,9 @@ public class WindowManagerService extends IWindowManager.Stub
boolean mTraversalScheduled = false;
boolean mDisplayFrozen = false;
+ long mDisplayFreezeTime = 0;
+ int mLastDisplayFreezeDuration = 0;
+ Object mLastFinishedFreezeSource = null;
boolean mWaitingForConfig = false;
boolean mWindowsFreezingScreen = false;
boolean mClientFreezingScreen = false;
@@ -484,31 +486,12 @@ public class WindowManagerService extends IWindowManager.Stub
// This is held as long as we have the screen frozen, to give us time to
// perform a rotation animation when turning off shows the lock screen which
// changes the orientation.
- PowerManager.WakeLock mScreenFrozenLock;
-
- // State management of app transitions. When we are preparing for a
- // transition, mNextAppTransition will be the kind of transition to
- // perform or TRANSIT_NONE if we are not waiting. If we are waiting,
- // mOpeningApps and mClosingApps are the lists of tokens that will be
- // made visible or hidden at the next transition.
- int mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
- int mNextAppTransitionType = ActivityOptions.ANIM_NONE;
- String mNextAppTransitionPackage;
- Bitmap mNextAppTransitionThumbnail;
- // Used for thumbnail transitions. True if we're scaling up, false if scaling down
- boolean mNextAppTransitionScaleUp;
- IRemoteCallback mNextAppTransitionCallback;
- int mNextAppTransitionEnter;
- int mNextAppTransitionExit;
- int mNextAppTransitionStartX;
- int mNextAppTransitionStartY;
- int mNextAppTransitionStartWidth;
- int mNextAppTransitionStartHeight;
- boolean mAppTransitionReady = false;
- boolean mAppTransitionRunning = false;
- boolean mAppTransitionTimeout = false;
+ private PowerManager.WakeLock mScreenFrozenLock;
+
+ final AppTransition mAppTransition;
boolean mStartingIconInTransition = false;
boolean mSkipAppTransitionAnimation = false;
+
final ArrayList<AppWindowToken> mOpeningApps = new ArrayList<AppWindowToken>();
final ArrayList<AppWindowToken> mClosingApps = new ArrayList<AppWindowToken>();
@@ -551,7 +534,7 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState mLowerWallpaperTarget = null;
// If non-null, we are in the middle of animating from one wallpaper target
// to another, and this is the higher one in Z-order.
- private WindowState mUpperWallpaperTarget = null;
+ WindowState mUpperWallpaperTarget = null;
int mWallpaperAnimLayerAdjustment;
float mLastWallpaperX = -1;
float mLastWallpaperY = -1;
@@ -587,6 +570,9 @@ public class WindowManagerService extends IWindowManager.Stub
DragState mDragState = null;
+ // For frozen screen animations.
+ int mExitAnimId, mEnterAnimId;
+
/** Pulled out of performLayoutAndPlaceSurfacesLockedInner in order to refactor into multiple
* methods. */
class LayoutFields {
@@ -595,11 +581,12 @@ public class WindowManagerService extends IWindowManager.Stub
static final int SET_FORCE_HIDING_CHANGED = 1 << 2;
static final int SET_ORIENTATION_CHANGE_COMPLETE = 1 << 3;
static final int SET_TURN_ON_SCREEN = 1 << 4;
+ static final int SET_WALLPAPER_ACTION_PENDING = 1 << 5;
boolean mWallpaperForceHidingChanged = false;
boolean mWallpaperMayChange = false;
boolean mOrientationChangeComplete = true;
- int mAdjResult = 0;
+ Object mLastWindowFreezeSource = null;
private Session mHoldScreen = null;
private boolean mObscured = false;
boolean mDimming = false;
@@ -608,6 +595,7 @@ public class WindowManagerService extends IWindowManager.Stub
private float mButtonBrightness = -1;
private long mUserActivityTimeout = -1;
private boolean mUpdateRotation = false;
+ boolean mWallpaperActionPending = false;
private static final int DISPLAY_CONTENT_UNKNOWN = 0;
private static final int DISPLAY_CONTENT_MIRROR = 1;
@@ -632,27 +620,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- static class LayoutToAnimatorParams {
- boolean mParamsModified;
-
- static final long WALLPAPER_TOKENS_CHANGED = 1 << 0;
- long mChanges;
-
- boolean mAnimationScheduled;
- SparseArray<WinAnimatorList> mWinAnimatorLists = new SparseArray<WinAnimatorList>();
- WindowState mWallpaperTarget;
- WindowState mLowerWallpaperTarget;
- WindowState mUpperWallpaperTarget;
- SparseArray<DimAnimator.Parameters> mDimParams = new SparseArray<DimAnimator.Parameters>();
- ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>();
- ArrayList<AppWindowAnimParams> mAppWindowAnimParams = new ArrayList<AppWindowAnimParams>();
- }
- /** Params from WindowManagerService to WindowAnimator. Do not modify or read without first
- * locking on either mWindowMap or mAnimator and then on mLayoutToAnim */
- final LayoutToAnimatorParams mLayoutToAnim = new LayoutToAnimatorParams();
-
- /** The lowest wallpaper target with a detached wallpaper animation on it. */
- WindowState mWindowDetachedWallpaper = null;
+ boolean mAnimationScheduled;
/** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
* is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
@@ -732,9 +700,6 @@ public class WindowManagerService extends IWindowManager.Stub
*/
boolean mInTouchMode = true;
- // Temp regions for intermediary calculations.
- private final Region mTempRegion = new Region();
-
private ViewServer mViewServer;
private ArrayList<WindowChangeListener> mWindowChangeListeners =
new ArrayList<WindowChangeListener>();
@@ -798,6 +763,8 @@ public class WindowManagerService extends IWindowManager.Stub
com.android.internal.R.bool.config_sf_limitedAlpha);
mDisplayManagerService = displayManager;
mHeadless = displayManager.isHeadless();
+ mDisplaySettings = new DisplaySettings(context);
+ mDisplaySettings.readSettingsLocked();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
mDisplayManager.registerDisplayListener(this, null);
@@ -811,12 +778,22 @@ public class WindowManagerService extends IWindowManager.Stub
mPowerManager = pm;
mPowerManager.setPolicy(mPolicy);
PowerManager pmc = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mScreenFrozenLock = pmc.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- "SCREEN_FROZEN");
+ mScreenFrozenLock = pmc.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
mScreenFrozenLock.setReferenceCounted(false);
+ mAppTransition = new AppTransition(context, mH);
+
mActivityManager = ActivityManagerNative.getDefault();
mBatteryStats = BatteryStatsService.getService();
+ mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+ mAppOps.startWatchingMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, null,
+ new AppOpsManager.Callback() {
+ @Override
+ public void opChanged(int op, String packageName) {
+ updateAppOpsState();
+ }
+ }
+ );
// Get persisted window scale setting
mWindowAnimationScale = Settings.Global.getFloat(context.getContentResolver(),
@@ -844,11 +821,11 @@ public class WindowManagerService extends IWindowManager.Stub
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
createWatermarkInTransaction();
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
}
}
@@ -1161,7 +1138,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- /** TODO(cmautner): Is this the same as {@link WindowState#canReceiveKeys()} */
static boolean canBeImeTarget(WindowState w) {
final int fl = w.mAttrs.flags
& (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
@@ -1170,7 +1146,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_INPUT_METHOD) {
Slog.i(TAG, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
if (!w.isVisibleOrAdding()) {
- Slog.i(TAG, " mSurface=" + w.mWinAnimator.mSurface
+ Slog.i(TAG, " mSurface=" + w.mWinAnimator.mSurfaceControl
+ " relayoutCalled=" + w.mRelayoutCalled + " viewVis=" + w.mViewVisibility
+ " policyVis=" + w.mPolicyVisibility
+ " policyVisAfterAnim=" + w.mPolicyVisibilityAfterAnim
@@ -1278,13 +1254,12 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (highestTarget != null) {
- if (DEBUG_INPUT_METHOD) Slog.v(TAG, "mNextAppTransition="
- + mNextAppTransition + " " + highestTarget
+ if (DEBUG_INPUT_METHOD) Slog.v(TAG, mAppTransition + " " + highestTarget
+ " animating=" + highestTarget.mWinAnimator.isAnimating()
+ " layer=" + highestTarget.mWinAnimator.mAnimLayer
+ " new layer=" + w.mWinAnimator.mAnimLayer);
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+ if (mAppTransition.isTransitionSet()) {
// If we are currently setting up for an animation,
// hold everything until we can find out what will happen.
mInputMethodTargetWaitingAnim = true;
@@ -1594,7 +1569,6 @@ public class WindowManagerService extends IWindowManager.Stub
int adjustWallpaperWindowsLocked() {
mInnerFields.mWallpaperMayChange = false;
- int changed = 0;
boolean targetChanged = false;
// TODO(multidisplay): Wallpapers on main screen only.
@@ -1624,7 +1598,7 @@ public class WindowManagerService extends IWindowManager.Stub
continue;
}
topCurW = null;
- if (w != mWindowDetachedWallpaper && w.mAppToken != null) {
+ if (w != mAnimator.mWindowDetachedWallpaper && w.mAppToken != null) {
// If this window's app token is hidden and not animating,
// it is of no interest to us.
if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) {
@@ -1633,10 +1607,10 @@ public class WindowManagerService extends IWindowManager.Stub
continue;
}
}
- if (DEBUG_WALLPAPER) Slog.v(TAG, "Win #" + i + " " + w + ": readyfordisplay="
- + w.isReadyForDisplay() + " mDrawState=" + w.mWinAnimator.mDrawState);
- if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 && w.isReadyForDisplay()
- && (mWallpaperTarget == w || w.isDrawnLw())) {
+ if (DEBUG_WALLPAPER) Slog.v(TAG, "Win #" + i + " " + w + ": isOnScreen="
+ + w.isOnScreen() + " mDrawState=" + w.mWinAnimator.mDrawState);
+ if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 && w.isOnScreen()
+ && (mWallpaperTarget == w || w.isDrawFinishedLw())) {
if (DEBUG_WALLPAPER) Slog.v(TAG,
"Found wallpaper target: #" + i + "=" + w);
foundW = w;
@@ -1650,7 +1624,7 @@ public class WindowManagerService extends IWindowManager.Stub
continue;
}
break;
- } else if (w == mWindowDetachedWallpaper) {
+ } else if (w == mAnimator.mWindowDetachedWallpaper) {
windowDetachedI = i;
}
}
@@ -1662,27 +1636,6 @@ public class WindowManagerService extends IWindowManager.Stub
foundI = windowDetachedI;
}
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
- // If we are currently waiting for an app transition, and either
- // the current target or the next target are involved with it,
- // then hold off on doing anything with the wallpaper.
- // Note that we are checking here for just whether the target
- // is part of an app token... which is potentially overly aggressive
- // (the app token may not be involved in the transition), but good
- // enough (we'll just wait until whatever transition is pending
- // executes).
- if (mWallpaperTarget != null && mWallpaperTarget.mAppToken != null) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "Wallpaper not changing: waiting for app anim in current target");
- return 0;
- }
- if (foundW != null && foundW.mAppToken != null) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "Wallpaper not changing: waiting for app anim in found target");
- return 0;
- }
- }
-
if (mWallpaperTarget != foundW
&& (mLowerWallpaperTarget == null || mLowerWallpaperTarget != foundW)) {
if (DEBUG_WALLPAPER_LIGHT) {
@@ -1700,12 +1653,8 @@ public class WindowManagerService extends IWindowManager.Stub
// Now what is happening... if the current and new targets are
// animating, then we are in our super special mode!
if (foundW != null && oldW != null) {
- boolean oldAnim = oldW.mWinAnimator.mAnimation != null
- || (oldW.mAppToken != null
- && oldW.mAppToken.mAppAnimator.animation != null);
- boolean foundAnim = foundW.mWinAnimator.mAnimation != null
- || (foundW.mAppToken != null &&
- foundW.mAppToken.mAppAnimator.animation != null);
+ boolean oldAnim = oldW.isAnimatingLw();
+ boolean foundAnim = foundW.isAnimatingLw();
if (DEBUG_WALLPAPER_LIGHT) {
Slog.v(TAG, "New animation: " + foundAnim
+ " old animation: " + oldAnim);
@@ -1757,13 +1706,7 @@ public class WindowManagerService extends IWindowManager.Stub
} else if (mLowerWallpaperTarget != null) {
// Is it time to stop animating?
- boolean lowerAnimating = mLowerWallpaperTarget.mWinAnimator.mAnimation != null
- || (mLowerWallpaperTarget.mAppToken != null
- && mLowerWallpaperTarget.mAppToken.mAppAnimator.animation != null);
- boolean upperAnimating = mUpperWallpaperTarget.mWinAnimator.mAnimation != null
- || (mUpperWallpaperTarget.mAppToken != null
- && mUpperWallpaperTarget.mAppToken.mAppAnimator.animation != null);
- if (!lowerAnimating || !upperAnimating) {
+ if (!mLowerWallpaperTarget.isAnimatingLw() || !mUpperWallpaperTarget.isAnimatingLw()) {
if (DEBUG_WALLPAPER_LIGHT) {
Slog.v(TAG, "No longer animating wallpaper targets!");
}
@@ -1839,6 +1782,7 @@ public class WindowManagerService extends IWindowManager.Stub
// Start stepping backwards from here, ensuring that our wallpaper windows
// are correctly placed.
+ int changed = 0;
int curTokenIndex = mWallpaperTokens.size();
while (curTokenIndex > 0) {
curTokenIndex--;
@@ -2053,12 +1997,7 @@ public class WindowManagerService extends IWindowManager.Stub
winAnimator.computeShownFrameLocked();
// No need to lay out the windows - we can just set the wallpaper position
// directly.
- // TODO(cmautner): Don't move this from here, just lock the WindowAnimator.
- if (winAnimator.mSurfaceX != wallpaper.mShownFrame.left
- || winAnimator.mSurfaceY != wallpaper.mShownFrame.top) {
- winAnimator.setWallpaperOffset((int) wallpaper.mShownFrame.left,
- (int) wallpaper.mShownFrame.top);
- }
+ winAnimator.setWallpaperOffset(wallpaper.mShownFrame);
// We only want to be synchronous with one wallpaper.
sync = false;
}
@@ -2118,7 +2057,8 @@ public class WindowManagerService extends IWindowManager.Stub
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
- int res = mPolicy.checkAddPermission(attrs);
+ int[] appOp = new int[1];
+ int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
@@ -2222,7 +2162,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
win = new WindowState(this, session, client, token,
- attachedWindow, seq, attrs, viewVisibility, displayContent);
+ attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
@@ -2260,6 +2200,12 @@ public class WindowManagerService extends IWindowManager.Stub
}
win.attach();
mWindowMap.put(client.asBinder(), win);
+ if (win.mAppOp != AppOpsManager.OP_NONE) {
+ if (mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(), win.getOwningPackage())
+ != AppOpsManager.MODE_ALLOWED) {
+ win.setAppOpVisibilityLw(false);
+ }
+ }
if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) {
token.appWindowToken.startingWindow = win;
@@ -2283,9 +2229,9 @@ public class WindowManagerService extends IWindowManager.Stub
addWindowToListInOrderLocked(win, true);
if (type == TYPE_WALLPAPER) {
mLastWallpaperTimeoutTime = 0;
- adjustWallpaperWindowsLocked();
+ displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
} else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
- adjustWallpaperWindowsLocked();
+ displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
} else if (mWallpaperTarget != null
&& mWallpaperTarget.mLayer >= win.mBaseLayer) {
// If there is currently a wallpaper being shown, and
@@ -2293,7 +2239,7 @@ public class WindowManagerService extends IWindowManager.Stub
// layer of the target window, then adjust the wallpaper.
// This is to avoid a new window being placed between the
// wallpaper and its target.
- adjustWallpaperWindowsLocked();
+ displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
}
@@ -2331,8 +2277,6 @@ public class WindowManagerService extends IWindowManager.Stub
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
- //dump();
-
if (focusChanged) {
finishUpdateFocusedWindowAfterAssignLayersLocked(false /*updateInputWindows*/);
}
@@ -2372,14 +2316,14 @@ public class WindowManagerService extends IWindowManager.Stub
TAG, "Remove " + win + " client="
+ Integer.toHexString(System.identityHashCode(
win.mClient.asBinder()))
- + ", surface=" + win.mWinAnimator.mSurface);
+ + ", surface=" + win.mWinAnimator.mSurfaceControl);
final long origId = Binder.clearCallingIdentity();
win.disposeInputChannel();
if (DEBUG_APP_TRANSITIONS) Slog.v(
- TAG, "Remove " + win + ": mSurface=" + win.mWinAnimator.mSurface
+ TAG, "Remove " + win + ": mSurface=" + win.mWinAnimator.mSurfaceControl
+ " mExiting=" + win.mExiting
+ " isAnimating=" + win.mWinAnimator.isAnimating()
+ " app-animation="
@@ -2407,7 +2351,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (win.mWinAnimator.applyAnimationLocked(transit, false)) {
win.mExiting = true;
}
- scheduleNotifyWindowTranstionIfNeededLocked(win, transit);
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (mDisplayMagnifier != null
+ && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDisplayMagnifier.onWindowTransitionLocked(win, transit);
+ }
}
if (win.mExiting || win.mWinAnimator.isAnimating()) {
// The exit animation is running... wait for it!
@@ -2431,9 +2379,7 @@ public class WindowManagerService extends IWindowManager.Stub
removeWindowInnerLocked(session, win);
// Removing a visible window will effect the computed orientation
// So just update orientation if needed.
- if (wasVisible && computeForcedAppOrientationLocked()
- != mForcedAppOrientation
- && updateOrientationFromAppTokensLocked(false)) {
+ if (wasVisible && updateOrientationFromAppTokensLocked(false)) {
mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
}
updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
@@ -2470,6 +2416,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_ADD_REMOVE) Slog.v(TAG, "removeWindowInnerLocked: " + win);
mWindowMap.remove(win.mClient.asBinder());
+ if (win.mAppOp != AppOpsManager.OP_NONE) {
+ mAppOps.finishOp(win.mAppOp, win.getOwningUid(), win.getOwningPackage());
+ }
final WindowList windows = win.getWindowList();
windows.remove(win);
@@ -2525,9 +2474,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (win.mAttrs.type == TYPE_WALLPAPER) {
mLastWallpaperTimeoutTime = 0;
- adjustWallpaperWindowsLocked();
+ getDefaultDisplayContentLocked().pendingLayoutChanges |=
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
} else if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
- adjustWallpaperWindowsLocked();
+ getDefaultDisplayContentLocked().pendingLayoutChanges |=
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
if (!mInLayout) {
@@ -2542,6 +2493,27 @@ public class WindowManagerService extends IWindowManager.Stub
mInputMonitor.updateInputWindowsLw(true /*force*/);
}
+ public void updateAppOpsState() {
+ synchronized(mWindowMap) {
+ boolean changed = false;
+ for (int i=0; i<mDisplayContents.size(); i++) {
+ DisplayContent display = mDisplayContents.valueAt(i);
+ WindowList windows = display.getWindowList();
+ for (int j=0; j<windows.size(); j++) {
+ final WindowState win = windows.get(j);
+ if (win.mAppOp != AppOpsManager.OP_NONE) {
+ changed |= win.setAppOpVisibilityLw(mAppOps.checkOpNoThrow(win.mAppOp,
+ win.getOwningUid(),
+ win.getOwningPackage()) == AppOpsManager.MODE_ALLOWED);
+ }
+ }
+ }
+ if (changed) {
+ scheduleAnimationLocked();
+ }
+ }
+ }
+
static void logSurface(WindowState w, String msg, RuntimeException where) {
String str = " SURFACE " + msg + ": " + w;
if (where != null) {
@@ -2551,7 +2523,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- static void logSurface(Surface s, String title, String msg, RuntimeException where) {
+ static void logSurface(SurfaceControl s, String title, String msg, RuntimeException where) {
String str = " SURFACE " + s + ": " + msg + " / " + title;
if (where != null) {
Slog.i(TAG, str, where);
@@ -2560,19 +2532,13 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- // TODO(cmautner): Move to WindowStateAnimator.
- void setTransparentRegionHint(final WindowStateAnimator winAnimator, final Region region) {
- mH.sendMessage(mH.obtainMessage(H.SET_TRANSPARENT_REGION,
- new Pair<WindowStateAnimator, Region>(winAnimator, region)));
- }
-
void setTransparentRegionWindow(Session session, IWindow client, Region region) {
long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
WindowState w = windowForClientLocked(session, client, false);
if ((w != null) && w.mHasSurface) {
- setTransparentRegionHint(w.mWinAnimator, region);
+ w.mWinAnimator.setTransparentRegionHintLocked(region);
}
}
} finally {
@@ -2699,56 +2665,27 @@ public class WindowManagerService extends IWindowManager.Stub
public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) {
synchronized (mWindowMap) {
- WindowState window = mWindowMap.get(token);
- if (window != null) {
- scheduleNotifyRectangleOnScreenRequestedIfNeededLocked(window, rectangle,
- immediate);
+ if (mDisplayMagnifier != null) {
+ WindowState window = mWindowMap.get(token);
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (window != null && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle, immediate);
+ }
}
}
}
- private void scheduleNotifyRectangleOnScreenRequestedIfNeededLocked(WindowState window,
- Rect rectangle, boolean immediate) {
- DisplayContent displayContent = window.mDisplayContent;
- if (displayContent.mDisplayContentChangeListeners != null
- && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) {
- mH.obtainMessage(H.NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED, displayContent.getDisplayId(),
- immediate? 1 : 0, new Rect(rectangle)).sendToTarget();
- }
- }
-
- private void handleNotifyRectangleOnScreenRequested(int displayId, Rect rectangle,
- boolean immediate) {
- RemoteCallbackList<IDisplayContentChangeListener> callbacks = null;
+ public IWindowId getWindowId(IBinder token) {
synchronized (mWindowMap) {
- DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent == null) {
- return;
- }
- callbacks = displayContent.mDisplayContentChangeListeners;
- if (callbacks == null) {
- return;
- }
- }
- final int callbackCount = callbacks.beginBroadcast();
- try {
- for (int i = 0; i < callbackCount; i++) {
- try {
- callbacks.getBroadcastItem(i).onRectangleOnScreenRequested(displayId,
- rectangle, immediate);
- } catch (RemoteException re) {
- /* ignore */
- }
- }
- } finally {
- callbacks.finishBroadcast();
+ WindowState window = mWindowMap.get(token);
+ return window != null ? window.mWindowId : null;
}
}
public int relayoutWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, int flags,
- Rect outFrame, Rect outContentInsets,
+ Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
boolean toBeDisplayed = false;
boolean inTouchMode;
@@ -2770,7 +2707,6 @@ public class WindowManagerService extends IWindowManager.Stub
long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
- // TODO(cmautner): synchronize on mAnimator or win.mWinAnimator.
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return 0;
@@ -2883,7 +2819,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) {
// To change the format, we need to re-build the surface.
- winAnimator.destroySurfaceLocked(false);
+ winAnimator.destroySurfaceLocked();
toBeDisplayed = true;
surfaceChanged = true;
}
@@ -2891,9 +2827,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (!win.mHasSurface) {
surfaceChanged = true;
}
- Surface surface = winAnimator.createSurfaceLocked();
- if (surface != null) {
- outSurface.copyFrom(surface);
+ SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
+ if (surfaceControl != null) {
+ outSurface.copyFrom(surfaceControl);
if (SHOW_TRANSACTIONS) Slog.i(TAG,
" OUT SURFACE " + outSurface + ": copied");
} else {
@@ -2933,7 +2869,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
} else {
winAnimator.mEnterAnimationPending = false;
- if (winAnimator.mSurface != null) {
+ if (winAnimator.mSurfaceControl != null) {
if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win
+ ": mExiting=" + win.mExiting);
// If we are not currently running the exit animation, we
@@ -2964,9 +2900,13 @@ public class WindowManagerService extends IWindowManager.Stub
if (mInputMethodWindow == win) {
mInputMethodWindow = null;
}
- winAnimator.destroySurfaceLocked(false);
+ winAnimator.destroySurfaceLocked();
+ }
+ //TODO (multidisplay): Magnification is supported only for the default
+ if (mDisplayMagnifier != null
+ && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDisplayMagnifier.onWindowTransitionLocked(win, transit);
}
- scheduleNotifyWindowTranstionIfNeededLocked(win, transit);
}
}
@@ -2985,29 +2925,22 @@ public class WindowManagerService extends IWindowManager.Stub
// updateFocusedWindowLocked() already assigned layers so we only need to
// reassign them at this point if the IM window state gets shuffled
- boolean assignLayers = false;
-
- if (imMayMove) {
- if (moveInputMethodWindowsIfNeededLocked(false) || toBeDisplayed) {
- // Little hack here -- we -should- be able to rely on the
- // function to return true if the IME has moved and needs
- // its layer recomputed. However, if the IME was hidden
- // and isn't actually moved in the list, its layer may be
- // out of data so we make sure to recompute it.
- assignLayers = true;
- }
+ if (imMayMove && (moveInputMethodWindowsIfNeededLocked(false) || toBeDisplayed)) {
+ // Little hack here -- we -should- be able to rely on the
+ // function to return true if the IME has moved and needs
+ // its layer recomputed. However, if the IME was hidden
+ // and isn't actually moved in the list, its layer may be
+ // out of data so we make sure to recompute it.
+ assignLayersLocked(win.getWindowList());
}
+
if (wallpaperMayMove) {
- if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
- assignLayers = true;
- }
+ getDefaultDisplayContentLocked().pendingLayoutChanges |=
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
win.mDisplayContent.layoutNeeded = true;
win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
- if (assignLayers) {
- assignLayersLocked(win.getWindowList());
- }
configChanged = updateOrientationFromAppTokensLocked(false);
performLayoutAndPlaceSurfacesLocked();
if (toBeDisplayed && win.mIsWallpaper) {
@@ -3019,6 +2952,7 @@ public class WindowManagerService extends IWindowManager.Stub
win.mAppToken.updateReportedVisibilityLocked();
}
outFrame.set(win.mCompatFrame);
+ outOverscanInsets.set(win.mOverscanInsets);
outContentInsets.set(win.mContentInsets);
outVisibleInsets.set(win.mVisibleInsets);
if (localLOGV) Slog.v(
@@ -3061,12 +2995,12 @@ public class WindowManagerService extends IWindowManager.Stub
long origId = Binder.clearCallingIdentity();
try {
- synchronized(mWindowMap) {
+ synchronized (mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return;
}
- win.mWinAnimator.destroyDeferredSurfaceLocked(false);
+ win.mWinAnimator.destroyDeferredSurfaceLocked();
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -3077,7 +3011,7 @@ public class WindowManagerService extends IWindowManager.Stub
long origId = Binder.clearCallingIdentity();
try {
- synchronized(mWindowMap) {
+ synchronized (mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return false;
@@ -3091,405 +3025,101 @@ public class WindowManagerService extends IWindowManager.Stub
public void finishDrawingWindow(Session session, IWindow client) {
final long origId = Binder.clearCallingIdentity();
- synchronized(mWindowMap) {
- WindowState win = windowForClientLocked(session, client, false);
- if (win != null && win.mWinAnimator.finishDrawingLocked()) {
- if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
- adjustWallpaperWindowsLocked();
+ try {
+ synchronized (mWindowMap) {
+ WindowState win = windowForClientLocked(session, client, false);
+ if (win != null && win.mWinAnimator.finishDrawingLocked()) {
+ if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+ getDefaultDisplayContentLocked().pendingLayoutChanges |=
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ win.mDisplayContent.layoutNeeded = true;
+ requestTraversalLocked();
}
- win.mDisplayContent.layoutNeeded = true;
- performLayoutAndPlaceSurfacesLocked();
}
- }
- Binder.restoreCallingIdentity(origId);
- }
-
- @Override
- public float getWindowCompatibilityScale(IBinder windowToken) {
- if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
- "getWindowCompatibilityScale()")) {
- throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
- }
- synchronized (mWindowMap) {
- WindowState windowState = mWindowMap.get(windowToken);
- return (windowState != null) ? windowState.mGlobalScale : 1.0f;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
}
@Override
- public WindowInfo getWindowInfo(IBinder token) {
+ public void getWindowFrame(IBinder token, Rect outBounds) {
if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
"getWindowInfo()")) {
throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
}
synchronized (mWindowMap) {
- WindowState window = mWindowMap.get(token);
- if (window != null) {
- return getWindowInfoForWindowStateLocked(window);
+ WindowState windowState = mWindowMap.get(token);
+ if (windowState != null) {
+ outBounds.set(windowState.mFrame);
+ } else {
+ outBounds.setEmpty();
}
- return null;
}
}
@Override
- public void getVisibleWindowsForDisplay(int displayId, List<WindowInfo> outInfos) {
- if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
- "getWindowInfos()")) {
- throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
+ public void setMagnificationSpec(MagnificationSpec spec) {
+ if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
+ "setMagnificationSpec()")) {
+ throw new SecurityException("Requires MAGNIFY_DISPLAY permission.");
}
synchronized (mWindowMap) {
- DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent == null) {
- return;
- }
- WindowList windows = displayContent.getWindowList();
- final int windowCount = windows.size();
- for (int i = 0; i < windowCount; i++) {
- WindowState window = windows.get(i);
- if (window.isVisibleLw() || window.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
- WindowInfo info = getWindowInfoForWindowStateLocked(window);
- outInfos.add(info);
- }
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.setMagnificationSpecLocked(spec);
+ } else {
+ throw new IllegalStateException("Magnification callbacks not set!");
}
}
+ if (Binder.getCallingPid() != android.os.Process.myPid()) {
+ spec.recycle();
+ }
}
@Override
- public void magnifyDisplay(int displayId, float scale, float offsetX, float offsetY) {
- if (!checkCallingPermission(
- android.Manifest.permission.MAGNIFY_DISPLAY, "magnifyDisplay()")) {
- throw new SecurityException("Requires MAGNIFY_DISPLAY permission");
+ public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) {
+ if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
+ "getCompatibleMagnificationSpecForWindow()")) {
+ throw new SecurityException("Requires MAGNIFY_DISPLAY permission.");
}
synchronized (mWindowMap) {
- MagnificationSpec spec = getDisplayMagnificationSpecLocked(displayId);
- if (spec != null) {
- final boolean scaleChanged = spec.mScale != scale;
- final boolean offsetChanged = spec.mOffsetX != offsetX || spec.mOffsetY != offsetY;
- if (!scaleChanged && !offsetChanged) {
- return;
- }
- spec.initialize(scale, offsetX, offsetY);
- // If the offset has changed we need to re-add the input windows
- // since the offsets have to be propagated to the input system.
- if (offsetChanged) {
- // TODO(multidisplay): Input only occurs on the default display.
- if (displayId == Display.DEFAULT_DISPLAY) {
- mInputMonitor.updateInputWindowsLw(true);
- }
- }
- scheduleAnimationLocked();
- }
- }
- }
-
- MagnificationSpec getDisplayMagnificationSpecLocked(int displayId) {
- DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
- if (displayContent.mMagnificationSpec == null) {
- displayContent.mMagnificationSpec = new MagnificationSpec();
- }
- return displayContent.mMagnificationSpec;
- }
- return null;
- }
-
- private WindowInfo getWindowInfoForWindowStateLocked(WindowState window) {
- WindowInfo info = WindowInfo.obtain();
- info.token = window.mToken.token;
- info.frame.set(window.mFrame);
- info.type = window.mAttrs.type;
- info.displayId = window.getDisplayId();
- info.compatibilityScale = window.mGlobalScale;
- info.visible = window.isVisibleLw() || info.type == TYPE_UNIVERSE_BACKGROUND;
- info.layer = window.mLayer;
- window.getTouchableRegion(mTempRegion);
- mTempRegion.getBounds(info.touchableRegion);
- return info;
- }
-
- private AttributeCache.Entry getCachedAnimations(int userId, WindowManager.LayoutParams lp) {
- if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg="
- + (lp != null ? lp.packageName : null)
- + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
- if (lp != null && lp.windowAnimations != 0) {
- // If this is a system resource, don't try to load it from the
- // application resources. It is nice to avoid loading application
- // resources if we can.
- String packageName = lp.packageName != null ? lp.packageName : "android";
- int resId = lp.windowAnimations;
- if ((resId&0xFF000000) == 0x01000000) {
- packageName = "android";
- }
- if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
- + packageName);
- return AttributeCache.instance().get(userId, packageName, resId,
- com.android.internal.R.styleable.WindowAnimation);
- }
- return null;
- }
-
- private AttributeCache.Entry getCachedAnimations(int userId, String packageName, int resId) {
- if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package="
- + packageName + " resId=0x" + Integer.toHexString(resId));
- if (packageName != null) {
- if ((resId&0xFF000000) == 0x01000000) {
- packageName = "android";
+ WindowState windowState = mWindowMap.get(windowToken);
+ if (windowState == null) {
+ return null;
}
- if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
- + packageName);
- return AttributeCache.instance().get(userId, packageName, resId,
- com.android.internal.R.styleable.WindowAnimation);
- }
- return null;
- }
-
- Animation loadAnimation(int userId, WindowManager.LayoutParams lp, int animAttr) {
- int anim = 0;
- Context context = mContext;
- if (animAttr >= 0) {
- AttributeCache.Entry ent = getCachedAnimations(userId, lp);
- if (ent != null) {
- context = ent.context;
- anim = ent.array.getResourceId(animAttr, 0);
+ MagnificationSpec spec = null;
+ if (mDisplayMagnifier != null) {
+ spec = mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState);
}
- }
- if (anim != 0) {
- return AnimationUtils.loadAnimation(context, anim);
- }
- return null;
- }
-
- private Animation loadAnimation(int userId, String packageName, int resId) {
- int anim = 0;
- Context context = mContext;
- if (resId >= 0) {
- AttributeCache.Entry ent = getCachedAnimations(userId, packageName, resId);
- if (ent != null) {
- context = ent.context;
- anim = resId;
+ if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) {
+ return null;
}
+ spec = (spec == null) ? MagnificationSpec.obtain() : MagnificationSpec.obtain(spec);
+ spec.scale *= windowState.mGlobalScale;
+ return spec;
}
- if (anim != 0) {
- return AnimationUtils.loadAnimation(context, anim);
- }
- return null;
}
- private Animation createExitAnimationLocked(int transit, int duration) {
- if (transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN ||
- transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE) {
- // If we are on top of the wallpaper, we need an animation that
- // correctly handles the wallpaper staying static behind all of
- // the animated elements. To do this, will just have the existing
- // element fade out.
- Animation a = new AlphaAnimation(1, 0);
- a.setDetachWallpaper(true);
- a.setDuration(duration);
- return a;
- }
- // For normal animations, the exiting element just holds in place.
- Animation a = new AlphaAnimation(1, 1);
- a.setDuration(duration);
- return a;
- }
-
- /**
- * Compute the pivot point for an animation that is scaling from a small
- * rect on screen to a larger rect. The pivot point varies depending on
- * the distance between the inner and outer edges on both sides. This
- * function computes the pivot point for one dimension.
- * @param startPos Offset from left/top edge of outer rectangle to
- * left/top edge of inner rectangle.
- * @param finalScale The scaling factor between the size of the outer
- * and inner rectangles.
- */
- private static float computePivot(int startPos, float finalScale) {
- final float denom = finalScale-1;
- if (Math.abs(denom) < .0001f) {
- return startPos;
- }
- return -startPos / denom;
- }
-
- private Animation createScaleUpAnimationLocked(int transit, boolean enter) {
- Animation a;
- // Pick the desired duration. If this is an inter-activity transition,
- // it is the standard duration for that. Otherwise we use the longer
- // task transition duration.
- int duration;
- switch (transit) {
- case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
- case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE:
- duration = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_shortAnimTime);
- break;
- default:
- duration = 300;
- break;
- }
- // TODO(multidisplay): For now assume all app animation is on main display.
- final DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
- if (enter) {
- // Entering app zooms out from the center of the initial rect.
- float scaleW = mNextAppTransitionStartWidth / (float) displayInfo.appWidth;
- float scaleH = mNextAppTransitionStartHeight / (float) displayInfo.appHeight;
- Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
- computePivot(mNextAppTransitionStartX, scaleW),
- computePivot(mNextAppTransitionStartY, scaleH));
- scale.setDuration(duration);
- AnimationSet set = new AnimationSet(true);
- Animation alpha = new AlphaAnimation(0, 1);
- scale.setDuration(duration);
- set.addAnimation(scale);
- alpha.setDuration(duration);
- set.addAnimation(alpha);
- set.setDetachWallpaper(true);
- a = set;
- } else {
- a = createExitAnimationLocked(transit, duration);
- }
- a.setFillAfter(true);
- final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext,
- com.android.internal.R.interpolator.decelerate_cubic);
- a.setInterpolator(interpolator);
- a.initialize(displayInfo.appWidth, displayInfo.appHeight,
- displayInfo.appWidth, displayInfo.appHeight);
- return a;
- }
-
- private Animation createThumbnailAnimationLocked(int transit,
- boolean enter, boolean thumb, boolean scaleUp) {
- Animation a;
- final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
- final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
- final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
- final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
- // Pick the desired duration. If this is an inter-activity transition,
- // it is the standard duration for that. Otherwise we use the longer
- // task transition duration.
- int duration;
- switch (transit) {
- case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
- case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE:
- duration = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_shortAnimTime);
- break;
- default:
- duration = 250;
- break;
+ @Override
+ public void setMagnificationCallbacks(IMagnificationCallbacks callbacks) {
+ if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
+ "setMagnificationCallbacks()")) {
+ throw new SecurityException("Requires MAGNIFY_DISPLAY permission.");
}
- // TOOD(multidisplay): For now assume all app animation is on the main screen.
- DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
- if (thumb) {
- // Animation for zooming thumbnail from its initial size to
- // filling the screen.
- if (scaleUp) {
- float scaleW = displayInfo.appWidth / thumbWidth;
- float scaleH = displayInfo.appHeight / thumbHeight;
-
- Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
- computePivot(mNextAppTransitionStartX, 1 / scaleW),
- computePivot(mNextAppTransitionStartY, 1 / scaleH));
- AnimationSet set = new AnimationSet(false);
- Animation alpha = new AlphaAnimation(1, 0);
- scale.setDuration(duration);
- scale.setInterpolator(AnimationUtils.loadInterpolator(mContext,
- com.android.internal.R.interpolator.decelerate_quad));
- set.addAnimation(scale);
- alpha.setDuration(duration);
- alpha.setInterpolator(new Interpolator() {
- @Override
- public float getInterpolation(float input) {
- if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
- // linear response
- return input / RECENTS_THUMBNAIL_FADEOUT_FRACTION;
- }
- // complete
- return 1;
- }
- });
- set.addAnimation(alpha);
- set.setFillBefore(true);
- a = set;
- } else {
- float scaleW = displayInfo.appWidth / thumbWidth;
- float scaleH = displayInfo.appHeight / thumbHeight;
-
- Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
- computePivot(mNextAppTransitionStartX, 1 / scaleW),
- computePivot(mNextAppTransitionStartY, 1 / scaleH));
- AnimationSet set = new AnimationSet(true);
- Animation alpha = new AlphaAnimation(1, 1);
- scale.setDuration(duration);
- scale.setInterpolator(
- new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR));
- set.addAnimation(scale);
- alpha.setDuration(duration);
- set.addAnimation(alpha);
- set.setFillBefore(true);
-
- a = set;
- }
- } else if (enter) {
- // Entering app zooms out from the center of the thumbnail.
- if (scaleUp) {
- float scaleW = thumbWidth / displayInfo.appWidth;
- float scaleH = thumbHeight / displayInfo.appHeight;
- Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
- computePivot(mNextAppTransitionStartX, scaleW),
- computePivot(mNextAppTransitionStartY, scaleH));
- scale.setDuration(duration);
- scale.setInterpolator(
- new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR));
- scale.setFillBefore(true);
- a = scale;
+ synchronized (mWindowMap) {
+ if (mDisplayMagnifier == null) {
+ mDisplayMagnifier = new DisplayMagnifier(this, callbacks);
} else {
- // noop animation
- a = new AlphaAnimation(1, 1);
- a.setDuration(duration);
- }
- } else {
- // Exiting app
- if (scaleUp) {
- if (transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN) {
- // Fade out while bringing up selected activity. This keeps the
- // current activity from showing through a launching wallpaper
- // activity.
- a = new AlphaAnimation(1, 0);
+ if (callbacks == null) {
+ if (mDisplayMagnifier != null) {
+ mDisplayMagnifier.destroyLocked();
+ mDisplayMagnifier = null;
+ }
} else {
- // noop animation
- a = new AlphaAnimation(1, 1);
+ throw new IllegalStateException("Magnification callbacks already set!");
}
- a.setDuration(duration);
- } else {
- float scaleW = thumbWidth / displayInfo.appWidth;
- float scaleH = thumbHeight / displayInfo.appHeight;
- Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
- computePivot(mNextAppTransitionStartX, scaleW),
- computePivot(mNextAppTransitionStartY, scaleH));
- scale.setDuration(duration);
- scale.setInterpolator(
- new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR));
- scale.setFillBefore(true);
- AnimationSet set = new AnimationSet(true);
- Animation alpha = new AlphaAnimation(1, 0);
- set.addAnimation(scale);
- alpha.setDuration(duration);
- alpha.setInterpolator(new DecelerateInterpolator(
- THUMBNAIL_ANIMATION_DECELERATE_FACTOR));
- set.addAnimation(alpha);
- set.setFillBefore(true);
- set.setZAdjustment(Animation.ZORDER_TOP);
- a = set;
- }
- }
- a.setFillAfter(true);
- final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext,
- com.android.internal.R.interpolator.decelerate_quad);
- a.setInterpolator(interpolator);
- a.initialize(displayInfo.appWidth, displayInfo.appHeight,
- displayInfo.appWidth, displayInfo.appHeight);
- return a;
+ }
+ }
}
private boolean applyAnimationLocked(AppWindowToken atoken,
@@ -3499,98 +3129,12 @@ public class WindowManagerService extends IWindowManager.Stub
// artifacts when we unfreeze the display if some different animation
// is running.
if (okToDisplay()) {
- Animation a;
- boolean initialized = false;
- if (mNextAppTransitionType == ActivityOptions.ANIM_CUSTOM) {
- a = loadAnimation(atoken.userId, mNextAppTransitionPackage, enter ?
- mNextAppTransitionEnter : mNextAppTransitionExit);
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
- "applyAnimation: atoken=" + atoken
- + " anim=" + a + " nextAppTransition=ANIM_CUSTOM"
- + " transit=" + transit + " isEntrance=" + enter
- + " Callers=" + Debug.getCallers(3));
- } else if (mNextAppTransitionType == ActivityOptions.ANIM_SCALE_UP) {
- a = createScaleUpAnimationLocked(transit, enter);
- initialized = true;
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
- "applyAnimation: atoken=" + atoken
- + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
- + " transit=" + transit + " isEntrance=" + enter
- + " Callers=" + Debug.getCallers(3));
- } else if (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP ||
- mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN) {
- boolean scaleUp = (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP);
- a = createThumbnailAnimationLocked(transit, enter, false, scaleUp);
- initialized = true;
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
- String animName = scaleUp ? "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
- Slog.v(TAG, "applyAnimation: atoken=" + atoken
- + " anim=" + a + " nextAppTransition=" + animName
- + " transit=" + transit + " isEntrance=" + enter
- + " Callers=" + Debug.getCallers(3));
- }
- } else {
- int animAttr = 0;
- switch (transit) {
- case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
- break;
- case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
- break;
- case WindowManagerPolicy.TRANSIT_TASK_OPEN:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
- break;
- case WindowManagerPolicy.TRANSIT_TASK_CLOSE:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
- break;
- case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
- break;
- case WindowManagerPolicy.TRANSIT_TASK_TO_BACK:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
- break;
- case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
- break;
- case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
- break;
- case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
- break;
- case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE:
- animAttr = enter
- ? com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
- break;
- }
- a = animAttr != 0 ? loadAnimation(atoken.userId, lp, animAttr) : null;
- if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
- "applyAnimation: atoken=" + atoken
- + " anim=" + a
- + " animAttr=0x" + Integer.toHexString(animAttr)
- + " transit=" + transit + " isEntrance=" + enter
- + " Callers=" + Debug.getCallers(3));
- }
+ DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
+ final int width = displayInfo.appWidth;
+ final int height = displayInfo.appHeight;
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation: atoken="
+ + atoken);
+ Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -3600,7 +3144,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e);
}
- atoken.mAppAnimator.setAnimation(a, initialized);
+ atoken.mAppAnimator.setAnimation(a, width, height);
}
} else {
atoken.mAppAnimator.clearAnimation();
@@ -3689,7 +3233,6 @@ public class WindowManagerService extends IWindowManager.Stub
mTokenMap.put(token, wtoken);
if (type == TYPE_WALLPAPER) {
mWallpaperTokens.add(wtoken);
- updateLayoutToAnimWallpaperTokens();
}
}
}
@@ -3720,8 +3263,12 @@ public class WindowManagerService extends IWindowManager.Stub
if (win.isVisibleNow()) {
win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT,
false);
- scheduleNotifyWindowTranstionIfNeededLocked(win,
- WindowManagerPolicy.TRANSIT_EXIT);
+ //TODO (multidisplay): Magnification is supported only for the default
+ if (mDisplayMagnifier != null
+ && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDisplayMagnifier.onWindowTransitionLocked(win,
+ WindowManagerPolicy.TRANSIT_EXIT);
+ }
changed = true;
win.mDisplayContent.layoutNeeded = true;
}
@@ -3739,7 +3286,6 @@ public class WindowManagerService extends IWindowManager.Stub
mExitingTokens.add(wtoken);
} else if (wtoken.windowType == TYPE_WALLPAPER) {
mWallpaperTokens.remove(wtoken);
- updateLayoutToAnimWallpaperTokens();
}
}
@@ -3770,7 +3316,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void addAppToken(int addPos, int userId, IApplicationToken token,
+ public void addAppToken(int addPos, IApplicationToken token,
int groupId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addAppToken()")) {
@@ -3797,7 +3343,7 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.w(TAG, "Attempted to add existing app token: " + token);
return;
}
- atoken = new AppWindowToken(this, userId, token);
+ atoken = new AppWindowToken(this, token);
atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
atoken.groupId = groupId;
atoken.appFullscreen = fullscreen;
@@ -3968,11 +3514,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (updateOrientationFromAppTokensLocked(false)) {
if (freezeThisOneIfNeeded != null) {
- AppWindowToken atoken = findAppWindowToken(
- freezeThisOneIfNeeded);
+ AppWindowToken atoken = findAppWindowToken(freezeThisOneIfNeeded);
if (atoken != null) {
- startAppFreezingScreenLocked(atoken,
- ActivityInfo.CONFIG_ORIENTATION);
+ startAppFreezingScreenLocked(atoken, ActivityInfo.CONFIG_ORIENTATION);
}
}
config = computeNewConfigurationLocked();
@@ -3988,7 +3532,13 @@ public class WindowManagerService extends IWindowManager.Stub
if (currentConfig.diff(mTempConfiguration) != 0) {
mWaitingForConfig = true;
getDefaultDisplayContentLocked().layoutNeeded = true;
- startFreezingDisplayLocked(false, 0, 0);
+ int anim[] = new int[2];
+ if (mAnimator.isDimmingLocked(Display.DEFAULT_DISPLAY)) {
+ anim[0] = anim[1] = 0;
+ } else {
+ mPolicy.selectRotationAnimationLw(anim);
+ }
+ startFreezingDisplayLocked(false, anim[0], anim[1]);
config = new Configuration(mTempConfiguration);
}
}
@@ -4014,7 +3564,10 @@ public class WindowManagerService extends IWindowManager.Stub
boolean updateOrientationFromAppTokensLocked(boolean inTransaction) {
long ident = Binder.clearCallingIdentity();
try {
- int req = computeForcedAppOrientationLocked();
+ int req = getOrientationFromWindowsLocked();
+ if (req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+ req = getOrientationFromAppTokensLocked();
+ }
if (req != mForcedAppOrientation) {
mForcedAppOrientation = req;
@@ -4033,14 +3586,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- int computeForcedAppOrientationLocked() {
- int req = getOrientationFromWindowsLocked();
- if (req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
- req = getOrientationFromAppTokensLocked();
- }
- return req;
- }
-
@Override
public void setNewConfiguration(Configuration config) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
@@ -4050,7 +3595,10 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized(mWindowMap) {
mCurConfiguration = new Configuration(config);
- mWaitingForConfig = false;
+ if (mWaitingForConfig) {
+ mWaitingForConfig = false;
+ mLastFinishedFreezeSource = "new-config";
+ }
performLayoutAndPlaceSurfacesLocked();
}
}
@@ -4134,26 +3682,26 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized(mWindowMap) {
if (DEBUG_APP_TRANSITIONS) Slog.v(
TAG, "Prepare app transition: transit=" + transit
- + " mNextAppTransition=" + mNextAppTransition
+ + " " + mAppTransition
+ " alwaysKeepCurrent=" + alwaysKeepCurrent
+ " Callers=" + Debug.getCallers(3));
if (okToDisplay()) {
- if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET
- || mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) {
- mNextAppTransition = transit;
+ if (!mAppTransition.isTransitionSet() || mAppTransition.isTransitionNone()) {
+ mAppTransition.setAppTransition(transit);
} else if (!alwaysKeepCurrent) {
- if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN
- && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) {
+ if (transit == AppTransition.TRANSIT_TASK_OPEN
+ && mAppTransition.isTransitionEqual(
+ AppTransition.TRANSIT_TASK_CLOSE)) {
// Opening a new task always supersedes a close for the anim.
- mNextAppTransition = transit;
- } else if (transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN
- && mNextAppTransition == WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE) {
+ mAppTransition.setAppTransition(transit);
+ } else if (transit == AppTransition.TRANSIT_ACTIVITY_OPEN
+ && mAppTransition.isTransitionEqual(
+ AppTransition.TRANSIT_ACTIVITY_CLOSE)) {
// Opening a new activity always supersedes a close for the anim.
- mNextAppTransition = transit;
+ mAppTransition.setAppTransition(transit);
}
}
- mAppTransitionReady = false;
- mAppTransitionTimeout = false;
+ mAppTransition.prepare();
mStartingIconInTransition = false;
mSkipAppTransitionAnimation = false;
mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
@@ -4164,30 +3712,15 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public int getPendingAppTransition() {
- return mNextAppTransition;
- }
-
- private void scheduleAnimationCallback(IRemoteCallback cb) {
- if (cb != null) {
- mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, cb));
- }
+ return mAppTransition.getAppTransition();
}
@Override
public void overridePendingAppTransition(String packageName,
int enterAnim, int exitAnim, IRemoteCallback startedCallback) {
synchronized(mWindowMap) {
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
- mNextAppTransitionType = ActivityOptions.ANIM_CUSTOM;
- mNextAppTransitionPackage = packageName;
- mNextAppTransitionThumbnail = null;
- mNextAppTransitionEnter = enterAnim;
- mNextAppTransitionExit = exitAnim;
- scheduleAnimationCallback(mNextAppTransitionCallback);
- mNextAppTransitionCallback = startedCallback;
- } else {
- scheduleAnimationCallback(startedCallback);
- }
+ mAppTransition.overridePendingAppTransition(packageName, enterAnim, exitAnim,
+ startedCallback);
}
}
@@ -4195,17 +3728,8 @@ public class WindowManagerService extends IWindowManager.Stub
public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
int startHeight) {
synchronized(mWindowMap) {
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
- mNextAppTransitionType = ActivityOptions.ANIM_SCALE_UP;
- mNextAppTransitionPackage = null;
- mNextAppTransitionThumbnail = null;
- mNextAppTransitionStartX = startX;
- mNextAppTransitionStartY = startY;
- mNextAppTransitionStartWidth = startWidth;
- mNextAppTransitionStartHeight = startHeight;
- scheduleAnimationCallback(mNextAppTransitionCallback);
- mNextAppTransitionCallback = null;
- }
+ mAppTransition.overridePendingAppTransitionScaleUp(startX, startY, startWidth,
+ startHeight);
}
}
@@ -4213,19 +3737,8 @@ public class WindowManagerService extends IWindowManager.Stub
public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX,
int startY, IRemoteCallback startedCallback, boolean scaleUp) {
synchronized(mWindowMap) {
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
- mNextAppTransitionType = scaleUp
- ? ActivityOptions.ANIM_THUMBNAIL_SCALE_UP : ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
- mNextAppTransitionPackage = null;
- mNextAppTransitionThumbnail = srcThumb;
- mNextAppTransitionScaleUp = scaleUp;
- mNextAppTransitionStartX = startX;
- mNextAppTransitionStartY = startY;
- scheduleAnimationCallback(mNextAppTransitionCallback);
- mNextAppTransitionCallback = startedCallback;
- } else {
- scheduleAnimationCallback(startedCallback);
- }
+ mAppTransition.overridePendingAppTransitionThumb(srcThumb, startX, startY,
+ startedCallback, scaleUp);
}
}
@@ -4240,11 +3753,10 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_APP_TRANSITIONS) {
RuntimeException e = new RuntimeException("here");
e.fillInStackTrace();
- Slog.w(TAG, "Execute app transition: mNextAppTransition="
- + mNextAppTransition, e);
+ Slog.w(TAG, "Execute app transition: " + mAppTransition, e);
}
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
- mAppTransitionReady = true;
+ if (mAppTransition.isTransitionSet()) {
+ mAppTransition.setReady();
final long origId = Binder.clearCallingIdentity();
performLayoutAndPlaceSurfacesLocked();
Binder.restoreCallingIdentity(origId);
@@ -4335,6 +3847,7 @@ public class WindowManagerService extends IWindowManager.Stub
// the new one.
if (ttoken.allDrawn) {
wtoken.allDrawn = true;
+ wtoken.deferClearAllDrawn = ttoken.deferClearAllDrawn;
}
if (ttoken.firstWindowDrawn) {
wtoken.firstWindowDrawn = true;
@@ -4413,8 +3926,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Checking theme of starting window: 0x"
+ Integer.toHexString(theme));
if (theme != 0) {
- AttributeCache.Entry ent = AttributeCache.instance().get(wtoken.userId,
- pkg, theme, com.android.internal.R.styleable.Window);
+ AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+ com.android.internal.R.styleable.Window, mCurrentUserId);
if (ent == null) {
// Whoops! App doesn't exist. Um. Okay. We'll just
// pretend like we didn't see that.
@@ -4465,6 +3978,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public void setAppWillBeHidden(IBinder token) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setAppWillBeHidden()")) {
@@ -4501,7 +4015,7 @@ public class WindowManagerService extends IWindowManager.Stub
boolean runningAppAnimation = false;
- if (transit != WindowManagerPolicy.TRANSIT_UNSET) {
+ if (transit != AppTransition.TRANSIT_UNSET) {
if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
wtoken.mAppAnimator.animation = null;
}
@@ -4509,8 +4023,10 @@ public class WindowManagerService extends IWindowManager.Stub
delayed = runningAppAnimation = true;
}
WindowState window = wtoken.findMainWindow();
- if (window != null) {
- scheduleNotifyWindowTranstionIfNeededLocked(window, transit);
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (window != null && mDisplayMagnifier != null
+ && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDisplayMagnifier.onAppWindowTransitionLocked(window, transit);
}
changed = true;
}
@@ -4529,8 +4045,12 @@ public class WindowManagerService extends IWindowManager.Stub
if (!runningAppAnimation) {
win.mWinAnimator.applyAnimationLocked(
WindowManagerPolicy.TRANSIT_ENTER, true);
- scheduleNotifyWindowTranstionIfNeededLocked(win,
- WindowManagerPolicy.TRANSIT_ENTER);
+ //TODO (multidisplay): Magnification is supported only for the default
+ if (mDisplayMagnifier != null
+ && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDisplayMagnifier.onWindowTransitionLocked(win,
+ WindowManagerPolicy.TRANSIT_ENTER);
+ }
}
changed = true;
win.mDisplayContent.layoutNeeded = true;
@@ -4539,8 +4059,12 @@ public class WindowManagerService extends IWindowManager.Stub
if (!runningAppAnimation) {
win.mWinAnimator.applyAnimationLocked(
WindowManagerPolicy.TRANSIT_EXIT, false);
- scheduleNotifyWindowTranstionIfNeededLocked(win,
- WindowManagerPolicy.TRANSIT_EXIT);
+ //TODO (multidisplay): Magnification is supported only for the default
+ if (mDisplayMagnifier != null
+ && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDisplayMagnifier.onWindowTransitionLocked(win,
+ WindowManagerPolicy.TRANSIT_EXIT);
+ }
}
changed = true;
win.mDisplayContent.layoutNeeded = true;
@@ -4588,6 +4112,7 @@ public class WindowManagerService extends IWindowManager.Stub
return delayed;
}
+ @Override
public void setAppVisibility(IBinder token, boolean visible) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setAppVisibility()")) {
@@ -4610,14 +4135,14 @@ public class WindowManagerService extends IWindowManager.Stub
e.fillInStackTrace();
}
Slog.v(TAG, "setAppVisibility(" + token + ", visible=" + visible
- + "): mNextAppTransition=" + mNextAppTransition
+ + "): " + mAppTransition
+ " hidden=" + wtoken.hidden
+ " hiddenRequested=" + wtoken.hiddenRequested, e);
}
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
- if (okToDisplay() && mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+ if (okToDisplay() && mAppTransition.isTransitionSet()) {
// Already in requested state, don't do anything more.
if (wtoken.hiddenRequested != visible) {
return;
@@ -4642,6 +4167,7 @@ public class WindowManagerService extends IWindowManager.Stub
// its windows to be ready.
if (wtoken.hidden) {
wtoken.allDrawn = false;
+ wtoken.deferClearAllDrawn = false;
wtoken.waitingToShow = true;
if (wtoken.clientHidden) {
@@ -4668,7 +4194,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
final long origId = Binder.clearCallingIdentity();
- setTokenVisibilityLocked(wtoken, null, visible, WindowManagerPolicy.TRANSIT_UNSET,
+ setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET,
true);
wtoken.updateReportedVisibilityLocked();
Binder.restoreCallingIdentity(origId);
@@ -4691,6 +4217,7 @@ public class WindowManagerService extends IWindowManager.Stub
w.mOrientationChanging = true;
mInnerFields.mOrientationChangeComplete = false;
}
+ w.mLastFreezeDuration = 0;
unfrozeWindows = true;
w.mDisplayContent.layoutNeeded = true;
}
@@ -4698,7 +4225,10 @@ public class WindowManagerService extends IWindowManager.Stub
if (force || unfrozeWindows) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "No longer freezing: " + wtoken);
wtoken.mAppAnimator.freezingScreen = false;
+ wtoken.mAppAnimator.lastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+ - mDisplayFreezeTime);
mAppsFreezingScreen--;
+ mLastFinishedFreezeSource = wtoken;
}
if (unfreezeSurfaceNow) {
if (unfrozeWindows) {
@@ -4724,6 +4254,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (!wtoken.hiddenRequested) {
if (!wtoken.mAppAnimator.freezingScreen) {
wtoken.mAppAnimator.freezingScreen = true;
+ wtoken.mAppAnimator.lastFreezeDuration = 0;
mAppsFreezingScreen++;
if (mAppsFreezingScreen == 1) {
startFreezingDisplayLocked(false, 0, 0);
@@ -4739,6 +4270,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public void startAppFreezingScreen(IBinder token, int configChanges) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setAppFreezingScreen()")) {
@@ -4762,6 +4294,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public void stopAppFreezingScreen(IBinder token, boolean force) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setAppFreezingScreen()")) {
@@ -4781,6 +4314,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public void removeAppToken(IBinder token) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"removeAppToken()")) {
@@ -4797,13 +4331,13 @@ public class WindowManagerService extends IWindowManager.Stub
if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken);
delayed = setTokenVisibilityLocked(wtoken, null, false,
- WindowManagerPolicy.TRANSIT_UNSET, true);
+ AppTransition.TRANSIT_UNSET, true);
wtoken.inPendingTransaction = false;
mOpeningApps.remove(wtoken);
wtoken.waitingToShow = false;
if (mClosingApps.contains(wtoken)) {
delayed = true;
- } else if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+ } else if (mAppTransition.isTransitionSet()) {
mClosingApps.add(wtoken);
wtoken.waitingToHide = true;
delayed = true;
@@ -4999,6 +4533,7 @@ public class WindowManagerService extends IWindowManager.Stub
return index;
}
+ @Override
public void moveAppToken(int index, IBinder token) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"moveAppToken()")) {
@@ -5013,8 +4548,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG,
"Start moving token " + wtoken + " initially at "
+ oldIndex);
- if (oldIndex > index && mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET
- && !mAppTransitionRunning) {
+ if (oldIndex > index && mAppTransition.isTransitionSet()) {
// animation towards back has not started, copy old list for duration of animation.
mAnimatingAppTokens.clear();
mAnimatingAppTokens.addAll(mAppTokens);
@@ -5028,7 +4562,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_REORDER) Slog.v(TAG, "Moved " + token + " to " + index + ":");
else if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "Moved " + token + " to " + index);
if (DEBUG_REORDER) dumpAppTokensLocked();
- if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET && !mAppTransitionRunning) {
+ if (!mAppTransition.isTransitionSet()) {
// Not animating, bring animating app list in line with mAppTokens.
mAnimatingAppTokens.clear();
mAnimatingAppTokens.addAll(mAppTokens);
@@ -5082,40 +4616,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private void moveAppWindowsLocked(AppWindowToken wtoken, int tokenPos,
- boolean updateFocusAndLayout) {
- // First remove all of the windows from the list.
- tmpRemoveAppWindowsLocked(wtoken);
-
- // And now add them back at the correct place.
- DisplayContentsIterator iterator = new DisplayContentsIterator();
- while (iterator.hasNext()) {
- final DisplayContent displayContent = iterator.next();
- final WindowList windows = displayContent.getWindowList();
- final int pos = findWindowOffsetLocked(windows, tokenPos);
- final int newPos = reAddAppWindowsLocked(displayContent, pos, wtoken);
- if (pos != newPos) {
- displayContent.layoutNeeded = true;
- }
-
- if (updateFocusAndLayout && !updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
- false /*updateInputWindows*/)) {
- assignLayersLocked(windows);
- }
- }
-
- if (updateFocusAndLayout) {
- mInputMonitor.setUpdateInputWindowsNeededLw();
-
- // Note that the above updateFocusedWindowLocked conditional used to sit here.
-
- if (!mInLayout) {
- performLayoutAndPlaceSurfacesLocked();
- }
- mInputMonitor.updateInputWindowsLw(false /*force*/);
- }
- }
-
private void moveAppWindowsLocked(List<IBinder> tokens, int tokenPos) {
// First remove all of the windows from the list.
final int N = tokens.size();
@@ -5160,6 +4660,7 @@ public class WindowManagerService extends IWindowManager.Stub
//dump();
}
+ @Override
public void moveAppTokensToTop(List<IBinder> tokens) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"moveAppTokensToTop()")) {
@@ -5176,17 +4677,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG,
"Adding next to top: " + wt);
mAppTokens.add(wt);
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+ if (mAppTransition.isTransitionSet()) {
wt.sendingToBottom = false;
}
}
}
- if (!mAppTransitionRunning) {
- mAnimatingAppTokens.clear();
- mAnimatingAppTokens.addAll(mAppTokens);
- moveAppWindowsLocked(tokens, mAppTokens.size());
- }
+ mAnimatingAppTokens.clear();
+ mAnimatingAppTokens.addAll(mAppTokens);
+ moveAppWindowsLocked(tokens, mAppTokens.size());
}
Binder.restoreCallingIdentity(origId);
}
@@ -5201,7 +4700,7 @@ public class WindowManagerService extends IWindowManager.Stub
final long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
final int N = tokens.size();
- if (N > 0 && !mAppTransitionRunning) {
+ if (N > 0) {
// animating towards back, hang onto old list for duration of animation.
mAnimatingAppTokens.clear();
mAnimatingAppTokens.addAll(mAppTokens);
@@ -5214,18 +4713,16 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
"Adding next to bottom: " + wt + " at " + pos);
mAppTokens.add(pos, wt);
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+ if (mAppTransition.isTransitionSet()) {
wt.sendingToBottom = true;
}
pos++;
}
}
- if (!mAppTransitionRunning) {
- mAnimatingAppTokens.clear();
- mAnimatingAppTokens.addAll(mAppTokens);
- moveAppWindowsLocked(tokens, 0);
- }
+ mAnimatingAppTokens.clear();
+ mAnimatingAppTokens.addAll(mAppTokens);
+ moveAppWindowsLocked(tokens, 0);
}
Binder.restoreCallingIdentity(origId);
}
@@ -5266,6 +4763,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized(mWindowMap) {
if (mClientFreezingScreen) {
mClientFreezingScreen = false;
+ mLastFinishedFreezeSource = "client";
final long origId = Binder.clearCallingIdentity();
try {
stopFreezingDisplayLocked();
@@ -5301,12 +4799,14 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* @see android.app.KeyguardManager#exitKeyguardSecurely
*/
+ @Override
public void exitKeyguardSecurely(final IOnKeyguardExitResult callback) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires DISABLE_KEYGUARD permission");
}
mPolicy.exitKeyguardSecurely(new WindowManagerPolicy.OnKeyguardExitResult() {
+ @Override
public void onKeyguardExitResult(boolean success) {
try {
callback.onKeyguardExitResult(success);
@@ -5317,18 +4817,22 @@ public class WindowManagerService extends IWindowManager.Stub
});
}
+ @Override
public boolean inKeyguardRestrictedInputMode() {
return mPolicy.inKeyguardRestrictedKeyInputMode();
}
+ @Override
public boolean isKeyguardLocked() {
return mPolicy.isKeyguardLocked();
}
+ @Override
public boolean isKeyguardSecure() {
return mPolicy.isKeyguardSecure();
}
+ @Override
public void dismissKeyguard() {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
!= PackageManager.PERMISSION_GRANTED) {
@@ -5339,6 +4843,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public void closeSystemDialogs(String reason) {
synchronized(mWindowMap) {
final AllWindowsIterator iterator = new AllWindowsIterator();
@@ -5360,6 +4865,7 @@ public class WindowManagerService extends IWindowManager.Stub
return Math.abs(scale);
}
+ @Override
public void setAnimationScale(int which, float scale) {
if (!checkCallingPermission(android.Manifest.permission.SET_ANIMATION_SCALE,
"setAnimationScale()")) {
@@ -5379,6 +4885,7 @@ public class WindowManagerService extends IWindowManager.Stub
mH.sendEmptyMessage(H.PERSIST_ANIMATION_SCALE);
}
+ @Override
public void setAnimationScales(float[] scales) {
if (!checkCallingPermission(android.Manifest.permission.SET_ANIMATION_SCALE,
"setAnimationScale()")) {
@@ -5406,6 +4913,7 @@ public class WindowManagerService extends IWindowManager.Stub
ValueAnimator.setDurationScale(scale);
}
+ @Override
public float getAnimationScale(int which) {
switch (which) {
case 0: return mWindowAnimationScale;
@@ -5415,6 +4923,7 @@ public class WindowManagerService extends IWindowManager.Stub
return 0;
}
+ @Override
public float[] getAnimationScales() {
return new float[] { mWindowAnimationScale, mTransitionAnimationScale,
mAnimatorDurationScale };
@@ -5461,6 +4970,7 @@ public class WindowManagerService extends IWindowManager.Stub
ShutdownThread.rebootSafeMode(mContext, confirm);
}
+ @Override
public void setInputFilter(IInputFilter filter) {
if (!checkCallingPermission(android.Manifest.permission.FILTER_EVENTS, "setInputFilter()")) {
throw new SecurityException("Requires FILTER_EVENTS permission");
@@ -5471,6 +4981,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void setCurrentUser(final int newUserId) {
synchronized (mWindowMap) {
mCurrentUserId = newUserId;
+ mAppTransition.setCurrentUser(newUserId);
mPolicy.setCurrentUserLw(newUserId);
// Hide windows that should not be seen by the new user.
@@ -5620,7 +5131,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (!mSystemBooted && !haveBootMsg) {
return;
}
-
+
// If we are turning on the screen after the boot is completed
// normally, don't do so until we have the application and
// wallpaper.
@@ -5708,6 +5219,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public void setInTouchMode(boolean mode) {
synchronized(mWindowMap) {
mInTouchMode = mode;
@@ -5746,7 +5258,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION showStrictModeViolation");
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
// TODO(multi-display): support multiple displays
if (mStrictModeFlash == null) {
@@ -5755,13 +5267,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
mStrictModeFlash.setVisibility(on);
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION showStrictModeViolation");
}
}
}
+ @Override
public void setStrictModeVisualIndicatorPreference(String value) {
SystemProperties.set(StrictMode.VISUAL_PROPERTY, value);
}
@@ -5770,7 +5283,7 @@ public class WindowManagerService extends IWindowManager.Stub
* Takes a snapshot of the screen. In landscape mode this grabs the whole screen.
* In portrait mode, it grabs the upper region of the screen based on the vertical dimension
* of the target image.
- *
+ *
* @param displayId the Display to take a screenshot of.
* @param width the width of the target bitmap
* @param height the height of the target bitmap
@@ -5782,40 +5295,57 @@ public class WindowManagerService extends IWindowManager.Stub
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
- Bitmap rawss;
+ Bitmap rawss = null;
int maxLayer = 0;
final Rect frame = new Rect();
- float scale;
+ float scale = 0;
int dw, dh;
- int rot;
-
- synchronized(mWindowMap) {
- long ident = Binder.clearCallingIdentity();
-
- final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent == null) {
- return null;
- }
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- dw = displayInfo.logicalWidth;
- dh = displayInfo.logicalHeight;
+ int rot = Surface.ROTATION_0;
- int aboveAppLayer = mPolicy.windowTypeToLayerLw(TYPE_APPLICATION)
- * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
- aboveAppLayer += TYPE_LAYER_MULTIPLIER;
+ boolean screenshotReady;
+ int minLayer;
+ if (appToken == null) {
+ screenshotReady = true;
+ minLayer = 0;
+ } else {
+ screenshotReady = false;
+ minLayer = Integer.MAX_VALUE;
+ }
- boolean isImeTarget = mInputMethodTarget != null
- && mInputMethodTarget.mAppToken != null
- && mInputMethodTarget.mAppToken.appToken != null
- && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken;
+ int retryCount = 0;
+ WindowState appWin = null;
- // Figure out the part of the screen that is actually the app.
- boolean including = false;
- final WindowList windows = displayContent.getWindowList();
- try {
- Surface.openTransaction();
+ do {
+ if (retryCount++ > 0) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+ synchronized(mWindowMap) {
+ final DisplayContent displayContent = getDisplayContentLocked(displayId);
+ if (displayContent == null) {
+ return null;
+ }
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ dw = displayInfo.logicalWidth;
+ dh = displayInfo.logicalHeight;
+
+ int aboveAppLayer = mPolicy.windowTypeToLayerLw(TYPE_APPLICATION)
+ * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
+ aboveAppLayer += TYPE_LAYER_MULTIPLIER;
+
+ boolean isImeTarget = mInputMethodTarget != null
+ && mInputMethodTarget.mAppToken != null
+ && mInputMethodTarget.mAppToken.appToken != null
+ && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken;
+
+ // Figure out the part of the screen that is actually the app.
+ boolean including = false;
+ appWin = null;
+ final WindowList windows = displayContent.getWindowList();
for (int i = windows.size() - 1; i >= 0; i--) {
WindowState ws = windows.get(i);
if (!ws.mHasSurface) {
@@ -5837,24 +5367,21 @@ public class WindowManagerService extends IWindowManager.Stub
if (ws.mAppToken == null || ws.mAppToken.token != appToken) {
continue;
}
+ appWin = ws;
}
}
// We keep on including windows until we go past a full-screen
// window.
- including = !ws.mIsImWindow && !ws.isFullscreen(dw, dh);
-
- final WindowStateAnimator winAnimator = ws.mWinAnimator;
-
- // The setSize() method causes all previous Surface transactions to sync to
- // the SurfaceFlinger. This will force any outstanding setLayer calls to be
- // synced as well for screen capture. Without this we can get black bitmaps.
- Surface surface = winAnimator.mSurface;
- surface.setSize(surface.getWidth(), surface.getHeight());
+ boolean fullscreen = ws.isFullscreen(dw, dh);
+ including = !ws.mIsImWindow && !fullscreen;
-
- if (maxLayer < winAnimator.mSurfaceLayer) {
- maxLayer = winAnimator.mSurfaceLayer;
+ final WindowStateAnimator winAnim = ws.mWinAnimator;
+ if (maxLayer < winAnim.mSurfaceLayer) {
+ maxLayer = winAnim.mSurfaceLayer;
+ }
+ if (minLayer > winAnim.mSurfaceLayer) {
+ minLayer = winAnim.mSurfaceLayer;
}
// Don't include wallpaper in bounds calculation
@@ -5867,67 +5394,94 @@ public class WindowManagerService extends IWindowManager.Stub
int bottom = wf.bottom - cr.bottom;
frame.union(left, top, right, bottom);
}
+
+ if (ws.mAppToken != null && ws.mAppToken.token == appToken &&
+ ws.isDisplayedLw()) {
+ screenshotReady = true;
+ }
+
+ if (fullscreen) {
+ // No point in continuing down through windows.
+ break;
+ }
}
- } finally {
- Surface.closeTransaction();
- Binder.restoreCallingIdentity(ident);
- }
- // Constrain frame to the screen size.
- frame.intersect(0, 0, dw, dh);
+ if (appToken != null && appWin == null) {
+ // Can't find a window to snapshot.
+ if (DEBUG_SCREENSHOT) Slog.i(TAG,
+ "Screenshot: Couldn't find a surface matching " + appToken);
+ return null;
+ }
+ if (!screenshotReady) {
+ // Delay and hope that window gets drawn.
+ if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot: No image ready for " + appToken
+ + ", " + appWin + " drawState=" + appWin.mWinAnimator.mDrawState);
+ continue;
+ }
- if (frame.isEmpty() || maxLayer == 0) {
- return null;
- }
+ // Constrain frame to the screen size.
+ frame.intersect(0, 0, dw, dh);
- // The screenshot API does not apply the current screen rotation.
- rot = getDefaultDisplayContentLocked().getDisplay().getRotation();
- int fw = frame.width();
- int fh = frame.height();
-
- // Constrain thumbnail to smaller of screen width or height. Assumes aspect
- // of thumbnail is the same as the screen (in landscape) or square.
- float targetWidthScale = width / (float) fw;
- float targetHeightScale = height / (float) fh;
- if (dw <= dh) {
- scale = targetWidthScale;
- // If aspect of thumbnail is the same as the screen (in landscape),
- // select the slightly larger value so we fill the entire bitmap
- if (targetHeightScale > scale && (int) (targetHeightScale * fw) == width) {
- scale = targetHeightScale;
+ if (frame.isEmpty() || maxLayer == 0) {
+ if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
+ + ": returning null frame=" + frame.toShortString() + " maxLayer="
+ + maxLayer);
+ return null;
}
- } else {
- scale = targetHeightScale;
- // If aspect of thumbnail is the same as the screen (in landscape),
- // select the slightly larger value so we fill the entire bitmap
- if (targetWidthScale > scale && (int) (targetWidthScale * fh) == height) {
+
+ // The screenshot API does not apply the current screen rotation.
+ rot = getDefaultDisplayContentLocked().getDisplay().getRotation();
+ int fw = frame.width();
+ int fh = frame.height();
+
+ // Constrain thumbnail to smaller of screen width or height. Assumes aspect
+ // of thumbnail is the same as the screen (in landscape) or square.
+ float targetWidthScale = width / (float) fw;
+ float targetHeightScale = height / (float) fh;
+ if (dw <= dh) {
scale = targetWidthScale;
+ // If aspect of thumbnail is the same as the screen (in landscape),
+ // select the slightly larger value so we fill the entire bitmap
+ if (targetHeightScale > scale && (int) (targetHeightScale * fw) == width) {
+ scale = targetHeightScale;
+ }
+ } else {
+ scale = targetHeightScale;
+ // If aspect of thumbnail is the same as the screen (in landscape),
+ // select the slightly larger value so we fill the entire bitmap
+ if (targetWidthScale > scale && (int) (targetWidthScale * fh) == height) {
+ scale = targetWidthScale;
+ }
}
- }
- // The screen shot will contain the entire screen.
- dw = (int)(dw*scale);
- dh = (int)(dh*scale);
- if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
- int tmp = dw;
- dw = dh;
- dh = tmp;
- rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90;
- }
- if (DEBUG_SCREENSHOT) {
- Slog.i(TAG, "Screenshot: " + dw + "x" + dh + " from 0 to " + maxLayer);
- for (int i = 0; i < windows.size(); i++) {
- WindowState win = windows.get(i);
- Slog.i(TAG, win + ": " + win.mLayer
- + " animLayer=" + win.mWinAnimator.mAnimLayer
- + " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer);
+ // The screen shot will contain the entire screen.
+ dw = (int)(dw*scale);
+ dh = (int)(dh*scale);
+ if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+ int tmp = dw;
+ dw = dh;
+ dh = tmp;
+ rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90;
}
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
+ + maxLayer + " appToken=" + appToken);
+ for (int i = 0; i < windows.size(); i++) {
+ WindowState win = windows.get(i);
+ Slog.i(TAG, win + ": " + win.mLayer
+ + " animLayer=" + win.mWinAnimator.mAnimLayer
+ + " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer);
+ }
+ }
+ rawss = SurfaceControl.screenshot(dw, dh, minLayer, maxLayer);
}
- rawss = Surface.screenshot(dw, dh, 0, maxLayer);
- }
+ } while (!screenshotReady && retryCount <= MAX_SCREENSHOT_RETRIES);
+ if (retryCount > MAX_SCREENSHOT_RETRIES) Slog.i(TAG, "Screenshot max retries " +
+ retryCount + " of " + appToken + " appWin=" + (appWin == null ?
+ "null" : (appWin + " drawState=" + appWin.mWinAnimator.mDrawState)));
if (rawss == null) {
- Slog.w(TAG, "Failure taking screenshot for (" + dw + "x" + dh
+ Slog.w(TAG, "Screenshot failure taking screenshot for (" + dw + "x" + dh
+ ") to layer " + maxLayer);
return null;
}
@@ -5937,9 +5491,28 @@ public class WindowManagerService extends IWindowManager.Stub
ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix);
matrix.postTranslate(-FloatMath.ceil(frame.left*scale), -FloatMath.ceil(frame.top*scale));
Canvas canvas = new Canvas(bm);
+ canvas.drawColor(0xFF000000);
canvas.drawBitmap(rawss, matrix, null);
canvas.setBitmap(null);
+ if (true || DEBUG_SCREENSHOT) {
+ // TEST IF IT's ALL BLACK
+ int[] buffer = new int[bm.getWidth() * bm.getHeight()];
+ bm.getPixels(buffer, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight());
+ boolean allBlack = true;
+ for (int i = 0; i < buffer.length; i++) {
+ if (buffer[i] != Color.BLACK) {
+ allBlack = false;
+ break;
+ }
+ }
+ if (allBlack) {
+ Slog.i(TAG, "Screenshot " + appWin + " was all black! mSurfaceLayer=" +
+ (appWin != null ? appWin.mWinAnimator.mSurfaceLayer : "null") +
+ " minLayer=" + minLayer + " maxLayer=" + maxLayer);
+ }
+ }
+
rawss.recycle();
return bm;
}
@@ -5950,6 +5523,7 @@ public class WindowManagerService extends IWindowManager.Stub
* @param rotation The desired rotation to freeze to, or -1 to use the
* current rotation.
*/
+ @Override
public void freezeRotation(int rotation) {
if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
"freezeRotation()")) {
@@ -5962,8 +5536,14 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_ORIENTATION) Slog.v(TAG, "freezeRotation: mRotation=" + mRotation);
- mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED,
- rotation == -1 ? mRotation : rotation);
+ long origId = Binder.clearCallingIdentity();
+ try {
+ mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED,
+ rotation == -1 ? mRotation : rotation);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
updateRotationUnchecked(false, false);
}
@@ -5971,6 +5551,7 @@ public class WindowManagerService extends IWindowManager.Stub
* Thaw rotation changes. (Disable "rotation lock".)
* Persists across reboots.
*/
+ @Override
public void thawRotation() {
if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
"thawRotation()")) {
@@ -5979,7 +5560,14 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_ORIENTATION) Slog.v(TAG, "thawRotation: mRotation=" + mRotation);
- mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE, 777); // rot not used
+ long origId = Binder.clearCallingIdentity();
+ try {
+ mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE,
+ 777); // rot not used
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
updateRotationUnchecked(false, false);
}
@@ -5990,6 +5578,7 @@ public class WindowManagerService extends IWindowManager.Stub
* such that the current rotation might need to be updated, such as when the
* device is docked or rotated into a new posture.
*/
+ @Override
public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
}
@@ -6000,7 +5589,8 @@ public class WindowManagerService extends IWindowManager.Stub
* This can be used to prevent rotation changes from occurring while the user is
* performing certain operations, such as drag and drop.
*
- * This call nests and must be matched by an equal number of calls to {@link #resumeRotation}.
+ * This call nests and must be matched by an equal number of calls to
+ * {@link #resumeRotationLocked}.
*/
void pauseRotationLocked() {
mDeferredRotationPauseCount += 1;
@@ -6107,11 +5697,16 @@ public class WindowManagerService extends IWindowManager.Stub
mWindowsFreezingScreen = true;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
- mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT,
- WINDOW_FREEZE_TIMEOUT_DURATION);
+ mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
mWaitingForConfig = true;
getDefaultDisplayContentLocked().layoutNeeded = true;
- startFreezingDisplayLocked(inTransaction, 0, 0);
+ final int[] anim = new int[2];
+ if (mAnimator.isDimmingLocked(Display.DEFAULT_DISPLAY)) {
+ anim[0] = anim[1] = 0;
+ } else {
+ mPolicy.selectRotationAnimationLw(anim);
+ }
+ startFreezingDisplayLocked(inTransaction, anim[0], anim[1]);
// startFreezingDisplayLocked can reset the ScreenRotationAnimation.
screenRotationAnimation =
mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
@@ -6130,7 +5725,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (SHOW_TRANSACTIONS) {
Slog.i(TAG, ">>> OPEN TRANSACTION setRotationUnchecked");
}
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
}
try {
// NOTE: We disable the rotation in the emulator because
@@ -6141,14 +5736,14 @@ public class WindowManagerService extends IWindowManager.Stub
rotation, mFxSession,
MAX_ANIMATION_DURATION, mTransitionAnimationScale,
displayInfo.logicalWidth, displayInfo.logicalHeight)) {
- updateLayoutToAnimationLocked();
+ scheduleAnimationLocked();
}
}
mDisplayManagerService.performTraversalInTransactionFromWindowManager();
} finally {
if (!inTransaction) {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (SHOW_LIGHT_TRANSACTIONS) {
Slog.i(TAG, "<<< CLOSE TRANSACTION setRotationUnchecked");
}
@@ -6163,6 +5758,7 @@ public class WindowManagerService extends IWindowManager.Stub
w.mOrientationChanging = true;
mInnerFields.mOrientationChangeComplete = false;
}
+ w.mLastFreezeDuration = 0;
}
for (int i=mRotationWatchers.size()-1; i>=0; i--) {
@@ -6172,18 +5768,30 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- scheduleNotifyRotationChangedIfNeededLocked(displayContent, rotation);
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (mDisplayMagnifier != null
+ && displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDisplayMagnifier.onRotationChangedLocked(getDefaultDisplayContentLocked(), rotation);
+ }
return true;
}
+ @Override
public int getRotation() {
return mRotation;
}
+ @Override
+ public boolean isRotationFrozen() {
+ return mPolicy.getUserRotationMode() == WindowManagerPolicy.USER_ROTATION_LOCKED;
+ }
+
+ @Override
public int watchRotation(IRotationWatcher watcher) {
final IBinder watcherBinder = watcher.asBinder();
IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
+ @Override
public void binderDied() {
synchronized (mWindowMap) {
for (int i=0; i<mRotationWatchers.size(); i++) {
@@ -6211,6 +5819,19 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
+ public void removeRotationWatcher(IRotationWatcher watcher) {
+ final IBinder watcherBinder = watcher.asBinder();
+ synchronized (mWindowMap) {
+ for (int i=0; i<mRotationWatchers.size(); i++) {
+ if (watcherBinder == mRotationWatchers.get(i).asBinder()) {
+ mRotationWatchers.remove(i);
+ i--;
+ }
+ }
+ }
+ }
+
/**
* Apps that use the compact menu panel (as controlled by the panelMenuIsCompact
* theme attribute) on devices that feature a physical options menu key attempt to position
@@ -6228,6 +5849,7 @@ public class WindowManagerService extends IWindowManager.Stub
*
* @return A {@link Gravity} value for placing the options menu window
*/
+ @Override
public int getPreferredOptionsPanelGravity() {
synchronized (mWindowMap) {
final int rotation = getRotation();
@@ -6247,19 +5869,19 @@ public class WindowManagerService extends IWindowManager.Stub
case Surface.ROTATION_270:
return Gravity.START | Gravity.BOTTOM;
}
- } else {
- // On devices with a natural orientation of landscape
- switch (rotation) {
- default:
- case Surface.ROTATION_0:
- return Gravity.RIGHT | Gravity.BOTTOM;
- case Surface.ROTATION_90:
- return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- case Surface.ROTATION_180:
- return Gravity.START | Gravity.BOTTOM;
- case Surface.ROTATION_270:
- return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- }
+ }
+
+ // On devices with a natural orientation of landscape
+ switch (rotation) {
+ default:
+ case Surface.ROTATION_0:
+ return Gravity.RIGHT | Gravity.BOTTOM;
+ case Surface.ROTATION_90:
+ return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ case Surface.ROTATION_180:
+ return Gravity.START | Gravity.BOTTOM;
+ case Surface.ROTATION_270:
+ return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
}
}
}
@@ -6274,6 +5896,7 @@ public class WindowManagerService extends IWindowManager.Stub
* @see com.android.server.wm.ViewServer
* @see com.android.server.wm.ViewServer#VIEW_SERVER_DEFAULT_PORT
*/
+ @Override
public boolean startViewServer(int port) {
if (isSystemSecure()) {
return false;
@@ -6320,6 +5943,7 @@ public class WindowManagerService extends IWindowManager.Stub
*
* @see com.android.server.wm.ViewServer
*/
+ @Override
public boolean stopViewServer() {
if (isSystemSecure()) {
return false;
@@ -6342,6 +5966,7 @@ public class WindowManagerService extends IWindowManager.Stub
*
* @see com.android.server.wm.ViewServer
*/
+ @Override
public boolean isViewServerRunning() {
if (isSystemSecure()) {
return false;
@@ -6554,146 +6179,6 @@ public class WindowManagerService extends IWindowManager.Stub
return success;
}
- @Override
- public void addDisplayContentChangeListener(int displayId,
- IDisplayContentChangeListener listener) {
- if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
- "addDisplayContentChangeListener()")) {
- throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission");
- }
- synchronized(mWindowMap) {
- DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
- if (displayContent.mDisplayContentChangeListeners == null) {
- displayContent.mDisplayContentChangeListeners =
- new RemoteCallbackList<IDisplayContentChangeListener>();
- displayContent.mDisplayContentChangeListeners.register(listener);
- }
- }
- }
- }
-
- @Override
- public void removeDisplayContentChangeListener(int displayId,
- IDisplayContentChangeListener listener) {
- if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
- "removeDisplayContentChangeListener()")) {
- throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission");
- }
- synchronized(mWindowMap) {
- DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
- if (displayContent.mDisplayContentChangeListeners != null) {
- displayContent.mDisplayContentChangeListeners.unregister(listener);
- if (displayContent.mDisplayContentChangeListeners
- .getRegisteredCallbackCount() == 0) {
- displayContent.mDisplayContentChangeListeners = null;
- }
- }
- }
- }
- }
-
- void scheduleNotifyWindowTranstionIfNeededLocked(WindowState window, int transition) {
- DisplayContent displayContent = window.mDisplayContent;
- if (displayContent.mDisplayContentChangeListeners != null) {
- WindowInfo info = getWindowInfoForWindowStateLocked(window);
- mH.obtainMessage(H.NOTIFY_WINDOW_TRANSITION, transition, 0, info).sendToTarget();
- }
- }
-
- private void handleNotifyWindowTranstion(int transition, WindowInfo info) {
- RemoteCallbackList<IDisplayContentChangeListener> callbacks = null;
- synchronized (mWindowMap) {
- DisplayContent displayContent = getDisplayContentLocked(info.displayId);
- if (displayContent == null) {
- return;
- }
- callbacks = displayContent.mDisplayContentChangeListeners;
- if (callbacks == null) {
- return;
- }
- }
- final int callbackCount = callbacks.beginBroadcast();
- try {
- for (int i = 0; i < callbackCount; i++) {
- try {
- callbacks.getBroadcastItem(i).onWindowTransition(info.displayId,
- transition, info);
- } catch (RemoteException re) {
- /* ignore */
- }
- }
- } finally {
- callbacks.finishBroadcast();
- }
- }
-
- private void scheduleNotifyRotationChangedIfNeededLocked(DisplayContent displayContent,
- int rotation) {
- if (displayContent.mDisplayContentChangeListeners != null
- && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) {
- mH.obtainMessage(H.NOTIFY_ROTATION_CHANGED, displayContent.getDisplayId(),
- rotation).sendToTarget();
- }
- }
-
- private void handleNotifyRotationChanged(int displayId, int rotation) {
- RemoteCallbackList<IDisplayContentChangeListener> callbacks = null;
- synchronized (mWindowMap) {
- DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent == null) {
- return;
- }
- callbacks = displayContent.mDisplayContentChangeListeners;
- if (callbacks == null) {
- return;
- }
- }
- try {
- final int watcherCount = callbacks.beginBroadcast();
- for (int i = 0; i < watcherCount; i++) {
- try {
- callbacks.getBroadcastItem(i).onRotationChanged(rotation);
- } catch (RemoteException re) {
- /* ignore */
- }
- }
- } finally {
- callbacks.finishBroadcast();
- }
- }
-
- private void scheduleNotifyWindowLayersChangedIfNeededLocked(DisplayContent displayContent) {
- if (displayContent.mDisplayContentChangeListeners != null
- && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) {
- mH.obtainMessage(H.NOTIFY_WINDOW_LAYERS_CHANGED, displayContent) .sendToTarget();
- }
- }
-
- private void handleNotifyWindowLayersChanged(DisplayContent displayContent) {
- RemoteCallbackList<IDisplayContentChangeListener> callbacks = null;
- synchronized (mWindowMap) {
- callbacks = displayContent.mDisplayContentChangeListeners;
- if (callbacks == null) {
- return;
- }
- }
- try {
- final int watcherCount = callbacks.beginBroadcast();
- for (int i = 0; i < watcherCount; i++) {
- try {
- callbacks.getBroadcastItem(i).onWindowLayersChanged(
- displayContent.getDisplayId());
- } catch (RemoteException re) {
- /* ignore */
- }
- }
- } finally {
- callbacks.finishBroadcast();
- }
- }
-
public void addWindowChangeListener(WindowChangeListener listener) {
synchronized(mWindowMap) {
mWindowChangeListeners.add(listener);
@@ -6772,6 +6257,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (config == null && mWaitingForConfig) {
// Nothing changed but we are waiting for something... stop that!
mWaitingForConfig = false;
+ mLastFinishedFreezeSource = "new-config";
performLayoutAndPlaceSurfacesLocked();
}
return config;
@@ -6944,8 +6430,6 @@ public class WindowManagerService extends IWindowManager.Stub
displayInfo.getAppMetrics(mDisplayMetrics, null);
mDisplayManagerService.setDisplayInfoOverrideFromWindowManager(
displayContent.getDisplayId(), displayInfo);
-
- mAnimator.setDisplayDimensions(dw, dh, appWidth, appHeight);
}
if (false) {
Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
@@ -7096,8 +6580,8 @@ public class WindowManagerService extends IWindowManager.Stub
// TODO(multi-display): support other displays
final DisplayContent displayContent = getDefaultDisplayContentLocked();
final Display display = displayContent.getDisplay();
- Surface surface = new Surface(session, "drag surface",
- width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+ SurfaceControl surface = new SurfaceControl(session, "drag surface",
+ width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
surface.setLayerStack(display.getLayerStack());
if (SHOW_TRANSACTIONS) Slog.i(TAG, " DRAG "
+ surface + ": CREATE");
@@ -7114,7 +6598,7 @@ public class WindowManagerService extends IWindowManager.Stub
} else {
Slog.w(TAG, "Drag already in progress");
}
- } catch (Surface.OutOfResourcesException e) {
+ } catch (SurfaceControl.OutOfResourcesException e) {
Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e);
if (mDragState != null) {
mDragState.reset();
@@ -7132,10 +6616,11 @@ public class WindowManagerService extends IWindowManager.Stub
// -------------------------------------------------------------
// Input Events and Focus Management
// -------------------------------------------------------------
-
+
final InputMonitor mInputMonitor = new InputMonitor(this);
private boolean mEventDispatchingEnabled;
+ @Override
public void pauseKeyDispatching(IBinder _token) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"pauseKeyDispatching()")) {
@@ -7150,6 +6635,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public void resumeKeyDispatching(IBinder _token) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"resumeKeyDispatching()")) {
@@ -7164,6 +6650,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public void setEventDispatching(boolean enabled) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setEventDispatching()")) {
@@ -7179,6 +6666,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public IBinder getFocusedWindowToken() {
if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
"getFocusedWindowToken()")) {
@@ -7250,11 +6738,6 @@ public class WindowManagerService extends IWindowManager.Stub
mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TOUCHSCREEN);
- final DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
- mAnimator.setDisplayDimensions(
- displayInfo.logicalWidth, displayInfo.logicalHeight,
- displayInfo.appWidth, displayInfo.appHeight);
-
mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
displayContent.mInitialDisplayWidth,
displayContent.mInitialDisplayHeight,
@@ -7334,26 +6817,14 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
public static final int BOOT_TIMEOUT = 23;
public static final int WAITING_FOR_DRAWN_TIMEOUT = 24;
- public static final int UPDATE_ANIM_PARAMETERS = 25;
- public static final int SHOW_STRICT_MODE_VIOLATION = 26;
- public static final int DO_ANIMATION_CALLBACK = 27;
- public static final int NOTIFY_ROTATION_CHANGED = 28;
- public static final int NOTIFY_WINDOW_TRANSITION = 29;
- public static final int NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 30;
- public static final int NOTIFY_WINDOW_LAYERS_CHANGED = 31;
-
- public static final int DO_DISPLAY_ADDED = 32;
- public static final int DO_DISPLAY_REMOVED = 33;
- public static final int DO_DISPLAY_CHANGED = 34;
-
- public static final int CLIENT_FREEZE_TIMEOUT = 35;
+ public static final int SHOW_STRICT_MODE_VIOLATION = 25;
+ public static final int DO_ANIMATION_CALLBACK = 26;
- public static final int ANIMATOR_WHAT_OFFSET = 100000;
- public static final int SET_TRANSPARENT_REGION = ANIMATOR_WHAT_OFFSET + 1;
- public static final int CLEAR_PENDING_ACTIONS = ANIMATOR_WHAT_OFFSET + 2;
+ public static final int DO_DISPLAY_ADDED = 27;
+ public static final int DO_DISPLAY_REMOVED = 28;
+ public static final int DO_DISPLAY_CHANGED = 29;
- public H() {
- }
+ public static final int CLIENT_FREEZE_TIMEOUT = 30;
@Override
public void handleMessage(Message msg) {
@@ -7387,22 +6858,14 @@ public class WindowManagerService extends IWindowManager.Stub
//System.out.println("Changing focus from " + lastFocus
// + " to " + newFocus);
if (newFocus != null) {
- try {
- //Slog.i(TAG, "Gaining focus: " + newFocus);
- newFocus.mClient.windowFocusChanged(true, mInTouchMode);
- } catch (RemoteException e) {
- // Ignore if process has died.
- }
+ //Slog.i(TAG, "Gaining focus: " + newFocus);
+ newFocus.reportFocusChangedSerialized(true, mInTouchMode);
notifyFocusChanged();
}
if (lastFocus != null) {
- try {
- //Slog.i(TAG, "Losing focus: " + lastFocus);
- lastFocus.mClient.windowFocusChanged(false, mInTouchMode);
- } catch (RemoteException e) {
- // Ignore if process has died.
- }
+ //Slog.i(TAG, "Losing focus: " + lastFocus);
+ lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
}
}
} break;
@@ -7417,12 +6880,8 @@ public class WindowManagerService extends IWindowManager.Stub
final int N = losers.size();
for (int i=0; i<N; i++) {
- try {
- //Slog.i(TAG, "Losing delayed focus: " + losers.get(i));
- losers.get(i).mClient.windowFocusChanged(false, mInTouchMode);
- } catch (RemoteException e) {
- // Ignore if process has died.
- }
+ //Slog.i(TAG, "Losing delayed focus: " + losers.get(i));
+ losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
}
} break;
@@ -7595,6 +7054,8 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState w = windows.get(i);
if (w.mOrientationChanging) {
w.mOrientationChanging = false;
+ w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+ - mDisplayFreezeTime);
Slog.w(TAG, "Force clearing orientation change: " + w);
}
}
@@ -7605,11 +7066,9 @@ public class WindowManagerService extends IWindowManager.Stub
case APP_TRANSITION_TIMEOUT: {
synchronized (mWindowMap) {
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
- "*** APP TRANSITION TIMEOUT");
- mAppTransitionReady = true;
- mAppTransitionTimeout = true;
+ if (mAppTransition.isTransitionSet()) {
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** APP TRANSITION TIMEOUT");
+ mAppTransition.setTimeout();
mAnimatingAppTokens.clear();
mAnimatingAppTokens.addAll(mAppTokens);
performLayoutAndPlaceSurfacesLocked();
@@ -7630,20 +7089,18 @@ public class WindowManagerService extends IWindowManager.Stub
case FORCE_GC: {
synchronized (mWindowMap) {
- synchronized (mAnimator) {
- // Since we're holding both mWindowMap and mAnimator we don't need to
- // hold mAnimator.mLayoutToAnim.
- if (mAnimator.mAnimating || mLayoutToAnim.mAnimationScheduled) {
- // If we are animating, don't do the gc now but
- // delay a bit so we don't interrupt the animation.
- sendEmptyMessageDelayed(H.FORCE_GC, 2000);
- return;
- }
- // If we are currently rotating the display, it will
- // schedule a new message when done.
- if (mDisplayFrozen) {
- return;
- }
+ // Since we're holding both mWindowMap and mAnimator we don't need to
+ // hold mAnimator.mLayoutToAnim.
+ if (mAnimator.mAnimating || mAnimationScheduled) {
+ // If we are animating, don't do the gc now but
+ // delay a bit so we don't interrupt the animation.
+ sendEmptyMessageDelayed(H.FORCE_GC, 2000);
+ return;
+ }
+ // If we are currently rotating the display, it will
+ // schedule a new message when done.
+ if (mDisplayFrozen) {
+ return;
}
}
Runtime.getRuntime().gc();
@@ -7657,16 +7114,14 @@ public class WindowManagerService extends IWindowManager.Stub
case APP_FREEZE_TIMEOUT: {
synchronized (mWindowMap) {
- synchronized (mAnimator) {
- Slog.w(TAG, "App freeze timeout expired.");
- int i = mAppTokens.size();
- while (i > 0) {
- i--;
- AppWindowToken tok = mAppTokens.get(i);
- if (tok.mAppAnimator.freezingScreen) {
- Slog.w(TAG, "Force clearing freeze: " + tok);
- unsetAppFreezingScreenLocked(tok, true, true);
- }
+ Slog.w(TAG, "App freeze timeout expired.");
+ int i = mAppTokens.size();
+ while (i > 0) {
+ i--;
+ AppWindowToken tok = mAppTokens.get(i);
+ if (tok.mAppAnimator.freezingScreen) {
+ Slog.w(TAG, "Force clearing freeze: " + tok);
+ unsetAppFreezingScreenLocked(tok, true, true);
}
}
}
@@ -7677,6 +7132,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
if (mClientFreezingScreen) {
mClientFreezingScreen = false;
+ mLastFinishedFreezeSource = "client-timeout";
stopFreezingDisplayLocked();
}
}
@@ -7757,36 +7213,11 @@ public class WindowManagerService extends IWindowManager.Stub
break;
}
- case UPDATE_ANIM_PARAMETERS: {
- // Used to send multiple changes from the animation side to the layout side.
- synchronized (mWindowMap) {
- if (copyAnimToLayoutParamsLocked()) {
- sendEmptyMessage(CLEAR_PENDING_ACTIONS);
- performLayoutAndPlaceSurfacesLocked();
- }
- }
- break;
- }
-
case SHOW_STRICT_MODE_VIOLATION: {
showStrictModeViolation(msg.arg1, msg.arg2);
break;
}
- // Animation messages. Move to Window{State}Animator
- case SET_TRANSPARENT_REGION: {
- Pair<WindowStateAnimator, Region> pair =
- (Pair<WindowStateAnimator, Region>) msg.obj;
- final WindowStateAnimator winAnimator = pair.first;
- winAnimator.setTransparentRegionHint(pair.second);
- break;
- }
-
- case CLEAR_PENDING_ACTIONS: {
- mAnimator.clearPendingActions();
- break;
- }
-
case DO_ANIMATION_CALLBACK: {
try {
((IRemoteCallback)msg.obj).sendResult(null);
@@ -7795,34 +7226,6 @@ public class WindowManagerService extends IWindowManager.Stub
break;
}
- case NOTIFY_ROTATION_CHANGED: {
- final int displayId = msg.arg1;
- final int rotation = msg.arg2;
- handleNotifyRotationChanged(displayId, rotation);
- break;
- }
-
- case NOTIFY_WINDOW_TRANSITION: {
- final int transition = msg.arg1;
- WindowInfo info = (WindowInfo) msg.obj;
- handleNotifyWindowTranstion(transition, info);
- break;
- }
-
- case NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
- final int displayId = msg.arg1;
- final boolean immediate = (msg.arg2 == 1);
- Rect rectangle = (Rect) msg.obj;
- handleNotifyRectangleOnScreenRequested(displayId, rectangle, immediate);
- break;
- }
-
- case NOTIFY_WINDOW_LAYERS_CHANGED: {
- DisplayContent displayContent = (DisplayContent) msg.obj;
- handleNotifyWindowLayersChanged(displayContent);
- break;
- }
-
case DO_DISPLAY_ADDED:
synchronized (mWindowMap) {
handleDisplayAddedLocked(msg.arg1);
@@ -7921,20 +7324,43 @@ public class WindowManagerService extends IWindowManager.Stub
return false;
}
+ @Override
public void getInitialDisplaySize(int displayId, Point size) {
- // TODO(cmautner): Access to DisplayContent should be locked on mWindowMap. Doing that
- // could lead to deadlock since this is called from ActivityManager.
- final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent != null) {
- synchronized(displayContent.mDisplaySizeLock) {
- size.x = displayContent.mInitialDisplayWidth;
- size.y = displayContent.mInitialDisplayHeight;
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = getDisplayContentLocked(displayId);
+ if (displayContent != null) {
+ synchronized(displayContent.mDisplaySizeLock) {
+ size.x = displayContent.mInitialDisplayWidth;
+ size.y = displayContent.mInitialDisplayHeight;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void getBaseDisplaySize(int displayId, Point size) {
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = getDisplayContentLocked(displayId);
+ if (displayContent != null) {
+ synchronized(displayContent.mDisplaySizeLock) {
+ size.x = displayContent.mBaseDisplayWidth;
+ size.y = displayContent.mBaseDisplayHeight;
+ }
}
}
}
@Override
public void setForcedDisplaySize(int displayId, int width, int height) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold permission " +
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ }
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ throw new IllegalArgumentException("Can only set the default display");
+ }
synchronized(mWindowMap) {
// Set some sort of reasonable bounds on the size of the display that we
// will try to emulate.
@@ -8006,6 +7432,15 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void clearForcedDisplaySize(int displayId) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold permission " +
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ }
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ throw new IllegalArgumentException("Can only set the default display");
+ }
synchronized(mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null) {
@@ -8018,7 +7453,42 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public int getInitialDisplayDensity(int displayId) {
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = getDisplayContentLocked(displayId);
+ if (displayContent != null) {
+ synchronized(displayContent.mDisplaySizeLock) {
+ return displayContent.mInitialDisplayDensity;
+ }
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public int getBaseDisplayDensity(int displayId) {
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = getDisplayContentLocked(displayId);
+ if (displayContent != null) {
+ synchronized(displayContent.mDisplaySizeLock) {
+ return displayContent.mBaseDisplayDensity;
+ }
+ }
+ }
+ return -1;
+ }
+
+ @Override
public void setForcedDisplayDensity(int displayId, int density) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold permission " +
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ }
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ throw new IllegalArgumentException("Can only set the default display");
+ }
synchronized(mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null) {
@@ -8041,6 +7511,15 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void clearForcedDisplayDensity(int displayId) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold permission " +
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ }
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ throw new IllegalArgumentException("Can only set the default display");
+ }
synchronized(mWindowMap) {
final DisplayContent displayContent = getDisplayContentLocked(displayId);
if (displayContent != null) {
@@ -8079,6 +7558,35 @@ public class WindowManagerService extends IWindowManager.Stub
performLayoutAndPlaceSurfacesLocked();
}
+ @Override
+ public void setOverscan(int displayId, int left, int top, int right, int bottom) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold permission " +
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ }
+ synchronized(mWindowMap) {
+ DisplayContent displayContent = getDisplayContentLocked(displayId);
+ if (displayContent != null) {
+ mDisplayManagerService.setOverscan(displayId, left, top, right, bottom);
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ synchronized(displayContent.mDisplaySizeLock) {
+ displayInfo.overscanLeft = left;
+ displayInfo.overscanTop = top;
+ displayInfo.overscanRight = right;
+ displayInfo.overscanBottom = bottom;
+ }
+ mPolicy.setDisplayOverscan(displayContent.getDisplay(), left, top, right, bottom);
+ displayContent.layoutNeeded = true;
+ mDisplaySettings.setOverscanLocked(displayInfo.name, left, top, right, bottom);
+ mDisplaySettings.writeSettingsLocked();
+ performLayoutAndPlaceSurfacesLocked();
+ }
+ }
+ }
+
+ @Override
public boolean hasSystemNavBar() {
return mPolicy.hasSystemNavBar();
}
@@ -8193,7 +7701,7 @@ public class WindowManagerService extends IWindowManager.Stub
pw.flush();
Slog.w(TAG, "This window was lost: " + ws);
Slog.w(TAG, sw.toString());
- ws.mWinAnimator.destroySurfaceLocked(false);
+ ws.mWinAnimator.destroySurfaceLocked();
}
}
Slog.w(TAG, "Current app token list:");
@@ -8255,7 +7763,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (layerChanged && mAnimator.isDimmingLocked(winAnimator)) {
// Force an animation pass just to update the mDimAnimator layer.
- updateLayoutToAnimationLocked();
+ scheduleAnimationLocked();
}
if (DEBUG_LAYERS) Slog.v(TAG, "Assign layer " + w + ": "
+ "mBase=" + w.mBaseLayer
@@ -8267,8 +7775,10 @@ public class WindowManagerService extends IWindowManager.Stub
// "Assigned layer " + curLayer + " to " + w.mClient.asBinder());
}
- if (anyLayerChanged) {
- scheduleNotifyWindowLayersChangedIfNeededLocked(getDefaultDisplayContentLocked());
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (mDisplayMagnifier != null && anyLayerChanged
+ && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mDisplayMagnifier.onWindowLayersChangedLocked();
}
}
@@ -8280,6 +7790,7 @@ public class WindowManagerService extends IWindowManager.Stub
mH.removeMessages(H.DO_TRAVERSAL);
loopCount--;
} while (mTraversalScheduled && loopCount > 0);
+ mInnerFields.mWallpaperActionPending = false;
}
private boolean mInLayout = false;
@@ -8299,7 +7810,7 @@ public class WindowManagerService extends IWindowManager.Stub
// applications. Don't do any window layout until we have it.
return;
}
-
+
if (!mDisplayReady) {
// Not yet initialized, nothing to do.
return;
@@ -8308,7 +7819,7 @@ public class WindowManagerService extends IWindowManager.Stub
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
mInLayout = true;
boolean recoveringMemory = false;
-
+
try {
if (mForceRemoves != null) {
recoveringMemory = true;
@@ -8440,8 +7951,7 @@ public class WindowManagerService extends IWindowManager.Stub
// windows, since that means "perform layout as normal,
// just don't display").
if (!gone || !win.mHaveFrame || win.mLayoutNeeded
- || ((win.mAttrs.type == TYPE_KEYGUARD || win.mAttrs.type == TYPE_WALLPAPER) &&
- win.isConfigChanged())
+ || (win.mAttrs.type == TYPE_KEYGUARD && win.isConfigChanged())
|| win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
if (!win.mLayoutAttached) {
if (initial) {
@@ -8522,7 +8032,7 @@ public class WindowManagerService extends IWindowManager.Stub
attachedBehindDream = behindDream;
}
}
-
+
// Window frames may have changed. Tell the input dispatcher about it.
mInputMonitor.setUpdateInputWindowsNeededLw();
if (updateInputWindows) {
@@ -8540,13 +8050,14 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_ORIENTATION) Slog.v(TAG,
"Changing surface while display frozen: " + w);
w.mOrientationChanging = true;
+ w.mLastFreezeDuration = 0;
mInnerFields.mOrientationChangeComplete = false;
if (!mWindowsFreezingScreen) {
mWindowsFreezingScreen = true;
// XXX should probably keep timeout from
// when we first froze the display.
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
- mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT,
+ mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT,
WINDOW_FREEZE_TIMEOUT_DURATION);
}
}
@@ -8565,8 +8076,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Checking " + NN + " opening apps (frozen="
+ mDisplayFrozen + " timeout="
- + mAppTransitionTimeout + ")...");
- if (!mDisplayFrozen && !mAppTransitionTimeout) {
+ + mAppTransition.isTimeout() + ")...");
+ if (!mDisplayFrozen && !mAppTransition.isTimeout()) {
// If the display isn't frozen, wait to do anything until
// all of the apps are ready. Otherwise just go because
// we'll unfreeze the display when everyone is ready.
@@ -8585,14 +8096,11 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (goodToGo) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
- int transit = mNextAppTransition;
+ int transit = mAppTransition.getAppTransition();
if (mSkipAppTransitionAnimation) {
- transit = WindowManagerPolicy.TRANSIT_UNSET;
+ transit = AppTransition.TRANSIT_UNSET;
}
- mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
- mAppTransitionReady = false;
- mAppTransitionRunning = true;
- mAppTransitionTimeout = false;
+ mAppTransition.goodToGo();
mStartingIconInTransition = false;
mSkipAppTransitionAnimation = false;
@@ -8606,7 +8114,6 @@ public class WindowManagerService extends IWindowManager.Stub
&& !mWallpaperTarget.mWinAnimator.isDummyAnimation()
? null : mWallpaperTarget;
- adjustWallpaperWindowsLocked();
mInnerFields.mWallpaperMayChange = false;
// The top-most window will supply the layout params,
@@ -8620,7 +8127,18 @@ public class WindowManagerService extends IWindowManager.Stub
+ ", oldWallpaper=" + oldWallpaper
+ ", lower target=" + mLowerWallpaperTarget
+ ", upper target=" + mUpperWallpaperTarget);
- int foundWallpapers = 0;
+
+ boolean openingAppHasWallpaper = false;
+ boolean closingAppHasWallpaper = false;
+ final AppWindowToken lowerWallpaperAppToken;
+ final AppWindowToken upperWallpaperAppToken;
+ if (mLowerWallpaperTarget == null) {
+ lowerWallpaperAppToken = upperWallpaperAppToken = null;
+ } else {
+ lowerWallpaperAppToken = mLowerWallpaperTarget.mAppToken;
+ upperWallpaperAppToken = mUpperWallpaperTarget.mAppToken;
+ }
+
// Do a first pass through the tokens for two
// things:
// (1) Determine if both the closing and opening
@@ -8634,21 +8152,19 @@ public class WindowManagerService extends IWindowManager.Stub
final int NC = mClosingApps.size();
NN = NC + mOpeningApps.size();
for (i=0; i<NN; i++) {
- AppWindowToken wtoken;
- int mode;
+ final AppWindowToken wtoken;
if (i < NC) {
wtoken = mClosingApps.get(i);
- mode = 1;
+ if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
+ closingAppHasWallpaper = true;
+ }
} else {
- wtoken = mOpeningApps.get(i-NC);
- mode = 2;
- }
- if (mLowerWallpaperTarget != null) {
- if (mLowerWallpaperTarget.mAppToken == wtoken
- || mUpperWallpaperTarget.mAppToken == wtoken) {
- foundWallpapers |= mode;
+ wtoken = mOpeningApps.get(i - NC);
+ if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
+ openingAppHasWallpaper = true;
}
}
+
if (wtoken.appFullscreen) {
WindowState ws = wtoken.findMainWindow();
if (ws != null) {
@@ -8667,33 +8183,31 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- if (foundWallpapers == 3) {
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
- "Wallpaper animation!");
+ if (closingAppHasWallpaper && openingAppHasWallpaper) {
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
switch (transit) {
- case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
- case WindowManagerPolicy.TRANSIT_TASK_OPEN:
- case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
- transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN;
+ case AppTransition.TRANSIT_ACTIVITY_OPEN:
+ case AppTransition.TRANSIT_TASK_OPEN:
+ case AppTransition.TRANSIT_TASK_TO_FRONT:
+ transit = AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN;
break;
- case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE:
- case WindowManagerPolicy.TRANSIT_TASK_CLOSE:
- case WindowManagerPolicy.TRANSIT_TASK_TO_BACK:
- transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE;
+ case AppTransition.TRANSIT_ACTIVITY_CLOSE:
+ case AppTransition.TRANSIT_TASK_CLOSE:
+ case AppTransition.TRANSIT_TASK_TO_BACK:
+ transit = AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
break;
}
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
- "New transit: " + transit);
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit: " + transit);
} else if ((oldWallpaper != null) && !mOpeningApps.contains(oldWallpaper.mAppToken)) {
// We are transitioning from an activity with
// a wallpaper to one without.
- transit = WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE;
+ transit = AppTransition.TRANSIT_WALLPAPER_CLOSE;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New transit away from wallpaper: " + transit);
} else if (mWallpaperTarget != null && mWallpaperTarget.isVisibleLw()) {
// We are transitioning from an activity without
// a wallpaper to now showing the wallpaper
- transit = WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN;
+ transit = AppTransition.TRANSIT_WALLPAPER_OPEN;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New transit into wallpaper: " + transit);
}
@@ -8715,7 +8229,6 @@ public class WindowManagerService extends IWindowManager.Stub
final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
appAnimator.clearThumbnail();
- wtoken.reportedVisible = false;
wtoken.inPendingTransaction = false;
appAnimator.animation = null;
setTokenVisibilityLocked(wtoken, animLp, true, transit, false);
@@ -8759,52 +8272,59 @@ public class WindowManagerService extends IWindowManager.Stub
// this guy's animations regardless of whether it's
// gotten drawn.
wtoken.allDrawn = true;
+ wtoken.deferClearAllDrawn = false;
}
- if (mNextAppTransitionThumbnail != null && topOpeningApp != null
- && topOpeningApp.mAppAnimator.animation != null) {
+ AppWindowAnimator appAnimator =
+ topOpeningApp == null ? null : topOpeningApp.mAppAnimator;
+ Bitmap nextAppTransitionThumbnail = mAppTransition.getNextAppTransitionThumbnail();
+ if (nextAppTransitionThumbnail != null && appAnimator != null
+ && appAnimator.animation != null) {
// This thumbnail animation is very special, we need to have
// an extra surface with the thumbnail included with the animation.
- Rect dirty = new Rect(0, 0, mNextAppTransitionThumbnail.getWidth(),
- mNextAppTransitionThumbnail.getHeight());
+ Rect dirty = new Rect(0, 0, nextAppTransitionThumbnail.getWidth(),
+ nextAppTransitionThumbnail.getHeight());
try {
// TODO(multi-display): support other displays
final DisplayContent displayContent = getDefaultDisplayContentLocked();
final Display display = displayContent.getDisplay();
- Surface surface = new Surface(mFxSession,
+ SurfaceControl surfaceControl = new SurfaceControl(mFxSession,
"thumbnail anim",
dirty.width(), dirty.height(),
- PixelFormat.TRANSLUCENT, Surface.HIDDEN);
- surface.setLayerStack(display.getLayerStack());
- topOpeningApp.mAppAnimator.thumbnail = surface;
- if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL "
- + surface + ": CREATE");
+ PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+ surfaceControl.setLayerStack(display.getLayerStack());
+ appAnimator.thumbnail = surfaceControl;
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL " + surfaceControl + ": CREATE");
Surface drawSurface = new Surface();
- drawSurface.copyFrom(surface);
+ drawSurface.copyFrom(surfaceControl);
Canvas c = drawSurface.lockCanvas(dirty);
- c.drawBitmap(mNextAppTransitionThumbnail, 0, 0, null);
+ c.drawBitmap(nextAppTransitionThumbnail, 0, 0, null);
drawSurface.unlockCanvasAndPost(c);
drawSurface.release();
- topOpeningApp.mAppAnimator.thumbnailLayer = topOpeningLayer;
- Animation anim = createThumbnailAnimationLocked(
- transit, true, true, mNextAppTransitionScaleUp);
- topOpeningApp.mAppAnimator.thumbnailAnimation = anim;
+ appAnimator.thumbnailLayer = topOpeningLayer;
+ DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
+ Animation anim = mAppTransition.createThumbnailAnimationLocked(
+ transit, true, true, displayInfo.appWidth, displayInfo.appHeight);
+ appAnimator.thumbnailAnimation = anim;
anim.restrictDuration(MAX_ANIMATION_DURATION);
anim.scaleCurrentDuration(mTransitionAnimationScale);
- topOpeningApp.mAppAnimator.thumbnailX = mNextAppTransitionStartX;
- topOpeningApp.mAppAnimator.thumbnailY = mNextAppTransitionStartY;
- } catch (Surface.OutOfResourcesException e) {
+ Point p = new Point();
+ mAppTransition.getStartingPoint(p);
+ appAnimator.thumbnailX = p.x;
+ appAnimator.thumbnailY = p.y;
+ } catch (SurfaceControl.OutOfResourcesException e) {
Slog.e(TAG, "Can't allocate thumbnail surface w=" + dirty.width()
+ " h=" + dirty.height(), e);
- topOpeningApp.mAppAnimator.clearThumbnail();
+ appAnimator.clearThumbnail();
+ } catch (Surface.OutOfResourcesException e) {
+ Slog.e(TAG, "Can't allocate Canvas surface w=" + dirty.width()
+ + " h=" + dirty.height(), e);
+ appAnimator.clearThumbnail();
}
}
- mNextAppTransitionType = ActivityOptions.ANIM_NONE;
- mNextAppTransitionPackage = null;
- mNextAppTransitionThumbnail = null;
- scheduleAnimationCallback(mNextAppTransitionCallback);
- mNextAppTransitionCallback = null;
+ mAppTransition.postAnimationCallback();
+ mAppTransition.clear();
mOpeningApps.clear();
mClosingApps.clear();
@@ -8834,7 +8354,7 @@ public class WindowManagerService extends IWindowManager.Stub
private int handleAnimatingStoppedAndTransitionLocked() {
int changes = 0;
- mAppTransitionRunning = false;
+ mAppTransition.setIdle();
// Restore window app tokens to the ActivityManager views
for (int i = mAnimatingAppTokens.size() - 1; i >= 0; i--) {
mAnimatingAppTokens.get(i).sendingToBottom = false;
@@ -8844,7 +8364,8 @@ public class WindowManagerService extends IWindowManager.Stub
rebuildAppWindowListLocked();
changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT;
- mInnerFields.mAdjResult |= ADJUST_WALLPAPER_LAYERS_CHANGED;
+ if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+ "Wallpaper layer changed: assigning layers + relayout");
moveInputMethodWindowsIfNeededLocked(true);
mInnerFields.mWallpaperMayChange = true;
// Since the window list has been rebuilt, focus might
@@ -8855,40 +8376,11 @@ public class WindowManagerService extends IWindowManager.Stub
return changes;
}
- /**
- * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
- *
- * @return bitmap indicating if another pass through layout must be made.
- */
- private int animateAwayWallpaperLocked() {
- int changes = 0;
- WindowState oldWallpaper = mWallpaperTarget;
- if (mLowerWallpaperTarget != null
- && mLowerWallpaperTarget.mAppToken != null) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "wallpaperForceHiding changed with lower="
- + mLowerWallpaperTarget);
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "hidden=" + mLowerWallpaperTarget.mAppToken.hidden +
- " hiddenRequested=" + mLowerWallpaperTarget.mAppToken.hiddenRequested);
- if (mLowerWallpaperTarget.mAppToken.hidden) {
- // The lower target has become hidden before we
- // actually started the animation... let's completely
- // re-evaluate everything.
- mLowerWallpaperTarget = mUpperWallpaperTarget = null;
- changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM;
- }
- }
- mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked();
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "****** OLD: " + oldWallpaper
- + " NEW: " + mWallpaperTarget
- + " LOWER: " + mLowerWallpaperTarget);
- return changes;
- }
-
private void updateResizingWindows(final WindowState w) {
final WindowStateAnimator winAnimator = w.mWinAnimator;
if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq) {
+ w.mOverscanInsetsChanged |=
+ !w.mLastOverscanInsets.equals(w.mOverscanInsets);
w.mContentInsetsChanged |=
!w.mLastContentInsets.equals(w.mContentInsets);
w.mVisibleInsetsChanged |=
@@ -8909,11 +8401,14 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_RESIZE || DEBUG_ORIENTATION) {
Slog.v(TAG, "Resize reasons: "
+ " contentInsetsChanged=" + w.mContentInsetsChanged
+ + " " + w.mContentInsets.toShortString()
+ " visibleInsetsChanged=" + w.mVisibleInsetsChanged
+ + " " + w.mVisibleInsets.toShortString()
+ " surfaceResized=" + winAnimator.mSurfaceResized
+ " configChanged=" + configChanged);
}
+ w.mLastOverscanInsets.set(w.mOverscanInsets);
w.mLastContentInsets.set(w.mContentInsets);
w.mLastVisibleInsets.set(w.mVisibleInsets);
makeWindowFreezingScreenIfNeededLocked(w);
@@ -8925,10 +8420,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (w.mOrientationChanging) {
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION) Slog.v(TAG,
"Orientation start waiting for draw mDrawState=DRAW_PENDING in "
- + w + ", surface " + winAnimator.mSurface);
+ + w + ", surface " + winAnimator.mSurfaceControl);
winAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
if (w.mAppToken != null) {
w.mAppToken.allDrawn = false;
+ w.mAppToken.deferClearAllDrawn = false;
}
}
if (!mResizingWindows.contains(w)) {
@@ -8941,8 +8437,10 @@ public class WindowManagerService extends IWindowManager.Stub
if (w.isDrawnLw()) {
if (DEBUG_ORIENTATION) Slog.v(TAG,
"Orientation not waiting for draw in "
- + w + ", surface " + winAnimator.mSurface);
+ + w + ", surface " + winAnimator.mSurfaceControl);
w.mOrientationChanging = false;
+ w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+ - mDisplayFreezeTime);
}
}
}
@@ -9004,27 +8502,19 @@ public class WindowManagerService extends IWindowManager.Stub
// so we want to leave all of them as undimmed (for
// performance reasons).
mInnerFields.mObscured = true;
- } else if (canBeSeen && (attrFlags & FLAG_DIM_BEHIND) != 0
- && !(w.mAppToken != null && w.mAppToken.hiddenRequested)
+ }
+ }
+
+ private void handleFlagDimBehind(WindowState w, int innerDw, int innerDh) {
+ final WindowManager.LayoutParams attrs = w.mAttrs;
+ if ((attrs.flags & FLAG_DIM_BEHIND) != 0
+ && w.isDisplayedLw()
&& !w.mExiting) {
- if (localLOGV) Slog.v(TAG, "Win " + w + " obscured=" + mInnerFields.mObscured);
- if (!mInnerFields.mDimming) {
- //Slog.i(TAG, "DIM BEHIND: " + w);
- mInnerFields.mDimming = true;
- final WindowStateAnimator winAnimator = w.mWinAnimator;
- if (!mAnimator.isDimmingLocked(winAnimator)) {
- final int width, height;
- if (attrs.type == TYPE_BOOT_PROGRESS) {
- final DisplayInfo displayInfo = w.mDisplayContent.getDisplayInfo();
- width = displayInfo.logicalWidth;
- height = displayInfo.logicalHeight;
- } else {
- width = innerDw;
- height = innerDh;
- }
- startDimmingLocked(
- winAnimator, w.mExiting ? 0 : w.mAttrs.dimAmount, width, height);
- }
+ mInnerFields.mDimming = true;
+ final WindowStateAnimator winAnimator = w.mWinAnimator;
+ if (!mAnimator.isDimmingLocked(winAnimator)) {
+ if (localLOGV) Slog.v(TAG, "Win " + w + " start dimming.");
+ startDimmingLocked(winAnimator, w.mExiting ? 0 : w.mAttrs.dimAmount);
}
}
}
@@ -9091,7 +8581,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
if (mWatermark != null) {
@@ -9133,10 +8623,10 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner",
displayContent.pendingLayoutChanges);
- if (isDefaultDisplay && ((displayContent.pendingLayoutChanges
- & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0)
- && ((adjustWallpaperWindowsLocked()
- & ADJUST_WALLPAPER_LAYERS_CHANGED) != 0)) {
+ if ((displayContent.pendingLayoutChanges &
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0 &&
+ (adjustWallpaperWindowsLocked() &
+ ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
assignLayersLocked(windows);
displayContent.layoutNeeded = true;
}
@@ -9203,6 +8693,10 @@ public class WindowManagerService extends IWindowManager.Stub
handleNotObscuredLocked(w, currentTime, innerDw, innerDh);
}
+ if (!mInnerFields.mDimming) {
+ handleFlagDimBehind(w, innerDw, innerDh);
+ }
+
if (isDefaultDisplay && obscuredChanged && (mWallpaperTarget == w)
&& w.isVisibleLw()) {
// This is the wallpaper target and its obscured state
@@ -9290,7 +8784,7 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
+ ", isAnimating=" + winAnimator.isAnimating());
if (!w.isDrawnLw()) {
- Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurface
+ Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl
+ " pv=" + w.mPolicyVisibility
+ " mDrawState=" + winAnimator.mDrawState
+ " ah=" + w.mAttachedHidden
@@ -9362,7 +8856,7 @@ public class WindowManagerService extends IWindowManager.Stub
} catch (RuntimeException e) {
Log.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
}
@@ -9372,15 +8866,13 @@ public class WindowManagerService extends IWindowManager.Stub
// If we are ready to perform an app transition, check through
// all of the app tokens to be shown and see if they are ready
// to go.
- if (mAppTransitionReady) {
+ if (mAppTransition.isReady()) {
defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked(defaultWindows);
if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked",
- defaultDisplay.pendingLayoutChanges);
+ defaultDisplay.pendingLayoutChanges);
}
- mInnerFields.mAdjResult = 0;
-
- if (!mAnimator.mAnimating && mAppTransitionRunning) {
+ if (!mAnimator.mAnimating && mAppTransition.isRunning()) {
// We have finished the animation of an app transition. To do
// this, we have delayed a lot of operations like showing and
// hiding apps, moving apps in Z-order, etc. The app token list
@@ -9393,14 +8885,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (mInnerFields.mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0
- && !mAppTransitionReady) {
+ && !mAppTransition.isReady()) {
// At this point, there was a window with a wallpaper that
// was force hiding other windows behind it, but now it
// is going away. This may be simple -- just animate
// away the wallpaper and its window -- or it may be
// hard -- the wallpaper now needs to be shown behind
// something that was hidden.
- defaultDisplay.pendingLayoutChanges |= animateAwayWallpaperLocked();
+ defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animateAwayWallpaperLocked",
defaultDisplay.pendingLayoutChanges);
}
@@ -9409,18 +8901,10 @@ public class WindowManagerService extends IWindowManager.Stub
if (mInnerFields.mWallpaperMayChange) {
if (WindowManagerService.DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
"Wallpaper may change! Adjusting");
- mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked();
- }
-
- if ((mInnerFields.mAdjResult&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "Wallpaper layer changed: assigning layers + relayout");
- defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
- assignLayersLocked(defaultWindows);
- } else if ((mInnerFields.mAdjResult&ADJUST_WALLPAPER_VISIBILITY_CHANGED) != 0) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "Wallpaper visibility changed: relayout");
- defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+ defaultDisplay.pendingLayoutChanges |=
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+ if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("WallpaperMayChange",
+ defaultDisplay.pendingLayoutChanges);
}
if (mFocusMayChange) {
@@ -9428,7 +8912,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
false /*updateInputWindows*/)) {
defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
- mInnerFields.mAdjResult = 0;
}
}
@@ -9461,14 +8944,18 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_ORIENTATION &&
winAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING) Slog.i(
TAG, "Resizing " + win + " WITH DRAW PENDING");
- win.mClient.resized(win.mFrame, win.mLastContentInsets, win.mLastVisibleInsets,
+ win.mClient.resized(win.mFrame, win.mLastOverscanInsets, win.mLastContentInsets,
+ win.mLastVisibleInsets,
winAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING,
configChanged ? win.mConfiguration : null);
+ win.mOverscanInsetsChanged = false;
win.mContentInsetsChanged = false;
win.mVisibleInsetsChanged = false;
winAnimator.mSurfaceResized = false;
} catch (RemoteException e) {
win.mOrientationChanging = false;
+ win.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+ - mDisplayFreezeTime);
}
mResizingWindows.remove(i);
}
@@ -9479,6 +8966,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (mInnerFields.mOrientationChangeComplete) {
if (mWindowsFreezingScreen) {
mWindowsFreezingScreen = false;
+ mLastFinishedFreezeSource = mInnerFields.mLastWindowFreezeSource;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
}
stopFreezingDisplayLocked();
@@ -9498,7 +8986,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (win == mWallpaperTarget) {
wallpaperDestroyed = true;
}
- win.mWinAnimator.destroySurfaceLocked(false);
+ win.mWinAnimator.destroySurfaceLocked();
} while (i > 0);
mDestroySurface.clear();
}
@@ -9510,7 +8998,6 @@ public class WindowManagerService extends IWindowManager.Stub
mExitingTokens.remove(i);
if (token.windowType == TYPE_WALLPAPER) {
mWallpaperTokens.remove(token);
- updateLayoutToAnimWallpaperTokens();
}
}
}
@@ -9542,8 +9029,10 @@ public class WindowManagerService extends IWindowManager.Stub
mRelayoutWhileAnimating.clear();
}
- if (wallpaperDestroyed && (adjustWallpaperWindowsLocked() != 0)) {
- getDefaultDisplayContentLocked().layoutNeeded = true;
+ if (wallpaperDestroyed) {
+ defaultDisplay.pendingLayoutChanges |=
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+ defaultDisplay.layoutNeeded = true;
}
DisplayContentsIterator iterator = new DisplayContentsIterator();
@@ -9621,7 +9110,7 @@ public class WindowManagerService extends IWindowManager.Stub
// be enabled, because the window obscured flags have changed.
enableScreenIfNeededLocked();
- updateLayoutToAnimationLocked();
+ scheduleAnimationLocked();
if (DEBUG_WINDOW_TRACE) {
Slog.e(TAG, "performLayoutAndPlaceSurfacesLockedInner exit: animating="
@@ -9664,6 +9153,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
public boolean waitForWindowDrawn(IBinder token, IRemoteCallback callback) {
if (token != null && callback != null) {
synchronized (mWindowMap) {
@@ -9718,82 +9208,19 @@ public class WindowManagerService extends IWindowManager.Stub
/** Note that Locked in this case is on mLayoutToAnim */
void scheduleAnimationLocked() {
- final LayoutToAnimatorParams layoutToAnim = mLayoutToAnim;
- if (!layoutToAnim.mAnimationScheduled) {
- layoutToAnim.mAnimationScheduled = true;
+ if (!mAnimationScheduled) {
+ mAnimationScheduled = true;
mChoreographer.postCallback(
Choreographer.CALLBACK_ANIMATION, mAnimator.mAnimationRunnable, null);
}
}
- void updateLayoutToAnimationLocked() {
- final LayoutToAnimatorParams layoutToAnim = mLayoutToAnim;
- synchronized (layoutToAnim) {
- // Copy local params to transfer params.
- SparseArray<WinAnimatorList> allWinAnimatorLists = layoutToAnim.mWinAnimatorLists;
- allWinAnimatorLists.clear();
- DisplayContentsIterator iterator = new DisplayContentsIterator();
- while (iterator.hasNext()) {
- final DisplayContent displayContent = iterator.next();
- WinAnimatorList winAnimatorList = new WinAnimatorList();
- final WindowList windows = displayContent.getWindowList();
- int N = windows.size();
- for (int i = 0; i < N; i++) {
- final WindowStateAnimator winAnimator = windows.get(i).mWinAnimator;
- if (winAnimator.mSurface != null) {
- winAnimatorList.add(winAnimator);
- }
- }
- allWinAnimatorLists.put(displayContent.getDisplayId(), winAnimatorList);
- }
-
- if (WindowManagerService.DEBUG_WALLPAPER_LIGHT) {
- if (mWallpaperTarget != layoutToAnim.mWallpaperTarget
- || mLowerWallpaperTarget != layoutToAnim.mLowerWallpaperTarget
- || mUpperWallpaperTarget != layoutToAnim.mUpperWallpaperTarget) {
- Slog.d(TAG, "Pushing anim wallpaper: target=" + mWallpaperTarget
- + " lower=" + mLowerWallpaperTarget + " upper="
- + mUpperWallpaperTarget + "\n" + Debug.getCallers(5, " "));
- }
- }
- layoutToAnim.mWallpaperTarget = mWallpaperTarget;
- layoutToAnim.mLowerWallpaperTarget = mLowerWallpaperTarget;
- layoutToAnim.mUpperWallpaperTarget = mUpperWallpaperTarget;
-
- final ArrayList<AppWindowAnimParams> paramList = layoutToAnim.mAppWindowAnimParams;
- paramList.clear();
- int N = mAnimatingAppTokens.size();
- for (int i = 0; i < N; i++) {
- paramList.add(new AppWindowAnimParams(mAnimatingAppTokens.get(i).mAppAnimator));
- }
-
- layoutToAnim.mParamsModified = true;
- scheduleAnimationLocked();
- }
- }
-
- void updateLayoutToAnimWallpaperTokens() {
- synchronized(mLayoutToAnim) {
- mLayoutToAnim.mWallpaperTokens = new ArrayList<WindowToken>(mWallpaperTokens);
- mLayoutToAnim.mChanges |= LayoutToAnimatorParams.WALLPAPER_TOKENS_CHANGED;
- }
- }
-
- void setAnimDimParams(int displayId, DimAnimator.Parameters params) {
- synchronized (mLayoutToAnim) {
- mLayoutToAnim.mDimParams.put(displayId, params);
- scheduleAnimationLocked();
- }
- }
-
- void startDimmingLocked(final WindowStateAnimator winAnimator, final float target,
- final int width, final int height) {
- setAnimDimParams(winAnimator.mWin.getDisplayId(),
- new DimAnimator.Parameters(winAnimator, width, height, target));
+ void startDimmingLocked(final WindowStateAnimator winAnimator, final float target) {
+ mAnimator.setDimWinAnimatorLocked(winAnimator.mWin.getDisplayId(), winAnimator);
}
void stopDimmingLocked(int displayId) {
- setAnimDimParams(displayId, null);
+ mAnimator.setDimWinAnimatorLocked(displayId, null);
}
private boolean needsLayout() {
@@ -9806,59 +9233,44 @@ public class WindowManagerService extends IWindowManager.Stub
return false;
}
- private boolean copyAnimToLayoutParamsLocked() {
+ boolean copyAnimToLayoutParamsLocked() {
boolean doRequest = false;
- final WindowAnimator.AnimatorToLayoutParams animToLayout = mAnimator.mAnimToLayout;
- synchronized (animToLayout) {
- animToLayout.mUpdateQueued = false;
- final int bulkUpdateParams = animToLayout.mBulkUpdateParams;
- // TODO(cmautner): As the number of bits grows, use masks of bit groups to
- // eliminate unnecessary tests.
- if ((bulkUpdateParams & LayoutFields.SET_UPDATE_ROTATION) != 0) {
- mInnerFields.mUpdateRotation = true;
- doRequest = true;
- }
- if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) {
- mInnerFields.mWallpaperMayChange = true;
- doRequest = true;
- }
- if ((bulkUpdateParams & LayoutFields.SET_FORCE_HIDING_CHANGED) != 0) {
- mInnerFields.mWallpaperForceHidingChanged = true;
- doRequest = true;
- }
- if ((bulkUpdateParams & LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE) == 0) {
- mInnerFields.mOrientationChangeComplete = false;
- } else {
- mInnerFields.mOrientationChangeComplete = true;
- if (mWindowsFreezingScreen) {
- doRequest = true;
- }
- }
- if ((bulkUpdateParams & LayoutFields.SET_TURN_ON_SCREEN) != 0) {
- mTurnOnScreen = true;
- }
- SparseIntArray pendingLayouts = animToLayout.mPendingLayoutChanges;
- final int count = pendingLayouts.size();
- if (count > 0) {
+ final int bulkUpdateParams = mAnimator.mBulkUpdateParams;
+ if ((bulkUpdateParams & LayoutFields.SET_UPDATE_ROTATION) != 0) {
+ mInnerFields.mUpdateRotation = true;
+ doRequest = true;
+ }
+ if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) {
+ mInnerFields.mWallpaperMayChange = true;
+ doRequest = true;
+ }
+ if ((bulkUpdateParams & LayoutFields.SET_FORCE_HIDING_CHANGED) != 0) {
+ mInnerFields.mWallpaperForceHidingChanged = true;
+ doRequest = true;
+ }
+ if ((bulkUpdateParams & LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE) == 0) {
+ mInnerFields.mOrientationChangeComplete = false;
+ } else {
+ mInnerFields.mOrientationChangeComplete = true;
+ mInnerFields.mLastWindowFreezeSource = mAnimator.mLastWindowFreezeSource;
+ if (mWindowsFreezingScreen) {
doRequest = true;
}
- for (int i = 0; i < count; ++i) {
- final DisplayContent displayContent =
- getDisplayContentLocked(pendingLayouts.keyAt(i));
- if (displayContent != null) {
- displayContent.pendingLayoutChanges |= pendingLayouts.valueAt(i);
- }
- }
-
- mWindowDetachedWallpaper = animToLayout.mWindowDetachedWallpaper;
}
+ if ((bulkUpdateParams & LayoutFields.SET_TURN_ON_SCREEN) != 0) {
+ mTurnOnScreen = true;
+ }
+ if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_ACTION_PENDING) != 0) {
+ mInnerFields.mWallpaperActionPending = true;
+ }
+
return doRequest;
}
boolean reclaimSomeSurfaceMemoryLocked(WindowStateAnimator winAnimator, String operation,
boolean secure) {
- final Surface surface = winAnimator.mSurface;
+ final SurfaceControl surface = winAnimator.mSurfaceControl;
boolean leakedSurface = false;
boolean killedApps = false;
@@ -9880,28 +9292,28 @@ public class WindowManagerService extends IWindowManager.Stub
while (iterator.hasNext()) {
WindowState ws = iterator.next();
WindowStateAnimator wsa = ws.mWinAnimator;
- if (wsa.mSurface != null) {
+ if (wsa.mSurfaceControl != null) {
if (!mSessions.contains(wsa.mSession)) {
Slog.w(TAG, "LEAKED SURFACE (session doesn't exist): "
- + ws + " surface=" + wsa.mSurface
+ + ws + " surface=" + wsa.mSurfaceControl
+ " token=" + ws.mToken
+ " pid=" + ws.mSession.mPid
+ " uid=" + ws.mSession.mUid);
if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null);
- wsa.mSurface.destroy();
+ wsa.mSurfaceControl.destroy();
wsa.mSurfaceShown = false;
- wsa.mSurface = null;
+ wsa.mSurfaceControl = null;
ws.mHasSurface = false;
mForceRemoves.add(ws);
leakedSurface = true;
} else if (ws.mAppToken != null && ws.mAppToken.clientHidden) {
Slog.w(TAG, "LEAKED SURFACE (app token hidden): "
- + ws + " surface=" + wsa.mSurface
+ + ws + " surface=" + wsa.mSurfaceControl
+ " token=" + ws.mAppToken);
if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null);
- wsa.mSurface.destroy();
+ wsa.mSurfaceControl.destroy();
wsa.mSurfaceShown = false;
- wsa.mSurface = null;
+ wsa.mSurfaceControl = null;
ws.mHasSurface = false;
leakedSurface = true;
}
@@ -9918,7 +9330,7 @@ public class WindowManagerService extends IWindowManager.Stub
continue;
}
WindowStateAnimator wsa = ws.mWinAnimator;
- if (wsa.mSurface != null) {
+ if (wsa.mSurfaceControl != null) {
pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid);
}
}
@@ -9945,7 +9357,7 @@ public class WindowManagerService extends IWindowManager.Stub
"RECOVER DESTROY", null);
surface.destroy();
winAnimator.mSurfaceShown = false;
- winAnimator.mSurface = null;
+ winAnimator.mSurfaceControl = null;
winAnimator.mWin.mHasSurface = false;
}
@@ -10016,7 +9428,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
return false;
}
-
+
private void finishUpdateFocusedWindowAfterAssignLayersLocked(boolean updateInputWindows) {
mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
}
@@ -10100,8 +9512,7 @@ public class WindowManagerService extends IWindowManager.Stub
return null;
}
- private void startFreezingDisplayLocked(boolean inTransaction,
- int exitAnim, int enterAnim) {
+ private void startFreezingDisplayLocked(boolean inTransaction, int exitAnim, int enterAnim) {
if (mDisplayFrozen) {
return;
}
@@ -10115,6 +9526,8 @@ public class WindowManagerService extends IWindowManager.Stub
mScreenFrozenLock.acquire();
mDisplayFrozen = true;
+ mDisplayFreezeTime = SystemClock.elapsedRealtime();
+ mLastFinishedFreezeSource = null;
mInputMonitor.freezeInputDispatchingLw();
@@ -10123,12 +9536,8 @@ public class WindowManagerService extends IWindowManager.Stub
// the screen then the whole world is changing behind the scenes.
mPolicy.setLastInputMethodWindowLw(null, null);
- if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
- mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
- mNextAppTransitionType = ActivityOptions.ANIM_NONE;
- mNextAppTransitionPackage = null;
- mNextAppTransitionThumbnail = null;
- mAppTransitionReady = true;
+ if (mAppTransition.isTransitionSet()) {
+ mAppTransition.freeze();
}
if (PROFILE_ORIENTATION) {
@@ -10137,6 +9546,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (CUSTOM_SCREEN_ROTATION) {
+ mExitAnimId = exitAnim;
+ mEnterAnimId = enterAnim;
final DisplayContent displayContent = getDefaultDisplayContentLocked();
final int displayId = displayContent.getDisplayId();
ScreenRotationAnimation screenRotationAnimation =
@@ -10150,8 +9561,7 @@ public class WindowManagerService extends IWindowManager.Stub
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
screenRotationAnimation = new ScreenRotationAnimation(mContext,
display, mFxSession, inTransaction, displayInfo.logicalWidth,
- displayInfo.logicalHeight, display.getRotation(),
- exitAnim, enterAnim);
+ displayInfo.logicalHeight, display.getRotation());
mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
}
}
@@ -10170,8 +9580,17 @@ public class WindowManagerService extends IWindowManager.Stub
+ ", mClientFreezingScreen=" + mClientFreezingScreen);
return;
}
-
+
mDisplayFrozen = false;
+ mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Screen frozen for ");
+ TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
+ if (mLastFinishedFreezeSource != null) {
+ sb.append(" due to ");
+ sb.append(mLastFinishedFreezeSource);
+ }
+ Slog.i(TAG, sb.toString());
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
if (PROFILE_ORIENTATION) {
@@ -10189,10 +9608,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation");
// TODO(multidisplay): rotation on main screen only.
DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ // Get rotation animation again, with new top window
+ boolean isDimming = mAnimator.isDimmingLocked(Display.DEFAULT_DISPLAY);
+ if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) {
+ mExitAnimId = mEnterAnimId = 0;
+ }
if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
mTransitionAnimationScale, displayInfo.logicalWidth,
- displayInfo.logicalHeight)) {
- updateLayoutToAnimationLocked();
+ displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
+ scheduleAnimationLocked();
} else {
screenRotationAnimation.kill();
screenRotationAnimation = null;
@@ -10211,7 +9635,7 @@ public class WindowManagerService extends IWindowManager.Stub
mInputMonitor.thawInputDispatchingLw();
boolean configChanged;
-
+
// While the display is frozen we don't re-compute the orientation
// to avoid inconsistent states. However, something interesting
// could have actually changed during that time so re-evaluate it
@@ -10227,12 +9651,12 @@ public class WindowManagerService extends IWindowManager.Stub
mH.sendEmptyMessageDelayed(H.FORCE_GC, 2000);
mScreenFrozenLock.release();
-
+
if (updateRotation) {
if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
configChanged |= updateRotationUncheckedLocked(false);
}
-
+
if (configChanged) {
mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
}
@@ -10264,9 +9688,10 @@ public class WindowManagerService extends IWindowManager.Stub
File file = new File("/system/etc/setup.conf");
FileInputStream in = null;
+ DataInputStream ind = null;
try {
in = new FileInputStream(file);
- DataInputStream ind = new DataInputStream(in);
+ ind = new DataInputStream(in);
String line = ind.readLine();
if (line != null) {
String[] toks = line.split("%");
@@ -10278,7 +9703,12 @@ public class WindowManagerService extends IWindowManager.Stub
} catch (FileNotFoundException e) {
} catch (IOException e) {
} finally {
- if (in != null) {
+ if (ind != null) {
+ try {
+ ind.close();
+ } catch (IOException e) {
+ }
+ } else if (in != null) {
try {
in.close();
} catch (IOException e) {
@@ -10331,7 +9761,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
}
-
+
@Override
public void reevaluateStatusBarVisibility() {
synchronized (mWindowMap) {
@@ -10388,14 +9818,17 @@ public class WindowManagerService extends IWindowManager.Stub
return mPolicy.hasNavigationBar();
}
+ @Override
public void lockNow(Bundle options) {
mPolicy.lockNow(options);
}
-
+
+ @Override
public boolean isSafeModeEnabled() {
return mSafeMode;
}
+ @Override
public void showAssistant() {
// TODO: What permission?
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
@@ -10500,7 +9933,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
}
- if (mAppTransitionRunning && mAnimatingAppTokens.size() > 0) {
+ if (mAppTransition.isRunning() && mAnimatingAppTokens.size() > 0) {
pw.println();
pw.println(" Application tokens during animation:");
for (int i=mAnimatingAppTokens.size()-1; i>=0; i--) {
@@ -10538,6 +9971,18 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ void dumpDisplayContentsLocked(PrintWriter pw, boolean dumpAll) {
+ pw.println("WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)");
+ if (mDisplayReady) {
+ DisplayContentsIterator dCIterator = new DisplayContentsIterator();
+ while (dCIterator.hasNext()) {
+ dCIterator.next().dump(" ", pw);
+ }
+ } else {
+ pw.println(" NO DISPLAY");
+ }
+ }
+
void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
ArrayList<WindowState> windows) {
pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
@@ -10659,15 +10104,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
pw.println();
- pw.println(" DisplayContents:");
- if (mDisplayReady) {
- DisplayContentsIterator dCIterator = new DisplayContentsIterator();
- while (dCIterator.hasNext()) {
- dCIterator.next().dump(" ", pw);
- }
- } else {
- pw.println(" NO DISPLAY");
- }
pw.print(" mCurConfiguration="); pw.println(this.mCurConfiguration);
pw.print(" mCurrentFocus="); pw.println(mCurrentFocus);
if (mLastFocus != mCurrentFocus) {
@@ -10679,10 +10115,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
pw.print(" mInTouchMode="); pw.print(mInTouchMode);
pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
+ pw.print(" mLastDisplayFreezeDuration=");
+ TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
+ if ( mLastFinishedFreezeSource != null) {
+ pw.print(" due to ");
+ pw.print(mLastFinishedFreezeSource);
+ }
+ pw.println();
if (dumpAll) {
pw.print(" mSystemDecorRect="); pw.print(mSystemDecorRect.toShortString());
pw.print(" mSystemDecorLayer="); pw.print(mSystemDecorLayer);
- pw.print(" mScreenRecr="); pw.println(mScreenRect.toShortString());
+ pw.print(" mScreenRect="); pw.println(mScreenRect.toShortString());
if (mLastStatusBarVisibility != 0) {
pw.print(" mLastStatusBarVisibility=0x");
pw.println(Integer.toHexString(mLastStatusBarVisibility));
@@ -10731,76 +10174,11 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale);
pw.print(" mTransitionWindowAnimationScale="); pw.print(mTransitionAnimationScale);
pw.print(" mAnimatorDurationScale="); pw.println(mAnimatorDurationScale);
- pw.print(" mTraversalScheduled="); pw.print(mTraversalScheduled);
- pw.print(" mNextAppTransition=0x");
- pw.print(Integer.toHexString(mNextAppTransition));
- pw.print(" mAppTransitionReady="); pw.println(mAppTransitionReady);
- pw.print(" mAppTransitionRunning="); pw.print(mAppTransitionRunning);
- pw.print(" mAppTransitionTimeout="); pw.println(mAppTransitionTimeout);
- if (mNextAppTransitionType != ActivityOptions.ANIM_NONE) {
- pw.print(" mNextAppTransitionType="); pw.println(mNextAppTransitionType);
- }
- switch (mNextAppTransitionType) {
- case ActivityOptions.ANIM_CUSTOM:
- pw.print(" mNextAppTransitionPackage=");
- pw.println(mNextAppTransitionPackage);
- pw.print(" mNextAppTransitionEnter=0x");
- pw.print(Integer.toHexString(mNextAppTransitionEnter));
- pw.print(" mNextAppTransitionExit=0x");
- pw.println(Integer.toHexString(mNextAppTransitionExit));
- break;
- case ActivityOptions.ANIM_SCALE_UP:
- pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX);
- pw.print(" mNextAppTransitionStartY=");
- pw.println(mNextAppTransitionStartY);
- pw.print(" mNextAppTransitionStartWidth=");
- pw.print(mNextAppTransitionStartWidth);
- pw.print(" mNextAppTransitionStartHeight=");
- pw.println(mNextAppTransitionStartHeight);
- break;
- case ActivityOptions.ANIM_THUMBNAIL_SCALE_UP:
- case ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN:
- pw.print(" mNextAppTransitionThumbnail=");
- pw.print(mNextAppTransitionThumbnail);
- pw.print(" mNextAppTransitionStartX=");
- pw.print(mNextAppTransitionStartX);
- pw.print(" mNextAppTransitionStartY=");
- pw.println(mNextAppTransitionStartY);
- pw.print(" mNextAppTransitionScaleUp="); pw.println(mNextAppTransitionScaleUp);
- break;
- }
- if (mNextAppTransitionCallback != null) {
- pw.print(" mNextAppTransitionCallback=");
- pw.println(mNextAppTransitionCallback);
- }
+ pw.print(" mTraversalScheduled="); pw.println(mTraversalScheduled);
pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition);
pw.print(" mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation);
pw.println(" mLayoutToAnim:");
- pw.print(" mParamsModified="); pw.print(mLayoutToAnim.mParamsModified);
- pw.print(" mAnimationScheduled="); pw.print(mLayoutToAnim.mAnimationScheduled);
- pw.print(" mChanges=0x");
- pw.println(Long.toHexString(mLayoutToAnim.mChanges));
- pw.print(" mWallpaperTarget="); pw.println(mLayoutToAnim.mWallpaperTarget);
- if (mLayoutToAnim.mLowerWallpaperTarget != null
- || mLayoutToAnim.mUpperWallpaperTarget != null) {
- pw.print(" mLowerWallpaperTarget=");
- pw.println(mLayoutToAnim.mLowerWallpaperTarget);
- pw.print(" mUpperWallpaperTarget=");
- pw.println(mLayoutToAnim.mUpperWallpaperTarget);
- }
- for (int i=0; i<mLayoutToAnim.mWinAnimatorLists.size(); i++) {
- pw.print(" Win Animator List #");
- pw.print(mLayoutToAnim.mWinAnimatorLists.keyAt(i)); pw.println(":");
- WinAnimatorList wanim = mLayoutToAnim.mWinAnimatorLists.valueAt(i);
- for (int wi=0; wi<wanim.size(); wi++) {
- pw.print(" "); pw.println(wanim.get(wi));
- }
- }
- for (int i=0; i<mLayoutToAnim.mWallpaperTokens.size(); i++) {
- pw.print(" Wallpaper Token #"); pw.print(i); pw.print(": ");
- pw.println(mLayoutToAnim.mWallpaperTokens.get(i));
- }
- // XXX also need to print mDimParams and mAppWindowAnimParams. I am lazy.
+ mAppTransition.dump(pw);
}
}
@@ -10912,6 +10290,7 @@ public class WindowManagerService extends IWindowManager.Stub
pw.println(" p[policy]: policy state");
pw.println(" a[animator]: animator state");
pw.println(" s[essions]: active sessions");
+ pw.println(" d[isplays]: active display contents");
pw.println(" t[okens]: token list");
pw.println(" w[indows]: window list");
pw.println(" cmd may also be a NAME to dump windows. NAME may");
@@ -10950,6 +10329,11 @@ public class WindowManagerService extends IWindowManager.Stub
dumpSessionsLocked(pw, true);
}
return;
+ } else if ("displays".equals(cmd) || "d".equals(cmd)) {
+ synchronized(mWindowMap) {
+ dumpDisplayContentsLocked(pw, true);
+ }
+ return;
} else if ("tokens".equals(cmd) || "t".equals(cmd)) {
synchronized(mWindowMap) {
dumpTokensLocked(pw, true);
@@ -11000,6 +10384,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
}
+ dumpDisplayContentsLocked(pw, dumpAll);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
dumpTokensLocked(pw, dumpAll);
pw.println();
if (dumpAll) {
@@ -11010,6 +10399,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
// Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection).
+ @Override
public void monitor() {
synchronized (mWindowMap) { }
}
@@ -11025,12 +10415,28 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ private DisplayContent newDisplayContentLocked(final Display display) {
+ DisplayContent displayContent = new DisplayContent(display);
+ mDisplayContents.put(display.getDisplayId(), displayContent);
+ final Rect rect = new Rect();
+ DisplayInfo info = displayContent.getDisplayInfo();
+ mDisplaySettings.getOverscanLocked(info.name, rect);
+ info.overscanLeft = rect.left;
+ info.overscanTop = rect.top;
+ info.overscanRight = rect.right;
+ info.overscanBottom = rect.bottom;
+ mDisplayManagerService.setOverscan(display.getDisplayId(), rect.left, rect.top,
+ rect.right, rect.bottom);
+ mPolicy.setDisplayOverscan(displayContent.getDisplay(), rect.left, rect.top,
+ rect.right, rect.bottom);
+ return displayContent;
+ }
+
public void createDisplayContentLocked(final Display display) {
if (display == null) {
throw new IllegalArgumentException("getDisplayContent: display must not be null");
}
- final DisplayContent displayContent = new DisplayContent(display);
- mDisplayContents.put(display.getDisplayId(), displayContent);
+ newDisplayContentLocked(display);
}
/**
@@ -11044,8 +10450,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (displayContent == null) {
final Display display = mDisplayManager.getDisplay(displayId);
if (display != null) {
- displayContent = new DisplayContent(display);
- mDisplayContents.put(displayId, displayContent);
+ displayContent = newDisplayContentLocked(display);
}
}
return displayContent;
@@ -11151,7 +10556,16 @@ public class WindowManagerService extends IWindowManager.Stub
* @return The list of WindowStates on the screen, or null if the there is no screen.
*/
public WindowList getWindowListLocked(final Display display) {
- final DisplayContent displayContent = getDisplayContentLocked(display.getDisplayId());
+ return getWindowListLocked(display.getDisplayId());
+ }
+
+ /**
+ * Return the list of WindowStates associated on the passed display.
+ * @param displayId The screen to return windows from.
+ * @return The list of WindowStates on the screen, or null if the there is no screen.
+ */
+ public WindowList getWindowListLocked(final int displayId) {
+ final DisplayContent displayContent = getDisplayContentLocked(displayId);
return displayContent != null ? displayContent.getWindowList() : null;
}
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 81eac20..dfb22a7 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -19,11 +19,17 @@ package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import android.app.AppOpsManager;
+import android.os.RemoteCallbackList;
+import android.util.TimeUtils;
+import android.view.IWindowFocusObserver;
+import android.view.IWindowId;
import com.android.server.input.InputWindowHandle;
import android.content.Context;
@@ -58,7 +64,7 @@ class WindowList extends ArrayList<WindowState> {
*/
final class WindowState implements WindowManagerPolicy.WindowState {
static final String TAG = "WindowState";
-
+
static final boolean DEBUG_VISIBILITY = WindowManagerService.DEBUG_VISIBILITY;
static final boolean SHOW_TRANSACTIONS = WindowManagerService.SHOW_TRANSACTIONS;
static final boolean SHOW_LIGHT_TRANSACTIONS = WindowManagerService.SHOW_LIGHT_TRANSACTIONS;
@@ -69,6 +75,10 @@ final class WindowState implements WindowManagerPolicy.WindowState {
final Context mContext;
final Session mSession;
final IWindow mClient;
+ final int mAppOp;
+ // UserId and appId of the owner. Don't display windows of non-current user.
+ final int mOwnerUid;
+ final IWindowId mWindowId;
WindowToken mToken;
WindowToken mRootToken;
AppWindowToken mAppToken;
@@ -92,10 +102,13 @@ final class WindowState implements WindowManagerPolicy.WindowState {
int mSystemUiVisibility;
boolean mPolicyVisibility = true;
boolean mPolicyVisibilityAfterAnim = true;
+ boolean mAppOpVisibility = true;
boolean mAppFreezing;
boolean mAttachedHidden; // is our parent window hidden?
boolean mWallpaperVisible; // for wallpaper, what was last vis report?
+ RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
+
/**
* The window size that was requested by the application. These are in
* the application's coordinate space (without compatibility scale applied).
@@ -142,6 +155,14 @@ final class WindowState implements WindowManagerPolicy.WindowState {
boolean mContentInsetsChanged;
/**
+ * Insets that determine the area covered by the display overscan region. These are in the
+ * application's coordinate space (without compatibility scale applied).
+ */
+ final Rect mOverscanInsets = new Rect();
+ final Rect mLastOverscanInsets = new Rect();
+ boolean mOverscanInsetsChanged;
+
+ /**
* Set to true if we are waiting for this window to receive its
* given internal insets before laying out other windows based on it.
*/
@@ -195,6 +216,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
final Rect mContainingFrame = new Rect();
final Rect mDisplayFrame = new Rect();
+ final Rect mOverscanFrame = new Rect();
final Rect mContentFrame = new Rect();
final Rect mParentFrame = new Rect();
final Rect mVisibleFrame = new Rect();
@@ -215,42 +237,55 @@ final class WindowState implements WindowManagerPolicy.WindowState {
int mXOffset;
int mYOffset;
- // This is set after IWindowSession.relayout() has been called at
- // least once for the window. It allows us to detect the situation
- // where we don't yet have a surface, but should have one soon, so
- // we can give the window focus before waiting for the relayout.
+ /**
+ * This is set after IWindowSession.relayout() has been called at
+ * least once for the window. It allows us to detect the situation
+ * where we don't yet have a surface, but should have one soon, so
+ * we can give the window focus before waiting for the relayout.
+ */
boolean mRelayoutCalled;
- // If the application has called relayout() with changes that can
- // impact its window's size, we need to perform a layout pass on it
- // even if it is not currently visible for layout. This is set
- // when in that case until the layout is done.
+ /**
+ * If the application has called relayout() with changes that can
+ * impact its window's size, we need to perform a layout pass on it
+ * even if it is not currently visible for layout. This is set
+ * when in that case until the layout is done.
+ */
boolean mLayoutNeeded;
- // Currently running an exit animation?
+ /** Currently running an exit animation? */
boolean mExiting;
- // Currently on the mDestroySurface list?
+ /** Currently on the mDestroySurface list? */
boolean mDestroying;
- // Completely remove from window manager after exit animation?
+ /** Completely remove from window manager after exit animation? */
boolean mRemoveOnExit;
- // Set when the orientation is changing and this window has not yet
- // been updated for the new orientation.
+ /**
+ * Set when the orientation is changing and this window has not yet
+ * been updated for the new orientation.
+ */
boolean mOrientationChanging;
- // Is this window now (or just being) removed?
+ /**
+ * How long we last kept the screen frozen.
+ */
+ int mLastFreezeDuration;
+
+ /** Is this window now (or just being) removed? */
boolean mRemoved;
- // Temp for keeping track of windows that have been removed when
- // rebuilding window list.
+ /**
+ * Temp for keeping track of windows that have been removed when
+ * rebuilding window list.
+ */
boolean mRebuilding;
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandle mInputWindowHandle;
InputChannel mInputChannel;
-
+
// Used to improve performance of toString()
String mStringNameCache;
CharSequence mLastTitle;
@@ -262,20 +297,32 @@ final class WindowState implements WindowManagerPolicy.WindowState {
DisplayContent mDisplayContent;
- // UserId and appId of the owner. Don't display windows of non-current user.
- int mOwnerUid;
-
/** When true this window can be displayed on screens owther than mOwnerUid's */
private boolean mShowToOwnerOnly;
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
- WindowState attachedWindow, int seq, WindowManager.LayoutParams a,
+ WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
mService = service;
mSession = s;
mClient = c;
+ mAppOp = appOp;
mToken = token;
mOwnerUid = s.mUid;
+ mWindowId = new IWindowId.Stub() {
+ @Override
+ public void registerFocusObserver(IWindowFocusObserver observer) {
+ WindowState.this.registerFocusObserver(observer);
+ }
+ @Override
+ public void unregisterFocusObserver(IWindowFocusObserver observer) {
+ WindowState.this.unregisterFocusObserver(observer);
+ }
+ @Override
+ public boolean isFocused() {
+ return WindowState.this.isFocused();
+ }
+ };
mAttrs.copyFrom(a);
mViewVisibility = viewVisibility;
mDisplayContent = displayContent;
@@ -374,7 +421,17 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
@Override
- public void computeFrameLw(Rect pf, Rect df, Rect cf, Rect vf) {
+ public int getOwningUid() {
+ return mOwnerUid;
+ }
+
+ @Override
+ public String getOwningPackage() {
+ return mAttrs.packageName;
+ }
+
+ @Override
+ public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf) {
mHaveFrame = true;
final Rect container = mContainingFrame;
@@ -431,6 +488,9 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mContentChanged = true;
}
+ final Rect overscan = mOverscanFrame;
+ overscan.set(of);
+
final Rect content = mContentFrame;
content.set(cf);
@@ -462,7 +522,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
// Now make sure the window fits in the overall display.
Gravity.applyDisplay(mAttrs.gravity, df, frame);
- // Make sure the system, content and visible frames are inside of the
+ // Make sure the content and visible frames are inside of the
// final window frame.
if (content.left < frame.left) content.left = frame.left;
if (content.top < frame.top) content.top = frame.top;
@@ -473,6 +533,12 @@ final class WindowState implements WindowManagerPolicy.WindowState {
if (visible.right > frame.right) visible.right = frame.right;
if (visible.bottom > frame.bottom) visible.bottom = frame.bottom;
+ final Rect overscanInsets = mOverscanInsets;
+ overscanInsets.left = overscan.left > frame.left ? overscan.left-frame.left : 0;
+ overscanInsets.top = overscan.top > frame.top ? overscan.top-frame.top : 0;
+ overscanInsets.right = overscan.right < frame.right ? frame.right-overscan.right : 0;
+ overscanInsets.bottom = overscan.bottom < frame.bottom ? frame.bottom-overscan.bottom : 0;
+
final Rect contentInsets = mContentInsets;
contentInsets.left = content.left-frame.left;
contentInsets.top = content.top-frame.top;
@@ -490,6 +556,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
// If there is a size compatibility scale being applied to the
// window, we need to apply this to its insets so that they are
// reported to the app in its coordinate space.
+ overscanInsets.scale(mInvGlobalScale);
contentInsets.scale(mInvGlobalScale);
visibleInsets.scale(mInvGlobalScale);
@@ -517,21 +584,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
}
- MagnificationSpec getWindowMagnificationSpecLocked() {
- MagnificationSpec spec = mDisplayContent.mMagnificationSpec;
- if (spec != null && !spec.isNop()) {
- if (mAttachedWindow != null) {
- if (!mPolicy.canMagnifyWindowLw(mAttachedWindow.mAttrs)) {
- return null;
- }
- }
- if (!mPolicy.canMagnifyWindowLw(mAttrs)) {
- return null;
- }
- }
- return spec;
- }
-
@Override
public Rect getFrameLw() {
return mFrame;
@@ -548,6 +600,11 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
@Override
+ public Rect getOverscanFrameLw() {
+ return mOverscanFrame;
+ }
+
+ @Override
public Rect getContentFrameLw() {
return mContentFrame;
}
@@ -677,7 +734,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
@Override
public boolean isVisibleOrBehindKeyguardLw() {
if (mRootToken.waitingToShow &&
- mService.mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+ mService.mAppTransition.isTransitionSet()) {
return false;
}
final AppWindowToken atoken = mAppToken;
@@ -745,7 +802,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
final AppWindowToken atoken = mAppToken;
if (atoken != null) {
return ((!mAttachedHidden && !atoken.hiddenRequested)
- || mWinAnimator.mAnimation != null || atoken.mAppAnimator.animation != null);
+ || mWinAnimator.mAnimation != null || atoken.mAppAnimator.animation != null);
}
return !mAttachedHidden || mWinAnimator.mAnimation != null;
}
@@ -756,7 +813,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
*/
boolean isReadyForDisplay() {
if (mRootToken.waitingToShow &&
- mService.mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+ mService.mAppTransition.isTransitionSet()) {
return false;
}
return mHasSurface && mPolicyVisibility && !mDestroying
@@ -771,8 +828,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
* to the keyguard.
*/
boolean isReadyForDisplayIgnoringKeyguard() {
- if (mRootToken.waitingToShow &&
- mService.mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+ if (mRootToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
return false;
}
final AppWindowToken atoken = mAppToken;
@@ -804,12 +860,12 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
/**
- * Return true if this window (or a window it is attached to, but not
- * considering its app token) is currently animating.
+ * Return true if this window or its app token is currently animating.
*/
@Override
public boolean isAnimatingLw() {
- return mWinAnimator.mAnimation != null;
+ return mWinAnimator.mAnimation != null
+ || (mAppToken != null && mAppToken.mAppAnimator.animation != null);
}
@Override
@@ -827,6 +883,17 @@ final class WindowState implements WindowManagerPolicy.WindowState {
* Returns true if the window has a surface that it has drawn a
* complete UI in to.
*/
+ public boolean isDrawFinishedLw() {
+ return mHasSurface && !mDestroying &&
+ (mWinAnimator.mDrawState == WindowStateAnimator.COMMIT_DRAW_PENDING
+ || mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW
+ || mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN);
+ }
+
+ /**
+ * Returns true if the window has a surface that it has drawn a
+ * complete UI in to.
+ */
public boolean isDrawnLw() {
return mHasSurface && !mDestroying &&
(mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW
@@ -853,6 +920,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
return mContentChanged && !mExiting && !mWinAnimator.mLastHidden && mService.okToDisplay()
&& (mFrame.top != mLastFrame.top
|| mFrame.left != mLastFrame.left)
+ && (mAttrs.privateFlags&PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
&& (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove());
}
@@ -883,13 +951,13 @@ final class WindowState implements WindowManagerPolicy.WindowState {
void removeLocked() {
disposeInputChannel();
-
+
if (mAttachedWindow != null) {
if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + this + " from " + mAttachedWindow);
mAttachedWindow.mChildWindows.remove(this);
}
- mWinAnimator.destroyDeferredSurfaceLocked(false);
- mWinAnimator.destroySurfaceLocked(false);
+ mWinAnimator.destroyDeferredSurfaceLocked();
+ mWinAnimator.destroySurfaceLocked();
mSession.windowRemovedLocked();
try {
mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
@@ -916,7 +984,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
void disposeInputChannel() {
if (mInputChannel != null) {
mService.mInputManager.unregisterInputChannel(mInputChannel);
-
+
mInputChannel.dispose();
mInputChannel = null;
}
@@ -933,6 +1001,9 @@ final class WindowState implements WindowManagerPolicy.WindowState {
Slog.i(TAG, "WIN DEATH: " + win);
if (win != null) {
mService.removeWindowLocked(mSession, win);
+ } else if (WindowState.this.mHasSurface) {
+ Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
+ mService.removeWindowLocked(mSession, WindowState.this);
}
}
} catch (IllegalArgumentException ex) {
@@ -942,11 +1013,11 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
}
- /** Returns true if this window desires key events.
- * TODO(cmautner): Is this the same as {@link WindowManagerService#canBeImeTarget}
+ /**
+ * @return true if this window desires key events.
*/
public final boolean canReceiveKeys() {
- return isVisibleOrAdding()
+ return isVisibleOrAdding()
&& (mViewVisibility == View.VISIBLE)
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0);
}
@@ -967,6 +1038,10 @@ final class WindowState implements WindowManagerPolicy.WindowState {
+ this + ", type " + mAttrs.type + ", belonging to " + mOwnerUid);
return false;
}
+ if (!mAppOpVisibility) {
+ // Being hidden due to app op request.
+ return false;
+ }
if (mPolicyVisibility && mPolicyVisibilityAfterAnim) {
// Already showing.
return false;
@@ -990,7 +1065,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true);
}
if (requestAnim) {
- mService.updateLayoutToAnimationLocked();
+ mService.scheduleAnimationLocked();
}
return true;
}
@@ -1033,11 +1108,30 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
}
if (requestAnim) {
- mService.updateLayoutToAnimationLocked();
+ mService.scheduleAnimationLocked();
}
return true;
}
+ public boolean setAppOpVisibilityLw(boolean state) {
+ if (mAppOpVisibility != state) {
+ mAppOpVisibility = state;
+ if (state) {
+ // If the policy visibility had last been to hide, then this
+ // will incorrectly show at this point since we lost that
+ // information. Not a big deal -- for the windows that have app
+ // ops modifies they should only be hidden by policy due to the
+ // lock screen, and the user won't be changing this if locked.
+ // Plus it will quickly be fixed the next time we do a layout.
+ showLw(true, false);
+ } else {
+ hideLw(true, false);
+ }
+ return true;
+ }
+ return false;
+ }
+
@Override
public boolean isAlive() {
return mClient.asBinder().isBinderAlive();
@@ -1111,12 +1205,63 @@ final class WindowState implements WindowManagerPolicy.WindowState {
return mDisplayContent.getWindowList();
}
+ /**
+ * Report a focus change. Must be called with no locks held, and consistently
+ * from the same serialized thread (such as dispatched from a handler).
+ */
+ public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
+ try {
+ mClient.windowFocusChanged(focused, inTouchMode);
+ } catch (RemoteException e) {
+ }
+ if (mFocusCallbacks != null) {
+ final int N = mFocusCallbacks.beginBroadcast();
+ for (int i=0; i<N; i++) {
+ IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
+ try {
+ if (focused) {
+ obs.focusGained(mWindowId.asBinder());
+ } else {
+ obs.focusLost(mWindowId.asBinder());
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ mFocusCallbacks.finishBroadcast();
+ }
+ }
+
+ public void registerFocusObserver(IWindowFocusObserver observer) {
+ synchronized(mService.mWindowMap) {
+ if (mFocusCallbacks == null) {
+ mFocusCallbacks = new RemoteCallbackList<IWindowFocusObserver>();
+ }
+ mFocusCallbacks.register(observer);
+ }
+ }
+
+ public void unregisterFocusObserver(IWindowFocusObserver observer) {
+ synchronized(mService.mWindowMap) {
+ if (mFocusCallbacks != null) {
+ mFocusCallbacks.unregister(observer);
+ }
+ }
+ }
+
+ public boolean isFocused() {
+ synchronized(mService.mWindowMap) {
+ return mService.mCurrentFocus == this;
+ }
+ }
+
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
pw.print(prefix); pw.print("mDisplayId="); pw.print(mDisplayContent.getDisplayId());
pw.print(" mSession="); pw.print(mSession);
pw.print(" mClient="); pw.println(mClient.asBinder());
pw.print(prefix); pw.print("mOwnerUid="); pw.print(mOwnerUid);
- pw.print(" mShowToOwnerOnly="); pw.println(mShowToOwnerOnly);
+ pw.print(" mShowToOwnerOnly="); pw.print(mShowToOwnerOnly);
+ pw.print(" package="); pw.print(mAttrs.packageName);
+ pw.print(" appop="); pw.println(AppOpsManager.opToName(mAppOp));
pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs);
pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
pw.print(" h="); pw.print(mRequestedHeight);
@@ -1162,11 +1307,14 @@ final class WindowState implements WindowManagerPolicy.WindowState {
pw.print(" mSystemUiVisibility=0x");
pw.println(Integer.toHexString(mSystemUiVisibility));
}
- if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || mAttachedHidden) {
+ if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || !mAppOpVisibility
+ || mAttachedHidden) {
pw.print(prefix); pw.print("mPolicyVisibility=");
pw.print(mPolicyVisibility);
pw.print(" mPolicyVisibilityAfterAnim=");
pw.print(mPolicyVisibilityAfterAnim);
+ pw.print(" mAppOpVisibility=");
+ pw.print(mAppOpVisibility);
pw.print(" mAttachedHidden="); pw.println(mAttachedHidden);
}
if (!mRelayoutCalled || mLayoutNeeded) {
@@ -1211,17 +1359,21 @@ final class WindowState implements WindowManagerPolicy.WindowState {
pw.print(prefix); pw.print("Frames: containing=");
mContainingFrame.printShortString(pw);
pw.print(" parent="); mParentFrame.printShortString(pw);
- pw.print(" display="); mDisplayFrame.printShortString(pw);
+ pw.println();
+ pw.print(prefix); pw.print(" display="); mDisplayFrame.printShortString(pw);
+ pw.print(" overscan="); mOverscanFrame.printShortString(pw);
pw.println();
pw.print(prefix); pw.print(" content="); mContentFrame.printShortString(pw);
pw.print(" visible="); mVisibleFrame.printShortString(pw);
pw.println();
- pw.print(prefix); pw.print("Cur insets: content=");
- mContentInsets.printShortString(pw);
+ pw.print(prefix); pw.print("Cur insets: overscan=");
+ mOverscanInsets.printShortString(pw);
+ pw.print(" content="); mContentInsets.printShortString(pw);
pw.print(" visible="); mVisibleInsets.printShortString(pw);
pw.println();
- pw.print(prefix); pw.print("Lst insets: content=");
- mLastContentInsets.printShortString(pw);
+ pw.print(prefix); pw.print("Lst insets: overscan=");
+ mLastOverscanInsets.printShortString(pw);
+ pw.print(" content="); mLastContentInsets.printShortString(pw);
pw.print(" visible="); mLastVisibleInsets.printShortString(pw);
pw.println();
}
@@ -1239,6 +1391,10 @@ final class WindowState implements WindowManagerPolicy.WindowState {
pw.print(" mAppFreezing="); pw.print(mAppFreezing);
pw.print(" mTurnOnScreen="); pw.println(mTurnOnScreen);
}
+ if (mLastFreezeDuration != 0) {
+ pw.print(prefix); pw.print("mLastFreezeDuration=");
+ TimeUtils.formatDuration(mLastFreezeDuration, pw); pw.println();
+ }
if (mHScale != 1 || mVScale != 1) {
pw.print(prefix); pw.print("mHScale="); pw.print(mHScale);
pw.print(" mVScale="); pw.println(mVScale);
@@ -1252,7 +1408,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep);
}
}
-
+
String makeInputChannelName() {
return Integer.toHexString(System.identityHashCode(this))
+ " " + mAttrs.getTitle();
@@ -1260,9 +1416,12 @@ final class WindowState implements WindowManagerPolicy.WindowState {
@Override
public String toString() {
- if (mStringNameCache == null || mLastTitle != mAttrs.getTitle()
- || mWasExiting != mExiting) {
- mLastTitle = mAttrs.getTitle();
+ CharSequence title = mAttrs.getTitle();
+ if (title == null || title.length() <= 0) {
+ title = mAttrs.packageName;
+ }
+ if (mStringNameCache == null || mLastTitle != title || mWasExiting != mExiting) {
+ mLastTitle = title;
mWasExiting = mExiting;
mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
+ " u" + UserHandle.getUserId(mSession.mUid)
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
index 10784fe..c07174b 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -12,12 +12,15 @@ import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.os.Debug;
-import android.os.UserHandle;
import android.util.Slog;
+import android.view.Display;
import android.view.DisplayInfo;
+import android.view.MagnificationSpec;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
import android.view.WindowManagerPolicy;
@@ -85,8 +88,8 @@ class WindowStateAnimator {
int mAnimLayer;
int mLastLayer;
- Surface mSurface;
- Surface mPendingDestroySurface;
+ SurfaceControl mSurfaceControl;
+ SurfaceControl mPendingDestroySurface;
/**
* Set when we have changed the size of the surface, to know that
@@ -226,7 +229,7 @@ class WindowStateAnimator {
mAnimation.cancel();
mAnimation = null;
mLocalAnimating = false;
- destroySurfaceLocked(true);
+ destroySurfaceLocked();
}
}
@@ -369,7 +372,7 @@ class WindowStateAnimator {
final int displayId = mWin.mDisplayContent.getDisplayId();
mAnimator.setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
if (WindowManagerService.DEBUG_LAYOUT_REPEATS) mService.debugLayoutRepeats(
- "WindowStateAnimator", mAnimator.mPendingLayoutChanges.get(displayId));
+ "WindowStateAnimator", mAnimator.getPendingLayoutChanges(displayId));
if (mWin.mAppToken != null) {
mWin.mAppToken.updateReportedVisibilityLocked();
@@ -401,7 +404,7 @@ class WindowStateAnimator {
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Exit animation finished in " + this
+ ": remove=" + mWin.mRemoveOnExit);
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
mService.mDestroySurface.add(mWin);
mWin.mDestroying = true;
if (WindowState.SHOW_TRANSACTIONS) WindowManagerService.logSurface(
@@ -413,7 +416,7 @@ class WindowStateAnimator {
mService.mPendingRemove.add(mWin);
mWin.mRemoveOnExit = false;
}
- mAnimator.hideWallpapersLocked(mWin, true);
+ mAnimator.hideWallpapersLocked(mWin);
}
void hide() {
@@ -422,10 +425,10 @@ class WindowStateAnimator {
mLastHidden = true;
if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin,
"HIDE (performLayout)", null);
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
mSurfaceShown = false;
try {
- mSurface.hide();
+ mSurfaceControl.hide();
} catch (RuntimeException e) {
Slog.w(TAG, "Exception hiding surface in " + mWin);
}
@@ -442,7 +445,7 @@ class WindowStateAnimator {
if (mDrawState == DRAW_PENDING) {
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + this + " in "
- + mSurface);
+ + mSurfaceControl);
if (DEBUG_STARTING_WINDOW &&
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
Slog.v(TAG, "Draw state now committed in " + mWin);
@@ -464,7 +467,7 @@ class WindowStateAnimator {
return false;
}
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) {
- Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurface);
+ Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceControl);
}
mDrawState = READY_TO_SHOW;
final boolean starting = mWin.mAttrs.type == TYPE_APPLICATION_STARTING;
@@ -475,7 +478,7 @@ class WindowStateAnimator {
return true;
}
- static class SurfaceTrace extends Surface {
+ static class SurfaceTrace extends SurfaceControl {
private final static String SURFACE_TAG = "SurfaceTrace";
final static ArrayList<SurfaceTrace> sSurfaces = new ArrayList<SurfaceTrace>();
@@ -502,20 +505,20 @@ class WindowStateAnimator {
public void setAlpha(float alpha) {
super.setAlpha(alpha);
if (alpha != mSurfaceTraceAlpha) {
+ mSurfaceTraceAlpha = alpha;
Slog.v(SURFACE_TAG, "setAlpha: " + this + ". Called by "
+ Debug.getCallers(3));
}
- mSurfaceTraceAlpha = alpha;
}
@Override
public void setLayer(int zorder) {
super.setLayer(zorder);
if (zorder != mLayer) {
+ mLayer = zorder;
Slog.v(SURFACE_TAG, "setLayer: " + this + ". Called by "
+ Debug.getCallers(3));
}
- mLayer = zorder;
sSurfaces.remove(this);
int i;
@@ -532,20 +535,20 @@ class WindowStateAnimator {
public void setPosition(float x, float y) {
super.setPosition(x, y);
if (x != mPosition.x || y != mPosition.y) {
+ mPosition.set(x, y);
Slog.v(SURFACE_TAG, "setPosition: " + this + ". Called by "
+ Debug.getCallers(3));
}
- mPosition.set(x, y);
}
@Override
public void setSize(int w, int h) {
super.setSize(w, h);
if (w != mSize.x || h != mSize.y) {
+ mSize.set(w, h);
Slog.v(SURFACE_TAG, "setSize: " + this + ". Called by "
+ Debug.getCallers(3));
}
- mSize.set(w, h);
}
@Override
@@ -553,10 +556,10 @@ class WindowStateAnimator {
super.setWindowCrop(crop);
if (crop != null) {
if (!crop.equals(mWindowCrop)) {
+ mWindowCrop.set(crop);
Slog.v(SURFACE_TAG, "setWindowCrop: " + this + ". Called by "
+ Debug.getCallers(3));
}
- mWindowCrop.set(crop);
}
}
@@ -564,28 +567,28 @@ class WindowStateAnimator {
public void setLayerStack(int layerStack) {
super.setLayerStack(layerStack);
if (layerStack != mLayerStack) {
+ mLayerStack = layerStack;
Slog.v(SURFACE_TAG, "setLayerStack: " + this + ". Called by " + Debug.getCallers(3));
}
- mLayerStack = layerStack;
}
@Override
public void hide() {
super.hide();
if (mShown) {
+ mShown = false;
Slog.v(SURFACE_TAG, "hide: " + this + ". Called by "
+ Debug.getCallers(3));
}
- mShown = false;
}
@Override
public void show() {
super.show();
if (!mShown) {
+ mShown = true;
Slog.v(SURFACE_TAG, "show: " + this + ". Called by "
+ Debug.getCallers(3));
}
- mShown = true;
}
@Override
@@ -621,22 +624,29 @@ class WindowStateAnimator {
}
}
- Surface createSurfaceLocked() {
- if (mSurface == null) {
+ SurfaceControl createSurfaceLocked() {
+ if (mSurfaceControl == null) {
if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG,
"createSurface " + this + ": mDrawState=DRAW_PENDING");
mDrawState = DRAW_PENDING;
if (mWin.mAppToken != null) {
- mWin.mAppToken.allDrawn = false;
+ if (mWin.mAppToken.mAppAnimator.animation == null) {
+ mWin.mAppToken.allDrawn = false;
+ mWin.mAppToken.deferClearAllDrawn = false;
+ } else {
+ // Currently animating, persist current state of allDrawn until animation
+ // is complete.
+ mWin.mAppToken.deferClearAllDrawn = true;
+ }
}
mService.makeWindowFreezingScreenIfNeededLocked(mWin);
- int flags = Surface.HIDDEN;
+ int flags = SurfaceControl.HIDDEN;
final WindowManager.LayoutParams attrs = mWin.mAttrs;
if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
- flags |= Surface.SECURE;
+ flags |= SurfaceControl.SECURE;
}
if (WindowState.DEBUG_VISIBILITY) Slog.v(
TAG, "Creating surface in session "
@@ -672,15 +682,15 @@ class WindowStateAnimator {
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
if (!PixelFormat.formatHasAlpha(attrs.format)) {
- flags |= Surface.OPAQUE;
+ flags |= SurfaceControl.OPAQUE;
}
if (DEBUG_SURFACE_TRACE) {
- mSurface = new SurfaceTrace(
+ mSurfaceControl = new SurfaceTrace(
mSession.mSurfaceSession,
attrs.getTitle().toString(),
w, h, format, flags);
} else {
- mSurface = new Surface(
+ mSurfaceControl = new SurfaceControl(
mSession.mSurfaceSession,
attrs.getTitle().toString(),
w, h, format, flags);
@@ -688,13 +698,13 @@ class WindowStateAnimator {
mWin.mHasSurface = true;
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
" CREATE SURFACE "
- + mSurface + " IN SESSION "
+ + mSurfaceControl + " IN SESSION "
+ mSession.mSurfaceSession
+ ": pid=" + mSession.mPid + " format="
+ attrs.format + " flags=0x"
+ Integer.toHexString(flags)
+ " / " + this);
- } catch (Surface.OutOfResourcesException e) {
+ } catch (SurfaceControl.OutOfResourcesException e) {
mWin.mHasSurface = false;
Slog.w(TAG, "OutOfResourcesException creating surface");
mService.reclaimSomeSurfaceMemoryLocked(this, "create", true);
@@ -708,7 +718,7 @@ class WindowStateAnimator {
}
if (WindowManagerService.localLOGV) Slog.v(
- TAG, "Got surface: " + mSurface
+ TAG, "Got surface: " + mSurfaceControl
+ ", set left=" + mWin.mFrame.left + " top=" + mWin.mFrame.top
+ ", animLayer=" + mAnimLayer);
if (SHOW_LIGHT_TRANSACTIONS) {
@@ -718,16 +728,16 @@ class WindowStateAnimator {
+ mWin.mCompatFrame.width() + "x" + mWin.mCompatFrame.height()
+ "), layer=" + mAnimLayer + " HIDE", null);
}
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
try {
mSurfaceX = mWin.mFrame.left + mWin.mXOffset;
mSurfaceY = mWin.mFrame.top + mWin.mYOffset;
- mSurface.setPosition(mSurfaceX, mSurfaceY);
+ mSurfaceControl.setPosition(mSurfaceX, mSurfaceY);
mSurfaceLayer = mAnimLayer;
- mSurface.setLayerStack(mLayerStack);
- mSurface.setLayer(mAnimLayer);
- mSurface.setAlpha(0);
+ mSurfaceControl.setLayerStack(mLayerStack);
+ mSurfaceControl.setLayer(mAnimLayer);
+ mSurfaceControl.setAlpha(0);
mSurfaceShown = false;
} catch (RuntimeException e) {
Slog.w(TAG, "Error creating surface in " + w, e);
@@ -735,22 +745,22 @@ class WindowStateAnimator {
}
mLastHidden = true;
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION createSurfaceLocked");
}
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Created surface " + this);
}
- return mSurface;
+ return mSurfaceControl;
}
- void destroySurfaceLocked(boolean fromAnimator) {
+ void destroySurfaceLocked() {
if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) {
mWin.mAppToken.startingDisplayed = false;
}
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
int i = mWin.mChildWindows.size();
while (i > 0) {
@@ -767,10 +777,10 @@ class WindowStateAnimator {
e.fillInStackTrace();
}
Slog.w(TAG, "Window " + this + " destroying surface "
- + mSurface + ", session " + mSession, e);
+ + mSurfaceControl + ", session " + mSession, e);
}
if (mSurfaceDestroyDeferred) {
- if (mSurface != null && mPendingDestroySurface != mSurface) {
+ if (mSurfaceControl != null && mPendingDestroySurface != mSurfaceControl) {
if (mPendingDestroySurface != null) {
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
RuntimeException e = null;
@@ -782,7 +792,7 @@ class WindowStateAnimator {
}
mPendingDestroySurface.destroy();
}
- mPendingDestroySurface = mSurface;
+ mPendingDestroySurface = mSurfaceControl;
}
} else {
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
@@ -793,23 +803,23 @@ class WindowStateAnimator {
}
WindowManagerService.logSurface(mWin, "DESTROY", e);
}
- mSurface.destroy();
+ mSurfaceControl.destroy();
}
- mAnimator.hideWallpapersLocked(mWin, fromAnimator);
+ mAnimator.hideWallpapersLocked(mWin);
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying Window " + this
- + " surface " + mSurface + " session " + mSession
+ + " surface " + mSurfaceControl + " session " + mSession
+ ": " + e.toString());
}
mSurfaceShown = false;
- mSurface = null;
+ mSurfaceControl = null;
mWin.mHasSurface = false;
mDrawState = NO_SURFACE;
}
}
- void destroyDeferredSurfaceLocked(boolean fromAnimator) {
+ void destroyDeferredSurfaceLocked() {
try {
if (mPendingDestroySurface != null) {
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
@@ -821,7 +831,7 @@ class WindowStateAnimator {
WindowManagerService.logSurface(mWin, "DESTROY PENDING", e);
}
mPendingDestroySurface.destroy();
- mAnimator.hideWallpapersLocked(mWin, fromAnimator);
+ mAnimator.hideWallpapersLocked(mWin);
}
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying Window "
@@ -842,9 +852,9 @@ class WindowStateAnimator {
// Wallpapers are animated based on the "real" window they
// are currently targeting.
- if (mIsWallpaper && mAnimator.mLowerWallpaperTarget == null
- && mAnimator.mWallpaperTarget != null) {
- final WindowStateAnimator wallpaperAnimator = mAnimator.mWallpaperTarget.mWinAnimator;
+ if (mIsWallpaper && mService.mLowerWallpaperTarget == null
+ && mService.mWallpaperTarget != null) {
+ final WindowStateAnimator wallpaperAnimator = mService.mWallpaperTarget.mWinAnimator;
if (wallpaperAnimator.mHasLocalTransformation &&
wallpaperAnimator.mAnimation != null &&
!wallpaperAnimator.mAnimation.getDetachWallpaper()) {
@@ -853,7 +863,7 @@ class WindowStateAnimator {
Slog.v(TAG, "WP target attached xform: " + attachedTransformation);
}
}
- final AppWindowAnimator wpAppAnimator = mAnimator.mWpAppAnimator;
+ final AppWindowAnimator wpAppAnimator = mAnimator.getWallpaperAppAnimator();
if (wpAppAnimator != null && wpAppAnimator.hasTransformation
&& wpAppAnimator.animation != null
&& !wpAppAnimator.animation.getDetachWallpaper()) {
@@ -912,10 +922,15 @@ class WindowStateAnimator {
if (screenAnimation) {
tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
}
- MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked();
- if (spec != null && !spec.isNop()) {
- tmpMatrix.postScale(spec.mScale, spec.mScale);
- tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY);
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (mService.mDisplayMagnifier != null
+ && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ MagnificationSpec spec = mService.mDisplayMagnifier
+ .getMagnificationSpecForWindowLocked(mWin);
+ if (spec != null && !spec.isNop()) {
+ tmpMatrix.postScale(spec.scale, spec.scale);
+ tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
+ }
}
// "convert" it into SurfaceFlinger's format
@@ -976,8 +991,7 @@ class WindowStateAnimator {
+ " screen=" + (screenAnimation ?
screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
return;
- } else if (mIsWallpaper &&
- (mAnimator.mPendingActions & WindowAnimator.WALLPAPER_ACTION_PENDING) != 0) {
+ } else if (mIsWallpaper && mService.mInnerFields.mWallpaperActionPending) {
return;
}
@@ -988,7 +1002,11 @@ class WindowStateAnimator {
final boolean applyUniverseTransformation = (mAnimator.mUniverseBackground != null
&& mWin.mAttrs.type != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
&& mWin.mBaseLayer < mAnimator.mAboveUniverseLayer);
- MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked();
+ MagnificationSpec spec = null;
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (mService.mDisplayMagnifier != null && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ spec = mService.mDisplayMagnifier.getMagnificationSpecForWindowLocked(mWin);
+ }
if (applyUniverseTransformation || spec != null) {
final Rect frame = mWin.mFrame;
final float tmpFloats[] = mService.mTmpFloats;
@@ -1002,8 +1020,8 @@ class WindowStateAnimator {
}
if (spec != null && !spec.isNop()) {
- tmpMatrix.postScale(spec.mScale, spec.mScale);
- tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY);
+ tmpMatrix.postScale(spec.scale, spec.scale);
+ tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
}
tmpMatrix.getValues(tmpFloats);
@@ -1102,7 +1120,7 @@ class WindowStateAnimator {
try {
if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
"CROP " + w.mSystemDecorRect.toShortString(), null);
- mSurface.setWindowCrop(w.mSystemDecorRect);
+ mSurfaceControl.setWindowCrop(w.mSystemDecorRect);
} catch (RuntimeException e) {
Slog.w(TAG, "Error setting crop surface of " + w
+ " crop=" + w.mSystemDecorRect.toShortString(), e);
@@ -1146,7 +1164,7 @@ class WindowStateAnimator {
"POS " + left + ", " + top, null);
mSurfaceX = left;
mSurfaceY = top;
- mSurface.setPosition(left, top);
+ mSurfaceControl.setPosition(left, top);
} catch (RuntimeException e) {
Slog.w(TAG, "Error positioning surface of " + w
+ " pos=(" + left
@@ -1162,14 +1180,12 @@ class WindowStateAnimator {
if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
"SIZE " + width + "x" + height, null);
mSurfaceResized = true;
- mSurface.setSize(width, height);
+ mSurfaceControl.setSize(width, height);
final int displayId = w.mDisplayContent.getDisplayId();
mAnimator.setPendingLayoutChanges(displayId,
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
if ((w.mAttrs.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) {
- final DisplayInfo displayInfo = mWin.mDisplayContent.getDisplayInfo();
- mService.startDimmingLocked(this, w.mExiting ? 0 : w.mAttrs.dimAmount,
- displayInfo.appWidth, displayInfo.appHeight);
+ mService.startDimmingLocked(this, w.mExiting ? 0 : w.mAttrs.dimAmount);
}
} catch (RuntimeException e) {
// If something goes wrong with the surface (such
@@ -1188,7 +1204,7 @@ class WindowStateAnimator {
public void prepareSurfaceLocked(final boolean recoveringMemory) {
final WindowState w = mWin;
- if (mSurface == null) {
+ if (mSurfaceControl == null) {
if (w.mOrientationChanging) {
if (DEBUG_ORIENTATION) {
Slog.v(TAG, "Orientation change skips hidden " + w);
@@ -1207,9 +1223,9 @@ class WindowStateAnimator {
if (mIsWallpaper && !mWin.mWallpaperVisible) {
// Wallpaper is no longer visible and there is no wp target => hide it.
hide();
- } else if (w.mAttachedHidden || !w.isReadyForDisplay()) {
+ } else if (w.mAttachedHidden || !w.isOnScreen()) {
hide();
- mAnimator.hideWallpapersLocked(w, true);
+ mAnimator.hideWallpapersLocked(w);
// If we are waiting for this window to handle an
// orientation change, well, it is hidden, so
@@ -1246,13 +1262,13 @@ class WindowStateAnimator {
+ "," + (mDtDx*w.mVScale)
+ "][" + (mDsDy*w.mHScale)
+ "," + (mDtDy*w.mVScale) + "]", null);
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
try {
mSurfaceAlpha = mShownAlpha;
- mSurface.setAlpha(mShownAlpha);
+ mSurfaceControl.setAlpha(mShownAlpha);
mSurfaceLayer = mAnimLayer;
- mSurface.setLayer(mAnimLayer);
- mSurface.setMatrix(
+ mSurfaceControl.setLayer(mAnimLayer);
+ mSurfaceControl.setMatrix(
mDsDx*w.mHScale, mDtDx*w.mVScale,
mDsDy*w.mHScale, mDtDy*w.mVScale);
@@ -1270,7 +1286,7 @@ class WindowStateAnimator {
w.mOrientationChanging = false;
}
}
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
w.mToken.hasVisible = true;
}
} catch (RuntimeException e) {
@@ -1291,6 +1307,7 @@ class WindowStateAnimator {
if (w.mOrientationChanging) {
if (!w.isDrawnLw()) {
mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
+ mAnimator.mLastWindowFreezeSource = w;
if (DEBUG_ORIENTATION) Slog.v(TAG,
"Orientation continue waiting for draw in " + w);
} else {
@@ -1302,50 +1319,54 @@ class WindowStateAnimator {
}
}
- void setTransparentRegionHint(final Region region) {
- if (mSurface == null) {
+ void setTransparentRegionHintLocked(final Region region) {
+ if (mSurfaceControl == null) {
Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
return;
}
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION setTransparentRegion");
- Surface.openTransaction();
+ SurfaceControl.openTransaction();
try {
if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin,
"transparentRegionHint=" + region, null);
- mSurface.setTransparentRegionHint(region);
+ mSurfaceControl.setTransparentRegionHint(region);
} finally {
- Surface.closeTransaction();
+ SurfaceControl.closeTransaction();
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
"<<< CLOSE TRANSACTION setTransparentRegion");
}
}
- void setWallpaperOffset(int left, int top) {
- mSurfaceX = left;
- mSurfaceY = top;
- if (mAnimating) {
- // If this window (or its app token) is animating, then the position
- // of the surface will be re-computed on the next animation frame.
- // We can't poke it directly here because it depends on whatever
- // transformation is being applied by the animation.
- return;
- }
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
- ">>> OPEN TRANSACTION setWallpaperOffset");
- Surface.openTransaction();
- try {
- if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin,
- "POS " + left + ", " + top, null);
- mSurface.setPosition(mWin.mFrame.left + left, mWin.mFrame.top + top);
- updateSurfaceWindowCrop(false);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error positioning surface of " + mWin
- + " pos=(" + left + "," + top + ")", e);
- } finally {
- Surface.closeTransaction();
+ void setWallpaperOffset(RectF shownFrame) {
+ final int left = (int) shownFrame.left;
+ final int top = (int) shownFrame.top;
+ if (mSurfaceX != left || mSurfaceY != top) {
+ mSurfaceX = left;
+ mSurfaceY = top;
+ if (mAnimating) {
+ // If this window (or its app token) is animating, then the position
+ // of the surface will be re-computed on the next animation frame.
+ // We can't poke it directly here because it depends on whatever
+ // transformation is being applied by the animation.
+ return;
+ }
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
- "<<< CLOSE TRANSACTION setWallpaperOffset");
+ ">>> OPEN TRANSACTION setWallpaperOffset");
+ SurfaceControl.openTransaction();
+ try {
+ if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin,
+ "POS " + left + ", " + top, null);
+ mSurfaceControl.setPosition(mWin.mFrame.left + left, mWin.mFrame.top + top);
+ updateSurfaceWindowCrop(false);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Error positioning surface of " + mWin
+ + " pos=(" + left + "," + top + ")", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+ "<<< CLOSE TRANSACTION setWallpaperOffset");
+ }
}
}
@@ -1403,7 +1424,7 @@ class WindowStateAnimator {
if (DEBUG_SURFACE_TRACE || DEBUG_ANIM)
Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + this);
mDrawState = HAS_DRAWN;
- mService.updateLayoutToAnimationLocked();
+ mService.scheduleAnimationLocked();
int i = mWin.mChildWindows.size();
while (i > 0) {
@@ -1411,7 +1432,7 @@ class WindowStateAnimator {
WindowState c = mWin.mChildWindows.get(i);
if (c.mAttachedHidden) {
c.mAttachedHidden = false;
- if (c.mWinAnimator.mSurface != null) {
+ if (c.mWinAnimator.mSurfaceControl != null) {
c.mWinAnimator.performShowLocked();
// It hadn't been shown, which means layout not
// performed on it, so now we want to make sure to
@@ -1459,9 +1480,9 @@ class WindowStateAnimator {
*/
boolean showSurfaceRobustlyLocked() {
try {
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
mSurfaceShown = true;
- mSurface.show();
+ mSurfaceControl.show();
if (mWin.mTurnOnScreen) {
if (DEBUG_VISIBILITY) Slog.v(TAG,
"Show surface turning screen on: " + mWin);
@@ -1471,7 +1492,7 @@ class WindowStateAnimator {
}
return true;
} catch (RuntimeException e) {
- Slog.w(TAG, "Failure showing surface " + mSurface + " in " + mWin, e);
+ Slog.w(TAG, "Failure showing surface " + mSurfaceControl + " in " + mWin, e);
}
mService.reclaimSomeSurfaceMemoryLocked(this, "show", true);
@@ -1488,13 +1509,16 @@ class WindowStateAnimator {
transit = WindowManagerPolicy.TRANSIT_SHOW;
}
applyAnimationLocked(transit, true);
- mService.scheduleNotifyWindowTranstionIfNeededLocked(mWin, transit);
+ //TODO (multidisplay): Magnification is supported only for the default display.
+ if (mService.mDisplayMagnifier != null
+ && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mService.mDisplayMagnifier.onWindowTransitionLocked(mWin, transit);
+ }
}
- // TODO(cmautner): Move back to WindowState?
/**
* Choose the correct animation and set it to the passed WindowState.
- * @param transit If WindowManagerPolicy.TRANSIT_PREVIEW_DONE and the app window has been drawn
+ * @param transit If AppTransition.TRANSIT_PREVIEW_DONE and the app window has been drawn
* then the animation will be app_starting_exit. Any other value loads the animation from
* the switch statement below.
* @param isEntrance The animation type the last time this was called. Used to keep from
@@ -1534,8 +1558,7 @@ class WindowStateAnimator {
break;
}
if (attr >= 0) {
- a = mService.loadAnimation(UserHandle.getUserId(mWin.mOwnerUid),
- mWin.mAttrs, attr);
+ a = mService.mAppTransition.loadAnimation(mWin.mAttrs, attr);
}
}
if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
@@ -1578,9 +1601,9 @@ class WindowStateAnimator {
pw.print(" "); mTransformation.printShortString(pw);
pw.println();
}
- if (mSurface != null) {
+ if (mSurfaceControl != null) {
if (dumpAll) {
- pw.print(prefix); pw.print("mSurface="); pw.println(mSurface);
+ pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
pw.print(prefix); pw.print("mDrawState=");
pw.print(drawStateToString(mDrawState));
pw.print(" mLastHidden="); pw.println(mLastHidden);
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index d097a93..b313d48 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -32,6 +32,7 @@ LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libandroidfw \
libcutils \
+ liblog \
libhardware \
libhardware_legacy \
libnativehelper \
diff --git a/services/jni/com_android_server_BatteryService.cpp b/services/jni/com_android_server_BatteryService.cpp
index e6133af..433950d 100644
--- a/services/jni/com_android_server_BatteryService.cpp
+++ b/services/jni/com_android_server_BatteryService.cpp
@@ -33,6 +33,8 @@
#include <unistd.h>
#include <dirent.h>
#include <linux/ioctl.h>
+#include <utils/Vector.h>
+#include <utils/String8.h>
namespace android {
@@ -70,21 +72,28 @@ struct BatteryManagerConstants {
static BatteryManagerConstants gConstants;
struct PowerSupplyPaths {
- char* acOnlinePath;
- char* usbOnlinePath;
- char* wirelessOnlinePath;
- char* batteryStatusPath;
- char* batteryHealthPath;
- char* batteryPresentPath;
- char* batteryCapacityPath;
- char* batteryVoltagePath;
- char* batteryTemperaturePath;
- char* batteryTechnologyPath;
+ String8 batteryStatusPath;
+ String8 batteryHealthPath;
+ String8 batteryPresentPath;
+ String8 batteryCapacityPath;
+ String8 batteryVoltagePath;
+ String8 batteryTemperaturePath;
+ String8 batteryTechnologyPath;
};
static PowerSupplyPaths gPaths;
+static Vector<String8> gChargerNames;
+
static int gVoltageDivisor = 1;
+enum PowerSupplyType {
+ ANDROID_POWER_SUPPLY_TYPE_UNKNOWN = 0,
+ ANDROID_POWER_SUPPLY_TYPE_AC,
+ ANDROID_POWER_SUPPLY_TYPE_USB,
+ ANDROID_POWER_SUPPLY_TYPE_WIRELESS,
+ ANDROID_POWER_SUPPLY_TYPE_BATTERY
+};
+
static jint getBatteryStatus(const char* status)
{
switch (status[0]) {
@@ -133,13 +142,13 @@ static jint getBatteryHealth(const char* status)
}
}
-static int readFromFile(const char* path, char* buf, size_t size)
+static int readFromFile(const String8& path, char* buf, size_t size)
{
- if (!path)
+ if (path.isEmpty())
return -1;
- int fd = open(path, O_RDONLY, 0);
+ int fd = open(path.string(), O_RDONLY, 0);
if (fd == -1) {
- ALOGE("Could not open '%s'", path);
+ ALOGE("Could not open '%s'", path.string());
return -1;
}
@@ -156,7 +165,7 @@ static int readFromFile(const char* path, char* buf, size_t size)
return count;
}
-static void setBooleanField(JNIEnv* env, jobject obj, const char* path, jfieldID fieldID)
+static void setBooleanField(JNIEnv* env, jobject obj, const String8& path, jfieldID fieldID)
{
const int SIZE = 16;
char buf[SIZE];
@@ -170,7 +179,7 @@ static void setBooleanField(JNIEnv* env, jobject obj, const char* path, jfieldID
env->SetBooleanField(obj, fieldID, value);
}
-static void setIntField(JNIEnv* env, jobject obj, const char* path, jfieldID fieldID)
+static void setIntField(JNIEnv* env, jobject obj, const String8& path, jfieldID fieldID)
{
const int SIZE = 128;
char buf[SIZE];
@@ -182,7 +191,7 @@ static void setIntField(JNIEnv* env, jobject obj, const char* path, jfieldID fie
env->SetIntField(obj, fieldID, value);
}
-static void setVoltageField(JNIEnv* env, jobject obj, const char* path, jfieldID fieldID)
+static void setVoltageField(JNIEnv* env, jobject obj, const String8& path, jfieldID fieldID)
{
const int SIZE = 128;
char buf[SIZE];
@@ -195,12 +204,30 @@ static void setVoltageField(JNIEnv* env, jobject obj, const char* path, jfieldID
env->SetIntField(obj, fieldID, value);
}
+static PowerSupplyType readPowerSupplyType(const String8& path) {
+ const int SIZE = 128;
+ char buf[SIZE];
+ int length = readFromFile(path, buf, SIZE);
+
+ if (length <= 0)
+ return ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
+ if (buf[length - 1] == '\n')
+ buf[length - 1] = 0;
+ if (strcmp(buf, "Battery") == 0)
+ return ANDROID_POWER_SUPPLY_TYPE_BATTERY;
+ else if (strcmp(buf, "Mains") == 0 || strcmp(buf, "USB_DCP") == 0 ||
+ strcmp(buf, "USB_CDP") == 0 || strcmp(buf, "USB_ACA") == 0)
+ return ANDROID_POWER_SUPPLY_TYPE_AC;
+ else if (strcmp(buf, "USB") == 0)
+ return ANDROID_POWER_SUPPLY_TYPE_USB;
+ else if (strcmp(buf, "Wireless") == 0)
+ return ANDROID_POWER_SUPPLY_TYPE_WIRELESS;
+ else
+ return ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
+}
static void android_server_BatteryService_update(JNIEnv* env, jobject obj)
{
- setBooleanField(env, obj, gPaths.acOnlinePath, gFieldIds.mAcOnline);
- setBooleanField(env, obj, gPaths.usbOnlinePath, gFieldIds.mUsbOnline);
- setBooleanField(env, obj, gPaths.wirelessOnlinePath, gFieldIds.mWirelessOnline);
setBooleanField(env, obj, gPaths.batteryPresentPath, gFieldIds.mBatteryPresent);
setIntField(env, obj, gPaths.batteryCapacityPath, gFieldIds.mBatteryLevel);
@@ -221,16 +248,54 @@ static void android_server_BatteryService_update(JNIEnv* env, jobject obj)
if (readFromFile(gPaths.batteryTechnologyPath, buf, SIZE) > 0)
env->SetObjectField(obj, gFieldIds.mBatteryTechnology, env->NewStringUTF(buf));
+
+ unsigned int i;
+ String8 path;
+ jboolean acOnline = false;
+ jboolean usbOnline = false;
+ jboolean wirelessOnline = false;
+
+ for (i = 0; i < gChargerNames.size(); i++) {
+ path.clear();
+ path.appendFormat("%s/%s/online", POWER_SUPPLY_PATH,
+ gChargerNames[i].string());
+
+ if (readFromFile(path, buf, SIZE) > 0) {
+ if (buf[0] != '0') {
+ path.clear();
+ path.appendFormat("%s/%s/type", POWER_SUPPLY_PATH,
+ gChargerNames[i].string());
+ switch(readPowerSupplyType(path)) {
+ case ANDROID_POWER_SUPPLY_TYPE_AC:
+ acOnline = true;
+ break;
+ case ANDROID_POWER_SUPPLY_TYPE_USB:
+ usbOnline = true;
+ break;
+ case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
+ wirelessOnline = true;
+ break;
+ default:
+ ALOGW("%s: Unknown power supply type",
+ gChargerNames[i].string());
+ }
+ }
+ }
+ }
+
+ env->SetBooleanField(obj, gFieldIds.mAcOnline, acOnline);
+ env->SetBooleanField(obj, gFieldIds.mUsbOnline, usbOnline);
+ env->SetBooleanField(obj, gFieldIds.mWirelessOnline, wirelessOnline);
}
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
- {"native_update", "()V", (void*)android_server_BatteryService_update},
+ {"native_update", "()V", (void*)android_server_BatteryService_update},
};
int register_android_server_BatteryService(JNIEnv* env)
{
- char path[PATH_MAX];
+ String8 path;
struct dirent* entry;
DIR* dir = opendir(POWER_SUPPLY_PATH);
@@ -247,76 +312,72 @@ int register_android_server_BatteryService(JNIEnv* env)
char buf[20];
// Look for "type" file in each subdirectory
- snprintf(path, sizeof(path), "%s/%s/type", POWER_SUPPLY_PATH, name);
- int length = readFromFile(path, buf, sizeof(buf));
- if (length > 0) {
- if (buf[length - 1] == '\n')
- buf[length - 1] = 0;
-
- if (strcmp(buf, "Mains") == 0) {
- snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.acOnlinePath = strdup(path);
- }
- else if (strcmp(buf, "USB") == 0) {
- snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
+ path.clear();
+ path.appendFormat("%s/%s/type", POWER_SUPPLY_PATH, name);
+ switch(readPowerSupplyType(path)) {
+ case ANDROID_POWER_SUPPLY_TYPE_AC:
+ case ANDROID_POWER_SUPPLY_TYPE_USB:
+ case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
+ path.clear();
+ path.appendFormat("%s/%s/online", POWER_SUPPLY_PATH, name);
+ if (access(path.string(), R_OK) == 0)
+ gChargerNames.add(String8(name));
+ break;
+
+ case ANDROID_POWER_SUPPLY_TYPE_BATTERY:
+ path.clear();
+ path.appendFormat("%s/%s/status", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryStatusPath = path;
+ path.clear();
+ path.appendFormat("%s/%s/health", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryHealthPath = path;
+ path.clear();
+ path.appendFormat("%s/%s/present", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryPresentPath = path;
+ path.clear();
+ path.appendFormat("%s/%s/capacity", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryCapacityPath = path;
+
+ path.clear();
+ path.appendFormat("%s/%s/voltage_now", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0) {
+ gPaths.batteryVoltagePath = path;
+ // voltage_now is in microvolts, not millivolts
+ gVoltageDivisor = 1000;
+ } else {
+ path.clear();
+ path.appendFormat("%s/%s/batt_vol", POWER_SUPPLY_PATH, name);
if (access(path, R_OK) == 0)
- gPaths.usbOnlinePath = strdup(path);
+ gPaths.batteryVoltagePath = path;
}
- else if (strcmp(buf, "Wireless") == 0) {
- snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.wirelessOnlinePath = strdup(path);
- }
- else if (strcmp(buf, "Battery") == 0) {
- snprintf(path, sizeof(path), "%s/%s/status", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryStatusPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/health", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryHealthPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/present", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryPresentPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/capacity", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryCapacityPath = strdup(path);
-
- snprintf(path, sizeof(path), "%s/%s/voltage_now", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0) {
- gPaths.batteryVoltagePath = strdup(path);
- // voltage_now is in microvolts, not millivolts
- gVoltageDivisor = 1000;
- } else {
- snprintf(path, sizeof(path), "%s/%s/batt_vol", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryVoltagePath = strdup(path);
- }
-
- snprintf(path, sizeof(path), "%s/%s/temp", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0) {
- gPaths.batteryTemperaturePath = strdup(path);
- } else {
- snprintf(path, sizeof(path), "%s/%s/batt_temp", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryTemperaturePath = strdup(path);
- }
-
- snprintf(path, sizeof(path), "%s/%s/technology", POWER_SUPPLY_PATH, name);
+
+ path.clear();
+ path.appendFormat("%s/%s/temp", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0) {
+ gPaths.batteryTemperaturePath = path;
+ } else {
+ path.clear();
+ path.appendFormat("%s/%s/batt_temp", POWER_SUPPLY_PATH, name);
if (access(path, R_OK) == 0)
- gPaths.batteryTechnologyPath = strdup(path);
+ gPaths.batteryTemperaturePath = path;
}
+
+ path.clear();
+ path.appendFormat("%s/%s/technology", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryTechnologyPath = path;
+ break;
}
}
closedir(dir);
}
- if (!gPaths.acOnlinePath)
- ALOGE("acOnlinePath not found");
- if (!gPaths.usbOnlinePath)
- ALOGE("usbOnlinePath not found");
- if (!gPaths.wirelessOnlinePath)
- ALOGE("wirelessOnlinePath not found");
+ if (!gChargerNames.size())
+ ALOGE("No charger supplies found");
if (!gPaths.batteryStatusPath)
ALOGE("batteryStatusPath not found");
if (!gPaths.batteryHealthPath)
diff --git a/services/jni/com_android_server_input_InputApplicationHandle.cpp b/services/jni/com_android_server_input_InputApplicationHandle.cpp
index 0109430..b9681ab 100644
--- a/services/jni/com_android_server_input_InputApplicationHandle.cpp
+++ b/services/jni/com_android_server_input_InputApplicationHandle.cpp
@@ -97,7 +97,7 @@ sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle(
} else {
jweak objWeak = env->NewWeakGlobalRef(inputApplicationHandleObj);
handle = new NativeInputApplicationHandle(objWeak);
- handle->incStrong(inputApplicationHandleObj);
+ handle->incStrong((void*)android_server_InputApplicationHandle_getHandle);
env->SetIntField(inputApplicationHandleObj, gInputApplicationHandleClassInfo.ptr,
reinterpret_cast<int>(handle));
}
@@ -115,7 +115,7 @@ static void android_server_InputApplicationHandle_nativeDispose(JNIEnv* env, job
env->SetIntField(obj, gInputApplicationHandleClassInfo.ptr, 0);
NativeInputApplicationHandle* handle = reinterpret_cast<NativeInputApplicationHandle*>(ptr);
- handle->decStrong(obj);
+ handle->decStrong((void*)android_server_InputApplicationHandle_getHandle);
}
}
diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp
index a97becf..09e5be4 100644
--- a/services/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/jni/com_android_server_input_InputManagerService.cpp
@@ -975,9 +975,14 @@ void NativeInputManager::loadPointerResources(PointerResources* outResources) {
static jint nativeInit(JNIEnv* env, jclass clazz,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
+ if (messageQueue == NULL) {
+ jniThrowRuntimeException(env, "MessageQueue is not initialized.");
+ return 0;
+ }
+
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
- im->incStrong(serviceObj);
+ im->incStrong(0);
return reinterpret_cast<jint>(im);
}
diff --git a/services/jni/com_android_server_input_InputWindowHandle.cpp b/services/jni/com_android_server_input_InputWindowHandle.cpp
index 6692994..bbb27d3 100644
--- a/services/jni/com_android_server_input_InputWindowHandle.cpp
+++ b/services/jni/com_android_server_input_InputWindowHandle.cpp
@@ -183,7 +183,7 @@ sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
handle = new NativeInputWindowHandle(inputApplicationHandle, objWeak);
- handle->incStrong(inputWindowHandleObj);
+ handle->incStrong((void*)android_server_InputWindowHandle_getHandle);
env->SetIntField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
reinterpret_cast<int>(handle));
}
@@ -201,7 +201,7 @@ static void android_server_InputWindowHandle_nativeDispose(JNIEnv* env, jobject
env->SetIntField(obj, gInputWindowHandleClassInfo.ptr, 0);
NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
- handle->decStrong(obj);
+ handle->decStrong((void*)android_server_InputWindowHandle_getHandle);
}
}
diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
index 036fc43..98de12a 100644
--- a/services/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -43,6 +43,12 @@ static jmethodID method_reportNiNotification;
static jmethodID method_requestRefLocation;
static jmethodID method_requestSetID;
static jmethodID method_requestUtcTime;
+static jmethodID method_reportGeofenceTransition;
+static jmethodID method_reportGeofenceStatus;
+static jmethodID method_reportGeofenceAddStatus;
+static jmethodID method_reportGeofenceRemoveStatus;
+static jmethodID method_reportGeofencePauseStatus;
+static jmethodID method_reportGeofenceResumeStatus;
static const GpsInterface* sGpsInterface = NULL;
static const GpsXtraInterface* sGpsXtraInterface = NULL;
@@ -50,6 +56,7 @@ static const AGpsInterface* sAGpsInterface = NULL;
static const GpsNiInterface* sGpsNiInterface = NULL;
static const GpsDebugInterface* sGpsDebugInterface = NULL;
static const AGpsRilInterface* sAGpsRilInterface = NULL;
+static const GpsGeofencingInterface* sGpsGeofencingInterface = NULL;
// temporary storage for GPS callbacks
static GpsSvStatus sGpsSvStatus;
@@ -107,7 +114,7 @@ static void nmea_callback(GpsUtcTime timestamp, const char* nmea, int length)
static void set_capabilities_callback(uint32_t capabilities)
{
- ALOGD("set_capabilities_callback: %ld\n", capabilities);
+ ALOGD("set_capabilities_callback: %du\n", capabilities);
JNIEnv* env = AndroidRuntime::getJNIEnv();
env->CallVoidMethod(mCallbacksObj, method_setEngineCapabilities, capabilities);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
@@ -233,6 +240,97 @@ AGpsRilCallbacks sAGpsRilCallbacks = {
create_thread_callback,
};
+static void gps_geofence_transition_callback(int32_t geofence_id, GpsLocation* location,
+ int32_t transition, GpsUtcTime timestamp)
+{
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceTransition, geofence_id,
+ location->flags, (jdouble)location->latitude, (jdouble)location->longitude,
+ (jdouble)location->altitude,
+ (jfloat)location->speed, (jfloat)location->bearing,
+ (jfloat)location->accuracy, (jlong)location->timestamp,
+ transition, timestamp);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+};
+
+static void gps_geofence_status_callback(int32_t status, GpsLocation* location)
+{
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jint flags = 0;
+ jdouble latitude = 0;
+ jdouble longitude = 0;
+ jdouble altitude = 0;
+ jfloat speed = 0;
+ jfloat bearing = 0;
+ jfloat accuracy = 0;
+ jlong timestamp = 0;
+ if (location != NULL) {
+ flags = location->flags;
+ latitude = location->latitude;
+ longitude = location->longitude;
+ altitude = location->altitude;
+ speed = location->speed;
+ bearing = location->bearing;
+ accuracy = location->accuracy;
+ timestamp = location->timestamp;
+ }
+
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, status,
+ flags, latitude, longitude, altitude, speed, bearing, accuracy, timestamp);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+};
+
+static void gps_geofence_add_callback(int32_t geofence_id, int32_t status)
+{
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (status != GPS_GEOFENCE_OPERATION_SUCCESS) {
+ ALOGE("Error in geofence_add_callback: %d\n", status);
+ }
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceAddStatus, geofence_id, status);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+};
+
+static void gps_geofence_remove_callback(int32_t geofence_id, int32_t status)
+{
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (status != GPS_GEOFENCE_OPERATION_SUCCESS) {
+ ALOGE("Error in geofence_remove_callback: %d\n", status);
+ }
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceRemoveStatus, geofence_id, status);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+};
+
+static void gps_geofence_resume_callback(int32_t geofence_id, int32_t status)
+{
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (status != GPS_GEOFENCE_OPERATION_SUCCESS) {
+ ALOGE("Error in geofence_resume_callback: %d\n", status);
+ }
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceResumeStatus, geofence_id, status);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+};
+
+static void gps_geofence_pause_callback(int32_t geofence_id, int32_t status)
+{
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (status != GPS_GEOFENCE_OPERATION_SUCCESS) {
+ ALOGE("Error in geofence_pause_callback: %d\n", status);
+ }
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofencePauseStatus, geofence_id, status);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+};
+
+GpsGeofenceCallbacks sGpsGeofenceCallbacks = {
+ gps_geofence_transition_callback,
+ gps_geofence_status_callback,
+ gps_geofence_add_callback,
+ gps_geofence_remove_callback,
+ gps_geofence_pause_callback,
+ gps_geofence_resume_callback,
+ create_thread_callback,
+};
+
static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
int err;
hw_module_t* module;
@@ -249,6 +347,18 @@ static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env,
method_requestRefLocation = env->GetMethodID(clazz,"requestRefLocation","(I)V");
method_requestSetID = env->GetMethodID(clazz,"requestSetID","(I)V");
method_requestUtcTime = env->GetMethodID(clazz,"requestUtcTime","()V");
+ method_reportGeofenceTransition = env->GetMethodID(clazz,"reportGeofenceTransition",
+ "(IIDDDFFFJIJ)V");
+ method_reportGeofenceStatus = env->GetMethodID(clazz,"reportGeofenceStatus",
+ "(IIDDDFFFJ)V");
+ method_reportGeofenceAddStatus = env->GetMethodID(clazz,"reportGeofenceAddStatus",
+ "(II)V");
+ method_reportGeofenceRemoveStatus = env->GetMethodID(clazz,"reportGeofenceRemoveStatus",
+ "(II)V");
+ method_reportGeofenceResumeStatus = env->GetMethodID(clazz,"reportGeofenceResumeStatus",
+ "(II)V");
+ method_reportGeofencePauseStatus = env->GetMethodID(clazz,"reportGeofencePauseStatus",
+ "(II)V");
err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == 0) {
@@ -270,6 +380,8 @@ static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env,
(const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
sAGpsRilInterface =
(const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE);
+ sGpsGeofencingInterface =
+ (const GpsGeofencingInterface*)sGpsInterface->get_extension(GPS_GEOFENCING_INTERFACE);
}
}
@@ -287,7 +399,7 @@ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject o
if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
return false;
- // if XTRA initialization fails we will disable it by sGpsXtraInterface to null,
+ // if XTRA initialization fails we will disable it by sGpsXtraInterface to NULL,
// but continue to allow the rest of the GPS interface to work.
if (sGpsXtraInterface && sGpsXtraInterface->init(&sGpsXtraCallbacks) != 0)
sGpsXtraInterface = NULL;
@@ -297,6 +409,8 @@ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject o
sGpsNiInterface->init(&sGpsNiCallbacks);
if (sAGpsRilInterface)
sAGpsRilInterface->init(&sAGpsRilCallbacks);
+ if (sGpsGeofencingInterface)
+ sGpsGeofencingInterface->init(&sGpsGeofenceCallbacks);
return true;
}
@@ -565,6 +679,62 @@ static void android_location_GpsLocationProvider_update_network_state(JNIEnv* en
}
}
+static jboolean android_location_GpsLocationProvider_is_geofence_supported(JNIEnv* env,
+ jobject obj) {
+ if (sGpsGeofencingInterface != NULL) {
+ return JNI_TRUE;
+ }
+ return JNI_FALSE;
+}
+
+static jboolean android_location_GpsLocationProvider_add_geofence(JNIEnv* env, jobject obj,
+ jint geofence_id, jdouble latitude, jdouble longitude, jdouble radius,
+ jint last_transition, jint monitor_transition, jint notification_responsiveness,
+ jint unknown_timer) {
+ if (sGpsGeofencingInterface != NULL) {
+ sGpsGeofencingInterface->add_geofence_area(geofence_id, latitude, longitude,
+ radius, last_transition, monitor_transition, notification_responsiveness,
+ unknown_timer);
+ return JNI_TRUE;
+ } else {
+ ALOGE("Geofence interface not available");
+ }
+ return JNI_FALSE;
+}
+
+static jboolean android_location_GpsLocationProvider_remove_geofence(JNIEnv* env, jobject obj,
+ jint geofence_id) {
+ if (sGpsGeofencingInterface != NULL) {
+ sGpsGeofencingInterface->remove_geofence_area(geofence_id);
+ return JNI_TRUE;
+ } else {
+ ALOGE("Geofence interface not available");
+ }
+ return JNI_FALSE;
+}
+
+static jboolean android_location_GpsLocationProvider_pause_geofence(JNIEnv* env, jobject obj,
+ jint geofence_id) {
+ if (sGpsGeofencingInterface != NULL) {
+ sGpsGeofencingInterface->pause_geofence(geofence_id);
+ return JNI_TRUE;
+ } else {
+ ALOGE("Geofence interface not available");
+ }
+ return JNI_FALSE;
+}
+
+static jboolean android_location_GpsLocationProvider_resume_geofence(JNIEnv* env, jobject obj,
+ jint geofence_id, jint monitor_transition) {
+ if (sGpsGeofencingInterface != NULL) {
+ sGpsGeofencingInterface->resume_geofence(geofence_id, monitor_transition);
+ return JNI_TRUE;
+ } else {
+ ALOGE("Geofence interface not available");
+ }
+ return JNI_FALSE;
+}
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
@@ -591,6 +761,11 @@ static JNINativeMethod sMethods[] = {
{"native_agps_ni_message", "([BI)V", (void *)android_location_GpsLocationProvider_agps_send_ni_message},
{"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state},
{"native_update_network_state", "(ZIZZLjava/lang/String;Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_update_network_state },
+ {"native_is_geofence_supported", "()Z", (void*) android_location_GpsLocationProvider_is_geofence_supported},
+ {"native_add_geofence", "(IDDDIIII)Z", (void *)android_location_GpsLocationProvider_add_geofence},
+ {"native_remove_geofence", "(I)Z", (void *)android_location_GpsLocationProvider_remove_geofence},
+ {"native_pause_geofence", "(I)Z", (void *)android_location_GpsLocationProvider_pause_geofence},
+ {"native_resume_geofence", "(II)Z", (void *)android_location_GpsLocationProvider_resume_geofence}
};
int register_android_server_location_GpsLocationProvider(JNIEnv* env)
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 81a2c14..4ff3899 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -10,7 +10,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
easymocklib \
guava \
- littlemock
+ mockito-target
LOCAL_JAVA_LIBRARIES := android.test.runner services
diff --git a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
index f14569c..ffd1568 100644
--- a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
+++ b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
@@ -22,6 +22,7 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
+import android.os.UserHandle;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.AbstractFuture;
@@ -133,12 +134,28 @@ public class BroadcastInterceptingContext extends ContextWrapper {
}
@Override
+ public void sendBroadcast(Intent intent, String receiverPermission) {
+ sendBroadcast(intent);
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ sendBroadcast(intent);
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission) {
+ sendBroadcast(intent);
+ }
+
+ @Override
public void sendStickyBroadcast(Intent intent) {
sendBroadcast(intent);
}
@Override
- public void sendBroadcast(Intent intent, String receiverPermission) {
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
sendBroadcast(intent);
}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 93ea6a2..f955f4f 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -21,16 +21,15 @@ import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.NetworkStateTracker.EVENT_STATE_CHANGED;
-import static com.google.testing.littlemock.LittleMock.anyInt;
-import static com.google.testing.littlemock.LittleMock.createCaptor;
-import static com.google.testing.littlemock.LittleMock.doNothing;
-import static com.google.testing.littlemock.LittleMock.doReturn;
-import static com.google.testing.littlemock.LittleMock.doThrow;
-import static com.google.testing.littlemock.LittleMock.eq;
-import static com.google.testing.littlemock.LittleMock.isA;
-import static com.google.testing.littlemock.LittleMock.mock;
-import static com.google.testing.littlemock.LittleMock.reset;
-import static com.google.testing.littlemock.LittleMock.verify;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.INetworkPolicyManager;
@@ -48,7 +47,7 @@ import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.util.LogPrinter;
-import com.google.testing.littlemock.ArgumentCaptor;
+import org.mockito.ArgumentCaptor;
import java.net.InetAddress;
import java.util.concurrent.Future;
@@ -63,13 +62,17 @@ public class ConnectivityServiceTest extends AndroidTestCase {
private static final String MOBILE_IFACE = "rmnet3";
private static final String WIFI_IFACE = "wlan6";
- private static final RouteInfo MOBILE_ROUTE_V4 = RouteInfo.makeHostRoute(parse("10.0.0.33"));
- private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33"));
+ private static final RouteInfo MOBILE_ROUTE_V4 = RouteInfo.makeHostRoute(parse("10.0.0.33"),
+ MOBILE_IFACE);
+ private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33"),
+ MOBILE_IFACE);
- private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute(
- parse("192.168.0.66"), parse("192.168.0.1"));
- private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute(
- parse("fd00::66"), parse("fd00::"));
+ private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute(parse("192.168.0.66"),
+ parse("192.168.0.1"),
+ WIFI_IFACE);
+ private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::66"),
+ parse("fd00::"),
+ WIFI_IFACE);
private INetworkManagementService mNetManager;
private INetworkStatsService mStatsService;
@@ -128,7 +131,7 @@ public class ConnectivityServiceTest extends AndroidTestCase {
doReturn(mWifi.tracker)
.when(mNetFactory).createTracker(eq(TYPE_WIFI), isA(NetworkConfig.class));
- final ArgumentCaptor<Handler> trackerHandler = createCaptor();
+ final ArgumentCaptor<Handler> trackerHandler = ArgumentCaptor.forClass(Handler.class);
doNothing().when(mMobile.tracker)
.startMonitoring(isA(Context.class), trackerHandler.capture());
diff --git a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java
index 21a8ec2..58d6dae 100644
--- a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java
+++ b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java
@@ -34,7 +34,7 @@ public class EntropyMixerTest extends AndroidTestCase {
assertEquals(0, FileUtils.readTextFile(file, 0, null).length());
// The constructor has the side effect of writing to file
- new EntropyMixer("/dev/null", file.getCanonicalPath());
+ new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath());
assertTrue(FileUtils.readTextFile(file, 0, null).length() > 0);
}
diff --git a/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java b/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java
index 275d807..e2253a2 100644
--- a/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java
@@ -17,10 +17,13 @@
package com.android.server;
import static com.android.server.NativeDaemonConnector.appendEscaped;
+import static com.android.server.NativeDaemonConnector.makeCommand;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
+import com.android.server.NativeDaemonConnector.SensitiveArg;
+
/**
* Tests for {@link NativeDaemonConnector}.
*/
@@ -67,4 +70,28 @@ public class NativeDaemonConnectorTest extends AndroidTestCase {
appendEscaped(builder, "caf\u00E9 c\u00F6ffee");
assertEquals("\"caf\u00E9 c\u00F6ffee\"", builder.toString());
}
+
+ public void testSensitiveArgs() throws Exception {
+ final StringBuilder rawBuilder = new StringBuilder();
+ final StringBuilder logBuilder = new StringBuilder();
+
+ rawBuilder.setLength(0);
+ logBuilder.setLength(0);
+ makeCommand(rawBuilder, logBuilder, 1, "foo", "bar", "baz");
+ assertEquals("1 foo bar baz\0", rawBuilder.toString());
+ assertEquals("1 foo bar baz", logBuilder.toString());
+
+ rawBuilder.setLength(0);
+ logBuilder.setLength(0);
+ makeCommand(rawBuilder, logBuilder, 1, "foo", new SensitiveArg("bar"), "baz");
+ assertEquals("1 foo bar baz\0", rawBuilder.toString());
+ assertEquals("1 foo [scrubbed] baz", logBuilder.toString());
+
+ rawBuilder.setLength(0);
+ logBuilder.setLength(0);
+ makeCommand(rawBuilder, logBuilder, 1, "foo", new SensitiveArg("foo bar"), "baz baz",
+ new SensitiveArg("wat"));
+ assertEquals("1 foo \"foo bar\" \"baz baz\" wat\0", rawBuilder.toString());
+ assertEquals("1 foo [scrubbed] \"baz baz\" [scrubbed]", logBuilder.toString());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 167b6c2..8b9f718 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -557,6 +557,25 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
}
}
+ public void testCycleTodayJanuary() throws Exception {
+ final NetworkPolicy policy = new NetworkPolicy(
+ sTemplateWifi, 14, "US/Pacific", 1024L, 1024L, false);
+
+ assertTimeEquals(parseTime("2013-01-14T00:00:00.000-08:00"),
+ computeNextCycleBoundary(parseTime("2013-01-13T23:59:59.000-08:00"), policy));
+ assertTimeEquals(parseTime("2013-02-14T00:00:00.000-08:00"),
+ computeNextCycleBoundary(parseTime("2013-01-14T00:00:01.000-08:00"), policy));
+ assertTimeEquals(parseTime("2013-02-14T00:00:00.000-08:00"),
+ computeNextCycleBoundary(parseTime("2013-01-14T15:11:00.000-08:00"), policy));
+
+ assertTimeEquals(parseTime("2012-12-14T00:00:00.000-08:00"),
+ computeLastCycleBoundary(parseTime("2013-01-13T23:59:59.000-08:00"), policy));
+ assertTimeEquals(parseTime("2013-01-14T00:00:00.000-08:00"),
+ computeLastCycleBoundary(parseTime("2013-01-14T00:00:01.000-08:00"), policy));
+ assertTimeEquals(parseTime("2013-01-14T00:00:00.000-08:00"),
+ computeLastCycleBoundary(parseTime("2013-01-14T15:11:00.000-08:00"), policy));
+ }
+
public void testNetworkPolicyAppliedCycleLastMonth() throws Exception {
NetworkState[] state = null;
NetworkStats stats = null;
@@ -845,7 +864,8 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
private Future<String> expectEnqueueNotification() throws Exception {
final FutureCapture<String> tag = new FutureCapture<String>();
- mNotifManager.enqueueNotificationWithTag(isA(String.class), capture(tag.capture), anyInt(),
+ mNotifManager.enqueueNotificationWithTag(isA(String.class), isA(String.class),
+ capture(tag.capture), anyInt(),
isA(Notification.class), isA(int[].class), UserHandle.myUserId());
return tag;
}
diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
deleted file mode 100644
index 569acee..0000000
--- a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * 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;
-
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStats.UID_ALL;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.isA;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.INetworkManagementEventObserver;
-import android.net.NetworkStats;
-import android.net.ThrottleManager;
-import android.os.IBinder;
-import android.os.INetworkManagementService;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.TrustedTime;
-
-import java.util.concurrent.Future;
-
-/**
- * Tests for {@link ThrottleService}.
- */
-@LargeTest
-public class ThrottleServiceTest extends AndroidTestCase {
- private static final String TAG = "ThrottleServiceTest";
-
- private static final long MB_IN_BYTES = 1024 * 1024;
-
- private static final int TEST_KBITPS = 222;
- private static final int TEST_RESET_DAY = 11;
-
- private static final String TEST_IFACE = "test0";
-
- private BroadcastInterceptingContext mWatchingContext;
- private INetworkManagementService mMockNMService;
- private TrustedTime mMockTime;
-
- private ThrottleService mThrottleService;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- mWatchingContext = new BroadcastInterceptingContext(getContext());
-
- mMockNMService = createMock(INetworkManagementService.class);
- mMockTime = createMock(TrustedTime.class);
-
- mThrottleService = new ThrottleService(
- mWatchingContext, mMockNMService, mMockTime, TEST_IFACE);
- }
-
- @Override
- public void tearDown() throws Exception {
- mWatchingContext = null;
- mMockNMService = null;
-
- mThrottleService.shutdown();
- mThrottleService = null;
-
- clearThrottlePolicy();
-
- super.tearDown();
- }
-
- public void testNoPolicyNotThrottled() throws Exception {
- expectTimeCurrent();
- expectSystemReady();
-
- // provide stats without policy, verify not throttled
- expectGetInterfaceCounter(1 * MB_IN_BYTES, 2 * MB_IN_BYTES);
- expectSetInterfaceThrottle(-1, -1);
-
- replay(mMockTime, mMockNMService);
- systemReady();
- verify(mMockTime, mMockNMService);
- }
-
- public void testUnderLimitNotThrottled() throws Exception {
- setThrottlePolicy(200 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
-
- expectTimeCurrent();
- expectSystemReady();
-
- // provide stats under limits, and verify not throttled
- expectGetInterfaceCounter(1 * MB_IN_BYTES, 2 * MB_IN_BYTES);
- expectSetInterfaceThrottle(-1, -1);
-
- replay(mMockTime, mMockNMService);
- systemReady();
- verify(mMockTime, mMockNMService);
- }
-
- public void testOverLimitThrottled() throws Exception {
- setThrottlePolicy(200 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
-
- expectTimeCurrent();
- expectSystemReady();
-
- // provide stats over limits, and verify throttled
- expectGetInterfaceCounter(500 * MB_IN_BYTES, 600 * MB_IN_BYTES);
- expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS);
-
- replay(mMockTime, mMockNMService);
- systemReady();
- verify(mMockTime, mMockNMService);
- }
-
- public void testUnderThenOverLimitThrottled() throws Exception {
- setThrottlePolicy(201 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
-
- expectTimeCurrent();
- expectSystemReady();
-
- // provide stats right under 201MB limit, verify not throttled
- expectGetInterfaceCounter(100 * MB_IN_BYTES, 100 * MB_IN_BYTES);
- expectSetInterfaceThrottle(-1, -1);
-
- replay(mMockTime, mMockNMService);
- systemReady();
- verify(mMockTime, mMockNMService);
- reset(mMockTime, mMockNMService);
-
- expectTimeCurrent();
-
- // adjust usage to bump over limit, verify throttle kicks in
- expectGetInterfaceCounter(105 * MB_IN_BYTES, 100 * MB_IN_BYTES);
- expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS);
-
- // and kick poll event which should throttle
- replay(mMockTime, mMockNMService);
- forceServicePoll();
- verify(mMockTime, mMockNMService);
- }
-
- public void testUpdatedPolicyThrottled() throws Exception {
- setThrottlePolicy(500 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
-
- expectTimeCurrent();
- expectSystemReady();
-
- // provide stats under limit, verify not throttled
- expectGetInterfaceCounter(50 * MB_IN_BYTES, 50 * MB_IN_BYTES);
- expectSetInterfaceThrottle(-1, -1);
-
- replay(mMockTime, mMockNMService);
- systemReady();
- verify(mMockTime, mMockNMService);
- reset(mMockTime, mMockNMService);
-
- expectTimeCurrent();
-
- // provide same stats, but verify that modified policy will throttle
- expectGetInterfaceCounter(50 * MB_IN_BYTES, 50 * MB_IN_BYTES);
- expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS);
-
- replay(mMockTime, mMockNMService);
-
- // now adjust policy to bump usage over limit
- setThrottlePolicy(5 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
-
- // and wait for policy updated broadcast
- mWatchingContext.nextBroadcastIntent(ThrottleManager.POLICY_CHANGED_ACTION).get();
-
- verify(mMockTime, mMockNMService);
- }
-
- public void testWithPolicyOverLimitThrottledAndRemovedAfterCycle() throws Exception {
- setThrottlePolicy(90 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY);
-
- final long baseTime = System.currentTimeMillis();
-
- expectTime(baseTime);
- expectSystemReady();
-
- // provide stats over limit, verify throttle kicks in
- expectGetInterfaceCounter(50 * MB_IN_BYTES, 50 * MB_IN_BYTES);
- expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS);
-
- replay(mMockTime, mMockNMService);
- systemReady();
- verify(mMockTime, mMockNMService);
- reset(mMockTime, mMockNMService);
-
- // pretend that time has jumped forward two months
- expectTime(baseTime + DateUtils.WEEK_IN_MILLIS * 8);
-
- // provide slightly updated stats, but verify throttle is removed
- expectGetInterfaceCounter(60 * MB_IN_BYTES, 60 * MB_IN_BYTES);
- expectSetInterfaceThrottle(-1, -1);
-
- // and kick poll event which should throttle
- replay(mMockTime, mMockNMService);
- forceServiceReset();
- verify(mMockTime, mMockNMService);
- }
-
- @Suppress
- public void testReturnStats() throws Exception {
- final IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- final INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b);
-
- // test is currently no-op, just exercises stats apis
- Log.d(TAG, nmService.getNetworkStatsSummaryDev().toString());
- Log.d(TAG, nmService.getNetworkStatsSummaryXt().toString());
- Log.d(TAG, nmService.getNetworkStatsDetail().toString());
- }
-
- /**
- * Persist the given {@link ThrottleService} policy into {@link Settings}.
- */
- public void setThrottlePolicy(long thresholdBytes, int valueKbitps, int resetDay) {
- final ContentResolver resolver = getContext().getContentResolver();
- Settings.Global.putLong(resolver, Settings.Global.THROTTLE_THRESHOLD_BYTES, thresholdBytes);
- Settings.Global.putInt(resolver, Settings.Global.THROTTLE_VALUE_KBITSPS, valueKbitps);
- Settings.Global.putInt(resolver, Settings.Global.THROTTLE_RESET_DAY, resetDay);
- }
-
- /**
- * Clear any {@link ThrottleService} policy from {@link Settings}.
- */
- public void clearThrottlePolicy() {
- final ContentResolver resolver = getContext().getContentResolver();
- Settings.Global.putString(resolver, Settings.Global.THROTTLE_THRESHOLD_BYTES, null);
- Settings.Global.putString(resolver, Settings.Global.THROTTLE_VALUE_KBITSPS, null);
- Settings.Global.putString(resolver, Settings.Global.THROTTLE_RESET_DAY, null);
- }
-
- /**
- * Expect any {@link TrustedTime} mock calls, and respond with
- * {@link System#currentTimeMillis()}.
- */
- public void expectTimeCurrent() throws Exception {
- expectTime(System.currentTimeMillis());
- }
-
- /**
- * Expect any {@link TrustedTime} mock calls, and respond with the given
- * time in response to {@link TrustedTime#currentTimeMillis()}.
- */
- public void expectTime(long currentTime) throws Exception {
- expect(mMockTime.forceRefresh()).andReturn(false).anyTimes();
- expect(mMockTime.hasCache()).andReturn(true).anyTimes();
- expect(mMockTime.currentTimeMillis()).andReturn(currentTime).anyTimes();
- expect(mMockTime.getCacheAge()).andReturn(0L).anyTimes();
- expect(mMockTime.getCacheCertainty()).andReturn(0L).anyTimes();
- }
-
- /**
- * Expect {@link ThrottleService#systemReady()} generated calls, such as
- * connecting with {@link NetworkManagementService} mock.
- */
- public void expectSystemReady() throws Exception {
- mMockNMService.registerObserver(isA(INetworkManagementEventObserver.class));
- expectLastCall().atLeastOnce();
- }
-
- /**
- * Expect {@link NetworkManagementService#getNetworkStatsSummaryDev()} mock
- * calls, responding with the given counter values.
- */
- public void expectGetInterfaceCounter(long rx, long tx) throws Exception {
- // TODO: provide elapsedRealtime mock to match TimeAuthority
- final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
- stats.addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, rx, 0L, tx, 0L, 0);
-
- expect(mMockNMService.getNetworkStatsSummaryDev()).andReturn(stats).atLeastOnce();
- }
-
- /**
- * Expect {@link NetworkManagementService#setInterfaceThrottle} mock call
- * with the specified parameters.
- */
- public void expectSetInterfaceThrottle(int rx, int tx) throws Exception {
- mMockNMService.setInterfaceThrottle(isA(String.class), eq(rx), eq(tx));
- expectLastCall().atLeastOnce();
- }
-
- /**
- * Dispatch {@link ThrottleService#systemReady()} and block until finished.
- */
- public void systemReady() throws Exception {
- final Future<Intent> policyChanged = mWatchingContext.nextBroadcastIntent(
- ThrottleManager.POLICY_CHANGED_ACTION);
- final Future<Intent> pollAction = mWatchingContext.nextBroadcastIntent(
- ThrottleManager.THROTTLE_POLL_ACTION);
-
- mThrottleService.systemReady();
-
- // wait for everything to settle; for policy to update and for first poll
- policyChanged.get();
- pollAction.get();
- }
-
- /**
- * Dispatch {@link ThrottleService#dispatchPoll()} and block until finished.
- */
- public void forceServicePoll() throws Exception {
- // during systemReady() service already pushed a sticky broadcast, so we
- // need to skip the immediate and wait for the updated sticky.
- final Future<Intent> pollAction = mWatchingContext.nextBroadcastIntent(
- ThrottleManager.THROTTLE_POLL_ACTION);
-
- mThrottleService.dispatchPoll();
-
- pollAction.get();
- }
-
- /**
- * Dispatch {@link ThrottleService#dispatchReset()} and block until finished.
- */
- public void forceServiceReset() throws Exception {
- // during systemReady() service already pushed a sticky broadcast, so we
- // need to skip the immediate and wait for the updated sticky.
- final Future<Intent> pollAction = mWatchingContext.nextBroadcastIntent(
- ThrottleManager.THROTTLE_POLL_ACTION);
-
- mThrottleService.dispatchReset();
-
- pollAction.get();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
new file mode 100644
index 0000000..8ae3824
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2009 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.accounts;
+
+import android.accounts.Account;
+import android.accounts.AuthenticatorDescription;
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.RegisteredServicesCache.ServiceInfo;
+import android.content.pm.RegisteredServicesCacheListener;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+import android.test.IsolatedContext;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.test.mock.MockPackageManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+
+public class AccountManagerServiceTest extends AndroidTestCase {
+ private AccountManagerService mAms;
+
+ @Override
+ protected void setUp() throws Exception {
+ final String filenamePrefix = "test.";
+ MockContentResolver resolver = new MockContentResolver();
+ RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(
+ new MyMockContext(), // The context that most methods are delegated to
+ getContext(), // The context that file methods are delegated to
+ filenamePrefix);
+ Context context = new IsolatedContext(resolver, targetContextWrapper);
+ setContext(context);
+ mAms = new MyAccountManagerService(getContext(),
+ new MyMockPackageManager(), new MockAccountAuthenticatorCache());
+ }
+
+ public class AccountSorter implements Comparator<Account> {
+ public int compare(Account object1, Account object2) {
+ if (object1 == object2) return 0;
+ if (object1 == null) return 1;
+ if (object2 == null) return -1;
+ int result = object1.type.compareTo(object2.type);
+ if (result != 0) return result;
+ return object1.name.compareTo(object2.name);
+ }
+ }
+
+ public void testCheckAddAccount() throws Exception {
+ Account a11 = new Account("account1", "type1");
+ Account a21 = new Account("account2", "type1");
+ Account a31 = new Account("account3", "type1");
+ Account a12 = new Account("account1", "type2");
+ Account a22 = new Account("account2", "type2");
+ Account a32 = new Account("account3", "type2");
+ mAms.addAccountExplicitly(a11, "p11", null);
+ mAms.addAccountExplicitly(a12, "p12", null);
+ mAms.addAccountExplicitly(a21, "p21", null);
+ mAms.addAccountExplicitly(a22, "p22", null);
+ mAms.addAccountExplicitly(a31, "p31", null);
+ mAms.addAccountExplicitly(a32, "p32", null);
+
+ Account[] accounts = mAms.getAccounts(null);
+ Arrays.sort(accounts, new AccountSorter());
+ assertEquals(6, accounts.length);
+ assertEquals(a11, accounts[0]);
+ assertEquals(a21, accounts[1]);
+ assertEquals(a31, accounts[2]);
+ assertEquals(a12, accounts[3]);
+ assertEquals(a22, accounts[4]);
+ assertEquals(a32, accounts[5]);
+
+ accounts = mAms.getAccounts("type1" );
+ Arrays.sort(accounts, new AccountSorter());
+ assertEquals(3, accounts.length);
+ assertEquals(a11, accounts[0]);
+ assertEquals(a21, accounts[1]);
+ assertEquals(a31, accounts[2]);
+
+ mAms.removeAccountInternal(a21);
+
+ accounts = mAms.getAccounts("type1" );
+ Arrays.sort(accounts, new AccountSorter());
+ assertEquals(2, accounts.length);
+ assertEquals(a11, accounts[0]);
+ assertEquals(a31, accounts[1]);
+ }
+
+ public void testPasswords() throws Exception {
+ Account a11 = new Account("account1", "type1");
+ Account a12 = new Account("account1", "type2");
+ mAms.addAccountExplicitly(a11, "p11", null);
+ mAms.addAccountExplicitly(a12, "p12", null);
+
+ assertEquals("p11", mAms.getPassword(a11));
+ assertEquals("p12", mAms.getPassword(a12));
+
+ mAms.setPassword(a11, "p11b");
+
+ assertEquals("p11b", mAms.getPassword(a11));
+ assertEquals("p12", mAms.getPassword(a12));
+ }
+
+ public void testUserdata() throws Exception {
+ Account a11 = new Account("account1", "type1");
+ Bundle u11 = new Bundle();
+ u11.putString("a", "a_a11");
+ u11.putString("b", "b_a11");
+ u11.putString("c", "c_a11");
+ Account a12 = new Account("account1", "type2");
+ Bundle u12 = new Bundle();
+ u12.putString("a", "a_a12");
+ u12.putString("b", "b_a12");
+ u12.putString("c", "c_a12");
+ mAms.addAccountExplicitly(a11, "p11", u11);
+ mAms.addAccountExplicitly(a12, "p12", u12);
+
+ assertEquals("a_a11", mAms.getUserData(a11, "a"));
+ assertEquals("b_a11", mAms.getUserData(a11, "b"));
+ assertEquals("c_a11", mAms.getUserData(a11, "c"));
+ assertEquals("a_a12", mAms.getUserData(a12, "a"));
+ assertEquals("b_a12", mAms.getUserData(a12, "b"));
+ assertEquals("c_a12", mAms.getUserData(a12, "c"));
+
+ mAms.setUserData(a11, "b", "b_a11b");
+ mAms.setUserData(a12, "c", null);
+
+ assertEquals("a_a11", mAms.getUserData(a11, "a"));
+ assertEquals("b_a11b", mAms.getUserData(a11, "b"));
+ assertEquals("c_a11", mAms.getUserData(a11, "c"));
+ assertEquals("a_a12", mAms.getUserData(a12, "a"));
+ assertEquals("b_a12", mAms.getUserData(a12, "b"));
+ assertNull(mAms.getUserData(a12, "c"));
+ }
+
+ public void testAuthtokens() throws Exception {
+ Account a11 = new Account("account1", "type1");
+ Account a12 = new Account("account1", "type2");
+ mAms.addAccountExplicitly(a11, "p11", null);
+ mAms.addAccountExplicitly(a12, "p12", null);
+
+ mAms.setAuthToken(a11, "att1", "a11_att1");
+ mAms.setAuthToken(a11, "att2", "a11_att2");
+ mAms.setAuthToken(a11, "att3", "a11_att3");
+ mAms.setAuthToken(a12, "att1", "a12_att1");
+ mAms.setAuthToken(a12, "att2", "a12_att2");
+ mAms.setAuthToken(a12, "att3", "a12_att3");
+
+ assertEquals("a11_att1", mAms.peekAuthToken(a11, "att1"));
+ assertEquals("a11_att2", mAms.peekAuthToken(a11, "att2"));
+ assertEquals("a11_att3", mAms.peekAuthToken(a11, "att3"));
+ assertEquals("a12_att1", mAms.peekAuthToken(a12, "att1"));
+ assertEquals("a12_att2", mAms.peekAuthToken(a12, "att2"));
+ assertEquals("a12_att3", mAms.peekAuthToken(a12, "att3"));
+
+ mAms.setAuthToken(a11, "att3", "a11_att3b");
+ mAms.invalidateAuthToken(a12.type, "a12_att2");
+
+ assertEquals("a11_att1", mAms.peekAuthToken(a11, "att1"));
+ assertEquals("a11_att2", mAms.peekAuthToken(a11, "att2"));
+ assertEquals("a11_att3b", mAms.peekAuthToken(a11, "att3"));
+ assertEquals("a12_att1", mAms.peekAuthToken(a12, "att1"));
+ assertNull(mAms.peekAuthToken(a12, "att2"));
+ assertEquals("a12_att3", mAms.peekAuthToken(a12, "att3"));
+
+ assertNull(mAms.peekAuthToken(a12, "att2"));
+ }
+
+ static public class MockAccountAuthenticatorCache implements IAccountAuthenticatorCache {
+ private ArrayList<ServiceInfo<AuthenticatorDescription>> mServices;
+
+ public MockAccountAuthenticatorCache() {
+ mServices = new ArrayList<ServiceInfo<AuthenticatorDescription>>();
+ AuthenticatorDescription d1 = new AuthenticatorDescription("type1", "p1", 0, 0, 0, 0);
+ AuthenticatorDescription d2 = new AuthenticatorDescription("type2", "p2", 0, 0, 0, 0);
+ mServices.add(new ServiceInfo<AuthenticatorDescription>(d1, null, 0));
+ mServices.add(new ServiceInfo<AuthenticatorDescription>(d2, null, 0));
+ }
+
+ @Override
+ public ServiceInfo<AuthenticatorDescription> getServiceInfo(
+ AuthenticatorDescription type, int userId) {
+ for (ServiceInfo<AuthenticatorDescription> service : mServices) {
+ if (service.type.equals(type)) {
+ return service;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<ServiceInfo<AuthenticatorDescription>> getAllServices(int userId) {
+ return mServices;
+ }
+
+ @Override
+ public void dump(
+ final FileDescriptor fd, final PrintWriter fout, final String[] args, int userId) {
+ }
+
+ @Override
+ public void setListener(
+ final RegisteredServicesCacheListener<AuthenticatorDescription> listener,
+ final Handler handler) {
+ }
+
+ @Override
+ public void invalidateCache(int userId) {
+ }
+ }
+
+ static public class MyMockContext extends MockContext {
+ @Override
+ public int checkCallingOrSelfPermission(final String permission) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+
+ static public class MyMockPackageManager extends MockPackageManager {
+ @Override
+ public int checkSignatures(final int uid1, final int uid2) {
+ return PackageManager.SIGNATURE_MATCH;
+ }
+ }
+
+ static public class MyAccountManagerService extends AccountManagerService {
+ public MyAccountManagerService(Context context, PackageManager packageManager,
+ IAccountAuthenticatorCache authenticatorCache) {
+ super(context, packageManager, authenticatorCache);
+ }
+
+ @Override
+ protected void installNotification(final int notificationId, final Notification n, UserHandle user) {
+ }
+
+ @Override
+ protected void cancelNotification(final int id, UserHandle user) {
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
new file mode 100644
index 0000000..5b70c17
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2007 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.content;
+
+import java.util.ArrayList;
+
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+
+import com.android.server.content.ContentService.ObserverCall;
+import com.android.server.content.ContentService.ObserverNode;
+
+public class ObserverNodeTest extends AndroidTestCase {
+ static class TestObserver extends ContentObserver {
+ public TestObserver() {
+ super(new Handler());
+ }
+ }
+
+ public void testUri() {
+ final int myUserHandle = UserHandle.myUserId();
+
+ ObserverNode root = new ObserverNode("");
+ Uri[] uris = new Uri[] {
+ Uri.parse("content://c/a/"),
+ Uri.parse("content://c/"),
+ Uri.parse("content://x/"),
+ Uri.parse("content://c/b/"),
+ Uri.parse("content://c/a/a1/1/"),
+ Uri.parse("content://c/a/a1/2/"),
+ Uri.parse("content://c/b/1/"),
+ Uri.parse("content://c/b/2/"),
+ };
+
+ int[] nums = new int[] {4, 7, 1, 4, 2, 2, 3, 3};
+
+ // special case
+ root.addObserverLocked(uris[0], new TestObserver().getContentObserver(), false, root,
+ 0, 0, myUserHandle);
+ for(int i = 1; i < uris.length; i++) {
+ root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), true, root,
+ 0, 0, myUserHandle);
+ }
+
+ ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
+
+ for (int i = nums.length - 1; i >=0; --i) {
+ root.collectObserversLocked(uris[i], 0, null, false, myUserHandle, calls);
+ assertEquals(nums[i], calls.size());
+ calls.clear();
+ }
+ }
+
+ public void testUriNotNotify() {
+ final int myUserHandle = UserHandle.myUserId();
+
+ ObserverNode root = new ObserverNode("");
+ Uri[] uris = new Uri[] {
+ Uri.parse("content://c/"),
+ Uri.parse("content://x/"),
+ Uri.parse("content://c/a/"),
+ Uri.parse("content://c/b/"),
+ Uri.parse("content://c/a/1/"),
+ Uri.parse("content://c/a/2/"),
+ Uri.parse("content://c/b/1/"),
+ Uri.parse("content://c/b/2/"),
+ };
+ int[] nums = new int[] {7, 1, 3, 3, 1, 1, 1, 1};
+
+ for(int i = 0; i < uris.length; i++) {
+ root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), false, root,
+ 0, 0, myUserHandle);
+ }
+
+ ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
+
+ for (int i = uris.length - 1; i >=0; --i) {
+ root.collectObserversLocked(uris[i], 0, null, false, myUserHandle, calls);
+ assertEquals(nums[i], calls.size());
+ calls.clear();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
new file mode 100644
index 0000000..f2772c8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 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;
+
+import android.accounts.Account;
+import android.os.Bundle;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.content.SyncOperation;
+
+/**
+ * You can run those tests with:
+ *
+ * adb shell am instrument
+ * -e debug false
+ * -w
+ * -e class android.content.SyncOperationTest com.android.frameworks.coretests/android.test.InstrumentationTestRunner
+ */
+
+public class SyncOperationTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testToKey() {
+ Account account1 = new Account("account1", "type1");
+ Account account2 = new Account("account2", "type2");
+
+ Bundle b1 = new Bundle();
+ Bundle b2 = new Bundle();
+ b2.putBoolean("b2", true);
+
+ SyncOperation op1 = new SyncOperation(account1, 0,
+ 1,
+ SyncOperation.REASON_PERIODIC,
+ "authority1",
+ b1,
+ 100,
+ 1000,
+ 10000,
+ false);
+
+ // Same as op1 but different time infos
+ SyncOperation op2 = new SyncOperation(account1, 0,
+ 1,
+ SyncOperation.REASON_PERIODIC,
+ "authority1",
+ b1,
+ 200,
+ 2000,
+ 20000,
+ false);
+
+ // Same as op1 but different authority
+ SyncOperation op3 = new SyncOperation(account1, 0,
+ 1,
+ SyncOperation.REASON_PERIODIC,
+ "authority2",
+ b1,
+ 100,
+ 1000,
+ 10000,
+ false);
+
+ // Same as op1 but different account
+ SyncOperation op4 = new SyncOperation(account2, 0,
+ 1,
+ SyncOperation.REASON_PERIODIC,
+ "authority1",
+ b1,
+ 100,
+ 1000,
+ 10000,
+ false);
+
+ // Same as op1 but different bundle
+ SyncOperation op5 = new SyncOperation(account1, 0,
+ 1,
+ SyncOperation.REASON_PERIODIC,
+ "authority1",
+ b2,
+ 100,
+ 1000,
+ 10000,
+ false);
+
+ assertEquals(op1.key, op2.key);
+ assertNotSame(op1.key, op3.key);
+ assertNotSame(op1.key, op4.key);
+ assertNotSame(op1.key, op5.key);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
new file mode 100644
index 0000000..8b00f2c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2007 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.content;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.PeriodicSync;
+import android.os.Bundle;
+import android.test.AndroidTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.os.AtomicFile;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.List;
+
+public class SyncStorageEngineTest extends AndroidTestCase {
+
+ private File getSyncDir() {
+ return new File(new File(getContext().getFilesDir(), "system"), "sync");
+ }
+
+ /**
+ * Test that we handle the case of a history row being old enough to purge before the
+ * correcponding sync is finished. This can happen if the clock changes while we are syncing.
+ *
+ */
+ // TODO: this test causes AidlTest to fail. Omit for now
+ // @SmallTest
+ public void testPurgeActiveSync() throws Exception {
+ final Account account = new Account("a@example.com", "example.type");
+ final String authority = "testprovider";
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+ new TestContext(mockResolver, getContext()));
+
+ long time0 = 1000;
+ long historyId = engine.insertStartSyncEvent(
+ account, 0, SyncOperation.REASON_PERIODIC, authority, time0,
+ SyncStorageEngine.SOURCE_LOCAL, false /* initialization */, null /* extras */);
+ long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
+ engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
+ }
+
+ /**
+ * Test that we can create, remove and retrieve periodic syncs
+ */
+ @MediumTest
+ public void testPeriodics() throws Exception {
+ final Account account1 = new Account("a@example.com", "example.type");
+ final Account account2 = new Account("b@example.com", "example.type.2");
+ final String authority = "testprovider";
+ final Bundle extras1 = new Bundle();
+ extras1.putString("a", "1");
+ final Bundle extras2 = new Bundle();
+ extras2.putString("a", "2");
+ final int period1 = 200;
+ final int period2 = 1000;
+
+ PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1);
+ PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1);
+ PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2);
+ PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2);
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+ new TestContext(mockResolver, getContext()));
+
+ removePeriodicSyncs(engine, account1, 0, authority);
+ removePeriodicSyncs(engine, account2, 0, authority);
+ removePeriodicSyncs(engine, account1, 1, authority);
+
+ // this should add two distinct periodic syncs for account1 and one for account2
+ engine.addPeriodicSync(sync1.account, 0, sync1.authority, sync1.extras, sync1.period);
+ engine.addPeriodicSync(sync2.account, 0, sync2.authority, sync2.extras, sync2.period);
+ engine.addPeriodicSync(sync3.account, 0, sync3.authority, sync3.extras, sync3.period);
+ engine.addPeriodicSync(sync4.account, 0, sync4.authority, sync4.extras, sync4.period);
+ // add a second user
+ engine.addPeriodicSync(sync2.account, 1, sync2.authority, sync2.extras, sync2.period);
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, 0, authority);
+
+ assertEquals(2, syncs.size());
+
+ assertEquals(sync1, syncs.get(0));
+ assertEquals(sync3, syncs.get(1));
+
+ engine.removePeriodicSync(sync1.account, 0, sync1.authority, sync1.extras);
+
+ syncs = engine.getPeriodicSyncs(account1, 0, authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account2, 0, authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync4, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(sync2.account, 1, sync2.authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync2, syncs.get(0));
+ }
+
+ private void removePeriodicSyncs(SyncStorageEngine engine, Account account, int userId,
+ String authority) {
+ engine.setIsSyncable(account, userId, authority,
+ engine.getIsSyncable(account, 0, authority));
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, userId, authority);
+ for (PeriodicSync sync : syncs) {
+ engine.removePeriodicSync(sync.account, userId, sync.authority, sync.extras);
+ }
+ }
+
+ @LargeTest
+ public void testAuthorityPersistence() throws Exception {
+ final Account account1 = new Account("a@example.com", "example.type");
+ final Account account2 = new Account("b@example.com", "example.type.2");
+ final String authority1 = "testprovider1";
+ final String authority2 = "testprovider2";
+ final Bundle extras1 = new Bundle();
+ extras1.putString("a", "1");
+ final Bundle extras2 = new Bundle();
+ extras2.putString("a", "2");
+ extras2.putLong("b", 2);
+ extras2.putInt("c", 1);
+ extras2.putBoolean("d", true);
+ extras2.putDouble("e", 1.2);
+ extras2.putFloat("f", 4.5f);
+ extras2.putParcelable("g", account1);
+ final int period1 = 200;
+ final int period2 = 1000;
+
+ PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1);
+ PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1);
+ PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1);
+ PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2);
+ PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1);
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+ new TestContext(mockResolver, getContext()));
+
+ removePeriodicSyncs(engine, account1, 0, authority1);
+ removePeriodicSyncs(engine, account2, 0, authority1);
+ removePeriodicSyncs(engine, account1, 0, authority2);
+ removePeriodicSyncs(engine, account2, 0, authority2);
+
+ engine.setMasterSyncAutomatically(false, 0);
+
+ engine.setIsSyncable(account1, 0, authority1, 1);
+ engine.setSyncAutomatically(account1, 0, authority1, true);
+
+ engine.setIsSyncable(account2, 0, authority1, 1);
+ engine.setSyncAutomatically(account2, 0, authority1, true);
+
+ engine.setIsSyncable(account1, 0, authority2, 1);
+ engine.setSyncAutomatically(account1, 0, authority2, false);
+
+ engine.setIsSyncable(account2, 0, authority2, 0);
+ engine.setSyncAutomatically(account2, 0, authority2, true);
+
+ engine.addPeriodicSync(sync1.account, 0, sync1.authority, sync1.extras, sync1.period);
+ engine.addPeriodicSync(sync2.account, 0, sync2.authority, sync2.extras, sync2.period);
+ engine.addPeriodicSync(sync3.account, 0, sync3.authority, sync3.extras, sync3.period);
+ engine.addPeriodicSync(sync4.account, 0, sync4.authority, sync4.extras, sync4.period);
+ engine.addPeriodicSync(sync5.account, 0, sync5.authority, sync5.extras, sync5.period);
+
+ engine.writeAllState();
+ engine.clearAndReadState();
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, 0, authority1);
+ assertEquals(2, syncs.size());
+ assertEquals(sync1, syncs.get(0));
+ assertEquals(sync2, syncs.get(1));
+
+ syncs = engine.getPeriodicSyncs(account1, 0, authority2);
+ assertEquals(2, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+ assertEquals(sync4, syncs.get(1));
+
+ syncs = engine.getPeriodicSyncs(account2, 0, authority1);
+ assertEquals(1, syncs.size());
+ assertEquals(sync5, syncs.get(0));
+
+ assertEquals(true, engine.getSyncAutomatically(account1, 0, authority1));
+ assertEquals(true, engine.getSyncAutomatically(account2, 0, authority1));
+ assertEquals(false, engine.getSyncAutomatically(account1, 0, authority2));
+ assertEquals(true, engine.getSyncAutomatically(account2, 0, authority2));
+
+ assertEquals(1, engine.getIsSyncable(account1, 0, authority1));
+ assertEquals(1, engine.getIsSyncable(account2, 0, authority1));
+ assertEquals(1, engine.getIsSyncable(account1, 0, authority2));
+ assertEquals(0, engine.getIsSyncable(account2, 0, authority2));
+ }
+
+ @MediumTest
+ public void testAuthorityParsing() throws Exception {
+ final Account account = new Account("account1", "type1");
+ final String authority1 = "auth1";
+ final String authority2 = "auth2";
+ final String authority3 = "auth3";
+ final Bundle extras = new Bundle();
+ PeriodicSync sync1 = new PeriodicSync(account, authority1, extras, (long) (60 * 60 * 24));
+ PeriodicSync sync2 = new PeriodicSync(account, authority2, extras, (long) (60 * 60 * 24));
+ PeriodicSync sync3 = new PeriodicSync(account, authority3, extras, (long) (60 * 60 * 24));
+ PeriodicSync sync1s = new PeriodicSync(account, authority1, extras, 1000);
+ PeriodicSync sync2s = new PeriodicSync(account, authority2, extras, 1000);
+ PeriodicSync sync3s = new PeriodicSync(account, authority3, extras, 1000);
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ final TestContext testContext = new TestContext(mockResolver, getContext());
+
+ byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts>\n"
+ + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+ + "<authority id=\"1\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
+ + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
+ + "<authority id=\"3\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
+ + "</accounts>\n").getBytes();
+
+ File syncDir = getSyncDir();
+ syncDir.mkdirs();
+ AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ FileOutputStream fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, 0, authority1);
+ assertEquals(1, syncs.size());
+ assertEquals(sync1, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority2);
+ assertEquals(1, syncs.size());
+ assertEquals(sync2, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority3);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 1, authority3);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+
+ accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts version=\"2\">\n"
+ + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+ + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
+ + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
+ + "</accounts>\n").getBytes();
+
+ accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ engine.clearAndReadState();
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority1);
+ assertEquals(0, syncs.size());
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority2);
+ assertEquals(0, syncs.size());
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority3);
+ assertEquals(0, syncs.size());
+
+ accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts version=\"2\">\n"
+ + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\">\n"
+ + "<periodicSync period=\"1000\" />\n"
+ + "</authority>"
+ + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\">\n"
+ + "<periodicSync period=\"1000\" />\n"
+ + "</authority>"
+ + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\">\n"
+ + "<periodicSync period=\"1000\" />\n"
+ + "</authority>"
+ + "</accounts>\n").getBytes();
+
+ accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ engine.clearAndReadState();
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority1);
+ assertEquals(1, syncs.size());
+ assertEquals(sync1s, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority2);
+ assertEquals(1, syncs.size());
+ assertEquals(sync2s, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority3);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3s, syncs.get(0));
+ }
+
+ @MediumTest
+ public void testListenForTicklesParsing() throws Exception {
+ byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts>\n"
+ + "<listenForTickles user=\"0\" enabled=\"false\" />"
+ + "<listenForTickles user=\"1\" enabled=\"true\" />"
+ + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+ + "<authority id=\"1\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+ + "</accounts>\n").getBytes();
+
+ MockContentResolver mockResolver = new MockContentResolver();
+ final TestContext testContext = new TestContext(mockResolver, getContext());
+
+ File syncDir = getSyncDir();
+ syncDir.mkdirs();
+ AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ FileOutputStream fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+ assertEquals(false, engine.getMasterSyncAutomatically(0));
+ assertEquals(true, engine.getMasterSyncAutomatically(1));
+ assertEquals(true, engine.getMasterSyncAutomatically(2));
+
+ }
+
+ @MediumTest
+ public void testAuthorityRenaming() throws Exception {
+ final Account account1 = new Account("acc1", "type1");
+ final Account account2 = new Account("acc2", "type2");
+ final String authorityContacts = "contacts";
+ final String authorityCalendar = "calendar";
+ final String authorityOther = "other";
+ final String authorityContactsNew = "com.android.contacts";
+ final String authorityCalendarNew = "com.android.calendar";
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ final TestContext testContext = new TestContext(mockResolver, getContext());
+
+ byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts>\n"
+ + "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n"
+ + "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n"
+ + "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n"
+ + "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n"
+ + "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n"
+ + "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n"
+ + "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+ + " authority=\"com.android.calendar\" />\n"
+ + "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+ + " authority=\"com.android.contacts\" />\n"
+ + "</accounts>\n").getBytes();
+
+ File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+ syncDir.mkdirs();
+ AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ FileOutputStream fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+ assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityContacts));
+ assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityCalendar));
+ assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityOther));
+ assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityContactsNew));
+ assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityCalendarNew));
+
+ assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContacts));
+ assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendar));
+ assertEquals(true, engine.getSyncAutomatically(account2, 0, authorityOther));
+ assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContactsNew));
+ assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendarNew));
+ }
+
+ @SmallTest
+ public void testSyncableMigration() throws Exception {
+ final Account account = new Account("acc", "type");
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ final TestContext testContext = new TestContext(mockResolver, getContext());
+
+ byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts>\n"
+ + "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n"
+ + "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n"
+ + "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\""
+ + " authority=\"other3\" />\n"
+ + "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\""
+ + " authority=\"other4\" />\n"
+ + "</accounts>\n").getBytes();
+
+ File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+ syncDir.mkdirs();
+ AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ FileOutputStream fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+ assertEquals(-1, engine.getIsSyncable(account, 0, "other1"));
+ assertEquals(1, engine.getIsSyncable(account, 0, "other2"));
+ assertEquals(0, engine.getIsSyncable(account, 0, "other3"));
+ assertEquals(1, engine.getIsSyncable(account, 0, "other4"));
+ }
+}
+
+class TestContext extends ContextWrapper {
+
+ ContentResolver mResolver;
+
+ private final Context mRealContext;
+
+ public TestContext(ContentResolver resolver, Context realContext) {
+ super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
+ mRealContext = realContext;
+ mResolver = resolver;
+ }
+
+ @Override
+ public File getFilesDir() {
+ return mRealContext.getFilesDir();
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 3cf48a0..a70ebf4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -139,7 +139,7 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
Settings settings = new Settings(getContext(), getContext().getFilesDir());
- assertEquals(true, settings.readLPw(null, 0, false));
+ assertEquals(true, settings.readLPw(null, null, 0, false));
assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_3));
assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_1));
@@ -157,11 +157,11 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
Settings settings = new Settings(getContext(), getContext().getFilesDir());
- assertEquals(true, settings.readLPw(null, 0, false));
+ assertEquals(true, settings.readLPw(null, null, 0, false));
// Create Settings again to make it read from the new files
settings = new Settings(getContext(), getContext().getFilesDir());
- assertEquals(true, settings.readLPw(null, 0, false));
+ assertEquals(true, settings.readLPw(null, null, 0, false));
PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_2);
assertEquals(COMPONENT_ENABLED_STATE_DISABLED_USER, ps.getEnabled(0));
@@ -172,12 +172,12 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
Settings settings = new Settings(getContext(), getContext().getFilesDir());
- assertEquals(true, settings.readLPw(null, 0, false));
+ assertEquals(true, settings.readLPw(null, null, 0, false));
// Enable/Disable a package
PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_1);
- ps.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0);
- ps.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 1);
+ ps.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0, null);
+ ps.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 1, null);
assertEquals(COMPONENT_ENABLED_STATE_DISABLED, ps.getEnabled(0));
assertEquals(COMPONENT_ENABLED_STATE_ENABLED, ps.getEnabled(1));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 1758d93..2e309be 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -21,8 +21,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
-import android.os.Debug;
-import android.os.Environment;
+import android.os.Bundle;
+import android.os.UserHandle;
import android.os.UserManager;
import android.test.AndroidTestCase;
@@ -65,6 +65,9 @@ public class UserManagerTest extends AndroidTestCase {
&& !user.isAdmin()
&& !user.isPrimary()) {
found = true;
+ Bundle restrictions = mUserManager.getUserRestrictions(user.getUserHandle());
+ assertFalse("New user should have DISALLOW_CONFIG_WIFI =false by default",
+ restrictions.getBoolean(UserManager.DISALLOW_CONFIG_WIFI));
}
}
assertTrue(found);
@@ -140,6 +143,20 @@ public class UserManagerTest extends AndroidTestCase {
}
}
+ public void testRestrictions() {
+ List<UserInfo> users = mUserManager.getUsers();
+ if (users.size() > 1) {
+ Bundle restrictions = new Bundle();
+ restrictions.putBoolean(UserManager.DISALLOW_INSTALL_APPS, true);
+ restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, false);
+ mUserManager.setUserRestrictions(restrictions, new UserHandle(users.get(1).id));
+ Bundle stored = mUserManager.getUserRestrictions(new UserHandle(users.get(1).id));
+ assertEquals(stored.getBoolean(UserManager.DISALLOW_CONFIG_WIFI), false);
+ assertEquals(stored.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS), false);
+ assertEquals(stored.getBoolean(UserManager.DISALLOW_INSTALL_APPS), true);
+ }
+ }
+
private void removeUser(int userId) {
synchronized (mUserLock) {
mUserManager.removeUser(userId);
@@ -151,4 +168,5 @@ public class UserManagerTest extends AndroidTestCase {
}
}
}
+
}
diff --git a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
new file mode 100644
index 0000000..79b9135
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2009 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.search;
+
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.app.SearchableInfo.ActionKeyInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.RemoteException;
+import com.android.server.search.Searchables;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.mock.MockContext;
+import android.test.mock.MockPackageManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * To launch this test from the command line:
+ *
+ * adb shell am instrument -w \
+ * -e class com.android.unit_tests.SearchablesTest \
+ * com.android.unit_tests/android.test.InstrumentationTestRunner
+ */
+@SmallTest
+public class SearchablesTest extends AndroidTestCase {
+
+ /*
+ * SearchableInfo tests
+ * Mock the context so I can provide very specific input data
+ * Confirm OK with "zero" searchables
+ * Confirm "good" metadata read properly
+ * Confirm "bad" metadata skipped properly
+ * Confirm ordering of searchables
+ * Confirm "good" actionkeys
+ * confirm "bad" actionkeys are rejected
+ * confirm XML ordering enforced (will fail today - bug in SearchableInfo)
+ * findActionKey works
+ * getIcon works
+ */
+
+ /**
+ * Test that non-searchable activities return no searchable info (this would typically
+ * trigger the use of the default searchable e.g. contacts)
+ */
+ public void testNonSearchable() {
+ // test basic array & hashmap
+ Searchables searchables = new Searchables(mContext, 0);
+ searchables.buildSearchableList();
+
+ // confirm that we return null for non-searchy activities
+ ComponentName nonActivity = new ComponentName(
+ "com.android.frameworks.coretests",
+ "com.android.frameworks.coretests.activity.NO_SEARCH_ACTIVITY");
+ SearchableInfo si = searchables.getSearchableInfo(nonActivity);
+ assertNull(si);
+ }
+
+ /**
+ * This is an attempt to run the searchable info list with a mocked context. Here are some
+ * things I'd like to test.
+ *
+ * Confirm OK with "zero" searchables
+ * Confirm "good" metadata read properly
+ * Confirm "bad" metadata skipped properly
+ * Confirm ordering of searchables
+ * Confirm "good" actionkeys
+ * confirm "bad" actionkeys are rejected
+ * confirm XML ordering enforced (will fail today - bug in SearchableInfo)
+ * findActionKey works
+ * getIcon works
+
+ */
+ public void testSearchablesListReal() {
+ MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
+ MyMockContext mockContext = new MyMockContext(mContext, mockPM);
+
+ // build item list with real-world source data
+ mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
+ Searchables searchables = new Searchables(mockContext, 0);
+ searchables.buildSearchableList();
+ // tests with "real" searchables (deprecate, this should be a unit test)
+ ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
+ int count = searchablesList.size();
+ assertTrue(count >= 1); // this isn't really a unit test
+ checkSearchables(searchablesList);
+ ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList();
+ checkSearchables(global);
+ }
+
+ /**
+ * This round of tests confirms good operations with "zero" searchables found
+ */
+ public void testSearchablesListEmpty() {
+ MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
+ MyMockContext mockContext = new MyMockContext(mContext, mockPM);
+
+ mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
+ Searchables searchables = new Searchables(mockContext, 0);
+ searchables.buildSearchableList();
+ ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
+ assertNotNull(searchablesList);
+ MoreAsserts.assertEmpty(searchablesList);
+ ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList();
+ MoreAsserts.assertEmpty(global);
+ }
+
+ /**
+ * Generic health checker for an array of searchables.
+ *
+ * This is designed to pass for any semi-legal searchable, without knowing much about
+ * the format of the underlying data. It's fairly easy for a non-compliant application
+ * to provide meta-data that will pass here (e.g. a non-existent suggestions authority).
+ *
+ * @param searchables The list of searchables to examine.
+ */
+ private void checkSearchables(ArrayList<SearchableInfo> searchablesList) {
+ assertNotNull(searchablesList);
+ int count = searchablesList.size();
+ for (int ii = 0; ii < count; ii++) {
+ SearchableInfo si = searchablesList.get(ii);
+ checkSearchable(si);
+ }
+ }
+
+ private void checkSearchable(SearchableInfo si) {
+ assertNotNull(si);
+ assertTrue(si.getLabelId() != 0); // This must be a useable string
+ assertNotEmpty(si.getSearchActivity().getClassName());
+ assertNotEmpty(si.getSearchActivity().getPackageName());
+ if (si.getSuggestAuthority() != null) {
+ // The suggestion fields are largely optional, so we'll just confirm basic health
+ assertNotEmpty(si.getSuggestAuthority());
+ assertNullOrNotEmpty(si.getSuggestPath());
+ assertNullOrNotEmpty(si.getSuggestSelection());
+ assertNullOrNotEmpty(si.getSuggestIntentAction());
+ assertNullOrNotEmpty(si.getSuggestIntentData());
+ }
+ /* Add a way to get the entire action key list, then explicitly test its elements */
+ /* For now, test the most common action key (CALL) */
+ ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL);
+ if (ai != null) {
+ assertEquals(ai.getKeyCode(), KeyEvent.KEYCODE_CALL);
+ // one of these three fields must be non-null & non-empty
+ boolean m1 = (ai.getQueryActionMsg() != null) && (ai.getQueryActionMsg().length() > 0);
+ boolean m2 = (ai.getSuggestActionMsg() != null) && (ai.getSuggestActionMsg().length() > 0);
+ boolean m3 = (ai.getSuggestActionMsgColumn() != null) &&
+ (ai.getSuggestActionMsgColumn().length() > 0);
+ assertTrue(m1 || m2 || m3);
+ }
+
+ /*
+ * Find ways to test these:
+ *
+ * private int mSearchMode
+ * private Drawable mIcon
+ */
+
+ /*
+ * Explicitly not tested here:
+ *
+ * Can be null, so not much to see:
+ * public String mSearchHint
+ * private String mZeroQueryBanner
+ *
+ * To be deprecated/removed, so don't bother:
+ * public boolean mFilterMode
+ * public boolean mQuickStart
+ * private boolean mIconResized
+ * private int mIconResizeWidth
+ * private int mIconResizeHeight
+ *
+ * All of these are "internal" working variables, not part of any contract
+ * private ActivityInfo mActivityInfo
+ * private Rect mTempRect
+ * private String mSuggestProviderPackage
+ * private String mCacheActivityContext
+ */
+ }
+
+ /**
+ * Combo assert for "string not null and not empty"
+ */
+ private void assertNotEmpty(final String s) {
+ assertNotNull(s);
+ MoreAsserts.assertNotEqual(s, "");
+ }
+
+ /**
+ * Combo assert for "string null or (not null and not empty)"
+ */
+ private void assertNullOrNotEmpty(final String s) {
+ if (s != null) {
+ MoreAsserts.assertNotEqual(s, "");
+ }
+ }
+
+ /**
+ * This is a mock for context. Used to perform a true unit test on SearchableInfo.
+ *
+ */
+ private class MyMockContext extends MockContext {
+
+ protected Context mRealContext;
+ protected PackageManager mPackageManager;
+
+ /**
+ * Constructor.
+ *
+ * @param realContext Please pass in a real context for some pass-throughs to function.
+ */
+ MyMockContext(Context realContext, PackageManager packageManager) {
+ mRealContext = realContext;
+ mPackageManager = packageManager;
+ }
+
+ /**
+ * Resources. Pass through for now.
+ */
+ @Override
+ public Resources getResources() {
+ return mRealContext.getResources();
+ }
+
+ /**
+ * Package manager. Pass through for now.
+ */
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ /**
+ * Package manager. Pass through for now.
+ */
+ @Override
+ public Context createPackageContext(String packageName, int flags)
+ throws PackageManager.NameNotFoundException {
+ return mRealContext.createPackageContext(packageName, flags);
+ }
+
+ /**
+ * Message broadcast. Pass through for now.
+ */
+ @Override
+ public void sendBroadcast(Intent intent) {
+ mRealContext.sendBroadcast(intent);
+ }
+ }
+
+/**
+ * This is a mock for package manager. Used to perform a true unit test on SearchableInfo.
+ *
+ */
+ private class MyMockPackageManager extends MockPackageManager {
+
+ public final static int SEARCHABLES_PASSTHROUGH = 0;
+ public final static int SEARCHABLES_MOCK_ZERO = 1;
+ public final static int SEARCHABLES_MOCK_ONEGOOD = 2;
+ public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3;
+
+ protected PackageManager mRealPackageManager;
+ protected int mSearchablesMode;
+
+ public MyMockPackageManager(PackageManager realPM) {
+ mRealPackageManager = realPM;
+ mSearchablesMode = SEARCHABLES_PASSTHROUGH;
+ }
+
+ /**
+ * Set the mode for various tests.
+ */
+ public void setSearchablesMode(int newMode) {
+ switch (newMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ case SEARCHABLES_MOCK_ZERO:
+ mSearchablesMode = newMode;
+ break;
+
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Find activities that support a given intent.
+ *
+ * Retrieve all activities that can be performed for the given intent.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags. The most important is
+ * MATCH_DEFAULT_ONLY, to limit the resolution to only
+ * those activities that support the CATEGORY_DEFAULT.
+ *
+ * @return A List<ResolveInfo> containing one entry for each matching
+ * Activity. These are ordered from best to worst match -- that
+ * is, the first item in the list is what is returned by
+ * resolveActivity(). If there are no matching activities, an empty
+ * list is returned.
+ */
+ @Override
+ public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+ assertNotNull(intent);
+ assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH)
+ || intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
+ || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.queryIntentActivities(intent, flags);
+ case SEARCHABLES_MOCK_ZERO:
+ return null;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public ResolveInfo resolveActivity(Intent intent, int flags) {
+ assertNotNull(intent);
+ assertTrue(intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
+ || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.resolveActivity(intent, flags);
+ case SEARCHABLES_MOCK_ZERO:
+ return null;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Retrieve an XML file from a package. This is a low-level API used to
+ * retrieve XML meta data.
+ *
+ * @param packageName The name of the package that this xml is coming from.
+ * Can not be null.
+ * @param resid The resource identifier of the desired xml. Can not be 0.
+ * @param appInfo Overall information about <var>packageName</var>. This
+ * may be null, in which case the application information will be retrieved
+ * for you if needed; if you already have this information around, it can
+ * be much more efficient to supply it here.
+ *
+ * @return Returns an XmlPullParser allowing you to parse out the XML
+ * data. Returns null if the xml resource could not be found for any
+ * reason.
+ */
+ @Override
+ public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
+ assertNotNull(packageName);
+ MoreAsserts.assertNotEqual(packageName, "");
+ MoreAsserts.assertNotEqual(resid, 0);
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.getXml(packageName, resid, appInfo);
+ case SEARCHABLES_MOCK_ZERO:
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Find a single content provider by its base path name.
+ *
+ * @param name The name of the provider to find.
+ * @param flags Additional option flags. Currently should always be 0.
+ *
+ * @return ContentProviderInfo Information about the provider, if found,
+ * else null.
+ */
+ @Override
+ public ProviderInfo resolveContentProvider(String name, int flags) {
+ assertNotNull(name);
+ MoreAsserts.assertNotEqual(name, "");
+ assertEquals(flags, 0);
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.resolveContentProvider(name, flags);
+ case SEARCHABLES_MOCK_ZERO:
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Get the activity information for a particular activity.
+ *
+ * @param name The name of the activity to find.
+ * @param flags Additional option flags.
+ *
+ * @return ActivityInfo Information about the activity, if found, else null.
+ */
+ @Override
+ public ActivityInfo getActivityInfo(ComponentName name, int flags)
+ throws NameNotFoundException {
+ assertNotNull(name);
+ MoreAsserts.assertNotEqual(name, "");
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.getActivityInfo(name, flags);
+ case SEARCHABLES_MOCK_ZERO:
+ throw new NameNotFoundException();
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public int checkPermission(String permName, String pkgName) {
+ assertNotNull(permName);
+ assertNotNull(pkgName);
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.checkPermission(permName, pkgName);
+ case SEARCHABLES_MOCK_ZERO:
+ return PackageManager.PERMISSION_DENIED;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+}
+