summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/Android.mk5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java53
-rw-r--r--services/accessibility/java/com/android/server/accessibility/GestureUtils.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java21
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java43
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java531
-rw-r--r--services/backup/java/com/android/server/backup/KeyValueBackupJob.java121
-rw-r--r--services/backup/java/com/android/server/backup/Trampoline.java12
-rw-r--r--services/core/Android.mk3
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java393
-rw-r--r--services/core/java/com/android/server/AppOpsService.java6
-rw-r--r--services/core/java/com/android/server/AssetAtlasService.java56
-rw-r--r--services/core/java/com/android/server/BatteryService.java18
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java2
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java264
-rw-r--r--services/core/java/com/android/server/EventLogTags.logtags15
-rw-r--r--services/core/java/com/android/server/GraphicsStatsService.java256
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java143
-rw-r--r--services/core/java/com/android/server/IntentResolver.java26
-rw-r--r--services/core/java/com/android/server/LocationManagerService.java5
-rw-r--r--services/core/java/com/android/server/LockSettingsService.java401
-rw-r--r--services/core/java/com/android/server/LockSettingsStorage.java117
-rw-r--r--services/core/java/com/android/server/MmsServiceBroker.java188
-rw-r--r--services/core/java/com/android/server/MountService.java2346
-rw-r--r--services/core/java/com/android/server/NativeDaemonConnector.java14
-rw-r--r--services/core/java/com/android/server/NetworkManagementService.java104
-rw-r--r--services/core/java/com/android/server/NetworkTimeUpdateService.java1
-rw-r--r--services/core/java/com/android/server/NsdService.java1
-rw-r--r--services/core/java/com/android/server/PersistentDataBlockService.java47
-rw-r--r--services/core/java/com/android/server/SystemConfig.java18
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java155
-rw-r--r--services/core/java/com/android/server/TextServicesManagerService.java5
-rw-r--r--services/core/java/com/android/server/TwilightCalculator.java15
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java77
-rw-r--r--services/core/java/com/android/server/Watchdog.java21
-rw-r--r--services/core/java/com/android/server/WiredAccessoryManager.java14
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java117
-rwxr-xr-xservices/core/java/com/android/server/am/ActiveServices.java278
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerDebugConfig.java109
-rw-r--r--[-rwxr-xr-x]services/core/java/com/android/server/am/ActivityManagerService.java3272
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java335
-rw-r--r--[-rwxr-xr-x]services/core/java/com/android/server/am/ActivityStack.java892
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java1085
-rw-r--r--services/core/java/com/android/server/am/AppBindRecord.java1
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java363
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java216
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java5
-rw-r--r--services/core/java/com/android/server/am/CompatModePackages.java8
-rw-r--r--services/core/java/com/android/server/am/CoreSettingsObserver.java3
-rw-r--r--services/core/java/com/android/server/am/DumpHeapProvider.java89
-rw-r--r--services/core/java/com/android/server/am/EventLogTags.logtags8
-rw-r--r--services/core/java/com/android/server/am/LockTaskNotify.java22
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java80
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java27
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java35
-rw-r--r--services/core/java/com/android/server/am/ProcessStatsService.java32
-rw-r--r--services/core/java/com/android/server/am/RecentTasks.java579
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java13
-rw-r--r--services/core/java/com/android/server/am/TaskPersister.java22
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java171
-rw-r--r--services/core/java/com/android/server/am/UriPermission.java1
-rw-r--r--services/core/java/com/android/server/am/UriPermissionOwner.java1
-rw-r--r--services/core/java/com/android/server/am/UserSwitchingDialog.java42
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java5960
-rw-r--r--services/core/java/com/android/server/audio/FocusRequester.java333
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java2228
-rw-r--r--services/core/java/com/android/server/audio/PlayerRecord.java360
-rw-r--r--services/core/java/com/android/server/camera/CameraService.java66
-rw-r--r--services/core/java/com/android/server/connectivity/Nat464Xlat.java3
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkAgentInfo.java22
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkMonitor.java4
-rw-r--r--services/core/java/com/android/server/connectivity/Tethering.java4
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java25
-rw-r--r--services/core/java/com/android/server/content/AppIdleMonitor.java89
-rw-r--r--services/core/java/com/android/server/content/ContentService.java1
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java99
-rw-r--r--services/core/java/com/android/server/content/SyncOperation.java3
-rw-r--r--services/core/java/com/android/server/content/SyncStorageEngine.java11
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java56
-rw-r--r--services/core/java/com/android/server/display/ColorFade.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayBlanker.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java8
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceInfo.java63
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java99
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java19
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerState.java44
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java97
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java37
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayAdapter.java2
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayWindow.java2
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java2
-rw-r--r--services/core/java/com/android/server/display/WifiDisplayAdapter.java2
-rw-r--r--services/core/java/com/android/server/display/WifiDisplayController.java2
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java4
-rw-r--r--services/core/java/com/android/server/fingerprint/FingerprintService.java714
-rw-r--r--services/core/java/com/android/server/firewall/SenderFilter.java3
-rw-r--r--services/core/java/com/android/server/hdmi/ActiveSourceHandler.java4
-rw-r--r--services/core/java/com/android/server/hdmi/Constants.java4
-rw-r--r--services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java19
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java69
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java101
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java6
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java1
-rw-r--r--services/core/java/com/android/server/hdmi/HotplugDetectionAction.java14
-rw-r--r--services/core/java/com/android/server/hdmi/RequestArcAction.java13
-rw-r--r--services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java6
-rw-r--r--services/core/java/com/android/server/hdmi/RoutingControlAction.java14
-rw-r--r--services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java24
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java2
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java70
-rw-r--r--services/core/java/com/android/server/job/JobServiceContext.java5
-rw-r--r--services/core/java/com/android/server/job/controllers/AppIdleController.java175
-rw-r--r--services/core/java/com/android/server/job/controllers/BatteryController.java54
-rw-r--r--services/core/java/com/android/server/job/controllers/IdleController.java1
-rw-r--r--services/core/java/com/android/server/job/controllers/JobStatus.java9
-rw-r--r--services/core/java/com/android/server/job/controllers/StateController.java9
-rw-r--r--services/core/java/com/android/server/job/controllers/TimeController.java6
-rw-r--r--services/core/java/com/android/server/lights/LightsService.java51
-rw-r--r--services/core/java/com/android/server/location/FlpHardwareProvider.java72
-rw-r--r--services/core/java/com/android/server/location/FusedLocationHardwareSecure.java12
-rw-r--r--services/core/java/com/android/server/location/GpsLocationProvider.java238
-rw-r--r--services/core/java/com/android/server/location/GpsMeasurementsProvider.java29
-rw-r--r--services/core/java/com/android/server/location/GpsNavigationMessageProvider.java28
-rw-r--r--services/core/java/com/android/server/location/GpsStatusListenerHelper.java16
-rw-r--r--services/core/java/com/android/server/location/GpsXtraDownloader.java86
-rw-r--r--services/core/java/com/android/server/location/RemoteListenerHelper.java80
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java103
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java118
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java5
-rw-r--r--services/core/java/com/android/server/net/IpConfigStore.java6
-rw-r--r--services/core/java/com/android/server/net/LockdownVpnTracker.java2
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java100
-rw-r--r--services/core/java/com/android/server/net/NetworkStatsCollection.java54
-rw-r--r--services/core/java/com/android/server/net/NetworkStatsRecorder.java10
-rw-r--r--services/core/java/com/android/server/net/NetworkStatsService.java123
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java426
-rw-r--r--services/core/java/com/android/server/notification/CountdownConditionProvider.java33
-rw-r--r--services/core/java/com/android/server/notification/DowntimeConditionProvider.java409
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java83
-rw-r--r--services/core/java/com/android/server/notification/NextAlarmConditionProvider.java224
-rw-r--r--services/core/java/com/android/server/notification/NextAlarmTracker.java263
-rw-r--r--services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java9
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java313
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java15
-rw-r--r--services/core/java/com/android/server/notification/NotificationUsageStats.java11
-rw-r--r--services/core/java/com/android/server/notification/RankingConfig.java4
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java231
-rw-r--r--services/core/java/com/android/server/notification/ScheduleCalendar.java (renamed from services/core/java/com/android/server/notification/DowntimeCalendar.java)58
-rw-r--r--services/core/java/com/android/server/notification/ScheduleConditionProvider.java238
-rw-r--r--services/core/java/com/android/server/notification/SystemConditionProviderService.java36
-rw-r--r--services/core/java/com/android/server/notification/ValidateNotificationPeople.java9
-rw-r--r--services/core/java/com/android/server/notification/ZenLog.java26
-rw-r--r--services/core/java/com/android/server/notification/ZenModeConditions.java188
-rw-r--r--services/core/java/com/android/server/notification/ZenModeFiltering.java279
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java672
-rw-r--r--services/core/java/com/android/server/pm/BasePermission.java36
-rw-r--r--services/core/java/com/android/server/pm/CrossProfileIntentFilter.java14
-rw-r--r--services/core/java/com/android/server/pm/CrossProfileIntentResolver.java1
-rw-r--r--services/core/java/com/android/server/pm/GrantedPermissions.java50
-rw-r--r--services/core/java/com/android/server/pm/Installer.java174
-rw-r--r--services/core/java/com/android/server/pm/InstructionSets.java124
-rw-r--r--services/core/java/com/android/server/pm/IntentFilterVerificationKey.java63
-rw-r--r--services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java43
-rw-r--r--services/core/java/com/android/server/pm/IntentFilterVerificationState.java125
-rw-r--r--services/core/java/com/android/server/pm/KeySetHandle.java45
-rw-r--r--services/core/java/com/android/server/pm/KeySetManagerService.java516
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java247
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java225
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java25
-rw-r--r--services/core/java/com/android/server/pm/PackageKeySetData.java82
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java2928
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java24
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java68
-rw-r--r--services/core/java/com/android/server/pm/PendingPackage.java4
-rw-r--r--services/core/java/com/android/server/pm/PermissionsState.java542
-rw-r--r--services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java2
-rw-r--r--services/core/java/com/android/server/pm/SELinuxMMAC.java860
-rw-r--r--services/core/java/com/android/server/pm/SettingBase.java74
-rw-r--r--services/core/java/com/android/server/pm/Settings.java1160
-rw-r--r--services/core/java/com/android/server/pm/SharedUserSetting.java16
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java345
-rw-r--r--services/core/java/com/android/server/policy/BarController.java284
-rw-r--r--services/core/java/com/android/server/policy/BurnInProtectionHelper.java282
-rw-r--r--services/core/java/com/android/server/policy/EnableAccessibilityController.java283
-rw-r--r--services/core/java/com/android/server/policy/GlobalActions.java1267
-rw-r--r--services/core/java/com/android/server/policy/GlobalKeyManager.java145
-rw-r--r--services/core/java/com/android/server/policy/IconUtilities.java190
-rw-r--r--services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java349
-rw-r--r--services/core/java/com/android/server/policy/LogDecelerateInterpolator.java42
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java6602
-rw-r--r--services/core/java/com/android/server/policy/PolicyControl.java258
-rw-r--r--services/core/java/com/android/server/policy/RecentApplicationsBackground.java157
-rw-r--r--services/core/java/com/android/server/policy/ShortcutManager.java177
-rw-r--r--services/core/java/com/android/server/policy/StatusBarController.java186
-rw-r--r--services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java201
-rw-r--r--services/core/java/com/android/server/policy/WakeGestureListener.java100
-rw-r--r--services/core/java/com/android/server/policy/WindowOrientationListener.java847
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java338
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java206
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java86
-rw-r--r--services/core/java/com/android/server/power/DeviceIdleController.java489
-rw-r--r--services/core/java/com/android/server/power/Notifier.java38
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java66
-rw-r--r--services/core/java/com/android/server/power/ShutdownThread.java6
-rw-r--r--services/core/java/com/android/server/search/SearchManagerService.java6
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java37
-rw-r--r--services/core/java/com/android/server/storage/DeviceStorageMonitorService.java11
-rw-r--r--services/core/java/com/android/server/trust/TrustAgentWrapper.java29
-rw-r--r--services/core/java/com/android/server/trust/TrustArchive.java43
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java30
-rw-r--r--services/core/java/com/android/server/tv/TvInputHardwareManager.java35
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java178
-rw-r--r--services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java58
-rw-r--r--services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java82
-rw-r--r--services/core/java/com/android/server/updates/TZInfoInstallReceiver.java33
-rw-r--r--services/core/java/com/android/server/updates/TzDataInstallReceiver.java54
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java74
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateService.java21
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java33
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java208
-rw-r--r--services/core/java/com/android/server/wm/AppWindowAnimator.java27
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java33
-rw-r--r--services/core/java/com/android/server/wm/CircularDisplayMask.java8
-rw-r--r--services/core/java/com/android/server/wm/DimLayer.java39
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java34
-rw-r--r--services/core/java/com/android/server/wm/DragState.java8
-rw-r--r--services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java5
-rw-r--r--services/core/java/com/android/server/wm/FocusedStackFrame.java124
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java23
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java10
-rw-r--r--services/core/java/com/android/server/wm/Session.java24
-rw-r--r--services/core/java/com/android/server/wm/StackTapPointerEventListener.java28
-rw-r--r--services/core/java/com/android/server/wm/Task.java66
-rw-r--r--services/core/java/com/android/server/wm/TaskStack.java191
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java98
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java1481
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java209
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java81
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java3
-rw-r--r--services/core/jni/Android.mk13
-rw-r--r--services/core/jni/com_android_server_AlarmManagerService.cpp82
-rw-r--r--services/core/jni/com_android_server_AssetAtlasService.cpp58
-rw-r--r--services/core/jni/com_android_server_ConsumerIrService.cpp6
-rw-r--r--services/core/jni/com_android_server_SerialService.cpp2
-rw-r--r--services/core/jni/com_android_server_SystemServer.cpp2
-rw-r--r--services/core/jni/com_android_server_UsbDeviceManager.cpp20
-rw-r--r--services/core/jni/com_android_server_UsbHostManager.cpp5
-rw-r--r--services/core/jni/com_android_server_UsbMidiDevice.cpp132
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp6
-rw-r--r--services/core/jni/com_android_server_connectivity_Vpn.cpp14
-rw-r--r--services/core/jni/com_android_server_fingerprint_FingerprintService.cpp265
-rw-r--r--services/core/jni/com_android_server_hdmi_HdmiCecController.cpp1
-rw-r--r--services/core/jni/com_android_server_input_InputApplicationHandle.cpp1
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp77
-rw-r--r--services/core/jni/com_android_server_input_InputWindowHandle.cpp1
-rw-r--r--services/core/jni/com_android_server_lights_LightsService.cpp6
-rw-r--r--services/core/jni/com_android_server_location_FlpHardwareProvider.cpp123
-rw-r--r--services/core/jni/com_android_server_location_GpsLocationProvider.cpp134
-rw-r--r--services/core/jni/com_android_server_power_PowerManagerService.cpp9
-rw-r--r--services/core/jni/com_android_server_tv_TvInputHal.cpp1
-rw-r--r--services/core/jni/onload.cpp4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java138
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java1539
-rw-r--r--services/java/com/android/server/SystemServer.java142
-rw-r--r--services/midi/Android.mk12
-rw-r--r--services/midi/java/com/android/server/midi/MidiService.java674
-rw-r--r--services/net/java/android/net/dhcp/DhcpClient.java4
-rw-r--r--services/net/java/android/net/dhcp/DhcpDeclinePacket.java5
-rw-r--r--services/net/java/android/net/dhcp/DhcpDiscoverPacket.java1
-rw-r--r--services/net/java/android/net/dhcp/DhcpInformPacket.java9
-rw-r--r--services/net/java/android/net/dhcp/DhcpPacket.java10
-rw-r--r--services/net/java/android/net/dhcp/DhcpRequestPacket.java8
-rw-r--r--services/print/java/com/android/server/print/PrintManagerService.java2
-rw-r--r--services/print/java/com/android/server/print/UserState.java4
-rw-r--r--services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java44
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java29
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java36
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java102
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java111
-rw-r--r--services/usage/java/com/android/server/usage/IntervalStats.java17
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsDatabase.java75
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java154
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsXmlV1.java2
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java53
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaManager.java532
-rw-r--r--services/usb/java/com/android/server/usb/UsbAudioDevice.java66
-rw-r--r--services/usb/java/com/android/server/usb/UsbAudioManager.java197
-rw-r--r--services/usb/java/com/android/server/usb/UsbDebuggingManager.java201
-rw-r--r--services/usb/java/com/android/server/usb/UsbDeviceManager.java69
-rw-r--r--services/usb/java/com/android/server/usb/UsbHostManager.java14
-rw-r--r--services/usb/java/com/android/server/usb/UsbMidiDevice.java223
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java10
-rw-r--r--services/usb/java/com/android/server/usb/UsbSettingsManager.java1
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java167
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java137
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java473
302 files changed, 47079 insertions, 11983 deletions
diff --git a/services/Android.mk b/services/Android.mk
index e4b0cbb..1918db5 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -24,6 +24,7 @@ services := \
appwidget \
backup \
devicepolicy \
+ midi \
net \
print \
restrictions \
@@ -49,10 +50,6 @@ include $(wildcard $(LOCAL_PATH)/*/jni/Android.mk)
LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
-ifeq ($(WITH_MALLOC_LEAK_CHECK),true)
- LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
-endif
-
LOCAL_MODULE:= libandroid_servers
include $(BUILD_SHARED_LIBRARY)
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 93c65f3..82a77d2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -352,6 +352,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
intentFilter.addAction(Intent.ACTION_USER_PRESENT);
+ intentFilter.addAction(Intent.ACTION_SETTING_RESTORED);
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
@@ -369,6 +370,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
onUserStateChangedLocked(userState);
}
}
+ } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
+ final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+ if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) {
+ synchronized (mLock) {
+ restoreEnabledAccessibilityServicesLocked(
+ intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE),
+ intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE));
+ }
+ }
}
}
}, UserHandle.ALL, intentFilter, null, null);
@@ -857,6 +867,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ // Called only during settings restore; currently supports only the owner user
+ void restoreEnabledAccessibilityServicesLocked(String oldSetting, String newSetting) {
+ readComponentNamesFromStringLocked(oldSetting, mTempComponentNameSet, false);
+ readComponentNamesFromStringLocked(newSetting, mTempComponentNameSet, true);
+
+ UserState userState = getUserStateLocked(UserHandle.USER_OWNER);
+ userState.mEnabledServices.clear();
+ userState.mEnabledServices.addAll(mTempComponentNameSet);
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices,
+ UserHandle.USER_OWNER);
+ onUserStateChangedLocked(userState);
+ }
+
private InteractionBridge getInteractionBridgeLocked() {
if (mInteractionBridge == null) {
mInteractionBridge = new InteractionBridge();
@@ -944,7 +969,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
new Intent(AccessibilityService.SERVICE_INTERFACE),
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ PackageManager.GET_SERVICES
+ | PackageManager.GET_META_DATA
+ | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
mCurrentUserId);
for (int i = 0, count = installedServices.size(); i < count; i++) {
@@ -1127,10 +1154,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
Set<ComponentName> outComponentNames) {
String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
settingName, userId);
- outComponentNames.clear();
- if (settingValue != null) {
+ readComponentNamesFromStringLocked(settingValue, outComponentNames, false);
+ }
+
+ /**
+ * Populates a set with the {@link ComponentName}s contained in a colon-delimited string.
+ *
+ * @param names The colon-delimited string to parse.
+ * @param outComponentNames The set of component names to be populated based on
+ * the contents of the <code>names</code> string.
+ * @param doMerge If true, the parsed component names will be merged into the output
+ * set, rather than replacing the set's existing contents entirely.
+ */
+ private void readComponentNamesFromStringLocked(String names,
+ Set<ComponentName> outComponentNames,
+ boolean doMerge) {
+ if (!doMerge) {
+ outComponentNames.clear();
+ }
+ if (names != null) {
TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
- splitter.setString(settingValue);
+ splitter.setString(names);
while (splitter.hasNext()) {
String str = splitter.next();
if (str == null || str.length() <= 0) {
@@ -3110,6 +3154,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
case WindowManager.LayoutParams.TYPE_APPLICATION_STARTING:
case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL:
case WindowManager.LayoutParams.TYPE_BASE_APPLICATION:
case WindowManager.LayoutParams.TYPE_PHONE:
case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
diff --git a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
index b68b09f..bc76191 100644
--- a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java
@@ -69,8 +69,7 @@ final class GestureUtils {
return true;
}
- final float firstMagnitude =
- (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY);
+ final float firstMagnitude = (float) Math.hypot(firstDeltaX, firstDeltaY);
final float firstXNormalized =
(firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX;
final float firstYNormalized =
@@ -83,8 +82,7 @@ final class GestureUtils {
return true;
}
- final float secondMagnitude =
- (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY);
+ final float secondMagnitude = (float) Math.hypot(secondDeltaX, secondDeltaY);
final float secondXNormalized =
(secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX;
final float secondYNormalized =
diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
index c8b080e..b4613d6 100644
--- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -34,6 +34,7 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Property;
import android.util.Slog;
+import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MagnificationSpec;
@@ -110,7 +111,6 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
private static final int STATE_MAGNIFIED_INTERACTION = 4;
private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
- private static final int MULTI_TAP_TIME_SLOP_ADJUSTMENT = 50;
private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
@@ -135,9 +135,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
private final AccessibilityManagerService mAms;
- private final int mTapTimeSlop = ViewConfiguration.getTapTimeout();
- private final int mMultiTapTimeSlop =
- ViewConfiguration.getDoubleTapTimeout() - MULTI_TAP_TIME_SLOP_ADJUSTMENT;
+ private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout();
+ private final int mMultiTapTimeSlop;
private final int mTapDistanceSlop;
private final int mMultiTapDistanceSlop;
@@ -192,6 +191,9 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
mAms = service;
+ mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout()
+ + mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
mLongAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_longAnimTime);
mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
@@ -481,15 +483,20 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
private static final float MIN_SCALE = 1.3f;
private static final float MAX_SCALE = 5.0f;
- private static final float SCALING_THRESHOLD = 0.3f;
-
private final ScaleGestureDetector mScaleGestureDetector;
private final GestureDetector mGestureDetector;
+ private final float mScalingThreshold;
+
private float mInitialScaleFactor = -1;
private boolean mScaling;
public MagnifiedContentInteractonStateHandler(Context context) {
+ final TypedValue scaleValue = new TypedValue();
+ context.getResources().getValue(
+ com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
+ scaleValue, false);
+ mScalingThreshold = scaleValue.getFloat();
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mScaleGestureDetector.setQuickScaleEnabled(false);
mGestureDetector = new GestureDetector(context, this);
@@ -537,7 +544,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio
mInitialScaleFactor = detector.getScaleFactor();
} else {
final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
- if (Math.abs(deltaScale) > SCALING_THRESHOLD) {
+ if (Math.abs(deltaScale) > mScalingThreshold) {
mScaling = true;
return true;
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 7623514..f42aef1 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -674,7 +674,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
@Override
public IntentSender createAppWidgetConfigIntentSender(String callingPackage, int appWidgetId,
- int intentFlags) {
+ final int intentFlags) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG) {
@@ -701,18 +701,21 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
throw new IllegalArgumentException("Widget not bound " + appWidgetId);
}
+ // Make sure only safe flags can be passed it.
+ final int secureFlags = intentFlags & ~Intent.IMMUTABLE_FLAGS;
+
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setComponent(provider.info.configure);
- intent.setFlags(intentFlags);
+ intent.setFlags(secureFlags);
// All right, create the sender.
final long identity = Binder.clearCallingIdentity();
try {
return PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT, null,
- new UserHandle(provider.getUserId()))
+ | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
+ null, new UserHandle(provider.getUserId()))
.getIntentSender();
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2837,10 +2840,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
return providersUpdated;
}
- private boolean removeHostsAndProvidersForPackageLocked(String pkgName, int userId) {
+ private boolean removeProvidersForPackageLocked(String pkgName, int userId) {
boolean removed = false;
- int N = mProviders.size();
+ final int N = mProviders.size();
for (int i = N - 1; i >= 0; i--) {
Provider provider = mProviders.get(i);
if (pkgName.equals(provider.info.provider.getPackageName())
@@ -2849,11 +2852,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
removed = true;
}
}
+ return removed;
+ }
+
+ private boolean removeHostsAndProvidersForPackageLocked(String pkgName, int userId) {
+ boolean removed = removeProvidersForPackageLocked(pkgName, userId);
// Delete the hosts for this package too
// By now, we have removed any AppWidgets that were in any hosts here,
// so we don't need to worry about sending DISABLE broadcasts to them.
- N = mHosts.size();
+ final int N = mHosts.size();
for (int i = N - 1; i >= 0; i--) {
Host host = mHosts.get(i);
if (pkgName.equals(host.id.packageName)
@@ -2925,13 +2933,30 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
synchronized (mLock) {
boolean providersChanged = false;
+ ArraySet<String> previousPackages = new ArraySet<String>();
+ final int providerCount = mProviders.size();
+ for (int i = 0; i < providerCount; ++i) {
+ Provider provider = mProviders.get(i);
+ if (provider.getUserId() == userId) {
+ previousPackages.add(provider.id.componentName.getPackageName());
+ }
+ }
+
final int packageCount = packages.size();
for (int i = 0; i < packageCount; i++) {
String packageName = packages.get(i);
+ previousPackages.remove(packageName);
providersChanged |= updateProvidersForPackageLocked(packageName,
userId, null);
}
+ // Some packages are no longer whitelisted.
+ final int removedCount = previousPackages.size();
+ for (int i = 0; i < removedCount; ++i) {
+ providersChanged |= removeProvidersForPackageLocked(
+ previousPackages.valueAt(i), userId);
+ }
+
if (providersChanged) {
saveGroupStateAsync(userId);
scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
@@ -3142,10 +3167,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
if (parentId != callerId) {
return false;
}
- return isProviderWhitelListed(packageName, profileId);
+ return isProviderWhiteListed(packageName, profileId);
}
- public boolean isProviderWhitelListed(String packageName, int profileId) {
+ public boolean isProviderWhiteListed(String packageName, int profileId) {
DevicePolicyManagerInternal devicePolicyManager = LocalServices.getService(
DevicePolicyManagerInternal.class);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index c1e4994..1bed4f3 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -22,12 +22,14 @@ import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.IApplicationThread;
import android.app.IBackupAgent;
+import android.app.PackageInstallObserver;
import android.app.PendingIntent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupTransport;
import android.app.backup.FullBackup;
+import android.app.backup.FullBackupDataOutput;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.app.backup.IBackupManager;
@@ -45,7 +47,6 @@ import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -93,6 +94,7 @@ import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.SystemService;
import com.android.server.backup.PackageManagerBackupAgent.Metadata;
+import com.android.server.pm.PackageManagerService;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -134,6 +136,7 @@ import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.Deflater;
@@ -157,9 +160,9 @@ import libcore.io.IoUtils;
public class BackupManagerService {
private static final String TAG = "BackupManagerService";
- private static final boolean DEBUG = true;
- private static final boolean MORE_DEBUG = false;
- private static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;
+ static final boolean DEBUG = true;
+ static final boolean MORE_DEBUG = false;
+ static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true;
// System-private key used for backing up an app's widget state. Must
// begin with U+FFxx by convention (we reserve all keys starting
@@ -195,23 +198,11 @@ public class BackupManagerService {
static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
- // How often we perform a backup pass. Privileged external callers can
- // trigger an immediate pass.
- private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR;
-
- // Random variation in backup scheduling time to avoid server load spikes
- private static final int FUZZ_MILLIS = 5 * 60 * 1000;
-
- // The amount of time between the initial provisioning of the device and
- // the first backup pass.
- private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR;
-
// Retry interval for clear/init when the transport is unavailable
private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
private static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
- private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR";
private static final int MSG_RUN_BACKUP = 1;
private static final int MSG_RUN_ADB_BACKUP = 2;
private static final int MSG_RUN_RESTORE = 3;
@@ -299,7 +290,6 @@ public class BackupManagerService {
volatile boolean mBackupRunning;
volatile boolean mConnecting;
volatile long mLastBackupPass;
- volatile long mNextBackupPass;
// For debugging, we maintain a progress trace of operations during backup
static final boolean DEBUG_BACKUP_TRACE = true;
@@ -381,8 +371,8 @@ public class BackupManagerService {
if (mProvisioned && !wasProvisioned && mEnabled) {
// we're now good to go, so start the backup alarms
if (MORE_DEBUG) Slog.d(TAG, "Now provisioned, so starting backups");
- startBackupAlarmsLocked(FIRST_BACKUP_INTERVAL);
- scheduleNextFullBackupJob();
+ KeyValueBackupJob.schedule(mContext);
+ scheduleNextFullBackupJob(0);
}
}
}
@@ -613,7 +603,7 @@ public class BackupManagerService {
return token;
}
- // High level policy: apps are ineligible for backup if certain conditions apply
+ // High level policy: apps are generally ineligible for backup if certain conditions apply
public static boolean appIsEligibleForBackup(ApplicationInfo app) {
// 1. their manifest states android:allowBackup="false"
if ((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
@@ -640,7 +630,7 @@ public class BackupManagerService {
return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0;
}
- // No agent means we do full backups for it
+ // No agent or fullBackupOnly="true" means we do indeed perform full-data backups for it
return true;
}
@@ -657,7 +647,6 @@ public class BackupManagerService {
case MSG_RUN_BACKUP:
{
mLastBackupPass = System.currentTimeMillis();
- mNextBackupPass = mLastBackupPass + BACKUP_INTERVAL;
IBackupTransport transport = getTransport(mCurrentTransport);
if (transport == null) {
@@ -740,7 +729,7 @@ public class BackupManagerService {
{
try {
BackupRestoreTask task = (BackupRestoreTask) msg.obj;
- task.operationComplete();
+ task.operationComplete(msg.arg1);
} catch (ClassCastException e) {
Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj);
}
@@ -1211,11 +1200,13 @@ public class BackupManagerService {
temp = new RandomAccessFile(tempProcessedFile, "rws");
in = new RandomAccessFile(mEverStored, "r");
+ // Loop until we hit EOF
while (true) {
- PackageInfo info;
String pkg = in.readUTF();
try {
- info = mPackageManager.getPackageInfo(pkg, 0);
+ // is this package still present?
+ mPackageManager.getPackageInfo(pkg, 0);
+ // if we get here then yes it is; remember it
mEverStoredApps.add(pkg);
temp.writeUTF(pkg);
if (MORE_DEBUG) Slog.v(TAG, " + " + pkg);
@@ -1279,7 +1270,23 @@ public class BackupManagerService {
for (int i = 0; i < N; i++) {
String pkgName = in.readUTF();
long lastBackup = in.readLong();
- schedule.add(new FullBackupEntry(pkgName, lastBackup));
+ try {
+ PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
+ if (appGetsFullBackup(pkg)
+ && appIsEligibleForBackup(pkg.applicationInfo)) {
+ schedule.add(new FullBackupEntry(pkgName, lastBackup));
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkgName
+ + " no longer eligible for full backup");
+ }
+ }
+ } catch (NameNotFoundException e) {
+ if (DEBUG) {
+ Slog.i(TAG, "Package " + pkgName
+ + " not installed; dropping from full backup");
+ }
+ }
}
Collections.sort(schedule);
} catch (Exception e) {
@@ -1302,7 +1309,7 @@ public class BackupManagerService {
schedule = new ArrayList<FullBackupEntry>(N);
for (int i = 0; i < N; i++) {
PackageInfo info = apps.get(i);
- if (appGetsFullBackup(info)) {
+ if (appGetsFullBackup(info) && appIsEligibleForBackup(info.applicationInfo)) {
schedule.add(new FullBackupEntry(info.packageName, 0));
}
}
@@ -1774,13 +1781,13 @@ public class BackupManagerService {
addPackageParticipantsLocked(pkgList);
}
// If they're full-backup candidates, add them there instead
+ final long now = System.currentTimeMillis();
for (String packageName : pkgList) {
try {
PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
- long now = System.currentTimeMillis();
- if (appGetsFullBackup(app)) {
+ if (appGetsFullBackup(app) && appIsEligibleForBackup(app.applicationInfo)) {
enqueueFullBackup(packageName, now);
- scheduleNextFullBackupJob();
+ scheduleNextFullBackupJob(0);
}
// Transport maintenance: rebind to known existing transports that have
@@ -1865,7 +1872,8 @@ public class BackupManagerService {
boolean tryBindTransport(ServiceInfo info) {
try {
PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0);
- if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+ if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+ != 0) {
return bindTransport(info);
} else {
Slog.w(TAG, "Transport package " + info.packageName + " not privileged");
@@ -2201,7 +2209,10 @@ public class BackupManagerService {
// Get the restore-set token for the best-available restore set for this package:
// the active set if possible, else the ancestral one. Returns zero if none available.
- long getAvailableRestoreToken(String packageName) {
+ public long getAvailableRestoreToken(String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getAvailableRestoreToken");
+
long token = mAncestralToken;
synchronized (mQueueLock) {
if (mEverStoredApps.contains(packageName)) {
@@ -2219,7 +2230,7 @@ public class BackupManagerService {
void execute();
// An operation that wanted a callback has completed
- void operationComplete();
+ void operationComplete(int result);
// An operation that wanted a callback has timed out
void handleTimeout();
@@ -2474,7 +2485,7 @@ public class BackupManagerService {
BackupRequest request = mQueue.get(0);
mQueue.remove(0);
- Slog.d(TAG, "starting agent for backup of " + request);
+ Slog.d(TAG, "starting key/value backup of " + request);
addBackupTrace("launch agent for " + request.packageName);
// Verify that the requested app exists; it might be something that
@@ -2485,13 +2496,24 @@ public class BackupManagerService {
try {
mCurrentPackage = mPackageManager.getPackageInfo(request.packageName,
PackageManager.GET_SIGNATURES);
- if (mCurrentPackage.applicationInfo.backupAgentName == null) {
+ if (!appIsEligibleForBackup(mCurrentPackage.applicationInfo)) {
// The manifest has changed but we had a stale backup request pending.
// This won't happen again because the app won't be requesting further
// backups.
Slog.i(TAG, "Package " + request.packageName
+ " no longer supports backup; skipping");
- addBackupTrace("skipping - no agent, completion is noop");
+ addBackupTrace("skipping - not eligible, completion is noop");
+ executeNextState(BackupState.RUNNING_QUEUE);
+ return;
+ }
+
+ if (appGetsFullBackup(mCurrentPackage)) {
+ // It's possible that this app *formerly* was enqueued for key/value backup,
+ // but has since been updated and now only supports the full-data path.
+ // Don't proceed with a key/value backup for it in this case.
+ Slog.i(TAG, "Package " + request.packageName
+ + " requests full-data rather than key/value; skipping");
+ addBackupTrace("skipping - fullBackupOnly, completion is noop");
executeNextState(BackupState.RUNNING_QUEUE);
return;
}
@@ -2776,8 +2798,23 @@ public class BackupManagerService {
}
@Override
- public void operationComplete() {
- // Okay, the agent successfully reported back to us!
+ public void operationComplete(int unusedResult) {
+ // The agent reported back to us!
+
+ if (mBackupData == null) {
+ // This callback was racing with our timeout, so we've cleaned up the
+ // agent state already and are on to the next thing. We have nothing
+ // further to do here: agent state having been cleared means that we've
+ // initiated the appropriate next operation.
+ final String pkg = (mCurrentPackage != null)
+ ? mCurrentPackage.packageName : "[none]";
+ if (DEBUG) {
+ Slog.i(TAG, "Callback after agent teardown: " + pkg);
+ }
+ addBackupTrace("late opComplete; curPkg = " + pkg);
+ return;
+ }
+
final String pkgName = mCurrentPackage.packageName;
final long filepos = mBackupDataName.length();
FileDescriptor fd = mBackupData.getFileDescriptor();
@@ -2923,12 +2960,22 @@ public class BackupManagerService {
void revertAndEndBackup() {
if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything");
addBackupTrace("transport error; reverting");
+
+ // We want to reset the backup schedule based on whatever the transport suggests
+ // by way of retry/backoff time.
+ long delay;
+ try {
+ delay = mTransport.requestBackupTime();
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to contact transport for recommended backoff");
+ delay = 0; // use the scheduler's default
+ }
+ KeyValueBackupJob.schedule(mContext, delay);
+
for (BackupRequest request : mOriginalQueue) {
dataChangedImpl(request.packageName);
}
- // We also want to reset the backup schedule based on whatever
- // the transport suggests by way of retry/backoff time.
- restartBackupAlarm();
+
}
void agentErrorCleanup() {
@@ -2961,15 +3008,6 @@ public class BackupManagerService {
}
}
- void restartBackupAlarm() {
- addBackupTrace("setting backup trigger");
- synchronized (mQueueLock) {
- try {
- startBackupAlarmsLocked(mTransport.requestBackupTime());
- } catch (RemoteException e) { /* cannot happen */ }
- }
- }
-
void executeNextState(BackupState nextState) {
if (MORE_DEBUG) Slog.i(TAG, " => executing next step on "
+ this + " nextState=" + nextState);
@@ -3096,8 +3134,23 @@ public class BackupManagerService {
// Core logic for performing one package's full backup, gathering the tarball from the
// application and emitting it to the designated OutputStream.
+
+ // Callout from the engine to an interested participant that might need to communicate
+ // with the agent prior to asking it to move data
+ interface FullBackupPreflight {
+ /**
+ * Perform the preflight operation necessary for the given package.
+ * @param pkg The name of the package being proposed for full-data backup
+ * @param agent Live BackupAgent binding to the target app's agent
+ * @return BackupTransport.TRANSPORT_OK to proceed with the backup operation,
+ * or one of the other BackupTransport.* error codes as appropriate
+ */
+ int preflightFullBackup(PackageInfo pkg, IBackupAgent agent);
+ };
+
class FullBackupEngine {
OutputStream mOutput;
+ FullBackupPreflight mPreflightHook;
IFullBackupRestoreObserver mObserver;
File mFilesDir;
File mManifestFile;
@@ -3128,8 +3181,7 @@ public class BackupManagerService {
@Override
public void run() {
try {
- BackupDataOutput output = new BackupDataOutput(
- mPipe.getFileDescriptor());
+ FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
if (mWriteManifest) {
final boolean writeWidgetData = mWidgetData != null;
@@ -3172,15 +3224,16 @@ public class BackupManagerService {
}
}
- FullBackupEngine(OutputStream output, String packageName, boolean alsoApks) {
+ FullBackupEngine(OutputStream output, String packageName, FullBackupPreflight preflightHook,
+ boolean alsoApks) {
mOutput = output;
+ mPreflightHook = preflightHook;
mIncludeApks = alsoApks;
mFilesDir = new File("/data/system");
mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
}
-
public int backupOnePackage(PackageInfo pkg) throws RemoteException {
int result = BackupTransport.TRANSPORT_OK;
Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName);
@@ -3190,42 +3243,52 @@ public class BackupManagerService {
if (agent != null) {
ParcelFileDescriptor[] pipes = null;
try {
- pipes = ParcelFileDescriptor.createPipe();
-
- ApplicationInfo app = pkg.applicationInfo;
- final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
- final boolean sendApk = mIncludeApks
- && !isSharedStorage
- && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0)
- && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
+ // Call the preflight hook, if any
+ if (mPreflightHook != null) {
+ result = mPreflightHook.preflightFullBackup(pkg, agent);
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "preflight returned " + result);
+ }
+ }
+
+ // If we're still good to go after preflighting, start moving data
+ if (result == BackupTransport.TRANSPORT_OK) {
+ pipes = ParcelFileDescriptor.createPipe();
+
+ ApplicationInfo app = pkg.applicationInfo;
+ final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
+ final boolean sendApk = mIncludeApks
+ && !isSharedStorage
+ && ((app.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) == 0)
+ && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
(app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
- byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName,
- UserHandle.USER_OWNER);
+ byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName,
+ UserHandle.USER_OWNER);
- final int token = generateToken();
- FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1],
- token, sendApk, !isSharedStorage, widgetBlob);
- pipes[1].close(); // the runner has dup'd it
- pipes[1] = null;
- Thread t = new Thread(runner, "app-data-runner");
- t.start();
+ final int token = generateToken();
+ FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1],
+ token, sendApk, !isSharedStorage, widgetBlob);
+ pipes[1].close(); // the runner has dup'd it
+ pipes[1] = null;
+ Thread t = new Thread(runner, "app-data-runner");
+ t.start();
- // Now pull data from the app and stuff it into the output
- try {
- routeSocketDataToOutput(pipes[0], mOutput);
- } catch (IOException e) {
- Slog.i(TAG, "Caught exception reading from agent", e);
- result = BackupTransport.AGENT_ERROR;
- }
+ // Now pull data from the app and stuff it into the output
+ try {
+ routeSocketDataToOutput(pipes[0], mOutput);
+ } catch (IOException e) {
+ Slog.i(TAG, "Caught exception reading from agent", e);
+ result = BackupTransport.AGENT_ERROR;
+ }
- if (!waitUntilOperationComplete(token)) {
- Slog.e(TAG, "Full backup failed on package " + pkg.packageName);
- result = BackupTransport.AGENT_ERROR;
- } else {
- if (DEBUG) Slog.d(TAG, "Full package backup success: " + pkg.packageName);
+ if (!waitUntilOperationComplete(token)) {
+ Slog.e(TAG, "Full backup failed on package " + pkg.packageName);
+ result = BackupTransport.AGENT_ERROR;
+ } else {
+ if (DEBUG) Slog.d(TAG, "Full package backup success: " + pkg.packageName);
+ }
}
-
} catch (IOException e) {
Slog.e(TAG, "Error backing up " + pkg.packageName, e);
result = BackupTransport.AGENT_ERROR;
@@ -3250,7 +3313,7 @@ public class BackupManagerService {
return result;
}
- private void writeApkToBackup(PackageInfo pkg, BackupDataOutput output) {
+ private void writeApkToBackup(PackageInfo pkg, FullBackupDataOutput output) {
// Forward-locked apps, system-bundled .apks, etc are filtered out before we get here
// TODO: handle backing up split APKs
final String appSourceDir = pkg.applicationInfo.getBaseCodePath();
@@ -3749,7 +3812,7 @@ public class BackupManagerService {
final boolean isSharedStorage =
pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
- mBackupEngine = new FullBackupEngine(out, pkg.packageName, mIncludeApks);
+ mBackupEngine = new FullBackupEngine(out, pkg.packageName, null, mIncludeApks);
sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
mBackupEngine.backupOnePackage(pkg);
@@ -3796,13 +3859,13 @@ public class BackupManagerService {
static final String TAG = "PFTBT";
ArrayList<PackageInfo> mPackages;
boolean mUpdateSchedule;
- AtomicBoolean mLatch;
+ CountDownLatch mLatch;
AtomicBoolean mKeepRunning; // signal from job scheduler
FullBackupJob mJob; // if a scheduled job needs to be finished afterwards
PerformFullTransportBackupTask(IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
- FullBackupJob runningJob, AtomicBoolean latch) {
+ FullBackupJob runningJob, CountDownLatch latch) {
super(observer);
mUpdateSchedule = updateSchedule;
mLatch = latch;
@@ -3831,6 +3894,14 @@ public class BackupManagerService {
Slog.d(TAG, "Ignoring non-agent system package " + pkg);
}
continue;
+ } else if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+ // Cull any packages in the 'stopped' state: they've either just been
+ // installed or have explicitly been force-stopped by the user. In both
+ // cases we do not want to launch them for backup.
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Ignoring stopped package " + pkg);
+ }
+ continue;
}
mPackages.add(info);
} catch (NameNotFoundException e) {
@@ -3852,6 +3923,7 @@ public class BackupManagerService {
ParcelFileDescriptor[] transportPipes = null;
PackageInfo currentPackage;
+ long backoff = 0;
try {
if (!mEnabled || !mProvisioned) {
@@ -3894,10 +3966,10 @@ public class BackupManagerService {
// Now set up the backup engine / data source end of things
enginePipes = ParcelFileDescriptor.createPipe();
- AtomicBoolean runnerLatch = new AtomicBoolean(false);
+ CountDownLatch runnerLatch = new CountDownLatch(1);
SinglePackageBackupRunner backupRunner =
new SinglePackageBackupRunner(enginePipes[1], currentPackage,
- runnerLatch);
+ transport, runnerLatch);
// The runner dup'd the pipe half, so we close it here
enginePipes[1].close();
enginePipes[1] = null;
@@ -3922,6 +3994,9 @@ public class BackupManagerService {
break;
}
nRead = in.read(buffer);
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "in.read(buffer) from app: " + nRead);
+ }
if (nRead > 0) {
out.write(buffer, 0, nRead);
result = transport.sendBackupData(nRead);
@@ -3953,6 +4028,14 @@ public class BackupManagerService {
Slog.e(TAG, "Error " + result
+ " backing up " + currentPackage.packageName);
}
+
+ // Also ask the transport how long it wants us to wait before
+ // moving on to the next package, if any.
+ backoff = transport.requestFullBackupTime();
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Transport suggested backoff=" + backoff);
+ }
+
}
// Roll this package to the end of the backup queue if we're
@@ -4005,15 +4088,12 @@ public class BackupManagerService {
mRunningFullBackupTask = null;
}
- synchronized (mLatch) {
- mLatch.set(true);
- mLatch.notifyAll();
- }
+ mLatch.countDown();
// Now that we're actually done with schedule-driven work, reschedule
// the next pass based on the new queue state.
if (mUpdateSchedule) {
- scheduleNextFullBackupJob();
+ scheduleNextFullBackupJob(backoff);
}
}
}
@@ -4044,16 +4124,79 @@ public class BackupManagerService {
// Run the backup and pipe it back to the given socket -- expects to run on
// a standalone thread. The runner owns this half of the pipe, and closes
// it to indicate EOD to the other end.
+ class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight {
+ final AtomicInteger mResult = new AtomicInteger();
+ final CountDownLatch mLatch = new CountDownLatch(1);
+ final IBackupTransport mTransport;
+
+ public SinglePackageBackupPreflight(IBackupTransport transport) {
+ mTransport = transport;
+ }
+
+ @Override
+ public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) {
+ int result;
+ try {
+ final int token = generateToken();
+ prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, this);
+ addBackupTrace("preflighting");
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
+ }
+ agent.doMeasureFullBackup(token, mBackupManagerBinder);
+
+ // now wait to get our result back
+ mLatch.await();
+ int totalSize = mResult.get();
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "Got preflight response; size=" + totalSize);
+ }
+
+ result = mTransport.checkFullBackupSize(totalSize);
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception preflighting " + pkg.packageName + ": " + e.getMessage());
+ result = BackupTransport.AGENT_ERROR;
+ }
+ return result;
+ }
+
+ @Override
+ public void execute() {
+ // Unused in this case
+ }
+
+ @Override
+ public void operationComplete(int result) {
+ // got the callback, and our preflightFullBackup() method is waiting for the result
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Preflight op complete, result=" + result);
+ }
+ mResult.set(result);
+ mLatch.countDown();
+ }
+
+ @Override
+ public void handleTimeout() {
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Preflight timeout; failing");
+ }
+ mResult.set(BackupTransport.AGENT_ERROR);
+ mLatch.countDown();
+ }
+
+ }
+
class SinglePackageBackupRunner implements Runnable {
final ParcelFileDescriptor mOutput;
final PackageInfo mTarget;
- final AtomicBoolean mLatch;
+ final FullBackupPreflight mPreflight;
+ final CountDownLatch mLatch;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
- AtomicBoolean latch) throws IOException {
- int oldfd = output.getFd();
+ IBackupTransport transport, CountDownLatch latch) throws IOException {
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
+ mPreflight = new SinglePackageBackupPreflight(transport);
mLatch = latch;
}
@@ -4061,15 +4204,13 @@ public class BackupManagerService {
public void run() {
try {
FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
- FullBackupEngine engine = new FullBackupEngine(out, mTarget.packageName, false);
+ FullBackupEngine engine = new FullBackupEngine(out, mTarget.packageName,
+ mPreflight, false);
engine.backupOnePackage(mTarget);
} catch (Exception e) {
Slog.e(TAG, "Exception during full package backup of " + mTarget);
} finally {
- synchronized (mLatch) {
- mLatch.set(true);
- mLatch.notifyAll();
- }
+ mLatch.countDown();
try {
mOutput.close();
} catch (IOException e) {
@@ -4077,7 +4218,6 @@ public class BackupManagerService {
}
}
}
-
}
}
@@ -4086,16 +4226,17 @@ public class BackupManagerService {
/**
* Schedule a job to tell us when it's a good time to run a full backup
*/
- void scheduleNextFullBackupJob() {
+ void scheduleNextFullBackupJob(long transportMinLatency) {
synchronized (mQueueLock) {
if (mFullBackupQueue.size() > 0) {
// schedule the next job at the point in the future when the least-recently
// backed up app comes due for backup again; or immediately if it's already
// due.
- long upcomingLastBackup = mFullBackupQueue.get(0).lastBackup;
- long timeSinceLast = System.currentTimeMillis() - upcomingLastBackup;
- final long latency = (timeSinceLast < MIN_FULL_BACKUP_INTERVAL)
+ final long upcomingLastBackup = mFullBackupQueue.get(0).lastBackup;
+ final long timeSinceLast = System.currentTimeMillis() - upcomingLastBackup;
+ final long appLatency = (timeSinceLast < MIN_FULL_BACKUP_INTERVAL)
? (MIN_FULL_BACKUP_INTERVAL - timeSinceLast) : 0;
+ final long latency = Math.min(transportMinLatency, appLatency);
Runnable r = new Runnable() {
@Override public void run() {
FullBackupJob.schedule(mContext, latency);
@@ -4149,6 +4290,31 @@ public class BackupManagerService {
writeFullBackupScheduleAsync();
}
+ private boolean fullBackupAllowable(IBackupTransport transport) {
+ if (transport == null) {
+ Slog.w(TAG, "Transport not present; full data backup not performed");
+ return false;
+ }
+
+ // Don't proceed unless we have already established package metadata
+ // for the current dataset via a key/value backup pass.
+ try {
+ File stateDir = new File(mBaseStateDir, transport.transportDirName());
+ File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL);
+ if (pmState.length() <= 0) {
+ if (DEBUG) {
+ Slog.i(TAG, "Full backup requested but dataset not yet initialized");
+ }
+ return false;
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to contact transport");
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Conditions are right for a full backup operation, so run one. The model we use is
* to perform one app backup per scheduled job execution, and to reschedule the job
@@ -4160,6 +4326,7 @@ public class BackupManagerService {
boolean beginFullBackup(FullBackupJob scheduledJob) {
long now = System.currentTimeMillis();
FullBackupEntry entry = null;
+ long latency = MIN_FULL_BACKUP_INTERVAL;
if (!mEnabled || !mProvisioned) {
// Backups are globally disabled, so don't proceed. We also don't reschedule
@@ -4191,17 +4358,41 @@ public class BackupManagerService {
return false;
}
- entry = mFullBackupQueue.get(0);
- long timeSinceRun = now - entry.lastBackup;
- if (timeSinceRun < MIN_FULL_BACKUP_INTERVAL) {
- // It's too early to back up the next thing in the queue, so bow out
+ // At this point we know that we have work to do, just not right now. Any
+ // exit without actually running backups will also require that we
+ // reschedule the job.
+ boolean runBackup = true;
+
+ if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
if (MORE_DEBUG) {
- Slog.i(TAG, "Device ready but too early to back up next app");
+ Slog.i(TAG, "Preconditions not met; not running full backup");
}
- final long latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+ runBackup = false;
+ // Typically this means we haven't run a key/value backup yet. Back off
+ // full-backup operations by the key/value job's run interval so that
+ // next time we run, we are likely to be able to make progress.
+ latency = KeyValueBackupJob.BATCH_INTERVAL;
+ }
+
+ if (runBackup) {
+ entry = mFullBackupQueue.get(0);
+ long timeSinceRun = now - entry.lastBackup;
+ runBackup = (timeSinceRun >= MIN_FULL_BACKUP_INTERVAL);
+ if (!runBackup) {
+ // It's too early to back up the next thing in the queue, so bow out
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Device ready but too early to back up next app");
+ }
+ // Wait until the next app in the queue falls due for a full data backup
+ latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+ }
+ }
+
+ if (!runBackup) {
+ final long deferTime = latency; // pin for the closure
mBackupHandler.post(new Runnable() {
@Override public void run() {
- FullBackupJob.schedule(mContext, latency);
+ FullBackupJob.schedule(mContext, deferTime);
}
});
return false;
@@ -4209,7 +4400,7 @@ public class BackupManagerService {
// Okay, the top thing is runnable now. Pop it off and get going.
mFullBackupQueue.remove(0);
- AtomicBoolean latch = new AtomicBoolean(false);
+ CountDownLatch latch = new CountDownLatch(1);
String[] pkg = new String[] {entry.packageName};
mRunningFullBackupTask = new PerformFullTransportBackupTask(null, pkg, true,
scheduledJob, latch);
@@ -4763,7 +4954,7 @@ public class BackupManagerService {
}
}
- class RestoreInstallObserver extends IPackageInstallObserver.Stub {
+ class RestoreInstallObserver extends PackageInstallObserver {
final AtomicBoolean mDone = new AtomicBoolean();
String mPackageName;
int mResult;
@@ -4789,8 +4980,8 @@ public class BackupManagerService {
}
@Override
- public void packageInstalled(String packageName, int returnCode)
- throws RemoteException {
+ public void onPackageInstalled(String packageName, int returnCode,
+ String msg, Bundle extras) {
synchronized (mDone) {
mResult = returnCode;
mPackageName = packageName;
@@ -5046,7 +5237,9 @@ public class BackupManagerService {
offset = extractLine(buffer, offset, str);
version = Integer.parseInt(str[0]); // app version
offset = extractLine(buffer, offset, str);
- int platformVersion = Integer.parseInt(str[0]);
+ // This is the platform version, which we don't use, but we parse it
+ // as a safety against corruption in the manifest.
+ Integer.parseInt(str[0]);
offset = extractLine(buffer, offset, str);
info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
offset = extractLine(buffer, offset, str);
@@ -6107,7 +6300,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
}
- class RestoreInstallObserver extends IPackageInstallObserver.Stub {
+ class RestoreInstallObserver extends PackageInstallObserver {
final AtomicBoolean mDone = new AtomicBoolean();
String mPackageName;
int mResult;
@@ -6133,8 +6326,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
@Override
- public void packageInstalled(String packageName, int returnCode)
- throws RemoteException {
+ public void onPackageInstalled(String packageName, int returnCode,
+ String msg, Bundle extras) {
synchronized (mDone) {
mResult = returnCode;
mPackageName = packageName;
@@ -6383,7 +6576,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
offset = extractLine(buffer, offset, str);
version = Integer.parseInt(str[0]); // app version
offset = extractLine(buffer, offset, str);
- int platformVersion = Integer.parseInt(str[0]);
+ // This is the platform version, which we don't use, but we parse it
+ // as a safety against corruption in the manifest.
+ Integer.parseInt(str[0]);
offset = extractLine(buffer, offset, str);
info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
offset = extractLine(buffer, offset, str);
@@ -7842,7 +8037,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
@Override
- public void operationComplete() {
+ public void operationComplete(int unusedResult) {
if (MORE_DEBUG) {
Slog.i(TAG, "operationComplete() during restore: target="
+ mCurrentPackage.packageName
@@ -8095,6 +8290,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
}
}
+
+ // ...and schedule a backup pass if necessary
+ KeyValueBackupJob.schedule(mContext);
}
// Note: packageName is currently unused, but may be in the future
@@ -8229,16 +8427,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
synchronized (mQueueLock) {
- // Because the alarms we are using can jitter, and we want an *immediate*
- // backup pass to happen, we restart the timer beginning with "next time,"
- // then manually fire the backup trigger intent ourselves.
- startBackupAlarmsLocked(BACKUP_INTERVAL);
+ // Fire the intent that kicks off the whole shebang...
try {
mRunBackupIntent.send();
} catch (PendingIntent.CanceledException e) {
// should never happen
Slog.e(TAG, "run-backup intent cancelled!");
}
+
+ // ...and cancel any pending scheduled job, because we've just superseded it
+ KeyValueBackupJob.cancel(mContext);
}
}
@@ -8305,7 +8503,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
// make sure the screen is lit for the user interaction
- mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ 0);
// start the confirmation countdown
startConfirmationTimeout(token, params);
@@ -8333,21 +8533,33 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
throw new IllegalStateException("Restore supported only for the device owner");
}
- if (DEBUG) {
- Slog.d(TAG, "fullTransportBackup()");
- }
+ if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+ Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?");
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "fullTransportBackup()");
+ }
- AtomicBoolean latch = new AtomicBoolean(false);
- PerformFullTransportBackupTask task =
- new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
- (new Thread(task, "full-transport-master")).start();
- synchronized (latch) {
- try {
- while (latch.get() == false) {
- latch.wait();
+ CountDownLatch latch = new CountDownLatch(1);
+ PerformFullTransportBackupTask task =
+ new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
+ (new Thread(task, "full-transport-master")).start();
+ do {
+ try {
+ latch.await();
+ break;
+ } catch (InterruptedException e) {
+ // Just go back to waiting for the latch to indicate completion
}
- } catch (InterruptedException e) {}
+ } while (true);
+
+ // We just ran a backup on these packages, so kick them to the end of the queue
+ final long now = System.currentTimeMillis();
+ for (String pkg : pkgNames) {
+ enqueueFullBackup(pkg, now);
+ }
}
+
if (DEBUG) {
Slog.d(TAG, "Done with full transport backup.");
}
@@ -8388,7 +8600,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
// make sure the screen is lit for the user interaction
- mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ 0);
// start the confirmation countdown
startConfirmationTimeout(token, params);
@@ -8514,13 +8728,13 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
synchronized (mQueueLock) {
if (enable && !wasEnabled && mProvisioned) {
// if we've just been enabled, start scheduling backup passes
- startBackupAlarmsLocked(BACKUP_INTERVAL);
- scheduleNextFullBackupJob();
+ KeyValueBackupJob.schedule(mContext);
+ scheduleNextFullBackupJob(0);
} else if (!enable) {
// No longer enabled, so stop running backups
if (DEBUG) Slog.i(TAG, "Opting out of backup");
- mAlarmManager.cancel(mRunBackupIntent);
+ KeyValueBackupJob.cancel(mContext);
// This also constitutes an opt-out, so we wipe any data for
// this device from the backend. We start that process with
@@ -8574,19 +8788,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
*/
}
- private void startBackupAlarmsLocked(long delayBeforeFirstBackup) {
- // We used to use setInexactRepeating(), but that may be linked to
- // backups running at :00 more often than not, creating load spikes.
- // Schedule at an exact time for now, and also add a bit of "fuzz".
-
- Random random = new Random();
- long when = System.currentTimeMillis() + delayBeforeFirstBackup +
- random.nextInt(FUZZ_MILLIS);
- mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, when,
- BACKUP_INTERVAL + random.nextInt(FUZZ_MILLIS), mRunBackupIntent);
- mNextBackupPass = when;
- }
-
// Report whether the backup mechanism is currently enabled
public boolean isBackupEnabled() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "isBackupEnabled");
@@ -8901,8 +9102,10 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// Note that a currently-active backup agent has notified us that it has
// completed the given outstanding asynchronous backup/restore operation.
- public void opComplete(int token) {
- if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token));
+ public void opComplete(int token, long result) {
+ if (MORE_DEBUG) {
+ Slog.v(TAG, "opComplete: " + Integer.toHexString(token) + " result=" + result);
+ }
Operation op = null;
synchronized (mCurrentOpLock) {
op = mCurrentOperations.get(token);
@@ -8915,6 +9118,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// The completion callback, if any, is invoked on the handler
if (op != null && op.callback != null) {
Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, op.callback);
+ // NB: this cannot distinguish between results > 2 gig
+ msg.arg1 = (result > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) result;
mBackupHandler.sendMessage(msg);
}
}
@@ -9167,6 +9372,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
// check whether there is data for it in the current dataset, falling back
// to the ancestral dataset if not.
long token = getAvailableRestoreToken(packageName);
+ if (DEBUG) Slog.v(TAG, "restorePackage pkg=" + packageName
+ + " token=" + Long.toHexString(token));
// If we didn't come up with a place to look -- no ancestral dataset and
// the app has never been backed up from this device -- there's nothing
@@ -9292,7 +9499,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
if (mBackupRunning) pw.println("Backup currently running");
pw.println("Last backup pass started: " + mLastBackupPass
+ " (now = " + System.currentTimeMillis() + ')');
- pw.println(" next scheduled: " + mNextBackupPass);
+ pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled());
pw.println("Available transports:");
final String[] transports = listAllTransports();
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
new file mode 100644
index 0000000..a4489c1
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 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.backup;
+
+import android.app.AlarmManager;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.Random;
+
+/**
+ * Job for scheduling key/value backup work. This module encapsulates all
+ * of the policy around when those backup passes are executed.
+ */
+public class KeyValueBackupJob extends JobService {
+ private static final String TAG = "KeyValueBackupJob";
+ private static ComponentName sKeyValueJobService =
+ new ComponentName("android", KeyValueBackupJob.class.getName());
+ private static final int JOB_ID = 0x5039;
+
+ // Once someone asks for a backup, this is how long we hold off, batching
+ // up additional requests, before running the actual backup pass. Privileged
+ // callers can always trigger an immediate pass via BackupManager.backupNow().
+ static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR;
+
+ // Random variation in next-backup scheduling time to avoid server load spikes
+ private static final int FUZZ_MILLIS = 10 * 60 * 1000;
+
+ // Don't let the job scheduler defer forever; give it a (lenient) deadline
+ private static final long MAX_DEFERRAL = 1 * AlarmManager.INTERVAL_HOUR;
+
+ private static boolean sScheduled = false;
+ private static long sNextScheduled = 0;
+
+ public static void schedule(Context ctx) {
+ schedule(ctx, 0);
+ }
+
+ public static void schedule(Context ctx, long delay) {
+ synchronized (KeyValueBackupJob.class) {
+ if (!sScheduled) {
+ if (delay <= 0) {
+ delay = BATCH_INTERVAL + new Random().nextInt(FUZZ_MILLIS);
+ }
+ if (BackupManagerService.DEBUG_SCHEDULING) {
+ Slog.v(TAG, "Scheduling k/v pass in "
+ + (delay / 1000 / 60) + " minutes");
+ }
+ JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
+ .setMinimumLatency(delay)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setOverrideDeadline(delay + MAX_DEFERRAL);
+ js.schedule(builder.build());
+
+ sNextScheduled = System.currentTimeMillis() + delay;
+ sScheduled = true;
+ }
+ }
+ }
+
+ public static void cancel(Context ctx) {
+ synchronized (KeyValueBackupJob.class) {
+ JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ js.cancel(JOB_ID);
+ sNextScheduled = 0;
+ sScheduled = false;
+ }
+ }
+
+ public static long nextScheduled() {
+ synchronized (KeyValueBackupJob.class) {
+ return sNextScheduled;
+ }
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ synchronized (KeyValueBackupJob.class) {
+ sNextScheduled = 0;
+ sScheduled = false;
+ }
+
+ // Time to run a key/value backup!
+ Trampoline service = BackupManagerService.getInstance();
+ try {
+ service.backupNow();
+ } catch (RemoteException e) {}
+
+ // This was just a trigger; ongoing wakelock management is done by the
+ // rest of the backup system.
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ // Intentionally empty; the job starting was just a trigger
+ return false;
+ }
+
+}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 8bd7132..5859c6a 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -309,15 +309,23 @@ public class Trampoline extends IBackupManager.Stub {
}
@Override
- public void opComplete(int token) throws RemoteException {
+ public void opComplete(int token, long result) throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.opComplete(token);
+ svc.opComplete(token, result);
}
}
@Override
+ public long getAvailableRestoreToken(String packageName) {
+ BackupManagerService svc = mService;
+ return (svc != null) ? svc.getAvailableRestoreToken(packageName) : 0;
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
BackupManagerService svc = mService;
if (svc != null) {
svc.dump(fd, pw, args);
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 5c45201..64b6134 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -9,6 +9,7 @@ LOCAL_SRC_FILES += \
java/com/android/server/EventLogTags.logtags \
java/com/android/server/am/EventLogTags.logtags
-LOCAL_JAVA_LIBRARIES := android.policy telephony-common
+LOCAL_JAVA_LIBRARIES := telephony-common
+LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 65a5c23..0e3867d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -34,6 +34,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
+import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -61,6 +62,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;
+import java.util.Random;
import java.util.TimeZone;
import static android.app.AlarmManager.RTC_WAKEUP;
@@ -97,6 +99,7 @@ class AlarmManagerService extends SystemService {
static final boolean DEBUG_BATCH = localLOGV || false;
static final boolean DEBUG_VALIDATE = localLOGV || false;
static final boolean DEBUG_ALARM_CLOCK = localLOGV || false;
+ static final boolean RECORD_ALARMS_IN_HISTORY = true;
static final int ALARM_EVENT = 1;
static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
@@ -128,6 +131,7 @@ class AlarmManagerService extends SystemService {
final ResultReceiver mResultReceiver = new ResultReceiver();
PendingIntent mTimeTickSender;
PendingIntent mDateChangeSender;
+ Random mRandom;
boolean mInteractive = true;
long mNonInteractiveStartTime;
long mNonInteractiveTime;
@@ -185,18 +189,20 @@ class AlarmManagerService extends SystemService {
final class Batch {
long start; // These endpoints are always in ELAPSED
long end;
- boolean standalone; // certain "batches" don't participate in coalescing
+ int flags; // Flags for alarms, such as FLAG_STANDALONE.
final ArrayList<Alarm> alarms = new ArrayList<Alarm>();
Batch() {
start = 0;
end = Long.MAX_VALUE;
+ flags = 0;
}
Batch(Alarm seed) {
start = seed.whenElapsed;
- end = seed.maxWhen;
+ end = seed.maxWhenElapsed;
+ flags = seed.flags;
alarms.add(seed);
}
@@ -227,9 +233,10 @@ class AlarmManagerService extends SystemService {
start = alarm.whenElapsed;
newStart = true;
}
- if (alarm.maxWhen < end) {
- end = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < end) {
+ end = alarm.maxWhenElapsed;
}
+ flags |= alarm.flags;
if (DEBUG_BATCH) {
Slog.v(TAG, " => now " + this);
@@ -241,6 +248,7 @@ class AlarmManagerService extends SystemService {
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
+ int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
if (alarm.operation.equals(operation)) {
@@ -253,9 +261,10 @@ class AlarmManagerService extends SystemService {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
+ newFlags |= alarm.flags;
i++;
}
}
@@ -263,6 +272,7 @@ class AlarmManagerService extends SystemService {
// commit the new batch bounds
start = newStart;
end = newEnd;
+ flags = newFlags;
}
return didRemove;
}
@@ -271,6 +281,7 @@ class AlarmManagerService extends SystemService {
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
+ int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
if (alarm.operation.getTargetPackage().equals(packageName)) {
@@ -283,9 +294,10 @@ class AlarmManagerService extends SystemService {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
+ newFlags |= alarm.flags;
i++;
}
}
@@ -293,6 +305,7 @@ class AlarmManagerService extends SystemService {
// commit the new batch bounds
start = newStart;
end = newEnd;
+ flags = newFlags;
}
return didRemove;
}
@@ -313,8 +326,8 @@ class AlarmManagerService extends SystemService {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
i++;
}
@@ -357,8 +370,9 @@ class AlarmManagerService extends SystemService {
b.append(" num="); b.append(size());
b.append(" start="); b.append(start);
b.append(" end="); b.append(end);
- if (standalone) {
- b.append(" STANDALONE");
+ if (flags != 0) {
+ b.append(" flgs=0x");
+ b.append(Integer.toHexString(flags));
}
b.append('}');
return b.toString();
@@ -441,7 +455,13 @@ class AlarmManagerService extends SystemService {
// minimum recurrence period or alarm futurity for us to be able to fuzz it
static final long MIN_FUZZABLE_INTERVAL = 10000;
static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
- final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>();
+ final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
+
+ // set to null if in idle mode; while in this mode, any alarms we don't want
+ // to run during this time are placed in mPendingWhileIdleAlarms
+ Alarm mPendingIdleUntil = null;
+ Alarm mNextWakeFromIdle = null;
+ final ArrayList<Alarm> mPendingWhileIdleAlarms = new ArrayList<>();
public AlarmManagerService(Context context) {
super(context);
@@ -486,7 +506,7 @@ class AlarmManagerService extends SystemService {
final int N = mAlarmBatches.size();
for (int i = 0; i < N; i++) {
Batch b = mAlarmBatches.get(i);
- if (!b.standalone && b.canHold(whenElapsed, maxWhen)) {
+ if ((b.flags&AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) {
return i;
}
}
@@ -503,31 +523,65 @@ class AlarmManagerService extends SystemService {
void rebatchAllAlarmsLocked(boolean doValidate) {
ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
mAlarmBatches.clear();
+ Alarm oldPendingIdleUntil = mPendingIdleUntil;
final long nowElapsed = SystemClock.elapsedRealtime();
final int oldBatches = oldSet.size();
for (int batchNum = 0; batchNum < oldBatches; batchNum++) {
Batch batch = oldSet.get(batchNum);
final int N = batch.size();
for (int i = 0; i < N; i++) {
- Alarm a = batch.get(i);
- long whenElapsed = convertToElapsed(a.when, a.type);
- final long maxElapsed;
- if (a.whenElapsed == a.maxWhen) {
- // Exact
- maxElapsed = whenElapsed;
- } else {
- // Not exact. Preserve any explicit window, otherwise recalculate
- // the window based on the alarm's new futurity. Note that this
- // reflects a policy of preferring timely to deferred delivery.
- maxElapsed = (a.windowLength > 0)
- ? (whenElapsed + a.windowLength)
- : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
- }
- setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed,
- a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource,
- a.alarmClock, a.userId);
+ reAddAlarmLocked(batch.get(i), nowElapsed, doValidate);
}
}
+ if (oldPendingIdleUntil != null && oldPendingIdleUntil != mPendingIdleUntil) {
+ Slog.wtf(TAG, "Rebatching: idle until changed from " + oldPendingIdleUntil
+ + " to " + mPendingIdleUntil);
+ if (mPendingIdleUntil == null) {
+ // Somehow we lost this... we need to restore all of the pending alarms.
+ restorePendingWhileIdleAlarmsLocked();
+ }
+ }
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
+
+ void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) {
+ a.when = a.origWhen;
+ long whenElapsed = convertToElapsed(a.when, a.type);
+ final long maxElapsed;
+ if (a.whenElapsed == a.maxWhenElapsed) {
+ // Exact
+ maxElapsed = whenElapsed;
+ } else {
+ // Not exact. Preserve any explicit window, otherwise recalculate
+ // the window based on the alarm's new futurity. Note that this
+ // reflects a policy of preferring timely to deferred delivery.
+ maxElapsed = (a.windowLength > 0)
+ ? (whenElapsed + a.windowLength)
+ : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
+ }
+ a.whenElapsed = whenElapsed;
+ a.maxWhenElapsed = maxElapsed;
+ setImplLocked(a, true, doValidate);
+ }
+
+ void restorePendingWhileIdleAlarmsLocked() {
+ // Bring pending alarms back into the main list.
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ for (int i=mPendingWhileIdleAlarms.size() - 1; i >= 0 && mPendingIdleUntil == null; i--) {
+ Alarm a = mPendingWhileIdleAlarms.remove(i);
+ reAddAlarmLocked(a, nowElapsed, false);
+ }
+
+ // Reschedule everything.
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+
+ // And send a TIME_TICK right now, since it is important to get the UI updated.
+ try {
+ mTimeTickSender.send();
+ } catch (PendingIntent.CanceledException e) {
+ }
}
static final class InFlight extends Intent {
@@ -539,7 +593,7 @@ class AlarmManagerService extends SystemService {
final int mAlarmType;
InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource,
- int alarmType, String tag) {
+ int alarmType, String tag, long nowELAPSED) {
mPendingIntent = pendingIntent;
mWorkSource = workSource;
mTag = tag;
@@ -549,6 +603,7 @@ class AlarmManagerService extends SystemService {
fs = new FilterStats(mBroadcastStats, mTag);
mBroadcastStats.filterStats.put(mTag, fs);
}
+ fs.lastTime = nowELAPSED;
mFilterStats = fs;
mAlarmType = alarmType;
}
@@ -558,6 +613,7 @@ class AlarmManagerService extends SystemService {
final BroadcastStats mBroadcastStats;
final String mTag;
+ long lastTime;
long aggregateTime;
int count;
int numWakeup;
@@ -687,7 +743,7 @@ class AlarmManagerService extends SystemService {
}
void setImpl(int type, long triggerAtTime, long windowLength, long interval,
- PendingIntent operation, boolean isStandalone, WorkSource workSource,
+ PendingIntent operation, int flags, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock) {
if (operation == null) {
Slog.w(TAG, "set/setRepeating ignored because there is no intent");
@@ -745,25 +801,71 @@ class AlarmManagerService extends SystemService {
Slog.v(TAG, "set(" + operation + ") : type=" + type
+ " triggerAtTime=" + triggerAtTime + " win=" + windowLength
+ " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed
- + " interval=" + interval + " standalone=" + isStandalone);
+ + " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
- interval, operation, isStandalone, true, workSource, alarmClock, userId);
+ interval, operation, flags, true, workSource, alarmClock, userId);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
- long maxWhen, long interval, PendingIntent operation, boolean isStandalone,
+ long maxWhen, long interval, PendingIntent operation, int flags,
boolean doValidate, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
int userId) {
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
- operation, workSource, alarmClock, userId);
+ operation, workSource, flags, alarmClock, userId);
removeLocked(operation);
+ setImplLocked(a, false, doValidate);
+ }
+
+ private void updateNextWakeFromIdleFuzzLocked() {
+ if (mNextWakeFromIdle != null) {
+
+ }
+ }
- int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);
+ private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
+ if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ // This is a special alarm that will put the system into idle until it goes off.
+ // The caller has given the time they want this to happen at, however we need
+ // to pull that earlier if there are existing alarms that have requested to
+ // bring us out of idle.
+ if (mNextWakeFromIdle != null) {
+ a.when = a.whenElapsed = a.maxWhenElapsed = mNextWakeFromIdle.whenElapsed;
+ }
+ // Add fuzz to make the alarm go off some time before the actual desired time.
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final int fuzz = fuzzForDuration(a.whenElapsed-nowElapsed);
+ if (fuzz > 0) {
+ if (mRandom == null) {
+ mRandom = new Random();
+ }
+ final int delta = mRandom.nextInt(fuzz);
+ a.whenElapsed -= delta;
+ if (false) {
+ Slog.d(TAG, "Alarm when: " + a.whenElapsed);
+ Slog.d(TAG, "Delta until alarm: " + (a.whenElapsed-nowElapsed));
+ Slog.d(TAG, "Applied fuzz: " + fuzz);
+ Slog.d(TAG, "Final delta: " + delta);
+ Slog.d(TAG, "Final when: " + a.whenElapsed);
+ }
+ a.when = a.maxWhenElapsed = a.whenElapsed;
+ }
+
+ } else if (mPendingIdleUntil != null) {
+ // We currently have an idle until alarm scheduled; if the new alarm has
+ // not explicitly stated it wants to run while idle, then put it on hold.
+ if ((a.flags&(AlarmManager.FLAG_ALLOW_WHILE_IDLE|AlarmManager.FLAG_WAKE_FROM_IDLE))
+ == 0) {
+ mPendingWhileIdleAlarms.add(a);
+ return;
+ }
+ }
+
+ int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
+ ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
if (whichBatch < 0) {
Batch batch = new Batch(a);
- batch.standalone = isStandalone;
addBatchLocked(mAlarmBatches, batch);
} else {
Batch batch = mAlarmBatches.get(whichBatch);
@@ -775,28 +877,53 @@ class AlarmManagerService extends SystemService {
}
}
- if (alarmClock != null) {
+ if (a.alarmClock != null) {
mNextAlarmClockMayChange = true;
- updateNextAlarmClockLocked();
}
- if (DEBUG_VALIDATE) {
- if (doValidate && !validateConsistencyLocked()) {
- Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when
- + " when(hex)=" + Long.toHexString(when)
- + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen
- + " interval=" + interval + " op=" + operation
- + " standalone=" + isStandalone);
- rebatchAllAlarmsLocked(false);
+ boolean needRebatch = false;
+
+ if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ mPendingIdleUntil = a;
+ needRebatch = true;
+ } else if ((a.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+ if (mNextWakeFromIdle == null || mNextWakeFromIdle.whenElapsed > a.whenElapsed) {
+ mNextWakeFromIdle = a;
+ // If this wake from idle is earlier than whatever was previously scheduled,
+ // and we are currently idling, then we need to rebatch alarms in case the idle
+ // until time needs to be updated.
+ if (mPendingIdleUntil != null) {
+ needRebatch = true;
+ }
}
}
- rescheduleKernelAlarmsLocked();
+ if (!rebatching) {
+ if (DEBUG_VALIDATE) {
+ if (doValidate && !validateConsistencyLocked()) {
+ Slog.v(TAG, "Tipping-point operation: type=" + a.type + " when=" + a.when
+ + " when(hex)=" + Long.toHexString(a.when)
+ + " whenElapsed=" + a.whenElapsed
+ + " maxWhenElapsed=" + a.maxWhenElapsed
+ + " interval=" + a.repeatInterval + " op=" + a.operation
+ + " flags=0x" + Integer.toHexString(a.flags));
+ rebatchAllAlarmsLocked(false);
+ needRebatch = false;
+ }
+ }
+
+ if (needRebatch) {
+ rebatchAllAlarmsLocked(false);
+ }
+
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
}
private final IBinder mService = new IAlarmManager.Stub() {
@Override
- public void set(int type, long triggerAtTime, long windowLength, long interval,
+ public void set(int type, long triggerAtTime, long windowLength, long interval, int flags,
PendingIntent operation, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock) {
if (workSource != null) {
@@ -805,8 +932,17 @@ class AlarmManagerService extends SystemService {
"AlarmManager.set");
}
+ if (windowLength == AlarmManager.WINDOW_EXACT) {
+ flags |= AlarmManager.FLAG_STANDALONE;
+ }
+ if (alarmClock != null) {
+ flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
+ }
+ if (Binder.getCallingUid() < Process.FIRST_APPLICATION_UID) {
+ flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+ }
setImpl(type, triggerAtTime, windowLength, interval, operation,
- windowLength == AlarmManager.WINDOW_EXACT, workSource, alarmClock);
+ flags, workSource, alarmClock);
}
@Override
@@ -846,6 +982,11 @@ class AlarmManagerService extends SystemService {
}
@Override
+ public long getNextWakeFromIdleTime() {
+ return getNextWakeFromIdleTimeImpl();
+ }
+
+ @Override
public AlarmManager.AlarmClockInfo getNextAlarmClock(int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false /* allowAll */, false /* requireFull */,
@@ -912,6 +1053,19 @@ class AlarmManagerService extends SystemService {
dumpAlarmList(pw, b.alarms, " ", nowELAPSED, nowRTC, sdf);
}
}
+ if (mPendingIdleUntil != null) {
+ pw.println();
+ pw.println("Idle mode state:");
+ pw.print(" Idling until: "); pw.println(mPendingIdleUntil);
+ mPendingIdleUntil.dump(pw, " ", nowRTC, nowELAPSED, sdf);
+ pw.println(" Pending alarms:");
+ dumpAlarmList(pw, mPendingWhileIdleAlarms, " ", nowELAPSED, nowRTC, sdf);
+ }
+ if (mNextWakeFromIdle != null) {
+ pw.println();
+ pw.print(" Next wake from idle: "); pw.println(mNextWakeFromIdle);
+ mNextWakeFromIdle.dump(pw, " ", nowRTC, nowELAPSED, sdf);
+ }
pw.println();
pw.print("Past-due non-wakeup alarms: ");
@@ -1018,7 +1172,10 @@ class AlarmManagerService extends SystemService {
TimeUtils.formatDuration(fs.aggregateTime, pw);
pw.print(" "); pw.print(fs.numWakeup);
pw.print(" wakes " ); pw.print(fs.count);
- pw.print(" alarms: ");
+ pw.print(" alarms, last ");
+ TimeUtils.formatDuration(fs.lastTime, nowELAPSED, pw);
+ pw.println(":");
+ pw.print(" ");
pw.print(fs.mTag);
pw.println();
}
@@ -1094,7 +1251,13 @@ class AlarmManagerService extends SystemService {
return null;
}
- private AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
+ long getNextWakeFromIdleTimeImpl() {
+ synchronized (mLock) {
+ return mNextWakeFromIdle != null ? mNextWakeFromIdle.whenElapsed : Long.MAX_VALUE;
+ }
+ }
+
+ AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
synchronized (mLock) {
return mNextAlarmClockForUser.get(userId);
}
@@ -1260,13 +1423,29 @@ class AlarmManagerService extends SystemService {
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (mPendingWhileIdleAlarms.get(i).operation.equals(operation)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
Slog.v(TAG, "remove(operation) changed bounds; rebatching");
}
+ boolean restorePending = false;
+ if (mPendingIdleUntil != null && mPendingIdleUntil.operation.equals(operation)) {
+ mPendingIdleUntil = null;
+ restorePending = true;
+ }
+ if (mNextWakeFromIdle != null && mNextWakeFromIdle.operation.equals(operation)) {
+ mNextWakeFromIdle = null;
+ }
rebatchAllAlarmsLocked(true);
- rescheduleKernelAlarmsLocked();
+ if (restorePending) {
+ restorePendingWhileIdleAlarmsLocked();
+ }
updateNextAlarmClockLocked();
}
}
@@ -1280,6 +1459,12 @@ class AlarmManagerService extends SystemService {
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
@@ -1300,6 +1485,13 @@ class AlarmManagerService extends SystemService {
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).operation.getCreatorUid())
+ == userHandle) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
@@ -1344,6 +1536,11 @@ class AlarmManagerService extends SystemService {
return true;
}
}
+ for (int i = 0; i < mPendingWhileIdleAlarms.size(); i++) {
+ if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ return true;
+ }
+ }
return false;
}
@@ -1432,6 +1629,19 @@ class AlarmManagerService extends SystemService {
Alarm alarm = batch.get(i);
alarm.count = 1;
triggerList.add(alarm);
+ if ((alarm.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+ EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
+ alarm.tag);
+ }
+ if (mPendingIdleUntil == alarm) {
+ mPendingIdleUntil = null;
+ rebatchAllAlarmsLocked(false);
+ restorePendingWhileIdleAlarmsLocked();
+ }
+ if (mNextWakeFromIdle == alarm) {
+ mNextWakeFromIdle = null;
+ rebatchAllAlarmsLocked(false);
+ }
// Recurring alarms may have passed several alarm intervals while the
// phone was asleep or off, so pass a trigger count when sending them.
@@ -1445,7 +1655,7 @@ class AlarmManagerService extends SystemService {
final long nextElapsed = alarm.whenElapsed + delta;
setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
- alarm.repeatInterval, alarm.operation, batch.standalone, true,
+ alarm.repeatInterval, alarm.operation, alarm.flags, true,
alarm.workSource, alarm.alarmClock, alarm.userId);
}
@@ -1494,34 +1704,38 @@ class AlarmManagerService extends SystemService {
private static class Alarm {
public final int type;
+ public final long origWhen;
public final boolean wakeup;
public final PendingIntent operation;
- public final String tag;
+ public final String tag;
public final WorkSource workSource;
+ public final int flags;
public int count;
public long when;
public long windowLength;
public long whenElapsed; // 'when' in the elapsed time base
- public long maxWhen; // also in the elapsed time base
+ public long maxWhenElapsed; // also in the elapsed time base
public long repeatInterval;
public final AlarmManager.AlarmClockInfo alarmClock;
public final int userId;
public PriorityClass priorityClass;
public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
- long _interval, PendingIntent _op, WorkSource _ws,
+ long _interval, PendingIntent _op, WorkSource _ws, int _flags,
AlarmManager.AlarmClockInfo _info, int _userId) {
type = _type;
+ origWhen = _when;
wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
|| _type == AlarmManager.RTC_WAKEUP;
when = _when;
whenElapsed = _whenElapsed;
windowLength = _windowLength;
- maxWhen = _maxWhen;
+ maxWhenElapsed = _maxWhen;
repeatInterval = _interval;
operation = _op;
tag = makeTag(_op, _type);
workSource = _ws;
+ flags = _flags;
alarmClock = _info;
userId = _userId;
}
@@ -1561,7 +1775,14 @@ class AlarmManagerService extends SystemService {
pw.println();
pw.print(prefix); pw.print("window="); pw.print(windowLength);
pw.print(" repeatInterval="); pw.print(repeatInterval);
- pw.print(" count="); pw.println(count);
+ pw.print(" count="); pw.print(count);
+ pw.print(" flags=0x"); pw.println(Integer.toHexString(flags));
+ if (alarmClock != null) {
+ pw.print(prefix); pw.println("Alarm clock:");
+ pw.print(prefix); pw.print(" triggerTime=");
+ pw.println(sdf.format(new Date(alarmClock.getTriggerTime())));
+ pw.print(prefix); pw.print(" showIntent="); pw.println(alarmClock.getShowIntent());
+ }
pw.print(prefix); pw.print("operation="); pw.println(operation);
}
}
@@ -1599,6 +1820,20 @@ class AlarmManagerService extends SystemService {
}
}
+ static int fuzzForDuration(long duration) {
+ if (duration < 15*60*1000) {
+ // If the duration until the time is less than 15 minutes, the maximum fuzz
+ // is the duration.
+ return (int)duration;
+ } else if (duration < 90*60*1000) {
+ // If duration is less than 1 1/2 hours, the maximum fuzz is 15 minutes,
+ return 15*60*1000;
+ } else {
+ // Otherwise, we will fuzz by at most half an hour.
+ return 30*60*1000;
+ }
+ }
+
boolean checkAllowNonWakeupDelayLocked(long nowELAPSED) {
if (mInteractive) {
return false;
@@ -1606,7 +1841,7 @@ class AlarmManagerService extends SystemService {
if (mLastAlarmDeliveryTime <= 0) {
return false;
}
- if (mPendingNonWakeupAlarms.size() > 0 && mNextNonWakeupDeliveryTime > nowELAPSED) {
+ if (mPendingNonWakeupAlarms.size() > 0 && mNextNonWakeupDeliveryTime < nowELAPSED) {
// This is just a little paranoia, if somehow we have pending non-wakeup alarms
// and the next delivery time is in the past, then just deliver them all. This
// avoids bugs where we get stuck in a loop trying to poll for alarms.
@@ -1624,6 +1859,17 @@ class AlarmManagerService extends SystemService {
if (localLOGV) {
Slog.v(TAG, "sending alarm " + alarm);
}
+ if (RECORD_ALARMS_IN_HISTORY) {
+ if (alarm.workSource != null && alarm.workSource.size() > 0) {
+ for (int wi=0; wi<alarm.workSource.size(); wi++) {
+ ActivityManagerNative.noteAlarmStart(
+ alarm.operation, alarm.workSource.get(wi), alarm.tag);
+ }
+ } else {
+ ActivityManagerNative.noteAlarmStart(
+ alarm.operation, -1, alarm.tag);
+ }
+ }
alarm.operation.send(getContext(), 0,
mBackgroundIntent.putExtra(
Intent.EXTRA_ALARM_COUNT, alarm.count),
@@ -1636,7 +1882,7 @@ class AlarmManagerService extends SystemService {
mWakeLock.acquire();
}
final InFlight inflight = new InFlight(AlarmManagerService.this,
- alarm.operation, alarm.workSource, alarm.type, alarm.tag);
+ alarm.operation, alarm.workSource, alarm.type, alarm.tag, nowELAPSED);
mInFlight.add(inflight);
mBroadcastRefCount++;
@@ -1664,11 +1910,11 @@ class AlarmManagerService extends SystemService {
for (int wi=0; wi<alarm.workSource.size(); wi++) {
ActivityManagerNative.noteWakeupAlarm(
alarm.operation, alarm.workSource.get(wi),
- alarm.workSource.getName(wi));
+ alarm.workSource.getName(wi), alarm.tag);
}
} else {
ActivityManagerNative.noteWakeupAlarm(
- alarm.operation, -1, null);
+ alarm.operation, -1, null, alarm.tag);
}
}
} catch (PendingIntent.CanceledException e) {
@@ -1886,7 +2132,7 @@ class AlarmManagerService extends SystemService {
final WorkSource workSource = null; // Let system take blame for time tick events.
setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
- 0, mTimeTickSender, true, workSource, null);
+ 0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null);
}
public void scheduleDateChangedEvent() {
@@ -1899,8 +2145,8 @@ class AlarmManagerService extends SystemService {
calendar.add(Calendar.DAY_OF_MONTH, 1);
final WorkSource workSource = null; // Let system take blame for date change events.
- setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource,
- null);
+ setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender,
+ AlarmManager.FLAG_STANDALONE, workSource, null);
}
}
@@ -2030,6 +2276,17 @@ class AlarmManagerService extends SystemService {
fs.nesting = 0;
fs.aggregateTime += nowELAPSED - fs.startTime;
}
+ if (RECORD_ALARMS_IN_HISTORY) {
+ if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
+ for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
+ ActivityManagerNative.noteAlarmFinish(
+ pi, inflight.mWorkSource.get(wi), inflight.mTag);
+ }
+ } else {
+ ActivityManagerNative.noteAlarmFinish(
+ pi, -1, inflight.mTag);
+ }
+ }
} else {
mLog.w("No in-flight alarm for " + pi + " " + intent);
}
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 42a5195..17b4939 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -813,7 +813,8 @@ public class AppOpsService extends IAppOpsService.Stub {
.getApplicationInfo(packageName, 0, UserHandle.getUserId(uid));
if (appInfo != null) {
pkgUid = appInfo.uid;
- isPrivileged = (appInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0;
+ isPrivileged = (appInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
} else {
if ("media".equals(packageName)) {
pkgUid = Process.MEDIA_UID;
@@ -996,7 +997,8 @@ public class AppOpsService extends IAppOpsService.Stub {
ApplicationInfo appInfo = ActivityThread.getPackageManager()
.getApplicationInfo(pkgName, 0, UserHandle.getUserId(uid));
if (appInfo != null) {
- isPrivileged = (appInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0;
+ isPrivileged = (appInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
} else {
// Could not load data, don't add to cache so it will be loaded later.
diff --git a/services/core/java/com/android/server/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java
index bc31450..9e28b64 100644
--- a/services/core/java/com/android/server/AssetAtlasService.java
+++ b/services/core/java/com/android/server/AssetAtlasService.java
@@ -46,7 +46,6 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -200,9 +199,6 @@ public class AssetAtlasService extends IAssetAtlas.Stub {
private final ArrayList<Bitmap> mBitmaps;
private final int mPixelCount;
- private long mNativeBitmap;
-
- // Used for debugging only
private Bitmap mAtlasBitmap;
Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) {
@@ -292,7 +288,7 @@ public class AssetAtlasService extends IAssetAtlas.Stub {
}
canvas.drawBitmap(bitmap, 0.0f, 0.0f, null);
canvas.restore();
- atlasMap[mapIndex++] = bitmap.mNativeBitmap;
+ atlasMap[mapIndex++] = bitmap.getSkBitmap();
atlasMap[mapIndex++] = entry.x;
atlasMap[mapIndex++] = entry.y;
atlasMap[mapIndex++] = entry.rotated ? 1 : 0;
@@ -300,9 +296,7 @@ public class AssetAtlasService extends IAssetAtlas.Stub {
}
final long endRender = System.nanoTime();
- if (mNativeBitmap != 0) {
- result = nUploadAtlas(buffer, mNativeBitmap);
- }
+ result = nUploadAtlas(buffer, mAtlasBitmap);
final long endUpload = System.nanoTime();
if (DEBUG_ATLAS) {
@@ -327,14 +321,8 @@ public class AssetAtlasService extends IAssetAtlas.Stub {
* @param height
*/
private Canvas acquireCanvas(int width, int height) {
- if (DEBUG_ATLAS_TEXTURE) {
- mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- return new Canvas(mAtlasBitmap);
- } else {
- Canvas canvas = new Canvas();
- mNativeBitmap = nAcquireAtlasCanvas(canvas, width, height);
- return canvas;
- }
+ mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ return new Canvas(mAtlasBitmap);
}
/**
@@ -344,8 +332,8 @@ public class AssetAtlasService extends IAssetAtlas.Stub {
* to disk in /data/system/atlas.png for debugging.
*/
private void releaseCanvas(Canvas canvas) {
+ canvas.setBitmap(null);
if (DEBUG_ATLAS_TEXTURE) {
- canvas.setBitmap(null);
File systemDirectory = new File(Environment.getDataDirectory(), "system");
File dataFile = new File(systemDirectory, "atlas.png");
@@ -359,18 +347,13 @@ public class AssetAtlasService extends IAssetAtlas.Stub {
} catch (IOException e) {
// Ignore
}
-
- mAtlasBitmap.recycle();
- mAtlasBitmap = null;
- } else {
- nReleaseAtlasCanvas(canvas, mNativeBitmap);
}
+ mAtlasBitmap.recycle();
+ mAtlasBitmap = null;
}
}
- private static native long nAcquireAtlasCanvas(Canvas canvas, int width, int height);
- private static native void nReleaseAtlasCanvas(Canvas canvas, long bitmap);
- private static native boolean nUploadAtlas(GraphicBuffer buffer, long bitmap);
+ private static native boolean nUploadAtlas(GraphicBuffer buffer, Bitmap bitmap);
@Override
public boolean isCompatible(int ppid) {
@@ -404,24 +387,32 @@ public class AssetAtlasService extends IAssetAtlas.Stub {
if (cpuCount == 1) {
new ComputeWorker(MIN_SIZE, MAX_SIZE, STEP, bitmaps, pixelCount, results, null).run();
} else {
- int start = MIN_SIZE;
- int end = MAX_SIZE - (cpuCount - 1) * STEP;
+ int start = MIN_SIZE + (cpuCount - 1) * STEP;
+ int end = MAX_SIZE;
int step = STEP * cpuCount;
final CountDownLatch signal = new CountDownLatch(cpuCount);
- for (int i = 0; i < cpuCount; i++, start += STEP, end += STEP) {
+ for (int i = 0; i < cpuCount; i++, start -= STEP, end -= STEP) {
ComputeWorker worker = new ComputeWorker(start, end, step,
bitmaps, pixelCount, results, signal);
new Thread(worker, "Atlas Worker #" + (i + 1)).start();
}
+ boolean isAllWorkerFinished;
try {
- signal.await(10, TimeUnit.SECONDS);
+ isAllWorkerFinished = signal.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.w(LOG_TAG, "Could not complete configuration computation");
return null;
}
+
+ if (!isAllWorkerFinished) {
+ // We have to abort here, otherwise the async updates on "results" would crash the
+ // sort later.
+ Log.w(LOG_TAG, "Could not complete configuration computation before timeout.");
+ return null;
+ }
}
// Maximize the number of packed bitmaps, minimize the texture size
@@ -436,7 +427,8 @@ public class AssetAtlasService extends IAssetAtlas.Stub {
if (DEBUG_ATLAS) {
float delay = (System.nanoTime() - begin) / 1000.0f / 1000.0f / 1000.0f;
- Log.d(LOG_TAG, String.format("Found best atlas configuration in %.2fs", delay));
+ Log.d(LOG_TAG, String.format("Found best atlas configuration (out of %d) in %.2fs",
+ results.size(), delay));
}
WorkerResult result = results.get(0);
@@ -697,8 +689,8 @@ public class AssetAtlasService extends IAssetAtlas.Stub {
Atlas.Entry entry = new Atlas.Entry();
for (Atlas.Type type : Atlas.Type.values()) {
- for (int width = mStart; width < mEnd; width += mStep) {
- for (int height = MIN_SIZE; height < MAX_SIZE; height += STEP) {
+ for (int width = mEnd; width > mStart; width -= mStep) {
+ for (int height = MAX_SIZE; height > MIN_SIZE; height -= STEP) {
// If the atlas is not big enough, skip it
if (width * height <= mThreshold) continue;
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 912a181..b3b4651 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -626,6 +626,22 @@ public final class BatteryService extends SystemService {
pw.println(" voltage: " + mBatteryProps.batteryVoltage);
pw.println(" temperature: " + mBatteryProps.batteryTemperature);
pw.println(" technology: " + mBatteryProps.batteryTechnology);
+
+ } else if ("unplug".equals(args[0])) {
+ if (!mUpdatesStopped) {
+ mLastBatteryProps.set(mBatteryProps);
+ }
+ mBatteryProps.chargerAcOnline = false;
+ mBatteryProps.chargerUsbOnline = false;
+ mBatteryProps.chargerWirelessOnline = false;
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mUpdatesStopped = true;
+ processValuesLocked(false);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
} else if (args.length == 3 && "set".equals(args[0])) {
String key = args[1];
String value = args[2];
@@ -662,6 +678,7 @@ public final class BatteryService extends SystemService {
} catch (NumberFormatException ex) {
pw.println("Bad value: " + value);
}
+
} else if (args.length == 1 && "reset".equals(args[0])) {
long ident = Binder.clearCallingIdentity();
try {
@@ -676,6 +693,7 @@ public final class BatteryService extends SystemService {
} else {
pw.println("Dump current battery state, or:");
pw.println(" set [ac|usb|wireless|status|level|invalid] <value>");
+ pw.println(" unplug");
pw.println(" reset");
}
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 46a4599..ef82bb7 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -55,8 +55,6 @@ import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
-import java.util.List;
-import java.util.Vector;
import java.util.*;
class BluetoothManagerService extends IBluetoothManager.Stub {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ff6dc38..7d8e9de 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -16,10 +16,8 @@
package com.android.server;
-import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_VPN;
@@ -28,6 +26,7 @@ import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -100,6 +99,7 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnInfo;
import com.android.internal.net.VpnProfile;
import com.android.internal.telephony.DctConstants;
import com.android.internal.util.AsyncChannel;
@@ -162,6 +162,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
"android.telephony.apn-restore";
+ // How long to wait before putting up a "This network doesn't have an Internet connection,
+ // connect anyway?" dialog after the user selects a network that doesn't validate.
+ private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
+
// How long to delay to removal of a pending intent based request.
// See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
private final int mReleasePendingIntentDelayMs;
@@ -325,6 +329,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27;
+ /**
+ * used to specify whether a network should be used even if unvalidated.
+ * arg1 = whether to accept the network if it's unvalidated (1 or 0)
+ * arg2 = whether to remember this choice in the future (1 or 0)
+ * obj = network
+ */
+ private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
+
+ /**
+ * used to ask the user to confirm a connection to an unvalidated network.
+ * obj = network
+ */
+ private static final int EVENT_PROMPT_UNVALIDATED = 29;
/** Handler used for internal events. */
final private InternalHandler mHandler;
@@ -1368,7 +1385,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
public void sendConnectedBroadcast(NetworkInfo info) {
enforceConnectivityInternalPermission();
- sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
@@ -1513,7 +1529,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
NetworkCapabilities.TRANSPORT_WIFI)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
- 0);
+ 5);
type = ConnectivityManager.TYPE_WIFI;
} else {
// do not track any other networks
@@ -1869,6 +1885,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
loge("ERROR: created network explicitly selected.");
}
nai.networkMisc.explicitlySelected = true;
+ nai.networkMisc.acceptUnvalidated = (boolean) msg.obj;
break;
}
case NetworkMonitor.EVENT_NETWORK_TESTED: {
@@ -1892,6 +1909,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS,
(valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
0, null);
+
+ // TODO: trigger a NetworkCapabilities update so that the dialog can know
+ // that the network is now validated and close itself.
}
break;
}
@@ -2051,6 +2071,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
ReapUnvalidatedNetworks.DONT_REAP);
}
}
+ NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
+ if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
}
// If this method proves to be too slow then we can maintain a separate
@@ -2253,6 +2275,91 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
+ public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
+ enforceConnectivityInternalPermission();
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
+ accept ? 1 : 0, always ? 1: 0, network));
+ }
+
+ private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
+ if (DBG) log("handleSetAcceptUnvalidated network=" + network +
+ " accept=" + accept + " always=" + always);
+
+ NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) {
+ // Nothing to do.
+ return;
+ }
+
+ if (nai.everValidated) {
+ // The network validated while the dialog box was up. Don't make any changes. There's a
+ // TODO in the dialog code to make it go away if the network validates; once that's
+ // implemented, taking action here will be confusing.
+ return;
+ }
+
+ if (!nai.networkMisc.explicitlySelected) {
+ Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
+ }
+
+ if (accept != nai.networkMisc.acceptUnvalidated) {
+ int oldScore = nai.getCurrentScore();
+ nai.networkMisc.acceptUnvalidated = accept;
+ rematchAllNetworksAndRequests(nai, oldScore);
+ sendUpdatedScoreToFactories(nai);
+ }
+
+ if (always) {
+ nai.asyncChannel.sendMessage(
+ NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0);
+ }
+
+ // TODO: should we also disconnect from the network if accept is false?
+ }
+
+ private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
+ PROMPT_UNVALIDATED_DELAY_MS);
+ }
+
+ private void handlePromptUnvalidated(Network network) {
+ NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+
+ // Only prompt if the network is unvalidated and was explicitly selected by the user, and if
+ // we haven't already been told to switch to it regardless of whether it validated or not.
+ if (nai == null || nai.everValidated ||
+ !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
+ return;
+ }
+
+ // TODO: What should we do if we've already switched to this network because we had no
+ // better option? There are two obvious alternatives.
+ //
+ // 1. Decide that there's no point prompting because this is our only usable network.
+ // However, because we didn't prompt, if later on a validated network comes along, we'll
+ // either a) silently switch to it - bad if the user wanted to connect to stay on this
+ // unvalidated network - or b) prompt the user at that later time - bad because the user
+ // might not understand why they are now being prompted.
+ //
+ // 2. Always prompt the user, even if we have no other network to use. The user could then
+ // try to find an alternative network to join (remember, if we got here, then the user
+ // selected this network manually). This is bad because the prompt isn't really very
+ // useful.
+ //
+ // For now we do #1, but we can revisit that later.
+ if (isDefaultNetwork(nai)) {
+ return;
+ }
+
+ Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK, network);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClassName("com.android.settings",
+ "com.android.settings.wifi.WifiNoInternetDialog");
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
@@ -2323,6 +2430,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
break;
}
+ case EVENT_SET_ACCEPT_UNVALIDATED: {
+ handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
+ break;
+ }
+ case EVENT_PROMPT_UNVALIDATED: {
+ handlePromptUnvalidated((Network) msg.obj);
+ break;
+ }
case EVENT_SYSTEM_READY: {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
nai.networkMonitor.systemReady = true;
@@ -2804,7 +2919,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
* Return the information of the ongoing legacy VPN. This method is used
* by VpnSettings and not available in ConnectivityManager. Permissions
* are checked in Vpn class.
- * @hide
*/
@Override
public LegacyVpnInfo getLegacyVpnInfo() {
@@ -2816,6 +2930,56 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
/**
+ * Return the information of all ongoing VPNs. This method is used by NetworkStatsService
+ * and not available in ConnectivityManager.
+ */
+ @Override
+ public VpnInfo[] getAllVpnInfo() {
+ enforceConnectivityInternalPermission();
+ if (mLockdownEnabled) {
+ return new VpnInfo[0];
+ }
+
+ synchronized(mVpns) {
+ List<VpnInfo> infoList = new ArrayList<>();
+ for (int i = 0; i < mVpns.size(); i++) {
+ VpnInfo info = createVpnInfo(mVpns.valueAt(i));
+ if (info != null) {
+ infoList.add(info);
+ }
+ }
+ return infoList.toArray(new VpnInfo[infoList.size()]);
+ }
+ }
+
+ /**
+ * @return VPN information for accounting, or null if we can't retrieve all required
+ * information, e.g primary underlying iface.
+ */
+ @Nullable
+ private VpnInfo createVpnInfo(Vpn vpn) {
+ VpnInfo info = vpn.getVpnInfo();
+ if (info == null) {
+ return null;
+ }
+ Network[] underlyingNetworks = vpn.getUnderlyingNetworks();
+ // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
+ // the underlyingNetworks list.
+ if (underlyingNetworks == null) {
+ NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+ if (defaultNetwork != null && defaultNetwork.linkProperties != null) {
+ info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName();
+ }
+ } else if (underlyingNetworks.length > 0) {
+ LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]);
+ if (linkProperties != null) {
+ info.primaryUnderlyingIface = linkProperties.getInterfaceName();
+ }
+ }
+ return info.primaryUnderlyingIface == null ? null : info;
+ }
+
+ /**
* Returns the information of the ongoing VPN. This method is used by VpnDialogs and
* not available in ConnectivityManager.
* Permissions are checked in Vpn class.
@@ -2962,7 +3126,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
notification.icon = icon;
notification.flags = Notification.FLAG_AUTO_CANCEL;
notification.tickerText = title;
- notification.color = mContext.getResources().getColor(
+ notification.color = mContext.getColor(
com.android.internal.R.color.system_notification_accent_color);
notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
notification.contentIntent = intent;
@@ -3289,6 +3453,24 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
+ @Override
+ public boolean requestBandwidthUpdate(Network network) {
+ enforceAccessPermission();
+ NetworkAgentInfo nai = null;
+ if (network == null) {
+ return false;
+ }
+ synchronized (mNetworkForNetId) {
+ nai = mNetworkForNetId.get(network.netId);
+ }
+ if (nai != null) {
+ nai.asyncChannel.sendMessage(android.net.NetworkAgent.CMD_REQUEST_BANDWIDTH_UPDATE);
+ return true;
+ }
+ return false;
+ }
+
+
private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
// if UID is restricted, don't allow them to bring up metered APNs
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
@@ -3336,10 +3518,34 @@ public class ConnectivityService extends IConnectivityManager.Stub
getCallingUid(), 0, operation));
}
+ // In order to implement the compatibility measure for pre-M apps that call
+ // WifiManager.enableNetwork(..., true) without also binding to that network explicitly,
+ // WifiManager registers a network listen for the purpose of calling setProcessDefaultNetwork.
+ // This ensures it has permission to do so.
+ private boolean hasWifiNetworkListenPermission(NetworkCapabilities nc) {
+ if (nc == null) {
+ return false;
+ }
+ int[] transportTypes = nc.getTransportTypes();
+ if (transportTypes.length != 1 || transportTypes[0] != NetworkCapabilities.TRANSPORT_WIFI) {
+ return false;
+ }
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_WIFI_STATE,
+ "ConnectivityService");
+ } catch (SecurityException e) {
+ return false;
+ }
+ return true;
+ }
+
@Override
public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, IBinder binder) {
- enforceAccessPermission();
+ if (!hasWifiNetworkListenPermission(networkCapabilities)) {
+ enforceAccessPermission();
+ }
NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities(
networkCapabilities), TYPE_NONE, nextNetworkRequestId());
@@ -4071,8 +4277,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
networkAgent.created = true;
updateLinkProperties(networkAgent, null);
notifyIfacesChanged();
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+
networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ scheduleUnvalidatedPrompt(networkAgent);
+
if (networkAgent.isVPN()) {
// Temporarily disable the default proxy (not global).
synchronized (mProxyLock) {
@@ -4085,9 +4293,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
// TODO: support proxy per network.
}
+
// Consider network even though it is not yet validated.
rematchNetworkAndRequests(networkAgent, NascentState.NOT_JUST_VALIDATED,
ReapUnvalidatedNetworks.REAP);
+
+ // This has to happen after matching the requests, because callbacks are just requests.
+ notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
} else if (state == NetworkInfo.State.DISCONNECTED ||
state == NetworkInfo.State.SUSPENDED) {
networkAgent.asyncChannel.disconnect();
@@ -4175,9 +4387,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION,
mDefaultInetConditionPublished);
- final Intent immediateIntent = new Intent(intent);
- immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
- sendStickyBroadcast(immediateIntent);
sendStickyBroadcast(intent);
if (newDefaultAgent != null) {
sendConnectedBroadcast(newDefaultAgent.networkInfo);
@@ -4245,8 +4454,39 @@ public class ConnectivityService extends IConnectivityManager.Stub
public boolean setUnderlyingNetworksForVpn(Network[] networks) {
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
+ boolean success;
synchronized (mVpns) {
- return mVpns.get(user).setUnderlyingNetworks(networks);
+ success = mVpns.get(user).setUnderlyingNetworks(networks);
+ }
+ if (success) {
+ notifyIfacesChanged();
+ }
+ return success;
+ }
+
+ @Override
+ public void factoryReset() {
+ enforceConnectivityInternalPermission();
+ // Turn airplane mode off
+ setAirplaneMode(false);
+
+ // Untether
+ for (String tether : getTetheredIfaces()) {
+ untether(tether);
+ }
+
+ // Turn VPN off
+ VpnConfig vpnConfig = getVpnConfig();
+ if (vpnConfig != null) {
+ if (vpnConfig.legacy) {
+ prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+ } else {
+ // Prevent this app from initiating VPN connections in the future without
+ // user intervention.
+ setVpnPackageAuthorization(false);
+
+ prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index f04487e..abd2ca0 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -72,7 +72,7 @@ option java_package com.android.server
# when a notification action button has been clicked
27521 notification_action_clicked (key|3),(action_index|1)
# when a notification has been canceled
-27530 notification_canceled (key|3),(reason|1)
+27530 notification_canceled (key|3),(reason|1),(lifespan|1)
# ---------------------------
# Watchdog.java
@@ -173,6 +173,13 @@ option java_package com.android.server
# ---------------------------
33000 wp_wallpaper_crashed (component|3)
+# ---------------------------
+# Device idle
+# ---------------------------
+34000 device_idle (state|1|5), (reason|3)
+34001 device_idle_step
+34002 device_idle_wake_from_idle (is_idle|1|5), (reason|3)
+
# ---------------------------
# ConnectivityService.java
@@ -216,3 +223,9 @@ option java_package com.android.server
# ---------------------------
2755 fstrim_start (time|2|3)
2756 fstrim_finish (time|2|3)
+
+# ---------------------------
+# AudioService.java
+# ---------------------------
+40000 volume_changed (stream|1), (prev_level|1), (level|1), (max_level|1), (caller|3)
+40001 stream_devices_changed (stream|1), (prev_devices|1), (devices|1)
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
new file mode 100644
index 0000000..c79fdfc
--- /dev/null
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015 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.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.MemoryFile;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IGraphicsStats;
+import android.view.ThreadedRenderer;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * This service's job is to collect aggregate rendering profile data. It
+ * does this by allowing rendering processes to request an ashmem buffer
+ * to place their stats into. This buffer will be pre-initialized with historical
+ * data for that process if it exists (if the userId & packageName match a buffer
+ * in the historical log)
+ *
+ * This service does not itself attempt to understand the data in the buffer,
+ * its primary job is merely to manage distributing these buffers. However,
+ * it is assumed that this buffer is for ThreadedRenderer and delegates
+ * directly to ThreadedRenderer for dumping buffers.
+ *
+ * MEMORY USAGE:
+ *
+ * This class consumes UP TO:
+ * 1) [active rendering processes] * (ASHMEM_SIZE * 2)
+ * 2) ASHMEM_SIZE (for scratch space used during dumping)
+ * 3) ASHMEM_SIZE * HISTORY_SIZE
+ *
+ * Currently ASHMEM_SIZE is 256 bytes and HISTORY_SIZE is 10. Assuming
+ * the system then also has 10 active rendering processes in the worst case
+ * this would end up using under 10KiB (8KiB for the buffers, plus some overhead
+ * for userId, pid, package name, and a couple other objects)
+ *
+ * @hide */
+public class GraphicsStatsService extends IGraphicsStats.Stub {
+ public static final String GRAPHICS_STATS_SERVICE = "graphicsstats";
+
+ private static final String TAG = "GraphicsStatsService";
+ private static final int ASHMEM_SIZE = 256;
+ private static final int HISTORY_SIZE = 10;
+
+ private final Context mContext;
+ private final Object mLock = new Object();
+ private ArrayList<ActiveBuffer> mActive = new ArrayList<>();
+ private HistoricalData[] mHistoricalLog = new HistoricalData[HISTORY_SIZE];
+ private int mNextHistoricalSlot = 0;
+ private byte[] mTempBuffer = new byte[ASHMEM_SIZE];
+
+ public GraphicsStatsService(Context context) {
+ mContext = context;
+ }
+
+ private boolean isValid(int uid, String packageName) {
+ try {
+ PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0);
+ return info.applicationInfo.uid == uid;
+ } catch (NameNotFoundException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token)
+ throws RemoteException {
+ int uid = Binder.getCallingUid();
+ int pid = Binder.getCallingPid();
+ ParcelFileDescriptor pfd = null;
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ if (!isValid(uid, packageName)) {
+ throw new RemoteException("Invalid package name");
+ }
+ synchronized (mLock) {
+ pfd = requestBufferForProcessLocked(token, uid, pid, packageName);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ return pfd;
+ }
+
+ private ParcelFileDescriptor getPfd(MemoryFile file) {
+ try {
+ return new ParcelFileDescriptor(file.getFileDescriptor());
+ } catch (IOException ex) {
+ throw new IllegalStateException("Failed to get PFD from memory file", ex);
+ }
+ }
+
+ private ParcelFileDescriptor requestBufferForProcessLocked(IBinder token,
+ int uid, int pid, String packageName) throws RemoteException {
+ ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName);
+ return getPfd(buffer.mProcessBuffer);
+ }
+
+ private void processDied(ActiveBuffer buffer) {
+ synchronized (mLock) {
+ mActive.remove(buffer);
+ Log.d("GraphicsStats", "Buffer count: " + mActive.size());
+ }
+ HistoricalData data = buffer.mPreviousData;
+ buffer.mPreviousData = null;
+ if (data == null) {
+ data = mHistoricalLog[mNextHistoricalSlot];
+ if (data == null) {
+ data = new HistoricalData();
+ }
+ }
+ data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer);
+ buffer.closeAllBuffers();
+
+ mHistoricalLog[mNextHistoricalSlot] = data;
+ mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length;
+ }
+
+ private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid,
+ String packageName) throws RemoteException {
+ int size = mActive.size();
+ for (int i = 0; i < size; i++) {
+ ActiveBuffer buffers = mActive.get(i);
+ if (buffers.mPid == pid
+ && buffers.mUid == uid) {
+ return buffers;
+ }
+ }
+ // Didn't find one, need to create it
+ try {
+ ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName);
+ mActive.add(buffers);
+ return buffers;
+ } catch (IOException ex) {
+ throw new RemoteException("Failed to allocate space");
+ }
+ }
+
+ private HistoricalData removeHistoricalDataLocked(int uid, String packageName) {
+ for (int i = 0; i < mHistoricalLog.length; i++) {
+ final HistoricalData data = mHistoricalLog[i];
+ if (data != null && data.mUid == uid
+ && data.mPackageName.equals(packageName)) {
+ if (i == mNextHistoricalSlot) {
+ mHistoricalLog[i] = null;
+ } else {
+ mHistoricalLog[i] = mHistoricalLog[mNextHistoricalSlot];
+ mHistoricalLog[mNextHistoricalSlot] = null;
+ }
+ return data;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ synchronized (mLock) {
+ for (int i = 0; i < mActive.size(); i++) {
+ final ActiveBuffer buffer = mActive.get(i);
+ fout.print("Package: ");
+ fout.print(buffer.mPackageName);
+ fout.flush();
+ try {
+ buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE);
+ ThreadedRenderer.dumpProfileData(mTempBuffer, fd);
+ } catch (IOException e) {
+ fout.println("Failed to dump");
+ }
+ fout.println();
+ }
+ for (HistoricalData buffer : mHistoricalLog) {
+ if (buffer == null) continue;
+ fout.print("Package: ");
+ fout.print(buffer.mPackageName);
+ fout.flush();
+ ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd);
+ fout.println();
+ }
+ }
+ }
+
+ private final class ActiveBuffer implements DeathRecipient {
+ final int mUid;
+ final int mPid;
+ final String mPackageName;
+ final IBinder mToken;
+ MemoryFile mProcessBuffer;
+ HistoricalData mPreviousData;
+
+ ActiveBuffer(IBinder token, int uid, int pid, String packageName)
+ throws RemoteException, IOException {
+ mUid = uid;
+ mPid = pid;
+ mPackageName = packageName;
+ mToken = token;
+ mToken.linkToDeath(this, 0);
+ mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE);
+ mPreviousData = removeHistoricalDataLocked(mUid, mPackageName);
+ if (mPreviousData != null) {
+ mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mToken.unlinkToDeath(this, 0);
+ processDied(this);
+ }
+
+ void closeAllBuffers() {
+ if (mProcessBuffer != null) {
+ mProcessBuffer.close();
+ mProcessBuffer = null;
+ }
+ }
+ }
+
+ private final static class HistoricalData {
+ final byte[] mBuffer = new byte[ASHMEM_SIZE];
+ int mUid;
+ String mPackageName;
+
+ void update(String packageName, int uid, MemoryFile file) {
+ mUid = uid;
+ mPackageName = packageName;
+ try {
+ file.readBytes(mBuffer, 0, 0, ASHMEM_SIZE);
+ } catch (IOException e) {}
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 4d0868a..0f9090d 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -15,6 +15,7 @@
package com.android.server;
+import android.annotation.NonNull;
import com.android.internal.content.PackageMonitor;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
@@ -86,7 +87,10 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
import android.text.style.SuggestionSpan;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.EventLog;
import android.util.LruCache;
@@ -125,6 +129,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -134,8 +139,12 @@ import java.util.Locale;
public class InputMethodManagerService extends IInputMethodManager.Stub
implements ServiceConnection, Handler.Callback {
static final boolean DEBUG = false;
+ static final boolean DEBUG_RESTORE = DEBUG || false;
static final String TAG = "InputMethodManagerService";
+ private static final char INPUT_METHOD_SEPARATOR = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
+
static final int MSG_SHOW_IM_PICKER = 1;
static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
@@ -466,12 +475,101 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|| Intent.ACTION_USER_REMOVED.equals(action)) {
updateCurrentProfileIds();
return;
+ } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
+ final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+ if (Settings.Secure.ENABLED_INPUT_METHODS.equals(name)) {
+ final String prevValue = intent.getStringExtra(
+ Intent.EXTRA_SETTING_PREVIOUS_VALUE);
+ final String newValue = intent.getStringExtra(
+ Intent.EXTRA_SETTING_NEW_VALUE);
+ restoreEnabledInputMethods(mContext, prevValue, newValue);
+ }
} else {
Slog.w(TAG, "Unexpected intent " + intent);
}
}
}
+ // Apply the results of a restore operation to the set of enabled IMEs. Note that this
+ // does not attempt to validate on the fly with any installed device policy, so must only
+ // be run in the context of initial device setup.
+ //
+ // TODO: Move this method to InputMethodUtils with adding unit tests.
+ static void restoreEnabledInputMethods(Context context, String prevValue, String newValue) {
+ if (DEBUG_RESTORE) {
+ Slog.i(TAG, "Restoring enabled input methods:");
+ Slog.i(TAG, "prev=" + prevValue);
+ Slog.i(TAG, " new=" + newValue);
+ }
+ // 'new' is the just-restored state, 'prev' is what was in settings prior to the restore
+ ArrayMap<String, ArraySet<String>> prevMap = parseInputMethodsAndSubtypesString(prevValue);
+ ArrayMap<String, ArraySet<String>> newMap = parseInputMethodsAndSubtypesString(newValue);
+
+ // Merge the restored ime+subtype enabled states into the live state
+ for (ArrayMap.Entry<String, ArraySet<String>> entry : newMap.entrySet()) {
+ final String imeId = entry.getKey();
+ ArraySet<String> prevSubtypes = prevMap.get(imeId);
+ if (prevSubtypes == null) {
+ prevSubtypes = new ArraySet<String>(2);
+ prevMap.put(imeId, prevSubtypes);
+ }
+ prevSubtypes.addAll(entry.getValue());
+ }
+
+ final String mergedImesAndSubtypesString = buildInputMethodsAndSubtypesString(prevMap);
+ if (DEBUG_RESTORE) {
+ Slog.i(TAG, "Merged IME string:");
+ Slog.i(TAG, " " + mergedImesAndSubtypesString);
+ }
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS, mergedImesAndSubtypesString);
+ }
+
+ // TODO: Move this method to InputMethodUtils with adding unit tests.
+ static String buildInputMethodsAndSubtypesString(ArrayMap<String, ArraySet<String>> map) {
+ // we want to use the canonical InputMethodSettings implementation,
+ // so we convert data structures first.
+ List<Pair<String, ArrayList<String>>> imeMap =
+ new ArrayList<Pair<String, ArrayList<String>>>(4);
+ for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) {
+ final String imeName = entry.getKey();
+ final ArraySet<String> subtypeSet = entry.getValue();
+ final ArrayList<String> subtypes = new ArrayList<String>(2);
+ if (subtypeSet != null) {
+ subtypes.addAll(subtypeSet);
+ }
+ imeMap.add(new Pair<String, ArrayList<String>>(imeName, subtypes));
+ }
+ return InputMethodSettings.buildInputMethodsSettingString(imeMap);
+ }
+
+ // TODO: Move this method to InputMethodUtils with adding unit tests.
+ static ArrayMap<String, ArraySet<String>> parseInputMethodsAndSubtypesString(
+ final String inputMethodsAndSubtypesString) {
+ final ArrayMap<String, ArraySet<String>> imeMap =
+ new ArrayMap<String, ArraySet<String>>();
+ if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
+ return imeMap;
+ }
+
+ final SimpleStringSplitter typeSplitter =
+ new SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final SimpleStringSplitter subtypeSplitter =
+ new SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ List<Pair<String, ArrayList<String>>> allImeSettings =
+ InputMethodSettings.buildInputMethodsAndSubtypeList(inputMethodsAndSubtypesString,
+ typeSplitter,
+ subtypeSplitter);
+ for (Pair<String, ArrayList<String>> ime : allImeSettings) {
+ ArraySet<String> subtypes = new ArraySet<String>();
+ if (ime.second != null) {
+ subtypes.addAll(ime.second);
+ }
+ imeMap.put(ime.first, subtypes);
+ }
+ return imeMap;
+ }
+
class MyPackageMonitor extends PackageMonitor {
private boolean isChangingPackagesOfCurrentUser() {
final int userId = getChangingUserId();
@@ -675,6 +773,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
+ broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED);
mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
mNotificationShown = false;
@@ -698,6 +797,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void onUserSwitchComplete(int newUserId) throws RemoteException {
}
+
+ @Override
+ public void onForegroundProfileSwitch(int newProfileId) {
+ // Ignore.
+ }
});
userId = ActivityManagerNative.getDefault().getCurrentUser().id;
} catch (RemoteException e) {
@@ -764,9 +868,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
InputMethodInfo defIm = null;
for (InputMethodInfo imi : mMethodList) {
- if (defIm == null) {
- if (InputMethodUtils.isValidSystemDefaultIme(
- mSystemReady, imi, context)) {
+ if (defIm == null && mSystemReady) {
+ final Locale systemLocale = context.getResources().getConfiguration().locale;
+ if (InputMethodUtils.isSystemImeThatHasSubtypeOf(imi, context,
+ true /* checkDefaultAttribute */, systemLocale, false /* checkCountry */,
+ InputMethodUtils.SUBTYPE_MODE_ANY)) {
defIm = imi;
Slog.i(TAG, "Selected default: " + imi.getId());
}
@@ -1185,13 +1291,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
}
- InputBindResult startInputUncheckedLocked(ClientState cs,
+ InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
}
+ if (attribute != null) {
+ // We accept an empty package name as a valid data.
+ if (!TextUtils.isEmpty(attribute.packageName) &&
+ !InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
+ attribute.packageName)) {
+ Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
+ + " uid=" + cs.uid + " package=" + attribute.packageName);
+ return mNoBinding;
+ }
+ }
+
if (mCurClient != cs) {
// Was the keyguard locked when switching over to the new client?
mCurClientInKeyguard = isKeyguardLocked();
@@ -1597,7 +1714,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
mContext, imi, mCurrentSubtype);
- mImeSwitcherNotification.color = mContext.getResources().getColor(
+ mImeSwitcherNotification.color = mContext.getColor(
com.android.internal.R.color.system_notification_accent_color);
mImeSwitcherNotification.setLatestEventInfo(
mContext, title, summary, mImeSwitchPendingIntent);
@@ -1755,16 +1872,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (mCurClient != null && mCurAttribute != null) {
- final int uid = mCurClient.uid;
- final String packageName = mCurAttribute.packageName;
- if (SystemConfig.getInstance().getFixedImeApps().contains(packageName)) {
- if (InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, uid, packageName)) {
- return;
- }
- // TODO: Do we need to lock the input method when the application reported an
- // incorrect package name?
- Slog.e(TAG, "Ignoring FixedImeApps due to the validation failure. uid=" + uid
- + " package=" + packageName);
+ // We have already made sure that the package name belongs to the application's UID.
+ // No further UID check is required.
+ if (SystemConfig.getInstance().getFixedImeApps().contains(mCurAttribute.packageName)) {
+ return;
}
}
@@ -2048,7 +2159,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// more quickly (not get stuck behind it initializing itself for the
// new focused input, even if its window wants to hide the IME).
boolean didStart = false;
-
+
switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
if (!isTextEditor || !doAutoShow) {
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index cea1ebe..744156b 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -47,6 +47,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
final private static String TAG = "IntentResolver";
final private static boolean DEBUG = false;
final private static boolean localLOGV = DEBUG || false;
+ final private static boolean localVerificationLOGV = DEBUG || false;
public void addFilter(F f) {
if (localLOGV) {
@@ -478,7 +479,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
/**
* Returns whether the object associated with the given filter is
- * "stopped," that is whether it should not be included in the result
+ * "stopped", that is whether it should not be included in the result
* if the intent requests to excluded stopped objects.
*/
protected boolean isFilterStopped(F filter, int userId) {
@@ -486,6 +487,22 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
}
/**
+ * Returns whether the given filter is "verified" that is whether it has been verified against
+ * its data URIs.
+ *
+ * The verification would happen only and only if the Intent action is
+ * {@link android.content.Intent#ACTION_VIEW} and the Intent category is
+ * {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent data scheme
+ * is "http" or "https".
+ *
+ * @see android.content.IntentFilter#setAutoVerify(boolean)
+ * @see android.content.IntentFilter#getAutoVerify()
+ */
+ protected boolean isFilterVerified(F filter) {
+ return filter.isVerified();
+ }
+
+ /**
* 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.
@@ -710,6 +727,13 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
continue;
}
+ // Are we verified ?
+ if (filter.getAutoVerify()) {
+ if (localVerificationLOGV || debug) {
+ Slog.v(TAG, " Filter verified: " + isFilterVerified(filter));
+ }
+ }
+
// Do we already have this one?
if (!allowFilterResult(filter, dest)) {
if (debug) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index be83b9b..1683485 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -60,8 +60,6 @@ import android.location.Address;
import android.location.Criteria;
import android.location.GeocoderParams;
import android.location.Geofence;
-import android.location.GpsMeasurementsEvent;
-import android.location.GpsNavigationMessageEvent;
import android.location.IGpsMeasurementsListener;
import android.location.IGpsNavigationMessageListener;
import android.location.IGpsStatusListener;
@@ -1452,14 +1450,13 @@ public class LocationManagerService extends ILocationManager.Stub {
if (receiver == null) {
receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
hideFromAppOps);
- mReceivers.put(binder, receiver);
-
try {
receiver.getListener().asBinder().linkToDeath(receiver, 0);
} catch (RemoteException e) {
Slog.e(TAG, "linkToDeath failed:", e);
return null;
}
+ mReceivers.put(binder, receiver);
}
return receiver;
}
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 77662cc..5df74c5 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import android.app.admin.DevicePolicyManager;
+import android.app.backup.BackupManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -42,13 +44,15 @@ import android.provider.Settings;
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
import android.security.KeyStore;
+import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LockSettingsStorage.CredentialHash;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -70,6 +74,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private LockPatternUtils mLockPatternUtils;
private boolean mFirstCallToVold;
+ private IGateKeeperService mGateKeeperService;
public LockSettingsService(Context context) {
mContext = context;
@@ -81,6 +86,7 @@ public class LockSettingsService extends ILockSettings.Stub {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_STARTING);
+ filter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() {
@@ -117,12 +123,22 @@ public class LockSettingsService extends ILockSettings.Stub {
} else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
mStorage.prefetchUser(userHandle);
+ } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ if (userHandle > 0) {
+ removeUser(userHandle);
+ }
}
}
};
public void systemReady() {
migrateOldData();
+ try {
+ getGateKeeperService();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failure retrieving IGateKeeperService", e);
+ }
mStorage.prefetchUser(UserHandle.USER_OWNER);
}
@@ -179,6 +195,60 @@ public class LockSettingsService extends ILockSettings.Stub {
setString("migrated_user_specific", "true", 0);
Slog.i(TAG, "Migrated per-user lock settings to new location");
}
+
+ // Migrates biometric weak such that the fallback mechanism becomes the primary.
+ if (getString("migrated_biometric_weak", null, 0) == null) {
+ final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+ List<UserInfo> users = um.getUsers();
+ for (int i = 0; i < users.size(); i++) {
+ int userId = users.get(i).id;
+ long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
+ setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+ alternateType,
+ userId);
+ }
+ setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ userId);
+ }
+ setString("migrated_biometric_weak", "true", 0);
+ Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
+ }
+
+ // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
+ // user was present on the system, so if we're upgrading to M and there is more than one
+ // user we disable the flag to remain consistent.
+ if (getString("migrated_lockscreen_disabled", null, 0) == null) {
+ final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+
+ final List<UserInfo> users = um.getUsers();
+ final int userCount = users.size();
+ int switchableUsers = 0;
+ for (int i = 0; i < userCount; i++) {
+ if (users.get(i).supportsSwitchTo()) {
+ switchableUsers++;
+ }
+ }
+
+ if (switchableUsers > 1) {
+ for (int i = 0; i < userCount; i++) {
+ int id = users.get(i).id;
+
+ if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
+ setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+ }
+ }
+ }
+
+ setString("migrated_lockscreen_disabled", "true", 0);
+ Slog.i(TAG, "Migrated lockscreen disabled flag");
+ }
} catch (RemoteException re) {
Slog.e(TAG, "Unable to migrate old data", re);
}
@@ -194,6 +264,7 @@ public class LockSettingsService extends ILockSettings.Stub {
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)
@@ -203,6 +274,16 @@ public class LockSettingsService extends ILockSettings.Stub {
+ requestedKey + " for user " + userId);
}
}
+
+ for (int i = 0; i < READ_PASSWORD_PROTECTED_SETTINGS.length; i++) {
+ String key = READ_PASSWORD_PROTECTED_SETTINGS[i];
+ if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(PERMISSION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("uid=" + callingUid
+ + " needs permission " + PERMISSION + " to read "
+ + requestedKey + " for user " + userId);
+ }
+ }
}
@Override
@@ -225,13 +306,15 @@ public class LockSettingsService extends ILockSettings.Stub {
private void setStringUnchecked(String key, int userId, String value) {
mStorage.writeKeyValue(key, value, userId);
+ if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
+ BackupManager.dataChanged("com.android.providers.settings");
+ }
}
@Override
public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
checkReadPermission(key, userId);
-
- String value = mStorage.readKeyValue(key, null, userId);
+ String value = getStringUnchecked(key, null, userId);
return TextUtils.isEmpty(value) ?
defaultValue : (value.equals("1") || value.equals("true"));
}
@@ -240,7 +323,7 @@ public class LockSettingsService extends ILockSettings.Stub {
public long getLong(String key, long defaultValue, int userId) throws RemoteException {
checkReadPermission(key, userId);
- String value = mStorage.readKeyValue(key, null, userId);
+ String value = getStringUnchecked(key, null, userId);
return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
}
@@ -248,6 +331,14 @@ public class LockSettingsService extends ILockSettings.Stub {
public String getString(String key, String defaultValue, int userId) throws RemoteException {
checkReadPermission(key, userId);
+ return getStringUnchecked(key, defaultValue, userId);
+ }
+
+ public String getStringUnchecked(String key, String defaultValue, int userId) {
+ if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
+ return mLockPatternUtils.isLockPatternEnabled(userId) ? "1" : "0";
+ }
+
return mStorage.readKeyValue(key, defaultValue, userId);
}
@@ -290,61 +381,255 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
+
+ private byte[] getCurrentHandle(int userId) {
+ CredentialHash credential;
+ byte[] currentHandle;
+
+ int currentHandleType = mStorage.getStoredCredentialType(userId);
+ switch (currentHandleType) {
+ case CredentialHash.TYPE_PATTERN:
+ credential = mStorage.readPatternHash(userId);
+ currentHandle = credential != null
+ ? credential.hash
+ : null;
+ break;
+ case CredentialHash.TYPE_PASSWORD:
+ credential = mStorage.readPasswordHash(userId);
+ currentHandle = credential != null
+ ? credential.hash
+ : null;
+ break;
+ case CredentialHash.TYPE_NONE:
+ default:
+ currentHandle = null;
+ break;
+ }
+
+ // sanity check
+ if (currentHandleType != CredentialHash.TYPE_NONE && currentHandle == null) {
+ Slog.e(TAG, "Stored handle type [" + currentHandleType + "] but no handle available");
+ }
+
+ return currentHandle;
+ }
+
+
@Override
- public void setLockPattern(String pattern, int userId) throws RemoteException {
- checkWritePermission(userId);
+ public void setLockPattern(String pattern, String savedCredential, int userId)
+ throws RemoteException {
+ byte[] currentHandle = getCurrentHandle(userId);
+
+ if (pattern == null) {
+ getGateKeeperService().clearSecureUserId(userId);
+ mStorage.writePatternHash(null, userId);
+ maybeUpdateKeystore(null, userId);
+ return;
+ }
- maybeUpdateKeystore(pattern, userId);
+ if (currentHandle == null) {
+ if (savedCredential != null) {
+ Slog.w(TAG, "Saved credential provided, but none stored");
+ }
+ savedCredential = null;
+ }
- final byte[] hash = LockPatternUtils.patternToHash(
- LockPatternUtils.stringToPattern(pattern));
- mStorage.writePatternHash(hash, userId);
+ byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, pattern, userId);
+ if (enrolledHandle != null) {
+ mStorage.writePatternHash(enrolledHandle, userId);
+ } else {
+ Slog.e(TAG, "Failed to enroll pattern");
+ }
}
+
@Override
- public void setLockPassword(String password, int userId) throws RemoteException {
- checkWritePermission(userId);
+ public void setLockPassword(String password, String savedCredential, int userId)
+ throws RemoteException {
+ byte[] currentHandle = getCurrentHandle(userId);
- maybeUpdateKeystore(password, userId);
+ if (password == null) {
+ getGateKeeperService().clearSecureUserId(userId);
+ mStorage.writePasswordHash(null, userId);
+ maybeUpdateKeystore(null, userId);
+ return;
+ }
+
+ if (currentHandle == null) {
+ if (savedCredential != null) {
+ Slog.w(TAG, "Saved credential provided, but none stored");
+ }
+ savedCredential = null;
+ }
+
+ byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, password, userId);
+ if (enrolledHandle != null) {
+ mStorage.writePasswordHash(enrolledHandle, userId);
+ } else {
+ Slog.e(TAG, "Failed to enroll password");
+ }
+ }
+
+ private byte[] enrollCredential(byte[] enrolledHandle,
+ String enrolledCredential, String toEnroll, int userId)
+ throws RemoteException {
+ checkWritePermission(userId);
+ byte[] enrolledCredentialBytes = enrolledCredential == null
+ ? null
+ : enrolledCredential.getBytes();
+ byte[] toEnrollBytes = toEnroll == null
+ ? null
+ : toEnroll.getBytes();
+ byte[] hash = getGateKeeperService().enroll(userId, enrolledHandle, enrolledCredentialBytes,
+ toEnrollBytes);
+
+ if (hash != null) {
+ maybeUpdateKeystore(toEnroll, userId);
+ }
- mStorage.writePasswordHash(mLockPatternUtils.passwordToHash(password, userId), userId);
+ return hash;
}
@Override
public boolean checkPattern(String pattern, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
- byte[] hash = LockPatternUtils.patternToHash(LockPatternUtils.stringToPattern(pattern));
- byte[] storedHash = mStorage.readPatternHash(userId);
+ try {
+ doVerifyPattern(pattern, false, 0, userId);
+ } catch (VerificationFailedException ex) {
+ return false;
+ }
+
+ return true;
+ }
- if (storedHash == null) {
- return true;
+ @Override
+ public byte[] verifyPattern(String pattern, long challenge, int userId)
+ throws RemoteException {
+ try {
+ return doVerifyPattern(pattern, true, challenge, userId);
+ } catch (VerificationFailedException ex) {
+ return null;
}
+ }
+
+ private byte[] doVerifyPattern(String pattern, boolean hasChallenge, long challenge,
+ int userId) throws VerificationFailedException, RemoteException {
+ checkPasswordReadPermission(userId);
- boolean matched = Arrays.equals(hash, storedHash);
- if (matched && !TextUtils.isEmpty(pattern)) {
- maybeUpdateKeystore(pattern, userId);
+ CredentialHash storedHash = mStorage.readPatternHash(userId);
+
+ if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(pattern)) {
+ // don't need to pass empty passwords to GateKeeper
+ return null;
}
- return matched;
+
+ if (TextUtils.isEmpty(pattern)) {
+ throw new VerificationFailedException();
+ }
+
+ if (storedHash.version == CredentialHash.VERSION_LEGACY) {
+ byte[] hash = mLockPatternUtils.patternToHash(
+ mLockPatternUtils.stringToPattern(pattern));
+ if (Arrays.equals(hash, storedHash.hash)) {
+ maybeUpdateKeystore(pattern, userId);
+ // migrate password to GateKeeper
+ setLockPattern(pattern, null, userId);
+ if (!hasChallenge) {
+ return null;
+ }
+ // Fall through to get the auth token. Technically this should never happen,
+ // as a user that had a legacy pattern would have to unlock their device
+ // before getting to a flow with a challenge, but supporting for consistency.
+ } else {
+ throw new VerificationFailedException();
+ }
+ }
+
+ byte[] token = null;
+ if (hasChallenge) {
+ token = getGateKeeperService()
+ .verifyChallenge(userId, challenge, storedHash.hash, pattern.getBytes());
+ if (token == null) {
+ throw new VerificationFailedException();
+ }
+ } else if (!getGateKeeperService().verify(userId, storedHash.hash, pattern.getBytes())) {
+ throw new VerificationFailedException();
+ }
+
+ // pattern has matched
+ maybeUpdateKeystore(pattern, userId);
+ return token;
+
}
@Override
public boolean checkPassword(String password, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
+ try {
+ doVerifyPassword(password, false, 0, userId);
+ } catch (VerificationFailedException ex) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public byte[] verifyPassword(String password, long challenge, int userId)
+ throws RemoteException {
+ try {
+ return doVerifyPassword(password, true, challenge, userId);
+ } catch (VerificationFailedException ex) {
+ return null;
+ }
+ }
+
+ private byte[] doVerifyPassword(String password, boolean hasChallenge, long challenge,
+ int userId) throws VerificationFailedException, RemoteException {
+ checkPasswordReadPermission(userId);
+
+ CredentialHash storedHash = mStorage.readPasswordHash(userId);
- byte[] hash = mLockPatternUtils.passwordToHash(password, userId);
- byte[] storedHash = mStorage.readPasswordHash(userId);
+ if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(password)) {
+ // don't need to pass empty passwords to GateKeeper
+ return null;
+ }
- if (storedHash == null) {
- return true;
+ if (TextUtils.isEmpty(password)) {
+ throw new VerificationFailedException();
}
- boolean matched = Arrays.equals(hash, storedHash);
- if (matched && !TextUtils.isEmpty(password)) {
- maybeUpdateKeystore(password, userId);
+ if (storedHash.version == CredentialHash.VERSION_LEGACY) {
+ byte[] hash = mLockPatternUtils.passwordToHash(password, userId);
+ if (Arrays.equals(hash, storedHash.hash)) {
+ maybeUpdateKeystore(password, userId);
+ // migrate password to GateKeeper
+ setLockPassword(password, null, userId);
+ if (!hasChallenge) {
+ return null;
+ }
+ // Fall through to get the auth token. Technically this should never happen,
+ // as a user that had a legacy password would have to unlock their device
+ // before getting to a flow with a challenge, but supporting for consistency.
+ } else {
+ throw new VerificationFailedException();
+ }
}
- return matched;
+
+ byte[] token = null;
+ if (hasChallenge) {
+ token = getGateKeeperService()
+ .verifyChallenge(userId, challenge, storedHash.hash, password.getBytes());
+ if (token == null) {
+ throw new VerificationFailedException();
+ }
+ } else if (!getGateKeeperService().verify(userId, storedHash.hash, password.getBytes())) {
+ throw new VerificationFailedException();
+ }
+
+ // password has matched
+ maybeUpdateKeystore(password, userId);
+ return token;
}
+
@Override
public boolean checkVoldPassword(int userId) throws RemoteException {
if (!mFirstCallToVold) {
@@ -370,7 +655,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
try {
- if (mLockPatternUtils.isLockPatternEnabled()) {
+ if (mLockPatternUtils.isLockPatternEnabled(userId)) {
if (checkPattern(password, userId)) {
return true;
}
@@ -379,7 +664,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
try {
- if (mLockPatternUtils.isLockPasswordEnabled()) {
+ if (mLockPatternUtils.isLockPasswordEnabled(userId)) {
if (checkPassword(password, userId)) {
return true;
}
@@ -390,10 +675,7 @@ public class LockSettingsService extends ILockSettings.Stub {
return false;
}
- @Override
- public void removeUser(int userId) {
- checkWritePermission(userId);
-
+ private void removeUser(int userId) {
mStorage.removeUser(userId);
final KeyStore ks = KeyStore.getInstance();
@@ -420,12 +702,24 @@ public class LockSettingsService extends ILockSettings.Stub {
Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
};
- // These are protected with a read permission
+ // Reading these settings needs the profile permission
private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] {
Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
Secure.LOCK_SCREEN_OWNER_INFO
};
+ // Reading these settings needs the same permission as checking the password
+ private static final String[] READ_PASSWORD_PROTECTED_SETTINGS = new String[] {
+ LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
+ LockPatternUtils.PASSWORD_HISTORY_KEY,
+ LockPatternUtils.PASSWORD_TYPE_KEY,
+ };
+
+ private static final String[] SETTINGS_TO_BACKUP = new String[] {
+ Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
+ Secure.LOCK_SCREEN_OWNER_INFO
+ };
+
private IMountService getMountService() {
final IBinder service = ServiceManager.getService("mount");
if (service != null) {
@@ -433,4 +727,33 @@ public class LockSettingsService extends ILockSettings.Stub {
}
return null;
}
+
+ private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
+ @Override
+ public void binderDied() {
+ mGateKeeperService.asBinder().unlinkToDeath(this, 0);
+ mGateKeeperService = null;
+ }
+ }
+
+ private synchronized IGateKeeperService getGateKeeperService()
+ throws RemoteException {
+ if (mGateKeeperService != null) {
+ return mGateKeeperService;
+ }
+
+ final IBinder service =
+ ServiceManager.getService("android.service.gatekeeper.IGateKeeperService");
+ if (service != null) {
+ service.linkToDeath(new GateKeeperDiedRecipient(), 0);
+ mGateKeeperService = IGateKeeperService.Stub.asInterface(service);
+ return mGateKeeperService;
+ }
+
+ Slog.e(TAG, "Unable to acquire GateKeeperService");
+ return null;
+ }
+
+ private class VerificationFailedException extends Exception {}
+
}
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index c03bb58..f202c36 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -56,8 +56,10 @@ class LockSettingsStorage {
};
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 static final String LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
+ private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
+ private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
+ private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
private static final Object DEFAULT = new Object();
@@ -66,6 +68,25 @@ class LockSettingsStorage {
private final Cache mCache = new Cache();
private final Object mFileWriteLock = new Object();
+ private int mStoredCredentialType;
+
+ class CredentialHash {
+ static final int TYPE_NONE = -1;
+ static final int TYPE_PATTERN = 1;
+ static final int TYPE_PASSWORD = 2;
+
+ static final int VERSION_LEGACY = 0;
+ static final int VERSION_GATEKEEPER = 1;
+
+ CredentialHash(byte[] hash, int version) {
+ this.hash = hash;
+ this.version = version;
+ }
+
+ byte[] hash;
+ int version;
+ }
+
public LockSettingsStorage(Context context, Callback callback) {
mContext = context;
mOpenHelper = new DatabaseHelper(context, callback);
@@ -148,28 +169,72 @@ class LockSettingsStorage {
readPatternHash(userId);
}
- public byte[] readPasswordHash(int userId) {
- final byte[] stored = readFile(getLockPasswordFilename(userId));
+ public int getStoredCredentialType(int userId) {
+ if (mStoredCredentialType != 0) {
+ return mStoredCredentialType;
+ }
+
+ CredentialHash pattern = readPatternHash(userId);
+ if (pattern == null) {
+ if (readPasswordHash(userId) != null) {
+ mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
+ } else {
+ mStoredCredentialType = CredentialHash.TYPE_NONE;
+ }
+ } else {
+ CredentialHash password = readPasswordHash(userId);
+ if (password != null) {
+ // Both will never be GateKeeper
+ if (password.version == CredentialHash.VERSION_GATEKEEPER) {
+ mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
+ } else {
+ mStoredCredentialType = CredentialHash.TYPE_PATTERN;
+ }
+ } else {
+ mStoredCredentialType = CredentialHash.TYPE_PATTERN;
+ }
+ }
+
+ return mStoredCredentialType;
+ }
+
+
+ public CredentialHash readPasswordHash(int userId) {
+ byte[] stored = readFile(getLockPasswordFilename(userId));
if (stored != null && stored.length > 0) {
- return stored;
+ return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
}
+
+ stored = readFile(getLegacyLockPasswordFilename(userId));
+ if (stored != null && stored.length > 0) {
+ return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
+ }
+
return null;
}
- public byte[] readPatternHash(int userId) {
- final byte[] stored = readFile(getLockPatternFilename(userId));
+ public CredentialHash readPatternHash(int userId) {
+ byte[] stored = readFile(getLockPatternFilename(userId));
+ if (stored != null && stored.length > 0) {
+ return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
+ }
+
+ stored = readFile(getLegacyLockPatternFilename(userId));
if (stored != null && stored.length > 0) {
- return stored;
+ return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
}
+
return null;
}
public boolean hasPassword(int userId) {
- return hasFile(getLockPasswordFilename(userId));
+ return hasFile(getLockPasswordFilename(userId)) ||
+ hasFile(getLegacyLockPasswordFilename(userId));
}
public boolean hasPattern(int userId) {
- return hasFile(getLockPatternFilename(userId));
+ return hasFile(getLockPatternFilename(userId)) ||
+ hasFile(getLegacyLockPatternFilename(userId));
}
private boolean hasFile(String name) {
@@ -237,13 +302,28 @@ class LockSettingsStorage {
}
public void writePatternHash(byte[] hash, int userId) {
+ mStoredCredentialType = hash == null
+ ? CredentialHash.TYPE_NONE
+ : CredentialHash.TYPE_PATTERN;
writeFile(getLockPatternFilename(userId), hash);
+ clearPasswordHash(userId);
+ }
+
+ private void clearPatternHash(int userId) {
+ writeFile(getLockPatternFilename(userId), null);
}
public void writePasswordHash(byte[] hash, int userId) {
+ mStoredCredentialType = hash == null
+ ? CredentialHash.TYPE_NONE
+ : CredentialHash.TYPE_PASSWORD;
writeFile(getLockPasswordFilename(userId), hash);
+ clearPatternHash(userId);
}
+ private void clearPasswordHash(int userId) {
+ writeFile(getLockPasswordFilename(userId), null);
+ }
@VisibleForTesting
String getLockPatternFilename(int userId) {
@@ -255,6 +335,16 @@ class LockSettingsStorage {
return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
}
+ @VisibleForTesting
+ String getLegacyLockPatternFilename(int userId) {
+ return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
+ }
+
+ @VisibleForTesting
+ String getLegacyLockPasswordFilename(int userId) {
+ return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
+ }
+
private String getLockCredentialFilePathForUser(int userId, String basename) {
userId = getUserParentOrSelfId(userId);
String dataSystemDirectory =
@@ -279,16 +369,15 @@ class LockSettingsStorage {
return userId;
}
-
public void removeUser(int userId) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
final UserInfo parentInfo = um.getProfileParent(userId);
- synchronized (mFileWriteLock) {
- if (parentInfo == null) {
- // This user owns its lock settings files - safe to delete them
+ if (parentInfo == null) {
+ // This user owns its lock settings files - safe to delete them
+ synchronized (mFileWriteLock) {
String name = getLockPasswordFilename(userId);
File file = new File(name);
if (file.exists()) {
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index 0de6a03..e0352e0 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -36,6 +36,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.service.carrier.CarrierMessagingService;
+import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import android.util.Slog;
@@ -111,6 +112,106 @@ public class MmsServiceBroker extends SystemService {
}
};
+ // Instance of IMms for returning failure to service API caller,
+ // used when MmsService cannot be connected.
+ private final IMms mServiceStubForFailure = new IMms() {
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public void sendMessage(int subId, String callingPkg, Uri contentUri, String locationUrl,
+ Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
+ returnPendingIntentWithError(sentIntent);
+ }
+
+ @Override
+ public void downloadMessage(int subId, String callingPkg, String locationUrl,
+ Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent)
+ throws RemoteException {
+ returnPendingIntentWithError(downloadedIntent);
+ }
+
+ @Override
+ public Bundle getCarrierConfigValues(int subId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public Uri importTextMessage(String callingPkg, String address, int type, String text,
+ long timestampMillis, boolean seen, boolean read) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public Uri importMultimediaMessage(String callingPkg, Uri contentUri, String messageId,
+ long timestampSecs, boolean seen, boolean read) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public boolean deleteStoredMessage(String callingPkg, Uri messageUri)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean deleteStoredConversation(String callingPkg, long conversationId)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri,
+ ContentValues statusValues) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean archiveStoredConversation(String callingPkg, long conversationId,
+ boolean archived) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public Uri addTextMessageDraft(String callingPkg, String address, String text)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void sendStoredMessage(int subId, String callingPkg, Uri messageUri,
+ Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
+ returnPendingIntentWithError(sentIntent);
+ }
+
+ @Override
+ public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException {
+ // Do nothing
+ }
+
+ @Override
+ public boolean getAutoPersisting() throws RemoteException {
+ return false;
+ }
+
+ private void returnPendingIntentWithError(PendingIntent pendingIntent) {
+ try {
+ pendingIntent.send(mContext, SmsManager.MMS_ERROR_UNSPECIFIED, null);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.e(TAG, "Failed to return pending intent result", e);
+ }
+ }
+ };
+
public MmsServiceBroker(Context context) {
super(context);
mContext = context;
@@ -145,44 +246,51 @@ public class MmsServiceBroker extends SystemService {
}
}
- private void ensureService() {
+ private IMms getOrConnectService() {
synchronized (this) {
- if (mService == null) {
- // Service is not connected. Try blocking connecting.
- Slog.w(TAG, "MmsService not connected. Try connecting...");
- mConnectionHandler.sendMessage(
- mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING));
- final long shouldEnd =
- SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS;
- long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS;
- while (waitTime > 0) {
- try {
- // TODO: consider using Java concurrent construct instead of raw object wait
- this.wait(waitTime);
- } catch (InterruptedException e) {
- Slog.w(TAG, "Connection wait interrupted", e);
- }
- if (mService != null) {
- // Success
- return;
- }
- // Calculate remaining waiting time to make sure we wait the full timeout period
- waitTime = shouldEnd - SystemClock.elapsedRealtime();
+ if (mService != null) {
+ return mService;
+ }
+ // Service is not connected. Try blocking connecting.
+ Slog.w(TAG, "MmsService not connected. Try connecting...");
+ mConnectionHandler.sendMessage(
+ mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING));
+ final long shouldEnd =
+ SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS;
+ long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS;
+ while (waitTime > 0) {
+ try {
+ // TODO: consider using Java concurrent construct instead of raw object wait
+ this.wait(waitTime);
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Connection wait interrupted", e);
}
- // Timed out. Something's really wrong.
- Slog.e(TAG, "Can not connect to MmsService (timed out)");
- throw new RuntimeException("Timed out in connecting to MmsService");
+ if (mService != null) {
+ // Success
+ return mService;
+ }
+ // Calculate remaining waiting time to make sure we wait the full timeout period
+ waitTime = shouldEnd - SystemClock.elapsedRealtime();
}
+ // Timed out. Something's really wrong.
+ Slog.e(TAG, "Can not connect to MmsService (timed out)");
+ return null;
}
}
/**
- * Making sure when we obtain the mService instance it is always valid.
- * Throws {@link RuntimeException} when it is empty.
+ * Make sure to return a non-empty service instance. Return the connected MmsService
+ * instance, if not connected, try connecting. If fail to connect, return a fake service
+ * instance which returns failure to service caller.
+ *
+ * @return a non-empty service instance, real or fake
*/
private IMms getServiceGuarded() {
- ensureService();
- return mService;
+ final IMms service = getOrConnectService();
+ if (service != null) {
+ return service;
+ }
+ return mServiceStubForFailure;
}
private AppOpsManager getAppOpsManager() {
@@ -264,7 +372,6 @@ public class MmsServiceBroker extends SystemService {
@Override
public Uri importTextMessage(String callingPkg, String address, int type, String text,
long timestampMillis, boolean seen, boolean read) throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import SMS message");
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg) != AppOpsManager.MODE_ALLOWED) {
// Silently fail AppOps failure due to not being the default SMS app
@@ -279,7 +386,6 @@ public class MmsServiceBroker extends SystemService {
public Uri importMultimediaMessage(String callingPkg, Uri contentUri,
String messageId, long timestampSecs, boolean seen, boolean read)
throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import MMS message");
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg) != AppOpsManager.MODE_ALLOWED) {
// Silently fail AppOps failure due to not being the default SMS app
@@ -293,8 +399,6 @@ public class MmsServiceBroker extends SystemService {
@Override
public boolean deleteStoredMessage(String callingPkg, Uri messageUri)
throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
- "Delete SMS/MMS message");
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg) != AppOpsManager.MODE_ALLOWED) {
return false;
@@ -305,7 +409,6 @@ public class MmsServiceBroker extends SystemService {
@Override
public boolean deleteStoredConversation(String callingPkg, long conversationId)
throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Delete conversation");
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg) != AppOpsManager.MODE_ALLOWED) {
return false;
@@ -316,8 +419,10 @@ public class MmsServiceBroker extends SystemService {
@Override
public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri,
ContentValues statusValues) throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
- "Update SMS/MMS message");
+ if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
+ callingPkg) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
return getServiceGuarded()
.updateStoredMessageStatus(callingPkg, messageUri, statusValues);
}
@@ -325,8 +430,10 @@ public class MmsServiceBroker extends SystemService {
@Override
public boolean archiveStoredConversation(String callingPkg, long conversationId,
boolean archived) throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
- "Update SMS/MMS message");
+ if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
+ callingPkg) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
return getServiceGuarded()
.archiveStoredConversation(callingPkg, conversationId, archived);
}
@@ -334,7 +441,6 @@ public class MmsServiceBroker extends SystemService {
@Override
public Uri addTextMessageDraft(String callingPkg, String address, String text)
throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add SMS draft");
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg) != AppOpsManager.MODE_ALLOWED) {
// Silently fail AppOps failure due to not being the default SMS app
@@ -347,7 +453,6 @@ public class MmsServiceBroker extends SystemService {
@Override
public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add MMS draft");
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg) != AppOpsManager.MODE_ALLOWED) {
// Silently fail AppOps failure due to not being the default SMS app
@@ -360,8 +465,6 @@ public class MmsServiceBroker extends SystemService {
@Override
public void sendStoredMessage(int subId, String callingPkg, Uri messageUri,
Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.SEND_SMS,
- "Send stored MMS message");
if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
callingPkg) != AppOpsManager.MODE_ALLOWED) {
return;
@@ -372,7 +475,6 @@ public class MmsServiceBroker extends SystemService {
@Override
public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException {
- mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Set auto persist");
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg) != AppOpsManager.MODE_ALLOWED) {
return;
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 6c981c0..89a7173 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -16,40 +16,41 @@
package com.android.server;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.Manifest;
import android.app.ActivityManagerNative;
import android.app.AppOpsManager;
-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.PackageManager;
-import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.ObbInfo;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.hardware.usb.UsbManager;
+import android.mtp.MtpStorage;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.Environment.UserEnvironment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.DiskInfo;
import android.os.storage.IMountService;
import android.os.storage.IMountServiceListener;
import android.os.storage.IMountShutdownObserver;
@@ -58,31 +59,40 @@ import android.os.storage.OnObbStateChangeListener;
import android.os.storage.StorageManager;
import android.os.storage.StorageResultCode;
import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
import android.text.TextUtils;
-import android.util.AttributeSet;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.DebugUtils;
+import android.util.Log;
import android.util.Slog;
import android.util.Xml;
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+import libcore.util.HexEncoding;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IMediaContainerService;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
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;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import org.apache.commons.codec.binary.Hex;
-import org.apache.commons.codec.DecoderException;
+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;
@@ -102,7 +112,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -111,21 +121,50 @@ import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
/**
- * MountService implements back-end services for platform storage
- * management.
- * @hide - Applications should use android.os.storage.StorageManager
- * to access the MountService.
+ * Service responsible for various storage media. Connects to {@code vold} to
+ * watch for and manage dynamically added storage, such as SD cards and USB mass
+ * storage. Also decides how storage should be presented to users on the device.
*/
class MountService extends IMountService.Stub
implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
+ // TODO: finish enforcing UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA
+
// Static direct instance pointer for the tightly-coupled idle service to use
static MountService sSelf = null;
- // TODO: listen for user creation/deletion
+ public static class Lifecycle extends SystemService {
+ private MountService mMountService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mMountService = new MountService(getContext());
+ publishBinderService("mount", mMountService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mMountService.systemReady();
+ }
+ }
+
+ @Override
+ public void onStartUser(int userHandle) {
+ mMountService.onStartUser(userHandle);
+ }
+
+ @Override
+ public void onCleanupUser(int userHandle) {
+ mMountService.onCleanupUser(userHandle);
+ }
+ }
private static final boolean LOCAL_LOGD = false;
- private static final boolean DEBUG_UNMOUNT = false;
private static final boolean DEBUG_EVENTS = false;
private static final boolean DEBUG_OBB = false;
@@ -140,22 +179,6 @@ class MountService extends IMountService.Stub
private static final int MAX_CONTAINERS = 250;
/*
- * Internal vold volume state constants
- */
- class VolumeState {
- public static final int Init = -1;
- public static final int NoMedia = 0;
- public static final int Idle = 1;
- public static final int Pending = 2;
- public static final int Checking = 3;
- public static final int Mounted = 4;
- public static final int Unmounting = 5;
- public static final int Formatting = 6;
- public static final int Shared = 7;
- public static final int SharedMnt = 8;
- }
-
- /*
* Internal vold response code constants
*/
class VoldResponseCode {
@@ -189,12 +212,19 @@ class MountService extends IMountService.Stub
/*
* 600 series - Unsolicited broadcasts.
*/
- public static final int VolumeStateChange = 605;
- public static final int VolumeUuidChange = 613;
- public static final int VolumeUserLabelChange = 614;
- public static final int VolumeDiskInserted = 630;
- public static final int VolumeDiskRemoved = 631;
- public static final int VolumeBadRemoval = 632;
+ public static final int DISK_CREATED = 640;
+ public static final int DISK_SIZE_CHANGED = 641;
+ public static final int DISK_LABEL_CHANGED = 642;
+ public static final int DISK_SCANNED = 643;
+ public static final int DISK_DESTROYED = 649;
+
+ public static final int VOLUME_CREATED = 650;
+ public static final int VOLUME_STATE_CHANGED = 651;
+ public static final int VOLUME_FS_TYPE_CHANGED = 652;
+ public static final int VOLUME_FS_UUID_CHANGED = 653;
+ public static final int VOLUME_FS_LABEL_CHANGED = 654;
+ public static final int VOLUME_PATH_CHANGED = 655;
+ public static final int VOLUME_DESTROYED = 659;
/*
* 700 series - fstrim
@@ -202,6 +232,151 @@ class MountService extends IMountService.Stub
public static final int FstrimCompleted = 700;
}
+ private static final int VERSION_INIT = 1;
+ private static final int VERSION_ADD_PRIMARY = 2;
+
+ private static final String TAG_VOLUMES = "volumes";
+ private static final String ATTR_VERSION = "version";
+ private static final String ATTR_PRIMARY_STORAGE_UUID = "primaryStorageUuid";
+ private static final String TAG_VOLUME = "volume";
+ private static final String ATTR_TYPE = "type";
+ private static final String ATTR_FS_UUID = "fsUuid";
+ private static final String ATTR_NICKNAME = "nickname";
+ private static final String ATTR_USER_FLAGS = "userFlags";
+
+ private final AtomicFile mMetadataFile;
+
+ private static class VolumeMetadata {
+ public final int type;
+ public final String fsUuid;
+ public String nickname;
+ public int userFlags;
+
+ public VolumeMetadata(int type, String fsUuid) {
+ this.type = type;
+ this.fsUuid = Preconditions.checkNotNull(fsUuid);
+ }
+
+ public static VolumeMetadata read(XmlPullParser in) throws IOException {
+ final int type = readIntAttribute(in, ATTR_TYPE);
+ final String fsUuid = readStringAttribute(in, ATTR_FS_UUID);
+ final VolumeMetadata meta = new VolumeMetadata(type, fsUuid);
+ meta.nickname = readStringAttribute(in, ATTR_NICKNAME);
+ meta.userFlags = readIntAttribute(in, ATTR_USER_FLAGS);
+ return meta;
+ }
+
+ public static void write(XmlSerializer out, VolumeMetadata meta) throws IOException {
+ out.startTag(null, TAG_VOLUME);
+ writeIntAttribute(out, ATTR_TYPE, meta.type);
+ writeStringAttribute(out, ATTR_FS_UUID, meta.fsUuid);
+ writeStringAttribute(out, ATTR_NICKNAME, meta.nickname);
+ writeIntAttribute(out, ATTR_USER_FLAGS, meta.userFlags);
+ out.endTag(null, TAG_VOLUME);
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("VolumeMetadata:");
+ pw.increaseIndent();
+ pw.printPair("type", DebugUtils.valueToString(VolumeInfo.class, "TYPE_", type));
+ pw.printPair("fsUuid", fsUuid);
+ pw.printPair("nickname", nickname);
+ pw.printPair("userFlags",
+ DebugUtils.flagsToString(VolumeInfo.class, "USER_FLAG_", userFlags));
+ pw.decreaseIndent();
+ pw.println();
+ }
+ }
+
+ /**
+ * <em>Never</em> hold the lock while performing downcalls into vold, since
+ * unsolicited events can suddenly appear to update data structures.
+ */
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private int[] mStartedUsers = EmptyArray.INT;
+
+ /** Map from disk ID to disk */
+ @GuardedBy("mLock")
+ private ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>();
+ /** Map from volume ID to disk */
+ @GuardedBy("mLock")
+ private ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>();
+
+ /** Map from UUID to metadata */
+ @GuardedBy("mLock")
+ private ArrayMap<String, VolumeMetadata> mMetadata = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private String mPrimaryStorageUuid;
+
+ /** Map from disk ID to latches */
+ @GuardedBy("mLock")
+ private ArrayMap<String, CountDownLatch> mDiskScanLatches = new ArrayMap<>();
+
+ private DiskInfo findDiskById(String id) {
+ synchronized (mLock) {
+ final DiskInfo disk = mDisks.get(id);
+ if (disk != null) {
+ return disk;
+ }
+ }
+ throw new IllegalArgumentException("No disk found for ID " + id);
+ }
+
+ private VolumeInfo findVolumeById(String id) {
+ synchronized (mLock) {
+ final VolumeInfo vol = mVolumes.get(id);
+ if (vol != null) {
+ return vol;
+ }
+ }
+ throw new IllegalArgumentException("No volume found for ID " + id);
+ }
+
+ @Deprecated
+ private String findVolumeIdForPath(String path) {
+ synchronized (mLock) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.path != null && path.startsWith(vol.path)) {
+ return vol.id;
+ }
+ }
+ }
+ throw new IllegalArgumentException("No volume found for path " + path);
+ }
+
+ private VolumeMetadata findOrCreateMetadataLocked(VolumeInfo vol) {
+ VolumeMetadata meta = mMetadata.get(vol.fsUuid);
+ if (meta == null) {
+ meta = new VolumeMetadata(vol.type, vol.fsUuid);
+ mMetadata.put(meta.fsUuid, meta);
+ }
+ return meta;
+ }
+
+ private CountDownLatch findOrCreateDiskScanLatch(String diskId) {
+ synchronized (mLock) {
+ CountDownLatch latch = mDiskScanLatches.get(diskId);
+ if (latch == null) {
+ latch = new CountDownLatch(1);
+ mDiskScanLatches.put(diskId, latch);
+ }
+ return latch;
+ }
+ }
+
+ private static int sNextMtpIndex = 1;
+
+ private static int allocateMtpIndex(String volId) {
+ if (VolumeInfo.ID_EMULATED_INTERNAL.equals(volId)) {
+ return 0;
+ } else {
+ return sNextMtpIndex++;
+ }
+ }
+
/** List of crypto types.
* These must match CRYPT_TYPE_XXX in cryptfs.h AND their
* corresponding commands in CommandListener.cpp */
@@ -211,33 +386,19 @@ class MountService extends IMountService.Stub
private final Context mContext;
private final NativeDaemonConnector mConnector;
- private final Object mVolumesLock = new Object();
-
- /** When defined, base template for user-specific {@link StorageVolume}. */
- private StorageVolume mEmulatedTemplate;
+ private volatile boolean mSystemReady = false;
+ private volatile boolean mDaemonConnected = false;
- // TODO: separate storage volumes on per-user basis
+ private PackageManagerService mPms;
- @GuardedBy("mVolumesLock")
- private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList();
- /** Map from path to {@link StorageVolume} */
- @GuardedBy("mVolumesLock")
- private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap();
- /** Map from path to state */
- @GuardedBy("mVolumesLock")
- private final HashMap<String, String> mVolumeStates = Maps.newHashMap();
+ private final Callbacks mCallbacks;
- private volatile boolean mSystemReady = false;
-
- private PackageManagerService mPms;
- private boolean mUmsEnabling;
- private boolean mUmsAvailable = false;
- // Used as a lock for methods that register/unregister listeners.
- final private ArrayList<MountServiceBinderListener> mListeners =
- new ArrayList<MountServiceBinderListener>();
private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
- private boolean mSendUmsConnectedOnBoot = false;
+
+ private final Object mUnmountLock = new Object();
+ @GuardedBy("mUnmountLock")
+ private CountDownLatch mUnmountSignal;
/**
* Private hash of currently mounted secure containers.
@@ -346,6 +507,7 @@ class MountService extends IMountService.Stub
final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
class DefaultContainerConnection implements ServiceConnection {
+ @Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG_OBB)
Slog.i(TAG, "onServiceConnected");
@@ -353,6 +515,7 @@ class MountService extends IMountService.Stub
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
}
+ @Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG_OBB)
Slog.i(TAG, "onServiceDisconnected");
@@ -368,181 +531,35 @@ class MountService extends IMountService.Stub
private long mLastMaintenance;
// Handler messages
- private static final int H_UNMOUNT_PM_UPDATE = 1;
- private static final int H_UNMOUNT_PM_DONE = 2;
- private static final int H_UNMOUNT_MS = 3;
- private static final int H_SYSTEM_READY = 4;
- private static final int H_FSTRIM = 5;
-
- private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
- private static final int MAX_UNMOUNT_RETRIES = 4;
-
- class UnmountCallBack {
- final String path;
- final boolean force;
- final boolean removeEncryption;
- int retries;
-
- UnmountCallBack(String path, boolean force, boolean removeEncryption) {
- retries = 0;
- this.path = path;
- this.force = force;
- this.removeEncryption = removeEncryption;
- }
-
- void handleFinished() {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path);
- doUnmountVolume(path, true, removeEncryption);
- }
- }
-
- class UmsEnableCallBack extends UnmountCallBack {
- final String method;
-
- UmsEnableCallBack(String path, String method, boolean force) {
- super(path, force, false);
- this.method = method;
- }
-
- @Override
- void handleFinished() {
- super.handleFinished();
- doShareUnshareVolume(path, method, true);
- }
- }
-
- class ShutdownCallBack extends UnmountCallBack {
- MountShutdownLatch mMountShutdownLatch;
- ShutdownCallBack(String path, final MountShutdownLatch mountShutdownLatch) {
- super(path, true, false);
- mMountShutdownLatch = mountShutdownLatch;
- }
-
- @Override
- void handleFinished() {
- int ret = doUnmountVolume(path, true, removeEncryption);
- Slog.i(TAG, "Unmount completed: " + path + ", result code: " + ret);
- mMountShutdownLatch.countDown();
- }
- }
-
- static class MountShutdownLatch {
- private IMountShutdownObserver mObserver;
- private AtomicInteger mCount;
-
- MountShutdownLatch(final IMountShutdownObserver observer, int count) {
- mObserver = observer;
- mCount = new AtomicInteger(count);
- }
-
- void countDown() {
- boolean sendShutdown = false;
- if (mCount.decrementAndGet() == 0) {
- sendShutdown = true;
- }
- if (sendShutdown && mObserver != null) {
- try {
- mObserver.onShutDownComplete(StorageResultCode.OperationSucceeded);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException when shutting down");
- }
- }
- }
- }
+ private static final int H_SYSTEM_READY = 1;
+ private static final int H_DAEMON_CONNECTED = 2;
+ private static final int H_SHUTDOWN = 3;
+ private static final int H_FSTRIM = 4;
+ private static final int H_VOLUME_MOUNT = 5;
+ private static final int H_VOLUME_BROADCAST = 6;
class MountServiceHandler extends Handler {
- ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
- boolean mUpdatingStatus = false;
-
- MountServiceHandler(Looper l) {
- super(l);
+ public MountServiceHandler(Looper looper) {
+ super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case H_UNMOUNT_PM_UPDATE: {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE");
- UnmountCallBack ucb = (UnmountCallBack) msg.obj;
- mForceUnmounts.add(ucb);
- if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus);
- // Register only if needed.
- if (!mUpdatingStatus) {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
- mUpdatingStatus = true;
- mPms.updateExternalMediaStatus(false, true);
- }
- break;
- }
- case H_UNMOUNT_PM_DONE: {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE");
- if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests");
- mUpdatingStatus = false;
- int size = mForceUnmounts.size();
- int sizeArr[] = new int[size];
- int sizeArrN = 0;
- // Kill processes holding references first
- ActivityManagerService ams = (ActivityManagerService)
- ServiceManager.getService("activity");
- for (int i = 0; i < size; i++) {
- UnmountCallBack ucb = mForceUnmounts.get(i);
- String path = ucb.path;
- boolean done = false;
- if (!ucb.force) {
- done = true;
- } else {
- int pids[] = getStorageUsers(path);
- if (pids == null || pids.length == 0) {
- done = true;
- } else {
- // Eliminate system process here?
- ams.killPids(pids, "unmount media", true);
- // Confirm if file references have been freed.
- pids = getStorageUsers(path);
- if (pids == null || pids.length == 0) {
- done = true;
- }
- }
- }
- if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
- // Retry again
- Slog.i(TAG, "Retrying to kill storage users again");
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
- ucb.retries++),
- RETRY_UNMOUNT_DELAY);
- } else {
- if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
- Slog.i(TAG, "Failed to unmount media inspite of " +
- MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
- }
- sizeArr[sizeArrN++] = i;
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
- ucb));
- }
- }
- // Remove already processed elements from list.
- for (int i = (sizeArrN-1); i >= 0; i--) {
- mForceUnmounts.remove(sizeArr[i]);
- }
- break;
- }
- case H_UNMOUNT_MS: {
- if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS");
- UnmountCallBack ucb = (UnmountCallBack) msg.obj;
- ucb.handleFinished();
+ case H_SYSTEM_READY: {
+ handleSystemReady();
break;
}
- case H_SYSTEM_READY: {
- try {
- handleSystemReady();
- } catch (Exception ex) {
- Slog.e(TAG, "Boot-time mount exception", ex);
- }
+ case H_DAEMON_CONNECTED: {
+ handleDaemonConnected();
break;
}
case H_FSTRIM: {
- waitForReady();
+ if (!isReady()) {
+ Slog.i(TAG, "fstrim requested, but no daemon connection yet; trying again");
+ sendMessageDelayed(obtainMessage(H_FSTRIM), DateUtils.SECOND_IN_MILLIS);
+ }
+
Slog.i(TAG, "Running fstrim idle maintenance");
// Remember when we kicked it off
@@ -569,31 +586,72 @@ class MountService extends IMountService.Stub
}
break;
}
+ case H_SHUTDOWN: {
+ final IMountShutdownObserver obs = (IMountShutdownObserver) msg.obj;
+ boolean success = false;
+ try {
+ success = mConnector.execute("volume", "shutdown").isClassOk();
+ } catch (NativeDaemonConnectorException ignored) {
+ }
+ if (obs != null) {
+ try {
+ obs.onShutDownComplete(success ? 0 : -1);
+ } catch (RemoteException ignored) {
+ }
+ }
+ break;
+ }
+ case H_VOLUME_MOUNT: {
+ final VolumeInfo vol = (VolumeInfo) msg.obj;
+ try {
+ mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
+ vol.mountUserId);
+ } catch (NativeDaemonConnectorException ignored) {
+ }
+ break;
+ }
+ case H_VOLUME_BROADCAST: {
+ final StorageVolume userVol = (StorageVolume) msg.obj;
+ final String envState = userVol.getState();
+ Slog.d(TAG, "Volume " + userVol.getId() + " broadcasting " + envState + " to "
+ + userVol.getOwner());
+
+ final String action = VolumeInfo.getBroadcastForEnvironment(envState);
+ if (action != null) {
+ final Intent intent = new Intent(action,
+ Uri.fromFile(userVol.getPathFile()));
+ intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, userVol.getOwner());
+ }
+ break;
+ }
}
}
- };
+ }
private final Handler mHandler;
- void waitForAsecScan() {
- waitForLatch(mAsecsScanned);
+ @Override
+ public void waitForAsecScan() {
+ waitForLatch(mAsecsScanned, "mAsecsScanned");
}
private void waitForReady() {
- waitForLatch(mConnectedSignal);
+ waitForLatch(mConnectedSignal, "mConnectedSignal");
}
- private void waitForLatch(CountDownLatch latch) {
- for (;;) {
+ private void waitForLatch(CountDownLatch latch, String condition) {
+ while (true) {
try {
if (latch.await(5000, TimeUnit.MILLISECONDS)) {
return;
} else {
Slog.w(TAG, "Thread " + Thread.currentThread().getName()
- + " still waiting for MountService ready...");
+ + " still waiting for " + condition + "...");
}
} catch (InterruptedException e) {
- Slog.w(TAG, "Interrupt while waiting for MountService to be ready.");
+ Slog.w(TAG, "Interrupt while waiting for " + condition);
}
}
}
@@ -607,109 +665,72 @@ class MountService extends IMountService.Stub
}
private void handleSystemReady() {
- // Snapshot current volume states since it's not safe to call into vold
- // while holding locks.
- final HashMap<String, String> snapshot;
- synchronized (mVolumesLock) {
- snapshot = new HashMap<String, String>(mVolumeStates);
- }
-
- for (Map.Entry<String, String> entry : snapshot.entrySet()) {
- final String path = entry.getKey();
- final String state = entry.getValue();
-
- if (state.equals(Environment.MEDIA_UNMOUNTED)) {
- int rc = doMountVolume(path);
- if (rc != StorageResultCode.OperationSucceeded) {
- Slog.e(TAG, String.format("Boot-time mount failed (%d)",
- rc));
- }
- } else if (state.equals(Environment.MEDIA_SHARED)) {
- /*
- * Bootstrap UMS enabled state since vold indicates
- * the volume is shared (runtime restart while ums enabled)
- */
- notifyVolumeStateChange(null, path, VolumeState.NoMedia,
- VolumeState.Shared);
- }
- }
-
- // Push mounted state for all emulated storage
- synchronized (mVolumesLock) {
- for (StorageVolume volume : mVolumes) {
- if (volume.isEmulated()) {
- updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
- }
- }
- }
-
- /*
- * If UMS was connected on boot, send the connected event
- * now that we're up.
- */
- if (mSendUmsConnectedOnBoot) {
- sendUmsIntent(true);
- mSendUmsConnectedOnBoot = false;
- }
+ resetIfReadyAndConnected();
- /*
- * Start scheduling nominally-daily fstrim operations
- */
+ // Start scheduling nominally-daily fstrim operations
MountServiceIdler.scheduleIdlePass(mContext);
}
- private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userId == -1) return;
- final UserHandle user = new UserHandle(userId);
-
- final String action = intent.getAction();
- if (Intent.ACTION_USER_ADDED.equals(action)) {
- synchronized (mVolumesLock) {
- createEmulatedVolumeForUserLocked(user);
- }
+ private void resetIfReadyAndConnected() {
+ Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
+ + ", mDaemonConnected=" + mDaemonConnected);
+ if (mSystemReady && mDaemonConnected) {
+ mDisks.clear();
+ mVolumes.clear();
- } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
- synchronized (mVolumesLock) {
- final List<StorageVolume> toRemove = Lists.newArrayList();
- for (StorageVolume volume : mVolumes) {
- if (user.equals(volume.getOwner())) {
- toRemove.add(volume);
- }
- }
- for (StorageVolume volume : toRemove) {
- removeVolumeLocked(volume);
- }
- }
+ // Create a stub volume that represents internal storage
+ final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
+ VolumeInfo.TYPE_PRIVATE, null, 0);
+ internal.state = VolumeInfo.STATE_MOUNTED;
+ internal.path = Environment.getDataDirectory().getAbsolutePath();
+ mVolumes.put(internal.id, internal);
+
+ try {
+ mConnector.execute("volume", "reset");
+ } catch (NativeDaemonConnectorException e) {
+ Slog.w(TAG, "Failed to reset vold", e);
}
}
- };
+ }
- private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) &&
- intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false));
- notifyShareAvailabilityChange(available);
+ private void onStartUser(int userId) {
+ Slog.d(TAG, "onStartUser " + userId);
+
+ // We purposefully block here to make sure that user-specific
+ // staging area is ready so it's ready for zygote-forked apps to
+ // bind mount against.
+ try {
+ mConnector.execute("volume", "start_user", userId);
+ } catch (NativeDaemonConnectorException ignored) {
}
- };
- private final class MountServiceBinderListener implements IBinder.DeathRecipient {
- final IMountServiceListener mListener;
+ // Record user as started so newly mounted volumes kick off events
+ // correctly, then synthesize events for any already-mounted volumes.
+ synchronized (mVolumes) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.isVisibleToUser(userId) && vol.isMountedReadable()) {
+ final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
+ mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
+
+ final String envState = VolumeInfo.getEnvironmentForState(vol.getState());
+ mCallbacks.notifyStorageStateChanged(userVol.getPath(), envState, envState);
+ }
+ }
+ mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userId);
+ }
+ }
- MountServiceBinderListener(IMountServiceListener listener) {
- mListener = listener;
+ private void onCleanupUser(int userId) {
+ Slog.d(TAG, "onCleanupUser " + userId);
+ try {
+ mConnector.execute("volume", "cleanup_user", userId);
+ } catch (NativeDaemonConnectorException ignored) {
}
- public void binderDied() {
- if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
- synchronized (mListeners) {
- mListeners.remove(this);
- mListener.asBinder().unlinkToDeath(this, 0);
- }
+ synchronized (mVolumes) {
+ mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userId);
}
}
@@ -720,7 +741,7 @@ class MountService extends IMountService.Stub
// Binder entry point for kicking off an immediate fstrim
@Override
public void runMaintenance() {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
runIdleMaintenance(null);
}
@@ -729,144 +750,35 @@ class MountService extends IMountService.Stub
return mLastMaintenance;
}
- private void doShareUnshareVolume(String path, String method, boolean enable) {
- // TODO: Add support for multiple share methods
- if (!method.equals("ums")) {
- throw new IllegalArgumentException(String.format("Method %s not supported", method));
- }
-
- try {
- mConnector.execute("volume", enable ? "share" : "unshare", path, method);
- } catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to share/unshare", e);
- }
- }
-
- private void updatePublicVolumeState(StorageVolume volume, String state) {
- final String path = volume.getPath();
- final String oldState;
- synchronized (mVolumesLock) {
- oldState = mVolumeStates.put(path, state);
- volume.setState(state);
- }
-
- if (state.equals(oldState)) {
- Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
- state, state, path));
- return;
- }
-
- Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
-
- // Tell PackageManager about changes to primary volume state, but only
- // when not emulated.
- if (volume.isPrimary() && !volume.isEmulated()) {
- if (Environment.MEDIA_UNMOUNTED.equals(state)) {
- mPms.updateExternalMediaStatus(false, false);
-
- /*
- * Some OBBs might have been unmounted when this volume was
- * unmounted, so send a message to the handler to let it know to
- * remove those from the list of mounted OBBS.
- */
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
- OBB_FLUSH_MOUNT_STATE, path));
- } else if (Environment.MEDIA_MOUNTED.equals(state)) {
- mPms.updateExternalMediaStatus(true, false);
- }
- }
-
- synchronized (mListeners) {
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- bl.mListener.onStorageStateChanged(path, oldState, state);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Listener dead");
- mListeners.remove(i);
- } catch (Exception ex) {
- Slog.e(TAG, "Listener failed", ex);
- }
- }
- }
- }
-
/**
* Callback from NativeDaemonConnector
*/
+ @Override
public void onDaemonConnected() {
- /*
- * Since we'll be calling back into the NativeDaemonConnector,
- * we need to do our work in a new thread.
- */
- new Thread("MountService#onDaemonConnected") {
- @Override
- public void run() {
- /**
- * Determine media state and UMS detection status
- */
- try {
- final String[] vols = NativeDaemonEvent.filterMessageList(
- mConnector.executeForList("volume", "list", "broadcast"),
- VoldResponseCode.VolumeListResult);
- for (String volstr : vols) {
- String[] tok = volstr.split(" ");
- // FMT: <label> <mountpoint> <state>
- String path = tok[1];
- String state = Environment.MEDIA_REMOVED;
-
- final StorageVolume volume;
- synchronized (mVolumesLock) {
- volume = mVolumesByPath.get(path);
- }
-
- int st = Integer.parseInt(tok[2]);
- if (st == VolumeState.NoMedia) {
- state = Environment.MEDIA_REMOVED;
- } else if (st == VolumeState.Idle) {
- state = Environment.MEDIA_UNMOUNTED;
- } else if (st == VolumeState.Mounted) {
- state = Environment.MEDIA_MOUNTED;
- Slog.i(TAG, "Media already mounted on daemon connection");
- } else if (st == VolumeState.Shared) {
- state = Environment.MEDIA_SHARED;
- Slog.i(TAG, "Media shared on daemon connection");
- } else {
- throw new Exception(String.format("Unexpected state %d", st));
- }
+ mDaemonConnected = true;
+ mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget();
+ }
- if (state != null) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
- updatePublicVolumeState(volume, state);
- }
- }
- } catch (Exception e) {
- Slog.e(TAG, "Error processing initial volume state", e);
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null) {
- updatePublicVolumeState(primary, Environment.MEDIA_REMOVED);
- }
- }
+ private void handleDaemonConnected() {
+ resetIfReadyAndConnected();
- /*
- * Now that we've done our initialization, release
- * the hounds!
- */
- mConnectedSignal.countDown();
+ /*
+ * Now that we've done our initialization, release
+ * the hounds!
+ */
+ mConnectedSignal.countDown();
- // On an encrypted device we can't see system properties yet, so pull
- // the system locale out of the mount service.
- if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
- copyLocaleFromMountService();
- }
+ // On an encrypted device we can't see system properties yet, so pull
+ // the system locale out of the mount service.
+ if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
+ copyLocaleFromMountService();
+ }
- // Let package manager load internal ASECs.
- mPms.scanAvailableAsecs();
+ // Let package manager load internal ASECs.
+ mPms.scanAvailableAsecs();
- // Notify people waiting for ASECs to be scanned that it's done.
- mAsecsScanned.countDown();
- }
- }.start();
+ // Notify people waiting for ASECs to be scanned that it's done.
+ mAsecsScanned.countDown();
}
private void copyLocaleFromMountService() {
@@ -892,13 +804,13 @@ class MountService extends IMountService.Stub
// Temporary workaround for http://b/17945169.
Slog.d(TAG, "Setting system properties to " + systemLocale + " from mount service");
- SystemProperties.set("persist.sys.language", locale.getLanguage());
- SystemProperties.set("persist.sys.country", locale.getCountry());
+ SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
}
/**
* Callback from NativeDaemonConnector
*/
+ @Override
public boolean onCheckHoldWakeLock(int code) {
return false;
}
@@ -906,568 +818,282 @@ class MountService extends IMountService.Stub
/**
* Callback from NativeDaemonConnector
*/
+ @Override
public boolean onEvent(int code, String raw, String[] cooked) {
- if (DEBUG_EVENTS) {
- StringBuilder builder = new StringBuilder();
- builder.append("onEvent::");
- builder.append(" raw= " + raw);
- if (cooked != null) {
- builder.append(" cooked = " );
- for (String str : cooked) {
- builder.append(" " + str);
- }
- }
- Slog.i(TAG, builder.toString());
+ synchronized (mLock) {
+ return onEventLocked(code, raw, cooked);
}
- if (code == VoldResponseCode.VolumeStateChange) {
- /*
- * One of the volumes we're managing has changed state.
- * Format: "NNN Volume <label> <path> state changed
- * from <old_#> (<old_str>) to <new_#> (<new_str>)"
- */
- notifyVolumeStateChange(
- cooked[2], cooked[3], Integer.parseInt(cooked[7]),
- Integer.parseInt(cooked[10]));
- } else if (code == VoldResponseCode.VolumeUuidChange) {
- // Format: nnn <label> <path> <uuid>
- final String path = cooked[2];
- final String uuid = (cooked.length > 3) ? cooked[3] : null;
-
- final StorageVolume vol = mVolumesByPath.get(path);
- if (vol != null) {
- vol.setUuid(uuid);
- }
-
- } else if (code == VoldResponseCode.VolumeUserLabelChange) {
- // Format: nnn <label> <path> <label>
- final String path = cooked[2];
- final String userLabel = (cooked.length > 3) ? cooked[3] : null;
-
- final StorageVolume vol = mVolumesByPath.get(path);
- if (vol != null) {
- vol.setUserLabel(userLabel);
- }
-
- } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
- (code == VoldResponseCode.VolumeDiskRemoved) ||
- (code == VoldResponseCode.VolumeBadRemoval)) {
- // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
- // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
- // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
- String action = null;
- final String label = cooked[2];
- final String path = cooked[3];
- int major = -1;
- int minor = -1;
+ }
- try {
- String devComp = cooked[6].substring(1, cooked[6].length() -1);
- String[] devTok = devComp.split(":");
- major = Integer.parseInt(devTok[0]);
- minor = Integer.parseInt(devTok[1]);
- } catch (Exception ex) {
- Slog.e(TAG, "Failed to parse major/minor", ex);
+ private boolean onEventLocked(int code, String raw, String[] cooked) {
+ switch (code) {
+ case VoldResponseCode.DISK_CREATED: {
+ if (cooked.length != 3) break;
+ final String id = cooked[1];
+ int flags = Integer.parseInt(cooked[2]);
+ if (SystemProperties.getBoolean(StorageManager.PROP_FORCE_ADOPTABLE, false)) {
+ flags |= DiskInfo.FLAG_ADOPTABLE;
+ }
+ mDisks.put(id, new DiskInfo(id, flags));
+ break;
+ }
+ case VoldResponseCode.DISK_SIZE_CHANGED: {
+ if (cooked.length != 3) break;
+ final DiskInfo disk = mDisks.get(cooked[1]);
+ if (disk != null) {
+ disk.size = Long.parseLong(cooked[2]);
+ }
+ break;
+ }
+ case VoldResponseCode.DISK_LABEL_CHANGED: {
+ final DiskInfo disk = mDisks.get(cooked[1]);
+ if (disk != null) {
+ final StringBuilder builder = new StringBuilder();
+ for (int i = 2; i < cooked.length; i++) {
+ builder.append(cooked[i]).append(' ');
+ }
+ disk.label = builder.toString().trim();
+ }
+ break;
}
-
- final StorageVolume volume;
- final String state;
- synchronized (mVolumesLock) {
- volume = mVolumesByPath.get(path);
- state = mVolumeStates.get(path);
+ case VoldResponseCode.DISK_SCANNED: {
+ if (cooked.length != 2) break;
+ final DiskInfo disk = mDisks.get(cooked[1]);
+ if (disk != null) {
+ onDiskScannedLocked(disk);
+ }
+ break;
+ }
+ case VoldResponseCode.DISK_DESTROYED: {
+ if (cooked.length != 2) break;
+ mDisks.remove(cooked[1]);
+ break;
+ }
+
+ case VoldResponseCode.VOLUME_CREATED: {
+ final String id = cooked[1];
+ final int type = Integer.parseInt(cooked[2]);
+ final String diskId = (cooked.length == 4) ? cooked[3] : null;
+ final DiskInfo disk = mDisks.get(diskId);
+ final int mtpIndex = allocateMtpIndex(id);
+ final VolumeInfo vol = new VolumeInfo(id, type, disk, mtpIndex);
+ mVolumes.put(id, vol);
+ onVolumeCreatedLocked(vol);
+ break;
+ }
+ case VoldResponseCode.VOLUME_STATE_CHANGED: {
+ if (cooked.length != 3) break;
+ final VolumeInfo vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ final int oldState = vol.state;
+ final int newState = Integer.parseInt(cooked[2]);
+ vol.state = newState;
+ onVolumeStateChangedLocked(vol.clone(), oldState, newState);
+ }
+ break;
}
-
- if (code == VoldResponseCode.VolumeDiskInserted) {
- new Thread("MountService#VolumeDiskInserted") {
- @Override
- public void run() {
- try {
- int rc;
- if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
- Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
- }
- } catch (Exception ex) {
- Slog.w(TAG, "Failed to mount media on insertion", ex);
- }
+ case VoldResponseCode.VOLUME_FS_TYPE_CHANGED: {
+ if (cooked.length != 3) break;
+ final VolumeInfo vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ vol.fsType = cooked[2];
+ }
+ mCallbacks.notifyVolumeMetadataChanged(vol.clone());
+ break;
+ }
+ case VoldResponseCode.VOLUME_FS_UUID_CHANGED: {
+ if (cooked.length != 3) break;
+ final VolumeInfo vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ vol.fsUuid = cooked[2];
+ }
+ refreshMetadataLocked();
+ mCallbacks.notifyVolumeMetadataChanged(vol.clone());
+ break;
+ }
+ case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
+ final VolumeInfo vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ final StringBuilder builder = new StringBuilder();
+ for (int i = 2; i < cooked.length; i++) {
+ builder.append(cooked[i]).append(' ');
}
- }.start();
- } else if (code == VoldResponseCode.VolumeDiskRemoved) {
- /*
- * This event gets trumped if we're already in BAD_REMOVAL state
- */
- if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
- return true;
+ vol.fsLabel = builder.toString().trim();
}
- /* Send the media unmounted event first */
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
-
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
- updatePublicVolumeState(volume, Environment.MEDIA_REMOVED);
- action = Intent.ACTION_MEDIA_REMOVED;
- } else if (code == VoldResponseCode.VolumeBadRemoval) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
- /* Send the media unmounted event first */
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
-
- 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));
+ mCallbacks.notifyVolumeMetadataChanged(vol.clone());
+ break;
+ }
+ case VoldResponseCode.VOLUME_PATH_CHANGED: {
+ if (cooked.length != 3) break;
+ final VolumeInfo vol = mVolumes.get(cooked[1]);
+ if (vol != null) {
+ vol.path = cooked[2];
+ }
+ break;
+ }
+ case VoldResponseCode.VOLUME_DESTROYED: {
+ if (cooked.length != 2) break;
+ mVolumes.remove(cooked[1]);
+ break;
}
- if (action != null) {
- sendStorageIntent(action, volume, UserHandle.ALL);
+ case VoldResponseCode.FstrimCompleted: {
+ EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
+ break;
+ }
+ default: {
+ Slog.d(TAG, "Unhandled vold event " + code);
}
- } else {
- return false;
}
return true;
}
- private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
- final StorageVolume volume;
- final String state;
- synchronized (mVolumesLock) {
- volume = mVolumesByPath.get(path);
- state = getVolumeState(path);
- }
-
- if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state);
-
- String action = null;
-
- if (oldState == VolumeState.Shared && newState != oldState) {
- if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
- sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL);
- }
-
- if (newState == VolumeState.Init) {
- } else if (newState == VolumeState.NoMedia) {
- // NoMedia is handled via Disk Remove events
- } else if (newState == VolumeState.Idle) {
- /*
- * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
- * if we're in the process of enabling UMS
- */
- if (!state.equals(
- Environment.MEDIA_BAD_REMOVAL) && !state.equals(
- Environment.MEDIA_NOFS) && !state.equals(
- Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
- if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- action = Intent.ACTION_MEDIA_UNMOUNTED;
- }
- } else if (newState == VolumeState.Pending) {
- } else if (newState == VolumeState.Checking) {
- if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
- updatePublicVolumeState(volume, Environment.MEDIA_CHECKING);
- action = Intent.ACTION_MEDIA_CHECKING;
- } else if (newState == VolumeState.Mounted) {
- if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
- updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
- action = Intent.ACTION_MEDIA_MOUNTED;
- } else if (newState == VolumeState.Unmounting) {
- action = Intent.ACTION_MEDIA_EJECT;
- } else if (newState == VolumeState.Formatting) {
- } else if (newState == VolumeState.Shared) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
- /* Send the media unmounted event first */
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
- sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
-
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
- updatePublicVolumeState(volume, Environment.MEDIA_SHARED);
- action = Intent.ACTION_MEDIA_SHARED;
- if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
- } else if (newState == VolumeState.SharedMnt) {
- Slog.e(TAG, "Live shared mounts not supported yet!");
- return;
- } else {
- Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
- }
-
- if (action != null) {
- sendStorageIntent(action, volume, UserHandle.ALL);
- }
- }
-
- private int doMountVolume(String path) {
- int rc = StorageResultCode.OperationSucceeded;
-
- final StorageVolume volume;
- synchronized (mVolumesLock) {
- volume = mVolumesByPath.get(path);
- }
+ private void onDiskScannedLocked(DiskInfo disk) {
+ final Intent intent = new Intent(DiskInfo.ACTION_DISK_SCANNED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+ android.Manifest.permission.WRITE_MEDIA_STORAGE);
- if (!volume.isEmulated() && hasUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)) {
- Slog.w(TAG, "User has restriction DISALLOW_MOUNT_PHYSICAL_MEDIA; cannot mount volume.");
- return StorageResultCode.OperationFailedInternalError;
+ final CountDownLatch latch = mDiskScanLatches.remove(disk.id);
+ if (latch != null) {
+ latch.countDown();
}
- if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
- try {
- mConnector.execute("volume", "mount", path);
- } catch (NativeDaemonConnectorException e) {
- /*
- * Mount failed for some reason
- */
- String action = null;
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedNoMedia) {
- /*
- * Attempt to mount but no media inserted
- */
- rc = StorageResultCode.OperationFailedNoMedia;
- } else if (code == VoldResponseCode.OpFailedMediaBlank) {
- if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs");
- /*
- * Media is blank or does not contain a supported filesystem
- */
- updatePublicVolumeState(volume, Environment.MEDIA_NOFS);
- action = Intent.ACTION_MEDIA_NOFS;
- rc = StorageResultCode.OperationFailedMediaBlank;
- } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
- if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
- /*
- * Volume consistency check failed
- */
- updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE);
- action = Intent.ACTION_MEDIA_UNMOUNTABLE;
- rc = StorageResultCode.OperationFailedMediaCorrupt;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
- }
-
- /*
- * Send broadcast intent (if required for the failure)
- */
- if (action != null) {
- sendStorageIntent(action, volume, UserHandle.ALL);
+ int volumeCount = 0;
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (Objects.equals(disk.id, vol.getDiskId())) {
+ volumeCount++;
}
}
- return rc;
+ mCallbacks.notifyDiskScanned(disk, volumeCount);
}
- /*
- * If force is not set, we do not unmount if there are
- * processes holding references to the volume about to be unmounted.
- * If force is set, all the processes holding references need to be
- * killed via the ActivityManager before actually unmounting the volume.
- * This might even take a while and might be retried after timed delays
- * to make sure we dont end up in an instable state and kill some core
- * processes.
- * If removeEncryption is set, force is implied, and the system will remove any encryption
- * mapping set on the volume when unmounting.
- */
- private int doUnmountVolume(String path, boolean force, boolean removeEncryption) {
- if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
- return VoldResponseCode.OpFailedVolNotMounted;
- }
-
- /*
- * Force a GC to make sure AssetManagers in other threads of the
- * system_server are cleaned up. We have to do this since AssetManager
- * instances are kept as a WeakReference and it's possible we have files
- * open on the external storage.
- */
- Runtime.getRuntime().gc();
+ private void onVolumeCreatedLocked(VolumeInfo vol) {
+ final boolean primaryPhysical = SystemProperties.getBoolean(
+ StorageManager.PROP_PRIMARY_PHYSICAL, false);
+ // TODO: enable switching to another emulated primary
+ if (VolumeInfo.ID_EMULATED_INTERNAL.equals(vol.id) && !primaryPhysical) {
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
- // Redundant probably. But no harm in updating state again.
- mPms.updateExternalMediaStatus(false, false);
- try {
- final Command cmd = new Command("volume", "unmount", path);
- if (removeEncryption) {
- cmd.appendArg("force_and_revert");
- } else if (force) {
- cmd.appendArg("force");
- }
- mConnector.execute(cmd);
- // We unmounted the volume. None of the asec containers are available now.
- synchronized (mAsecMountSet) {
- mAsecMountSet.clear();
- }
- return StorageResultCode.OperationSucceeded;
- } catch (NativeDaemonConnectorException e) {
- // Don't worry about mismatch in PackageManager since the
- // call back will handle the status changes any way.
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedVolNotMounted) {
- return StorageResultCode.OperationFailedStorageNotMounted;
- } else if (code == VoldResponseCode.OpFailedStorageBusy) {
- return StorageResultCode.OperationFailedStorageBusy;
- } else {
- return StorageResultCode.OperationFailedInternalError;
+ } else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
+ if (primaryPhysical) {
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
}
- }
- }
- private int doFormatVolume(String path) {
- try {
- mConnector.execute("volume", "format", path);
- return StorageResultCode.OperationSucceeded;
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedNoMedia) {
- return StorageResultCode.OperationFailedNoMedia;
- } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
- return StorageResultCode.OperationFailedMediaCorrupt;
- } else {
- return StorageResultCode.OperationFailedInternalError;
+ // Adoptable public disks are visible to apps, since they meet
+ // public API requirement of being in a stable location.
+ final DiskInfo disk = mDisks.get(vol.getDiskId());
+ if (disk != null && disk.isAdoptable()) {
+ vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
}
- }
- }
- private boolean doGetVolumeShared(String path, String method) {
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("volume", "shared", path, method);
- } catch (NativeDaemonConnectorException ex) {
- Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method);
- return false;
- }
+ vol.mountUserId = UserHandle.USER_OWNER;
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
+
+ } else if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
- if (event.getCode() == VoldResponseCode.ShareEnabledResult) {
- return event.getMessage().endsWith("enabled");
} else {
- return false;
+ Slog.d(TAG, "Skipping automatic mounting of " + vol);
}
}
- private void notifyShareAvailabilityChange(final boolean avail) {
- synchronized (mListeners) {
- mUmsAvailable = avail;
- for (int i = mListeners.size() -1; i >= 0; i--) {
- MountServiceBinderListener bl = mListeners.get(i);
- try {
- bl.mListener.onUsbMassStorageConnectionChanged(avail);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Listener dead");
- mListeners.remove(i);
- } catch (Exception ex) {
- Slog.e(TAG, "Listener failed", ex);
- }
- }
+ private boolean isBroadcastWorthy(VolumeInfo vol) {
+ switch (vol.getType()) {
+ case VolumeInfo.TYPE_PUBLIC:
+ case VolumeInfo.TYPE_EMULATED:
+ break;
+ default:
+ return false;
}
- if (mSystemReady == true) {
- sendUmsIntent(avail);
- } else {
- mSendUmsConnectedOnBoot = avail;
+ switch (vol.getState()) {
+ case VolumeInfo.STATE_MOUNTED:
+ case VolumeInfo.STATE_MOUNTED_READ_ONLY:
+ case VolumeInfo.STATE_EJECTING:
+ case VolumeInfo.STATE_UNMOUNTED:
+ break;
+ default:
+ return false;
}
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (avail == false && primary != null
- && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) {
- final String path = primary.getPath();
- /*
- * USB mass storage disconnected while enabled
- */
- new Thread("MountService#AvailabilityChange") {
- @Override
- public void run() {
- try {
- int rc;
- Slog.w(TAG, "Disabling UMS after cable disconnect");
- doShareUnshareVolume(path, "ums", false);
- if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
- Slog.e(TAG, String.format(
- "Failed to remount {%s} on UMS enabled-disconnect (%d)",
- path, rc));
- }
- } catch (Exception ex) {
- Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
- }
- }
- }.start();
- }
- }
-
- private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) {
- final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath()));
- intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- Slog.d(TAG, "sendStorageIntent " + intent + " to " + user);
- mContext.sendBroadcastAsUser(intent, user);
- }
-
- private void sendUmsIntent(boolean c) {
- mContext.sendBroadcastAsUser(
- new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)),
- UserHandle.ALL);
- }
-
- private void validatePermission(String perm) {
- if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(String.format("Requires %s permission", perm));
- }
+ return true;
}
- private boolean hasUserRestriction(String restriction) {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- return um.hasUserRestriction(restriction, Binder.getCallingUserHandle());
- }
+ private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
+ mCallbacks.notifyVolumeStateChanged(vol, oldState, newState);
- private void validateUserRestriction(String restriction) {
- if (hasUserRestriction(restriction)) {
- throw new SecurityException("User has restriction " + restriction);
+ if (isBroadcastWorthy(vol)) {
+ final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+ android.Manifest.permission.WRITE_MEDIA_STORAGE);
}
- }
- // Storage list XML tags
- private static final String TAG_STORAGE_LIST = "StorageList";
- private static final String TAG_STORAGE = "storage";
+ final String oldStateEnv = VolumeInfo.getEnvironmentForState(oldState);
+ final String newStateEnv = VolumeInfo.getEnvironmentForState(newState);
- private void readStorageListLocked() {
- mVolumes.clear();
- mVolumeStates.clear();
-
- Resources resources = mContext.getResources();
-
- int id = com.android.internal.R.xml.storage_list;
- XmlResourceParser parser = resources.getXml(id);
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- try {
- XmlUtils.beginDocument(parser, TAG_STORAGE_LIST);
- while (true) {
- XmlUtils.nextElement(parser);
-
- String element = parser.getName();
- if (element == null) break;
-
- if (TAG_STORAGE.equals(element)) {
- TypedArray a = resources.obtainAttributes(attrs,
- com.android.internal.R.styleable.Storage);
-
- String path = a.getString(
- com.android.internal.R.styleable.Storage_mountPoint);
- int descriptionId = a.getResourceId(
- com.android.internal.R.styleable.Storage_storageDescription, -1);
- CharSequence description = a.getText(
- com.android.internal.R.styleable.Storage_storageDescription);
- boolean primary = a.getBoolean(
- com.android.internal.R.styleable.Storage_primary, false);
- boolean removable = a.getBoolean(
- com.android.internal.R.styleable.Storage_removable, false);
- boolean emulated = a.getBoolean(
- com.android.internal.R.styleable.Storage_emulated, false);
- int mtpReserve = a.getInt(
- com.android.internal.R.styleable.Storage_mtpReserve, 0);
- boolean allowMassStorage = a.getBoolean(
- com.android.internal.R.styleable.Storage_allowMassStorage, false);
- // resource parser does not support longs, so XML value is in megabytes
- long maxFileSize = a.getInt(
- com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L;
-
- Slog.d(TAG, "got storage path: " + path + " description: " + description +
- " primary: " + primary + " removable: " + removable +
- " emulated: " + emulated + " mtpReserve: " + mtpReserve +
- " allowMassStorage: " + allowMassStorage +
- " maxFileSize: " + maxFileSize);
-
- if (emulated) {
- // For devices with emulated storage, we create separate
- // volumes for each known user.
- mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false,
- true, mtpReserve, false, maxFileSize, null);
-
- final UserManagerService userManager = UserManagerService.getInstance();
- for (UserInfo user : userManager.getUsers(false)) {
- createEmulatedVolumeForUserLocked(user.getUserHandle());
- }
-
- } else {
- if (path == null || description == null) {
- Slog.e(TAG, "Missing storage path or description in readStorageList");
- } else {
- final StorageVolume volume = new StorageVolume(new File(path),
- descriptionId, primary, removable, emulated, mtpReserve,
- allowMassStorage, maxFileSize, null);
- addVolumeLocked(volume);
-
- // Until we hear otherwise, treat as unmounted
- mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
- volume.setState(Environment.MEDIA_UNMOUNTED);
- }
- }
+ if (!Objects.equals(oldStateEnv, newStateEnv)) {
+ // Kick state changed event towards all started users. Any users
+ // started after this point will trigger additional
+ // user-specific broadcasts.
+ for (int userId : mStartedUsers) {
+ if (vol.isVisibleToUser(userId)) {
+ final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
+ mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
- a.recycle();
+ mCallbacks.notifyStorageStateChanged(userVol.getPath(), oldStateEnv,
+ newStateEnv);
}
}
- } catch (XmlPullParserException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- // Compute storage ID for each physical volume; emulated storage is
- // always 0 when defined.
- int index = isExternalStorageEmulated() ? 1 : 0;
- for (StorageVolume volume : mVolumes) {
- if (!volume.isEmulated()) {
- volume.setStorageId(index++);
- }
- }
- parser.close();
- }
- }
-
- /**
- * Create and add new {@link StorageVolume} for given {@link UserHandle}
- * using {@link #mEmulatedTemplate} as template.
- */
- private void createEmulatedVolumeForUserLocked(UserHandle user) {
- if (mEmulatedTemplate == null) {
- throw new IllegalStateException("Missing emulated volume multi-user template");
}
- final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier());
- final File path = userEnv.getExternalStorageDirectory();
- final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user);
- volume.setStorageId(0);
- addVolumeLocked(volume);
-
- if (mSystemReady) {
- updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
- } else {
- // Place stub status for early callers to find
- mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
- volume.setState(Environment.MEDIA_MOUNTED);
+ if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.state == VolumeInfo.STATE_EJECTING) {
+ // TODO: this should eventually be handled by new ObbVolume state changes
+ /*
+ * Some OBBs might have been unmounted when this volume was
+ * unmounted, so send a message to the handler to let it know to
+ * remove those from the list of mounted OBBS.
+ */
+ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
+ OBB_FLUSH_MOUNT_STATE, vol.path));
}
}
- private void addVolumeLocked(StorageVolume volume) {
- Slog.d(TAG, "addVolumeLocked() " + volume);
- mVolumes.add(volume);
- final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume);
- if (existing != null) {
- throw new IllegalStateException(
- "Volume at " + volume.getPath() + " already exists: " + existing);
+ /**
+ * Refresh latest metadata into any currently active {@link VolumeInfo}.
+ */
+ private void refreshMetadataLocked() {
+ final int size = mVolumes.size();
+ for (int i = 0; i < size; i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ final VolumeMetadata meta = mMetadata.get(vol.fsUuid);
+
+ if (meta != null) {
+ vol.nickname = meta.nickname;
+ vol.userFlags = meta.userFlags;
+ } else {
+ vol.nickname = null;
+ vol.userFlags = 0;
+ }
}
}
- private void removeVolumeLocked(StorageVolume volume) {
- Slog.d(TAG, "removeVolumeLocked() " + volume);
- mVolumes.remove(volume);
- mVolumesByPath.remove(volume.getPath());
- mVolumeStates.remove(volume.getPath());
+ private void enforcePermission(String perm) {
+ mContext.enforceCallingOrSelfPermission(perm, perm);
}
- private StorageVolume getPrimaryPhysicalVolume() {
- synchronized (mVolumesLock) {
- for (StorageVolume volume : mVolumes) {
- if (volume.isPrimary() && !volume.isEmulated()) {
- return volume;
- }
- }
+ private void enforceUserRestriction(String restriction) {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (um.hasUserRestriction(restriction, Binder.getCallingUserHandle())) {
+ throw new SecurityException("User has restriction " + restriction);
}
- return null;
}
/**
@@ -1479,10 +1105,7 @@ class MountService extends IMountService.Stub
sSelf = this;
mContext = context;
-
- synchronized (mVolumesLock) {
- readStorageListLocked();
- }
+ mCallbacks = new Callbacks(FgThread.get().getLooper());
// XXX: This will go away soon in favor of IMountServiceObserver
mPms = (PackageManagerService) ServiceManager.getService("package");
@@ -1491,19 +1114,6 @@ class MountService extends IMountService.Stub
hthread.start();
mHandler = new MountServiceHandler(hthread.getLooper());
- // Watch for user changes
- final IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(Intent.ACTION_USER_ADDED);
- userFilter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
-
- // Watch for USB changes on primary volume
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null && primary.allowMassStorage()) {
- mContext.registerReceiver(
- mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
- }
-
// Add OBB Action Handler to MountService thread.
mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
@@ -1523,6 +1133,13 @@ class MountService extends IMountService.Stub
mLastMaintenance = mLastMaintenanceFile.lastModified();
}
+ mMetadataFile = new AtomicFile(
+ new File(Environment.getSystemSecureDirectory(), "storage.xml"));
+
+ synchronized (mLock) {
+ readMetadataLocked();
+ }
+
/*
* Create the connection to vold with a maximum queue of twice the
* amount of containers we'd ever expect to have. This keeps an
@@ -1530,6 +1147,7 @@ class MountService extends IMountService.Stub
*/
mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
null);
+ mConnector.setDebug(true);
Thread thread = new Thread(mConnector, VOLD_TAG);
thread.start();
@@ -1540,234 +1158,292 @@ class MountService extends IMountService.Stub
}
}
- public void systemReady() {
+ private void systemReady() {
mSystemReady = true;
mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
}
+ private void readMetadataLocked() {
+ mMetadata.clear();
+
+ FileInputStream fis = null;
+ try {
+ fis = mMetadataFile.openRead();
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(fis, null);
+
+ int type;
+ while ((type = in.next()) != END_DOCUMENT) {
+ if (type == START_TAG) {
+ final String tag = in.getName();
+ if (TAG_VOLUMES.equals(tag)) {
+ final int version = readIntAttribute(in, ATTR_VERSION, VERSION_INIT);
+ if (version >= VERSION_ADD_PRIMARY) {
+ mPrimaryStorageUuid = readStringAttribute(in,
+ ATTR_PRIMARY_STORAGE_UUID);
+ } else {
+ if (SystemProperties.getBoolean(StorageManager.PROP_PRIMARY_PHYSICAL,
+ false)) {
+ mPrimaryStorageUuid = StorageManager.UUID_PRIMARY_PHYSICAL;
+ } else {
+ mPrimaryStorageUuid = StorageManager.UUID_PRIVATE_INTERNAL;
+ }
+ }
+
+ } else if (TAG_VOLUME.equals(tag)) {
+ final VolumeMetadata meta = VolumeMetadata.read(in);
+ mMetadata.put(meta.fsUuid, meta);
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // Missing metadata is okay, probably first boot
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed reading metadata", e);
+ } catch (XmlPullParserException e) {
+ Slog.wtf(TAG, "Failed reading metadata", e);
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ }
+
+ private void writeMetadataLocked() {
+ FileOutputStream fos = null;
+ try {
+ fos = mMetadataFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, TAG_VOLUMES);
+ writeIntAttribute(out, ATTR_VERSION, VERSION_ADD_PRIMARY);
+ writeStringAttribute(out, ATTR_PRIMARY_STORAGE_UUID, mPrimaryStorageUuid);
+ final int size = mMetadata.size();
+ for (int i = 0; i < size; i++) {
+ final VolumeMetadata meta = mMetadata.valueAt(i);
+ VolumeMetadata.write(out, meta);
+ }
+ out.endTag(null, TAG_VOLUMES);
+ out.endDocument();
+
+ mMetadataFile.finishWrite(fos);
+ } catch (IOException e) {
+ if (fos != null) {
+ mMetadataFile.failWrite(fos);
+ }
+ }
+ }
+
/**
* Exposed API calls below here
*/
+ @Override
public void registerListener(IMountServiceListener listener) {
- synchronized (mListeners) {
- MountServiceBinderListener bl = new MountServiceBinderListener(listener);
- try {
- listener.asBinder().linkToDeath(bl, 0);
- mListeners.add(bl);
- } catch (RemoteException rex) {
- Slog.e(TAG, "Failed to link to listener death");
- }
- }
+ mCallbacks.register(listener);
}
+ @Override
public void unregisterListener(IMountServiceListener listener) {
- synchronized (mListeners) {
- for(MountServiceBinderListener bl : mListeners) {
- if (bl.mListener.asBinder() == listener.asBinder()) {
- mListeners.remove(mListeners.indexOf(bl));
- listener.asBinder().unlinkToDeath(bl, 0);
- return;
- }
- }
- }
+ mCallbacks.unregister(listener);
}
+ @Override
public void shutdown(final IMountShutdownObserver observer) {
- validatePermission(android.Manifest.permission.SHUTDOWN);
+ enforcePermission(android.Manifest.permission.SHUTDOWN);
Slog.i(TAG, "Shutting down");
- synchronized (mVolumesLock) {
- // Get all volumes to be unmounted.
- MountShutdownLatch mountShutdownLatch = new MountShutdownLatch(observer,
- mVolumeStates.size());
-
- for (String path : mVolumeStates.keySet()) {
- String state = mVolumeStates.get(path);
-
- if (state.equals(Environment.MEDIA_SHARED)) {
- /*
- * If the media is currently shared, unshare it.
- * XXX: This is still dangerous!. We should not
- * be rebooting at *all* if UMS is enabled, since
- * the UMS host could have dirty FAT cache entries
- * yet to flush.
- */
- setUsbMassStorageEnabled(false);
- } else if (state.equals(Environment.MEDIA_CHECKING)) {
- /*
- * If the media is being checked, then we need to wait for
- * it to complete before being able to proceed.
- */
- // XXX: @hackbod - Should we disable the ANR timer here?
- int retries = 30;
- while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException iex) {
- Slog.e(TAG, "Interrupted while waiting for media", iex);
- break;
- }
- state = Environment.getExternalStorageState();
- }
- if (retries == 0) {
- Slog.e(TAG, "Timed out waiting for media to check");
- }
- }
+ mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget();
+ }
- if (state.equals(Environment.MEDIA_MOUNTED)) {
- // Post a unmount message.
- ShutdownCallBack ucb = new ShutdownCallBack(path, mountShutdownLatch);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
- } else if (observer != null) {
- /*
- * Count down, since nothing will be done. The observer will be
- * notified when we are done so shutdown sequence can continue.
- */
- mountShutdownLatch.countDown();
- Slog.i(TAG, "Unmount completed: " + path +
- ", result code: " + StorageResultCode.OperationSucceeded);
- }
- }
- }
+ @Override
+ public boolean isUsbMassStorageConnected() {
+ throw new UnsupportedOperationException();
}
- private boolean getUmsEnabling() {
- synchronized (mListeners) {
- return mUmsEnabling;
- }
+ @Override
+ public void setUsbMassStorageEnabled(boolean enable) {
+ throw new UnsupportedOperationException();
}
- private void setUmsEnabling(boolean enable) {
- synchronized (mListeners) {
- mUmsEnabling = enable;
- }
+ @Override
+ public boolean isUsbMassStorageEnabled() {
+ throw new UnsupportedOperationException();
}
- public boolean isUsbMassStorageConnected() {
+ @Override
+ public String getVolumeState(String mountPoint) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isExternalStorageEmulated() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int mountVolume(String path) {
+ mount(findVolumeIdForPath(path));
+ return 0;
+ }
+
+ @Override
+ public void unmountVolume(String path, boolean force, boolean removeEncryption) {
+ unmount(findVolumeIdForPath(path));
+ }
+
+ @Override
+ public int formatVolume(String path) {
+ format(findVolumeIdForPath(path));
+ return 0;
+ }
+
+ @Override
+ public void mount(String volId) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
- if (getUmsEnabling()) {
- return true;
+ final VolumeInfo vol = findVolumeById(volId);
+ if (vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_PRIVATE) {
+ enforceUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
}
- synchronized (mListeners) {
- return mUmsAvailable;
+ try {
+ mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
}
}
- public void setUsbMassStorageEnabled(boolean enable) {
+ @Override
+ public void unmount(String volId) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- validateUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
-
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary == null) return;
- // TODO: Add support for multiple share methods
+ final VolumeInfo vol = findVolumeById(volId);
- /*
- * If the volume is mounted and we're enabling then unmount it
- */
- String path = primary.getPath();
- String vs = getVolumeState(path);
- String method = "ums";
- if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
- // Override for isUsbMassStorageEnabled()
- setUmsEnabling(enable);
- UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
- // Clear override
- setUmsEnabling(false);
- }
- /*
- * If we disabled UMS then mount the volume
- */
- if (!enable) {
- doShareUnshareVolume(path, method, enable);
- if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
- Slog.e(TAG, "Failed to remount " + path +
- " after disabling share method " + method);
- /*
- * Even though the mount failed, the unshare didn't so don't indicate an error.
- * The mountVolume() call will have set the storage state and sent the necessary
- * broadcasts.
- */
+ // TODO: expand PMS to know about multiple volumes
+ if (vol.isPrimary()) {
+ synchronized (mUnmountLock) {
+ mUnmountSignal = new CountDownLatch(1);
+ mPms.updateExternalMediaStatus(false, true);
+ waitForLatch(mUnmountSignal, "mUnmountSignal");
+ mUnmountSignal = null;
}
}
+
+ try {
+ mConnector.execute("volume", "unmount", vol.id);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
}
- public boolean isUsbMassStorageEnabled() {
+ @Override
+ public void format(String volId) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
waitForReady();
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null) {
- return doGetVolumeShared(primary.getPath(), "ums");
- } else {
- return false;
+ final VolumeInfo vol = findVolumeById(volId);
+ try {
+ mConnector.execute("volume", "format", vol.id);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
}
}
- /**
- * @return state of the volume at the specified mount point
- */
- public String getVolumeState(String mountPoint) {
- synchronized (mVolumesLock) {
- String state = mVolumeStates.get(mountPoint);
- if (state == null) {
- Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
- if (SystemProperties.get("vold.encrypt_progress").length() != 0) {
- state = Environment.MEDIA_REMOVED;
- } else {
- throw new IllegalArgumentException();
- }
- }
+ @Override
+ public void partitionPublic(String diskId) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+ waitForReady();
- return state;
+ final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+ try {
+ mConnector.execute("volume", "partition", diskId, "public");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
}
+ waitForLatch(latch, "partitionPublic");
}
@Override
- public boolean isExternalStorageEmulated() {
- return mEmulatedTemplate != null;
+ public void partitionPrivate(String diskId) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+ waitForReady();
+
+ final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+ try {
+ mConnector.execute("volume", "partition", diskId, "private");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ waitForLatch(latch, "partitionPrivate");
}
- public int mountVolume(String path) {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ @Override
+ public void partitionMixed(String diskId, int ratio) {
+ enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
waitForReady();
- return doMountVolume(path);
+
+ final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
+ try {
+ mConnector.execute("volume", "partition", diskId, "mixed", ratio);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ waitForLatch(latch, "partitionMixed");
}
- public void unmountVolume(String path, boolean force, boolean removeEncryption) {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ @Override
+ public void setVolumeNickname(String volId, String nickname) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
- String volState = getVolumeState(path);
- if (DEBUG_UNMOUNT) {
- Slog.i(TAG, "Unmounting " + path
- + " force = " + force
- + " removeEncryption = " + removeEncryption);
- }
- if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
- Environment.MEDIA_REMOVED.equals(volState) ||
- Environment.MEDIA_SHARED.equals(volState) ||
- Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
- // Media already unmounted or cannot be unmounted.
- // TODO return valid return code when adding observer call back.
- return;
+ synchronized (mLock) {
+ final VolumeInfo vol = findVolumeById(volId);
+ final VolumeMetadata meta = findOrCreateMetadataLocked(vol);
+ meta.nickname = nickname;
+ refreshMetadataLocked();
+ writeMetadataLocked();
+ mCallbacks.notifyVolumeMetadataChanged(vol.clone());
}
- UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
}
- public int formatVolume(String path) {
- validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+ @Override
+ public void setVolumeUserFlags(String volId, int flags, int mask) {
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
- return doFormatVolume(path);
+ synchronized (mLock) {
+ final VolumeInfo vol = findVolumeById(volId);
+ final VolumeMetadata meta = findOrCreateMetadataLocked(vol);
+ meta.userFlags = (meta.userFlags & ~mask) | (flags & mask);
+ refreshMetadataLocked();
+ writeMetadataLocked();
+ mCallbacks.notifyVolumeMetadataChanged(vol.clone());
+ }
+ }
+
+ @Override
+ public String getPrimaryStorageUuid() throws RemoteException {
+ synchronized (mLock) {
+ return mPrimaryStorageUuid;
+ }
+ }
+
+ @Override
+ public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException {
+ synchronized (mLock) {
+ Slog.d(TAG, "Changing primary storage UUID to " + volumeUuid);
+ mPrimaryStorageUuid = volumeUuid;
+ writeMetadataLocked();
+
+ // TODO: reevaluate all volumes we know about!
+ }
}
+ @Override
public int[] getStorageUsers(String path) {
- validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
try {
final String[] r = NativeDaemonEvent.filterMessageList(
@@ -1793,22 +1469,21 @@ class MountService extends IMountService.Stub
}
private void warnOnNotMounted() {
- final StorageVolume primary = getPrimaryPhysicalVolume();
- if (primary != null) {
- boolean mounted = false;
- try {
- mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()));
- } catch (IllegalArgumentException e) {
- }
-
- if (!mounted) {
- Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
+ synchronized (mLock) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.isPrimary() && vol.isMountedWritable()) {
+ // Cool beans, we have a mounted primary volume
+ return;
+ }
}
}
+
+ Slog.w(TAG, "No primary storage mounted!");
}
public String[] getSecureContainerList() {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ enforcePermission(android.Manifest.permission.ASEC_ACCESS);
waitForReady();
warnOnNotMounted();
@@ -1822,7 +1497,7 @@ class MountService extends IMountService.Stub
public int createSecureContainer(String id, int sizeMb, String fstype, String key,
int ownerUid, boolean external) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
+ enforcePermission(android.Manifest.permission.ASEC_CREATE);
waitForReady();
warnOnNotMounted();
@@ -1844,7 +1519,7 @@ class MountService extends IMountService.Stub
@Override
public int resizeSecureContainer(String id, int sizeMb, String key) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
+ enforcePermission(android.Manifest.permission.ASEC_CREATE);
waitForReady();
warnOnNotMounted();
@@ -1858,7 +1533,7 @@ class MountService extends IMountService.Stub
}
public int finalizeSecureContainer(String id) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
+ enforcePermission(android.Manifest.permission.ASEC_CREATE);
warnOnNotMounted();
int rc = StorageResultCode.OperationSucceeded;
@@ -1875,7 +1550,7 @@ class MountService extends IMountService.Stub
}
public int fixPermissionsSecureContainer(String id, int gid, String filename) {
- validatePermission(android.Manifest.permission.ASEC_CREATE);
+ enforcePermission(android.Manifest.permission.ASEC_CREATE);
warnOnNotMounted();
int rc = StorageResultCode.OperationSucceeded;
@@ -1892,7 +1567,7 @@ class MountService extends IMountService.Stub
}
public int destroySecureContainer(String id, boolean force) {
- validatePermission(android.Manifest.permission.ASEC_DESTROY);
+ enforcePermission(android.Manifest.permission.ASEC_DESTROY);
waitForReady();
warnOnNotMounted();
@@ -1932,7 +1607,7 @@ class MountService extends IMountService.Stub
}
public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) {
- validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+ enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
waitForReady();
warnOnNotMounted();
@@ -1962,7 +1637,7 @@ class MountService extends IMountService.Stub
}
public int unmountSecureContainer(String id, boolean force) {
- validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+ enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
waitForReady();
warnOnNotMounted();
@@ -2005,7 +1680,7 @@ class MountService extends IMountService.Stub
}
public boolean isSecureContainerMounted(String id) {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ enforcePermission(android.Manifest.permission.ASEC_ACCESS);
waitForReady();
warnOnNotMounted();
@@ -2015,7 +1690,7 @@ class MountService extends IMountService.Stub
}
public int renameSecureContainer(String oldId, String newId) {
- validatePermission(android.Manifest.permission.ASEC_RENAME);
+ enforcePermission(android.Manifest.permission.ASEC_RENAME);
waitForReady();
warnOnNotMounted();
@@ -2040,7 +1715,7 @@ class MountService extends IMountService.Stub
}
public String getSecureContainerPath(String id) {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ enforcePermission(android.Manifest.permission.ASEC_ACCESS);
waitForReady();
warnOnNotMounted();
@@ -2061,7 +1736,7 @@ class MountService extends IMountService.Stub
}
public String getSecureContainerFilesystemPath(String id) {
- validatePermission(android.Manifest.permission.ASEC_ACCESS);
+ enforcePermission(android.Manifest.permission.ASEC_ACCESS);
waitForReady();
warnOnNotMounted();
@@ -2081,8 +1756,13 @@ class MountService extends IMountService.Stub
}
}
+ @Override
public void finishMediaUpdate() {
- mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
+ if (mUnmountSignal != null) {
+ mUnmountSignal.countDown();
+ } else {
+ Slog.w(TAG, "Odd, nobody asked to unmount?");
+ }
}
private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
@@ -2204,25 +1884,21 @@ class MountService extends IMountService.Stub
}
}
- private String toHex(String password) {
+ private static String toHex(String password) {
if (password == null) {
- return new String();
+ return "";
}
byte[] bytes = password.getBytes(StandardCharsets.UTF_8);
- return new String(Hex.encodeHex(bytes));
+ return new String(HexEncoding.encode(bytes));
}
- private String fromHex(String hexPassword) {
+ private static String fromHex(String hexPassword) throws IllegalArgumentException {
if (hexPassword == null) {
return null;
}
- try {
- byte[] bytes = Hex.decodeHex(hexPassword.toCharArray());
- return new String(bytes, StandardCharsets.UTF_8);
- } catch (DecoderException e) {
- return null;
- }
+ final byte[] bytes = HexEncoding.decode(hexPassword.toCharArray(), false);
+ return new String(bytes, StandardCharsets.UTF_8);
}
@Override
@@ -2424,9 +2100,16 @@ class MountService extends IMountService.Stub
final NativeDaemonEvent event;
try {
event = mConnector.execute("cryptfs", "getpw");
+ if ("-1".equals(event.getMessage())) {
+ // -1 equals no password
+ return null;
+ }
return fromHex(event.getMessage());
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Invalid response to getPassword");
+ return null;
}
}
@@ -2454,108 +2137,106 @@ class MountService extends IMountService.Stub
Context.APP_OPS_SERVICE);
appOps.checkPackage(Binder.getCallingUid(), callingPkg);
+ File appFile = null;
try {
- appPath = new File(appPath).getCanonicalPath();
+ appFile = new File(appPath).getCanonicalFile();
} catch (IOException e) {
Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
return -1;
}
- if (!appPath.endsWith("/")) {
- appPath = appPath + "/";
- }
-
// Try translating the app path into a vold path, but require that it
// belong to the calling package.
- String voldPath = maybeTranslatePathForVold(appPath,
- userEnv.buildExternalStorageAppDataDirs(callingPkg),
- userEnv.buildExternalStorageAppDataDirsForVold(callingPkg));
- if (voldPath != null) {
- try {
- mConnector.execute("volume", "mkdirs", voldPath);
- return 0;
- } catch (NativeDaemonConnectorException e) {
- return e.getCode();
+ if (FileUtils.contains(userEnv.buildExternalStorageAppDataDirs(callingPkg), appFile) ||
+ FileUtils.contains(userEnv.buildExternalStorageAppObbDirs(callingPkg), appFile) ||
+ FileUtils.contains(userEnv.buildExternalStorageAppMediaDirs(callingPkg), appFile)) {
+ appPath = appFile.getAbsolutePath();
+ if (!appPath.endsWith("/")) {
+ appPath = appPath + "/";
}
- }
- voldPath = maybeTranslatePathForVold(appPath,
- userEnv.buildExternalStorageAppObbDirs(callingPkg),
- userEnv.buildExternalStorageAppObbDirsForVold(callingPkg));
- if (voldPath != null) {
try {
- mConnector.execute("volume", "mkdirs", voldPath);
+ mConnector.execute("volume", "mkdirs", appPath);
return 0;
} catch (NativeDaemonConnectorException e) {
return e.getCode();
}
}
- voldPath = maybeTranslatePathForVold(appPath,
- userEnv.buildExternalStorageAppMediaDirs(callingPkg),
- userEnv.buildExternalStorageAppMediaDirsForVold(callingPkg));
- if (voldPath != null) {
- try {
- mConnector.execute("volume", "mkdirs", voldPath);
- return 0;
- } catch (NativeDaemonConnectorException e) {
- return e.getCode();
+ throw new SecurityException("Invalid mkdirs path: " + appFile);
+ }
+
+ @Override
+ public StorageVolume[] getVolumeList(int userId) {
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ boolean foundPrimary = false;
+
+ synchronized (mLock) {
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (vol.isVisibleToUser(userId)) {
+ final StorageVolume userVol = vol.buildStorageVolume(mContext, userId);
+ if (vol.isPrimary()) {
+ res.add(0, userVol);
+ foundPrimary = true;
+ } else {
+ res.add(userVol);
+ }
+ }
}
}
- throw new SecurityException("Invalid mkdirs path: " + appPath);
+ if (!foundPrimary) {
+ Log.w(TAG, "No primary storage defined yet; hacking together a stub");
+
+ final boolean primaryPhysical = SystemProperties.getBoolean(
+ StorageManager.PROP_PRIMARY_PHYSICAL, false);
+
+ final String id = "stub_primary";
+ final File path = Environment.getLegacyExternalStorageDirectory();
+ final String description = mContext.getString(android.R.string.unknownName);
+ final boolean primary = true;
+ final boolean removable = primaryPhysical;
+ final boolean emulated = !primaryPhysical;
+ final long mtpReserveSize = 0L;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0L;
+ final UserHandle owner = new UserHandle(userId);
+ final String uuid = null;
+ final String state = Environment.MEDIA_REMOVED;
+
+ res.add(0, new StorageVolume(id, MtpStorage.getStorageIdForIndex(0), path,
+ description, primary, removable, emulated, mtpReserveSize,
+ allowMassStorage, maxFileSize, owner, uuid, state));
+ }
+
+ return res.toArray(new StorageVolume[res.size()]);
}
- /**
- * Translate the given path from an app-visible path to a vold-visible path,
- * but only if it's under the given whitelisted paths.
- *
- * @param path a canonicalized app-visible path.
- * @param appPaths list of app-visible paths that are allowed.
- * @param voldPaths list of vold-visible paths directly corresponding to the
- * allowed app-visible paths argument.
- * @return a vold-visible path representing the original path, or
- * {@code null} if the given path didn't have an app-to-vold
- * mapping.
- */
- @VisibleForTesting
- public static String maybeTranslatePathForVold(
- String path, File[] appPaths, File[] voldPaths) {
- if (appPaths.length != voldPaths.length) {
- throw new IllegalStateException("Paths must be 1:1 mapping");
- }
-
- for (int i = 0; i < appPaths.length; i++) {
- final String appPath = appPaths[i].getAbsolutePath() + "/";
- if (path.startsWith(appPath)) {
- path = new File(voldPaths[i], path.substring(appPath.length()))
- .getAbsolutePath();
- if (!path.endsWith("/")) {
- path = path + "/";
- }
- return path;
+ @Override
+ public DiskInfo[] getDisks() {
+ synchronized (mLock) {
+ final DiskInfo[] res = new DiskInfo[mDisks.size()];
+ for (int i = 0; i < mDisks.size(); i++) {
+ res[i] = mDisks.valueAt(i);
}
+ return res;
}
- return null;
}
@Override
- public StorageVolume[] getVolumeList() {
- final int callingUserId = UserHandle.getCallingUserId();
- final boolean accessAll = (mContext.checkPermission(
- android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
- Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED);
-
- synchronized (mVolumesLock) {
- final ArrayList<StorageVolume> filtered = Lists.newArrayList();
- for (StorageVolume volume : mVolumes) {
- final UserHandle owner = volume.getOwner();
- final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId;
- if (accessAll || ownerMatch) {
- filtered.add(volume);
- }
+ public VolumeInfo[] getVolumes(int flags) {
+ if ((flags & StorageManager.FLAG_ALL_METADATA) != 0) {
+ // TODO: implement support for returning all metadata
+ throw new UnsupportedOperationException();
+ }
+
+ synchronized (mLock) {
+ final VolumeInfo[] res = new VolumeInfo[mVolumes.size()];
+ for (int i = 0; i < mVolumes.size(); i++) {
+ res[i] = mVolumes.valueAt(i);
}
- return filtered.toArray(new StorageVolume[filtered.size()]);
+ return res;
}
}
@@ -3050,20 +2731,101 @@ class MountService extends IMountService.Stub
if (path.startsWith(obbPath)) {
path = path.substring(obbPath.length() + 1);
- if (forVold) {
- return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath();
- } else {
- final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
- return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
- .getAbsolutePath();
- }
+ final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
+ return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
+ .getAbsolutePath();
}
// Handle normal external storage paths
- if (forVold) {
- return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
- } else {
- return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath();
+ return new File(userEnv.getExternalStorageDirectory(), path).getAbsolutePath();
+ }
+
+ private static class Callbacks extends Handler {
+ private static final int MSG_STORAGE_STATE_CHANGED = 1;
+ private static final int MSG_VOLUME_STATE_CHANGED = 2;
+ private static final int MSG_VOLUME_METADATA_CHANGED = 3;
+ private static final int MSG_DISK_SCANNED = 4;
+
+ private final RemoteCallbackList<IMountServiceListener>
+ mCallbacks = new RemoteCallbackList<>();
+
+ public Callbacks(Looper looper) {
+ super(looper);
+ }
+
+ public void register(IMountServiceListener callback) {
+ mCallbacks.register(callback);
+ }
+
+ public void unregister(IMountServiceListener callback) {
+ mCallbacks.unregister(callback);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ final IMountServiceListener callback = mCallbacks.getBroadcastItem(i);
+ try {
+ invokeCallback(callback, msg.what, args);
+ } catch (RemoteException ignored) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ args.recycle();
+ }
+
+ private void invokeCallback(IMountServiceListener callback, int what, SomeArgs args)
+ throws RemoteException {
+ switch (what) {
+ case MSG_STORAGE_STATE_CHANGED: {
+ callback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
+ (String) args.arg3);
+ break;
+ }
+ case MSG_VOLUME_STATE_CHANGED: {
+ callback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
+ break;
+ }
+ case MSG_VOLUME_METADATA_CHANGED: {
+ callback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
+ break;
+ }
+ case MSG_DISK_SCANNED: {
+ callback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
+ break;
+ }
+ }
+ }
+
+ private void notifyStorageStateChanged(String path, String oldState, String newState) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = path;
+ args.arg2 = oldState;
+ args.arg3 = newState;
+ obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
+ }
+
+ private void notifyVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = vol;
+ args.argi2 = oldState;
+ args.argi3 = newState;
+ obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
+ }
+
+ private void notifyVolumeMetadataChanged(VolumeInfo vol) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = vol;
+ obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget();
+ }
+
+ private void notifyDiskScanned(DiskInfo disk, int volumeCount) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = disk;
+ args.argi2 = volumeCount;
+ obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
}
}
@@ -3071,9 +2833,47 @@ class MountService extends IMountService.Stub
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ for (String arg : args) {
+ if ("--clear-metadata".equals(arg)) {
+ synchronized (mLock) {
+ mMetadata.clear();
+ writeMetadataLocked();
+ }
+ }
+ }
+
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160);
+ synchronized (mLock) {
+ pw.println("Disks:");
+ pw.increaseIndent();
+ for (int i = 0; i < mDisks.size(); i++) {
+ final DiskInfo disk = mDisks.valueAt(i);
+ disk.dump(pw);
+ }
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Volumes:");
+ pw.increaseIndent();
+ for (int i = 0; i < mVolumes.size(); i++) {
+ final VolumeInfo vol = mVolumes.valueAt(i);
+ if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) continue;
+ vol.dump(pw);
+ }
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Metadata:");
+ pw.increaseIndent();
+ for (int i = 0; i < mMetadata.size(); i++) {
+ final VolumeMetadata meta = mMetadata.valueAt(i);
+ meta.dump(pw);
+ }
+ pw.decreaseIndent();
+ }
synchronized (mObbMounts) {
+ pw.println();
pw.println("mObbMounts:");
pw.increaseIndent();
final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet()
@@ -3103,19 +2903,6 @@ class MountService extends IMountService.Stub
pw.decreaseIndent();
}
- synchronized (mVolumesLock) {
- pw.println();
- pw.println("mVolumes:");
- pw.increaseIndent();
- for (StorageVolume volume : mVolumes) {
- pw.println(volume);
- pw.increaseIndent();
- pw.println("Current state: " + mVolumeStates.get(volume.getPath()));
- pw.decreaseIndent();
- }
- pw.decreaseIndent();
- }
-
pw.println();
pw.println("mConnection:");
pw.increaseIndent();
@@ -3130,6 +2917,7 @@ class MountService extends IMountService.Stub
}
/** {@inheritDoc} */
+ @Override
public void monitor() {
if (mConnector != null) {
mConnector.monitor();
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
index d2dfc7b..78c7f38 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -48,8 +48,6 @@ import java.util.LinkedList;
* {@code libsysutils} FrameworkListener protocol.
*/
final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
- private static final boolean LOGD = false;
-
private final static boolean VDBG = false;
private final String TAG;
@@ -58,6 +56,8 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
private OutputStream mOutputStream;
private LocalLog mLocalLog;
+ private volatile boolean mDebug = false;
+
private final ResponseQueue mResponseQueue;
private final PowerManager.WakeLock mWakeLock;
@@ -99,6 +99,14 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
mLocalLog = new LocalLog(maxLogSize);
}
+ /**
+ * Enable Set debugging mode, which causes messages to also be written to both
+ * {@link Slog} in addition to internal log.
+ */
+ public void setDebug(boolean debug) {
+ mDebug = debug;
+ }
+
@Override
public void run() {
mCallbackHandler = new Handler(mLooper, this);
@@ -513,7 +521,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo
}
private void log(String logstring) {
- if (LOGD) Slog.d(TAG, logstring);
+ if (mDebug) Slog.d(TAG, logstring);
mLocalLog.log(logstring);
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 0437a2a..b5b62b4 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -24,9 +24,6 @@ import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_TETHERING;
-import static android.net.RouteInfo.RTN_THROW;
-import static android.net.RouteInfo.RTN_UNICAST;
-import static android.net.RouteInfo.RTN_UNREACHABLE;
import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
@@ -38,6 +35,7 @@ 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.app.ActivityManagerNative;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetworkManagementEventObserver;
@@ -61,6 +59,7 @@ import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.telephony.DataConnectionRealTimeInfo;
@@ -70,9 +69,12 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.util.HexDump;
import com.android.internal.util.Preconditions;
import com.android.server.NativeDaemonConnector.Command;
import com.android.server.NativeDaemonConnector.SensitiveArg;
@@ -87,8 +89,6 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
@@ -145,6 +145,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
public static final int InterfaceAddressChange = 614;
public static final int InterfaceDnsServerInfo = 615;
public static final int RouteChange = 616;
+ public static final int StrictCleartext = 617;
}
static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1;
@@ -174,12 +175,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub
private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
private Object mQuotaLock = new Object();
+
/** Set of interfaces with active quotas. */
+ @GuardedBy("mQuotaLock")
private HashMap<String, Long> mActiveQuotas = Maps.newHashMap();
/** Set of interfaces with active alerts. */
+ @GuardedBy("mQuotaLock")
private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
/** Set of UIDs with active reject rules. */
+ @GuardedBy("mQuotaLock")
private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray();
+ /** Set of UIDs with cleartext penalties. */
+ @GuardedBy("mQuotaLock")
+ private SparseIntArray mUidCleartextPolicy = new SparseIntArray();
private Object mIdleTimerLock = new Object();
/** Set of interfaces with active idle timers. */
@@ -198,9 +206,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub
private volatile boolean mBandwidthControlEnabled;
private volatile boolean mFirewallEnabled;
+ private volatile boolean mStrictEnabled;
private boolean mMobileActivityFromRadio = false;
private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+ private int mLastPowerStateFromWifi = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners =
new RemoteCallbackList<INetworkActivityListener>();
@@ -425,6 +435,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
}
+ if (ConnectivityManager.isNetworkTypeWifi(type)) {
+ if (mLastPowerStateFromWifi != powerState) {
+ mLastPowerStateFromWifi = powerState;
+ try {
+ getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
|| powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
@@ -495,11 +515,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
}
+ try {
+ mConnector.execute("strict", "enable");
+ mStrictEnabled = true;
+ } catch (NativeDaemonConnectorException e) {
+ Log.wtf(TAG, "Failed strict enable", e);
+ }
+
// push any existing quota or UID rules
synchronized (mQuotaLock) {
int size = mActiveQuotas.size();
if (size > 0) {
- Slog.d(TAG, "pushing " + size + " active quota rules");
+ Slog.d(TAG, "Pushing " + size + " active quota rules");
final HashMap<String, Long> activeQuotas = mActiveQuotas;
mActiveQuotas = Maps.newHashMap();
for (Map.Entry<String, Long> entry : activeQuotas.entrySet()) {
@@ -509,7 +536,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
size = mActiveAlerts.size();
if (size > 0) {
- Slog.d(TAG, "pushing " + size + " active alert rules");
+ Slog.d(TAG, "Pushing " + size + " active alert rules");
final HashMap<String, Long> activeAlerts = mActiveAlerts;
mActiveAlerts = Maps.newHashMap();
for (Map.Entry<String, Long> entry : activeAlerts.entrySet()) {
@@ -519,13 +546,23 @@ public class NetworkManagementService extends INetworkManagementService.Stub
size = mUidRejectOnQuota.size();
if (size > 0) {
- Slog.d(TAG, "pushing " + size + " active uid rules");
+ Slog.d(TAG, "Pushing " + size + " active UID rules");
final SparseBooleanArray uidRejectOnQuota = mUidRejectOnQuota;
mUidRejectOnQuota = new SparseBooleanArray();
for (int i = 0; i < uidRejectOnQuota.size(); i++) {
setUidNetworkRules(uidRejectOnQuota.keyAt(i), uidRejectOnQuota.valueAt(i));
}
}
+
+ size = mUidCleartextPolicy.size();
+ if (size > 0) {
+ Slog.d(TAG, "Pushing " + size + " active UID cleartext policies");
+ final SparseIntArray local = mUidCleartextPolicy;
+ mUidCleartextPolicy = new SparseIntArray();
+ for (int i = 0; i < local.size(); i++) {
+ setUidCleartextNetworkPolicy(local.keyAt(i), local.valueAt(i));
+ }
+ }
}
// TODO: Push any existing firewall state
@@ -792,6 +829,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
throw new IllegalStateException(errorMessage);
// break;
+ case NetdResponseCode.StrictCleartext:
+ final int uid = Integer.parseInt(cooked[1]);
+ final byte[] firstPacket = HexDump.hexStringToByteArray(cooked[2]);
+ try {
+ ActivityManagerNative.getDefault().notifyCleartextNetwork(uid, firstPacket);
+ } catch (RemoteException ignored) {
+ }
+ break;
default: break;
}
return false;
@@ -1674,6 +1719,49 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
+ public void setUidCleartextNetworkPolicy(int uid, int policy) {
+ if (Binder.getCallingUid() != uid) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+ }
+
+ synchronized (mQuotaLock) {
+ final int oldPolicy = mUidCleartextPolicy.get(uid, StrictMode.NETWORK_POLICY_ACCEPT);
+ if (oldPolicy == policy) {
+ return;
+ }
+
+ if (!mStrictEnabled) {
+ // Module isn't enabled yet; stash the requested policy away to
+ // apply later once the daemon is connected.
+ mUidCleartextPolicy.put(uid, policy);
+ return;
+ }
+
+ final String policyString;
+ switch (policy) {
+ case StrictMode.NETWORK_POLICY_ACCEPT:
+ policyString = "accept";
+ break;
+ case StrictMode.NETWORK_POLICY_LOG:
+ policyString = "log";
+ break;
+ case StrictMode.NETWORK_POLICY_REJECT:
+ policyString = "reject";
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown policy " + policy);
+ }
+
+ try {
+ mConnector.execute("strict", "set_uid_cleartext_policy", uid, policyString);
+ mUidCleartextPolicy.put(uid, policy);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+ }
+
+ @Override
public boolean isBandwidthControlEnabled() {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
return mBandwidthControlEnabled;
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
index d6abce9..a0d305c 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -25,7 +25,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index 39aa972..f4c6225 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -38,7 +38,6 @@ import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.util.HashMap;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 97d16c0..56f9942 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -18,14 +18,18 @@ package com.android.server;
import android.Manifest;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.service.persistentdata.IPersistentDataBlockService;
+import android.service.persistentdata.PersistentDataBlockManager;
import android.util.Slog;
import com.android.internal.R;
@@ -70,6 +74,7 @@ public class PersistentDataBlockService extends SystemService {
// Limit to 100k as blocks larger than this might cause strain on Binder.
private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
public static final int DIGEST_SIZE_BYTES = 32;
+ private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
private final Context mContext;
private final String mDataBlockFile;
@@ -108,11 +113,14 @@ public class PersistentDataBlockService extends SystemService {
}
private void formatIfOemUnlockEnabled() {
- if (doGetOemUnlockEnabled()) {
+ boolean enabled = doGetOemUnlockEnabled();
+ if (enabled) {
synchronized (mLock) {
formatPartitionLocked(true);
}
}
+
+ SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
}
private void enforceOemUnlockPermission() {
@@ -132,7 +140,6 @@ public class PersistentDataBlockService extends SystemService {
throw new SecurityException("Only the Owner is allowed to change OEM unlock state");
}
}
-
private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
// skip over checksum
inputStream.skipBytes(DIGEST_SIZE_BYTES);
@@ -290,6 +297,7 @@ public class PersistentDataBlockService extends SystemService {
Slog.e(TAG, "unable to access persistent partition", e);
return;
} finally {
+ SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
IoUtils.closeQuietly(outputStream);
}
}
@@ -424,6 +432,29 @@ public class PersistentDataBlockService extends SystemService {
}
@Override
+ public void wipeIfAllowed(Bundle bundle, PendingIntent pi) {
+ // Should only be called by owner
+ if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
+ throw new SecurityException("Only the Owner is allowed to wipe");
+ }
+ // Caller must be able to query the the state of the PersistentDataBlock
+ enforcePersistentDataBlockAccess();
+ String allowedPackage = mContext.getResources()
+ .getString(R.string.config_persistentDataPackageName);
+ Intent intent = new Intent();
+ intent.setPackage(allowedPackage);
+ intent.setAction(PersistentDataBlockManager.ACTION_WIPE_IF_ALLOWED);
+ intent.putExtras(bundle);
+ intent.putExtra(PersistentDataBlockManager.EXTRA_WIPE_IF_ALLOWED_CALLBACK, pi);
+ long id = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+
+ @Override
public void setOemUnlockEnabled(boolean enabled) {
// do not allow monkey to flip the flag
if (ActivityManager.isUserAMonkey()) {
@@ -446,10 +477,7 @@ public class PersistentDataBlockService extends SystemService {
@Override
public int getDataBlockSize() {
- if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
- != PackageManager.PERMISSION_GRANTED) {
- enforceUid(Binder.getCallingUid());
- }
+ enforcePersistentDataBlockAccess();
DataInputStream inputStream;
try {
@@ -471,6 +499,13 @@ public class PersistentDataBlockService extends SystemService {
}
}
+ private void enforcePersistentDataBlockAccess() {
+ if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ enforceUid(Binder.getCallingUid());
+ }
+ }
+
@Override
public long getMaximumDataBlockSize() {
long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 6ad128c..4c9d7d3 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -71,9 +71,11 @@ public class SystemConfig {
public static final class PermissionEntry {
public final String name;
public int[] gids;
+ public boolean perUser;
- PermissionEntry(String _name) {
- name = _name;
+ PermissionEntry(String name, boolean perUser) {
+ this.name = name;
+ this.perUser = perUser;
}
}
@@ -363,14 +365,14 @@ public class SystemConfig {
void readPermission(XmlPullParser parser, String name)
throws IOException, XmlPullParserException {
+ if (mPermissions.containsKey(name)) {
+ throw new IllegalStateException("Duplicate permission definition for " + name);
+ }
- name = name.intern();
+ final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false);
+ final PermissionEntry perm = new PermissionEntry(name, perUser);
+ mPermissions.put(name, perm);
- PermissionEntry perm = mPermissions.get(name);
- if (perm == null) {
- perm = new PermissionEntry(name);
- mPermissions.put(name, perm);
- }
int outerDepth = parser.getDepth();
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4fbf23b..4ee6657 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.app.ActivityManager;
+import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -84,7 +85,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private static final boolean VDBG = false; // STOPSHIP if true
private static class Record {
- String pkgForDebug;
+ String callingPackage;
IBinder binder;
@@ -109,7 +110,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
@Override
public String toString() {
- return "{pkgForDebug=" + pkgForDebug + " binder=" + binder + " callback=" + callback
+ return "{callingPackage=" + callingPackage + " binder=" + binder
+ + " callback=" + callback
+ " onSubscriptionsChangedListenererCallback="
+ onSubscriptionsChangedListenerCallback
+ " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId
@@ -125,6 +127,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private final IBatteryStats mBatteryStats;
+ private final AppOpsManager mAppOps;
+
private boolean hasNotifySubscriptionInfoChangedOccurred = false;
private int mNumPhones;
@@ -181,6 +185,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private PreciseCallState mPreciseCallState = new PreciseCallState();
+ private boolean mCarrierNetworkChangeState = false;
+
private PreciseDataConnectionState mPreciseDataConnectionState =
new PreciseDataConnectionState();
@@ -325,6 +331,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
mConnectedApns = new ArrayList<String>();
+
+ mAppOps = mContext.getSystemService(AppOpsManager.class);
}
public void systemRunning() {
@@ -338,18 +346,24 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
@Override
- public void addOnSubscriptionsChangedListener(String pkgForDebug,
+ public void addOnSubscriptionsChangedListener(String callingPackage,
IOnSubscriptionsChangedListener callback) {
int callerUid = UserHandle.getCallingUserId();
int myUid = UserHandle.myUserId();
if (VDBG) {
- log("listen oscl: E pkg=" + pkgForDebug + " myUid=" + myUid
+ log("listen oscl: E pkg=" + callingPackage + " myUid=" + myUid
+ " callerUid=" + callerUid + " callback=" + callback
+ " callback.asBinder=" + callback.asBinder());
}
- /* Checks permission and throws Security exception */
- checkOnSubscriptionsChangedListenerPermission();
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PHONE_STATE, null);
+
+ if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+
Record r = null;
synchronized (mRecords) {
@@ -370,7 +384,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
r.onSubscriptionsChangedListenerCallback = callback;
- r.pkgForDebug = pkgForDebug;
+ r.callingPackage = callingPackage;
r.callerUid = callerUid;
r.events = 0;
if (DBG) {
@@ -399,12 +413,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
remove(callback.asBinder());
}
- private void checkOnSubscriptionsChangedListenerPermission() {
- mContext.enforceCallingOrSelfPermission(
- SubscriptionManager.OnSubscriptionsChangedListener
- .PERMISSION_ON_SUBSCRIPTIONS_CHANGED, null);
- }
-
@Override
public void notifySubscriptionInfoChanged() {
if (VDBG) log("notifySubscriptionInfoChanged:");
@@ -444,12 +452,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
listen(pkgForDebug, callback, events, notifyNow, subId);
}
- private void listen(String pkgForDebug, IPhoneStateListener callback, int events,
+ private void listen(String callingPackage, IPhoneStateListener callback, int events,
boolean notifyNow, int subId) {
int callerUid = UserHandle.getCallingUserId();
int myUid = UserHandle.myUserId();
if (VDBG) {
- log("listen: E pkg=" + pkgForDebug + " events=0x" + Integer.toHexString(events)
+ log("listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events)
+ " notifyNow=" + notifyNow + " subId=" + subId + " myUid=" + myUid
+ " callerUid=" + callerUid);
}
@@ -457,6 +465,14 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (events != PhoneStateListener.LISTEN_NONE) {
/* Checks permission and throws Security exception */
checkListenerPermission(events);
+
+ if ((events & PHONE_STATE_PERMISSION_MASK) != 0) {
+ if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+ }
+
synchronized (mRecords) {
// register
Record r = null;
@@ -476,7 +492,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
r.callback = callback;
- r.pkgForDebug = pkgForDebug;
+ r.callingPackage = callingPackage;
r.callerUid = callerUid;
// Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
// force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
@@ -607,6 +623,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
remove(r.binder);
}
}
+ if ((events & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) {
+ try {
+ r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
} else {
@@ -622,7 +645,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (mRecords.get(i).binder == binder) {
if (DBG) {
Record r = mRecords.get(i);
- log("remove: binder=" + binder + "r.pkgForDebug" + r.pkgForDebug
+ log("remove: binder=" + binder + "r.callingPackage" + r.callingPackage
+ "r.callback" + r.callback);
}
mRecords.remove(i);
@@ -736,50 +759,47 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
public void notifySignalStrengthForSubscriber(int subId, SignalStrength signalStrength) {
+ log("notifySignalStrengthForSubscriber: subId=" + subId
+ + " signalStrength=" + signalStrength);
if (!checkNotifyPermission("notifySignalStrength()")) {
+ log("notifySignalStrengthForSubscriber: permission check failure");
return;
}
- if (VDBG) {
- log("notifySignalStrengthForSubscriber: subId=" + subId
- + " signalStrength=" + signalStrength);
- toStringLogSSC("notifySignalStrengthForSubscriber");
- }
+ toStringLogSSC("notifySignalStrengthForSubscriber");
synchronized (mRecords) {
int phoneId = SubscriptionManager.getPhoneId(subId);
if (validatePhoneId(phoneId)) {
- if (VDBG) log("notifySignalStrengthForSubscriber: valid phoneId=" + phoneId);
+ log("notifySignalStrengthForSubscriber: valid phoneId=" + phoneId);
mSignalStrength[phoneId] = signalStrength;
for (Record r : mRecords) {
- if (VDBG) {
- log("notifySignalStrengthForSubscriber: r=" + r + " subId=" + subId
- + " phoneId=" + phoneId + " ss=" + signalStrength);
- }
+ log("notifySignalStrengthForSubscriber: r=" + r + " subId=" + subId
+ + " phoneId=" + phoneId + " ss=" + signalStrength);
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) &&
idMatch(r.subId, subId, phoneId)) {
try {
- if (DBG) {
- log("notifySignalStrengthForSubscriber: callback.onSsS r=" + r
- + " subId=" + subId + " phoneId=" + phoneId
- + " ss=" + signalStrength);
- }
+ log("notifySignalStrengthForSubscriber: callback.onSsS r=" + r
+ + " subId=" + subId + " phoneId=" + phoneId
+ + " ss=" + signalStrength);
r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
} catch (RemoteException ex) {
+ log("notifySignalStrengthForSubscriber: Exception while calling callback!!");
mRemoveList.add(r.binder);
}
+ } else {
+ log("notifySignalStrengthForSubscriber: no match for LISTEN_SIGNAL_STRENGTHS");
}
if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SIGNAL_STRENGTH) &&
idMatch(r.subId, subId, phoneId)){
try {
int gsmSignalStrength = signalStrength.getGsmSignalStrength();
int ss = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength);
- if (DBG) {
- log("notifySignalStrengthForSubscriber: callback.onSS r=" + r
- + " subId=" + subId + " phoneId=" + phoneId
- + " gsmSS=" + gsmSignalStrength + " ss=" + ss);
- }
+ log("notifySignalStrengthForSubscriber: callback.onSS r=" + r
+ + " subId=" + subId + " phoneId=" + phoneId
+ + " gsmSS=" + gsmSignalStrength + " ss=" + ss);
r.callback.onSignalStrengthChanged(ss);
} catch (RemoteException ex) {
+ log("notifySignalStrengthForSubscriber: Exception in deprecated LISTEN_SIGNAL_STRENGTH");
mRemoveList.add(r.binder);
}
}
@@ -787,11 +807,37 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
} else {
log("notifySignalStrengthForSubscriber: invalid phoneId=" + phoneId);
}
+ log("notifySignalStrengthForSubscriber: done with all records");
handleRemoveListLocked();
}
broadcastSignalStrengthChanged(signalStrength, subId);
}
+ @Override
+ public void notifyCarrierNetworkChange(boolean active) {
+ if (!checkNotifyPermissionOrCarrierPrivilege("notifyCarrierNetworkChange()")) {
+ return;
+ }
+ if (VDBG) {
+ log("notifyCarrierNetworkChange: active=" + active);
+ }
+
+ synchronized (mRecords) {
+ mCarrierNetworkChangeState = active;
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE)) {
+ try {
+ r.callback.onCarrierNetworkChange(active);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
public void notifyCellInfo(List<CellInfo> cellInfo) {
notifyCellInfoForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cellInfo);
}
@@ -1348,7 +1394,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- android.Manifest.permission.READ_PHONE_STATE);
+ android.Manifest.permission.READ_PHONE_STATE,
+ AppOpsManager.OP_READ_PHONE_STATE);
}
private void broadcastDataConnectionStateChanged(int state,
@@ -1424,9 +1471,19 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
android.Manifest.permission.READ_PRECISE_PHONE_STATE);
}
+ private boolean checkNotifyPermissionOrCarrierPrivilege(String method) {
+ if (checkNotifyPermission() || checkCarrierPrivilege()) {
+ return true;
+ }
+
+ String msg = "Modify Phone State or Carrier Privilege Permission Denial: " + method
+ + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+ if (DBG) log(msg);
+ return false;
+ }
+
private boolean checkNotifyPermission(String method) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- == PackageManager.PERMISSION_GRANTED) {
+ if (checkNotifyPermission()) {
return true;
}
String msg = "Modify Phone State Permission Denial: " + method + " from pid="
@@ -1435,6 +1492,24 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
return false;
}
+ private boolean checkNotifyPermission() {
+ return mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean checkCarrierPrivilege() {
+ TelephonyManager tm = TelephonyManager.getDefault();
+ String[] pkgs = mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
+ for (String pkg : pkgs) {
+ if (tm.checkCarrierPrivilegesForPackage(pkg) ==
+ TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private void checkListenerPermission(int events) {
if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index 5add88e..9a6f696 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -116,6 +116,11 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
@Override
public void onUserSwitchComplete(int newUserId) throws RemoteException {
}
+
+ @Override
+ public void onForegroundProfileSwitch(int newProfileId) {
+ // Ignore.
+ }
});
userId = ActivityManagerNative.getDefault().getCurrentUser().id;
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/TwilightCalculator.java b/services/core/java/com/android/server/TwilightCalculator.java
index a5c93b5..5839b16 100644
--- a/services/core/java/com/android/server/TwilightCalculator.java
+++ b/services/core/java/com/android/server/TwilightCalculator.java
@@ -17,7 +17,6 @@
package com.android.server;
import android.text.format.DateUtils;
-import android.util.FloatMath;
/** @hide */
public class TwilightCalculator {
@@ -75,24 +74,24 @@ public class TwilightCalculator {
final float meanAnomaly = 6.240059968f + daysSince2000 * 0.01720197f;
// true anomaly
- final float trueAnomaly = meanAnomaly + C1 * FloatMath.sin(meanAnomaly) + C2
- * FloatMath.sin(2 * meanAnomaly) + C3 * FloatMath.sin(3 * meanAnomaly);
+ final double trueAnomaly = meanAnomaly + C1 * Math.sin(meanAnomaly) + C2
+ * Math.sin(2 * meanAnomaly) + C3 * Math.sin(3 * meanAnomaly);
// ecliptic longitude
- final float solarLng = trueAnomaly + 1.796593063f + (float) Math.PI;
+ final double solarLng = trueAnomaly + 1.796593063d + Math.PI;
// solar transit in days since 2000
final double arcLongitude = -longitude / 360;
float n = Math.round(daysSince2000 - J0 - arcLongitude);
- double solarTransitJ2000 = n + J0 + arcLongitude + 0.0053f * FloatMath.sin(meanAnomaly)
- + -0.0069f * FloatMath.sin(2 * solarLng);
+ double solarTransitJ2000 = n + J0 + arcLongitude + 0.0053d * Math.sin(meanAnomaly)
+ + -0.0069d * Math.sin(2 * solarLng);
// declination of sun
- double solarDec = Math.asin(FloatMath.sin(solarLng) * FloatMath.sin(OBLIQUITY));
+ double solarDec = Math.asin(Math.sin(solarLng) * Math.sin(OBLIQUITY));
final double latRad = latiude * DEGREES_TO_RADIANS;
- double cosHourAngle = (FloatMath.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
+ double cosHourAngle = (Math.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
* Math.sin(solarDec)) / (Math.cos(latRad) * Math.cos(solarDec));
// The day or night never ends for the given date and location, if this value is out of
// range.
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index d1b4569..64f3070 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -31,6 +31,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Handler;
@@ -63,7 +64,7 @@ final class UiModeManagerService extends SystemService {
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
- int mNightMode = UiModeManager.MODE_NIGHT_NO;
+ private int mNightMode = UiModeManager.MODE_NIGHT_NO;
private boolean mCarModeEnabled = false;
private boolean mCharging = false;
@@ -156,7 +157,7 @@ final class UiModeManagerService extends SystemService {
@Override
public void onStart() {
final Context context = getContext();
- mTwilightManager = getLocalService(TwilightManager.class);
+
final PowerManager powerManager =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
@@ -168,22 +169,29 @@ final class UiModeManagerService extends SystemService {
mConfiguration.setToDefaults();
- mDefaultUiModeType = context.getResources().getInteger(
+ final Resources res = context.getResources();
+ mDefaultUiModeType = res.getInteger(
com.android.internal.R.integer.config_defaultUiModeType);
- mCarModeKeepsScreenOn = (context.getResources().getInteger(
+ mCarModeKeepsScreenOn = (res.getInteger(
com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
- mDeskModeKeepsScreenOn = (context.getResources().getInteger(
+ mDeskModeKeepsScreenOn = (res.getInteger(
com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
- mTelevision = context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TELEVISION) ||
- context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_LEANBACK);
- mWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ final PackageManager pm = context.getPackageManager();
+ mTelevision = pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
+ || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ mWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
+
+ final int defaultNightMode = res.getInteger(
+ com.android.internal.R.integer.config_defaultNightMode);
mNightMode = Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO);
+ Settings.Secure.UI_NIGHT_MODE, defaultNightMode);
- mTwilightManager.registerListener(mTwilightListener, mHandler);
+ // Update the initial, static configurations.
+ synchronized (this) {
+ updateConfigurationLocked();
+ sendConfigurationLocked();
+ }
publishBinderService(Context.UI_MODE_SERVICE, mService);
}
@@ -245,7 +253,7 @@ final class UiModeManagerService extends SystemService {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- if (isDoingNightModeLocked() && mNightMode != mode) {
+ if (mNightMode != mode) {
Settings.Secure.putInt(getContext().getContentResolver(),
Settings.Secure.UI_NIGHT_MODE, mode);
mNightMode = mode;
@@ -292,8 +300,11 @@ final class UiModeManagerService extends SystemService {
pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration);
pw.print(" mSystemReady="); pw.println(mSystemReady);
- pw.print(" mTwilightService.getCurrentState()=");
- pw.println(mTwilightManager.getCurrentState());
+ if (mTwilightManager != null) {
+ // We may not have a TwilightManager.
+ pw.print(" mTwilightService.getCurrentState()=");
+ pw.println(mTwilightManager.getCurrentState());
+ }
}
}
@@ -301,6 +312,10 @@ final class UiModeManagerService extends SystemService {
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
synchronized (mLock) {
+ mTwilightManager = getLocalService(TwilightManager.class);
+ if (mTwilightManager != null) {
+ mTwilightManager.registerListener(mTwilightListener, mHandler);
+ }
mSystemReady = true;
mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
updateComputedNightModeLocked();
@@ -309,10 +324,6 @@ final class UiModeManagerService extends SystemService {
}
}
- boolean isDoingNightModeLocked() {
- return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
- }
-
void setCarModeLocked(boolean enabled, int flags) {
if (mCarModeEnabled != enabled) {
mCarModeEnabled = enabled;
@@ -354,17 +365,13 @@ final class UiModeManagerService extends SystemService {
} else if (isDeskDockState(mDockState)) {
uiMode = Configuration.UI_MODE_TYPE_DESK;
}
- if (mCarModeEnabled) {
- if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
- updateComputedNightModeLocked();
- uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
- : Configuration.UI_MODE_NIGHT_NO;
- } else {
- uiMode |= mNightMode << 4;
- }
+
+ if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ updateComputedNightModeLocked();
+ uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
+ : Configuration.UI_MODE_NIGHT_NO;
} else {
- // Disabling the car mode clears the night mode.
- uiMode = (uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | Configuration.UI_MODE_NIGHT_NO;
+ uiMode |= mNightMode << 4;
}
if (LOG) {
@@ -599,7 +606,7 @@ final class UiModeManagerService extends SystemService {
n.defaults = Notification.DEFAULT_LIGHTS;
n.flags = Notification.FLAG_ONGOING_EVENT;
n.when = 0;
- n.color = context.getResources().getColor(
+ n.color = context.getColor(
com.android.internal.R.color.system_notification_accent_color);
n.setLatestEventInfo(
context,
@@ -618,7 +625,7 @@ final class UiModeManagerService extends SystemService {
void updateTwilight() {
synchronized (mLock) {
- if (isDoingNightModeLocked() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+ if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
updateComputedNightModeLocked();
updateLocked(0, 0);
}
@@ -626,9 +633,11 @@ final class UiModeManagerService extends SystemService {
}
private void updateComputedNightModeLocked() {
- TwilightState state = mTwilightManager.getCurrentState();
- if (state != null) {
- mComputedNightMode = state.isNight();
+ if (mTwilightManager != null) {
+ TwilightState state = mTwilightManager.getCurrentState();
+ if (state != null) {
+ mComputedNightMode = state.isNight();
+ }
}
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 8e46c4d..772a15c 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -46,7 +46,6 @@ import java.util.ArrayList;
/** This class calls its monitor every minute. Killing this process if they don't return **/
public class Watchdog extends Thread {
static final String TAG = "Watchdog";
- static final boolean localLOGV = false || false;
// Set this to true to use debug default values.
static final boolean DB = false;
@@ -73,7 +72,7 @@ public class Watchdog extends Thread {
static Watchdog sWatchdog;
/* This handler will be used to post message back onto the main thread */
- final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<HandlerChecker>();
+ final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<>();
final HandlerChecker mMonitorChecker;
ContentResolver mResolver;
ActivityManagerService mActivity;
@@ -106,8 +105,8 @@ public class Watchdog extends Thread {
}
public void scheduleCheckLocked() {
- if (mMonitors.size() == 0 && mHandler.getLooper().isIdling()) {
- // If the target looper is or just recently was idling, then
+ if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) {
+ // If the target looper has recently been polling, then
// there is no reason to enqueue our checker on it since that
// is as good as it not being deadlocked. This avoid having
// to do a context switch to check the thread. Note that we
@@ -191,6 +190,17 @@ public class Watchdog extends Thread {
}
}
+ /** Monitor for checking the availability of binder threads. The monitor will block until
+ * there is a binder thread available to process in coming IPCs to make sure other processes
+ * can still communicate with the service.
+ */
+ private static final class BinderThreadMonitor implements Watchdog.Monitor {
+ @Override
+ public void monitor() {
+ Binder.blockUntilThreadAvailable();
+ }
+ }
+
public interface Monitor {
void monitor();
}
@@ -228,6 +238,9 @@ public class Watchdog extends Thread {
// And the display thread.
mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
"display thread", DEFAULT_TIMEOUT));
+
+ // Initialize monitor for Binder threads.
+ addMonitor(new BinderThreadMonitor());
}
public void init(Context context, ActivityManagerService activity) {
diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java
index bffbb4c2..0de8c8d 100644
--- a/services/core/java/com/android/server/WiredAccessoryManager.java
+++ b/services/core/java/com/android/server/WiredAccessoryManager.java
@@ -16,10 +16,7 @@
package com.android.server;
-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.Message;
@@ -219,6 +216,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks {
mWakeLock.acquire();
+ Log.i(TAG, "MSG_NEW_DEVICE_STATE ");
Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
mHeadsetState, newName);
mHandler.sendMessage(msg);
@@ -286,14 +284,16 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks {
return;
}
- if (LOG)
- Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected"));
+ if (LOG) {
+ Slog.v(TAG, "headsetName: " + headsetName +
+ (state == 1 ? " connected" : " disconnected"));
+ }
if (outDevice != 0) {
- mAudioManager.setWiredDeviceConnectionState(outDevice, state, headsetName);
+ mAudioManager.setWiredDeviceConnectionState(outDevice, state, "", headsetName);
}
if (inDevice != 0) {
- mAudioManager.setWiredDeviceConnectionState(inDevice, state, headsetName);
+ mAudioManager.setWiredDeviceConnectionState(inDevice, state, "", headsetName);
}
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e52b2bf..1b32f57 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -109,7 +109,7 @@ public class AccountManagerService
private static final int TIMEOUT_DELAY_MS = 1000 * 60;
private static final String DATABASE_NAME = "accounts.db";
- private static final int DATABASE_VERSION = 6;
+ private static final int DATABASE_VERSION = 7;
private final Context mContext;
@@ -131,6 +131,8 @@ public class AccountManagerService
private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
private static final String ACCOUNTS_PASSWORD = "password";
private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name";
+ private static final String ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS =
+ "last_password_entry_time_millis_epoch";
private static final String TABLE_AUTHTOKENS = "authtokens";
private static final String AUTHTOKENS_ID = "_id";
@@ -697,7 +699,8 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
new Session(fromAccounts, response, account.type, false,
- false /* stripAuthTokenFromResult */) {
+ false /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */) {
@Override
protected String toDebugString(long now) {
return super.toDebugString(now) + ", getAccountCredentialsForClone"
@@ -725,12 +728,43 @@ public class AccountManagerService
}
}
+ @Override
+ public boolean accountAuthenticated(final Account account) {
+ if (account == null) {
+ throw new IllegalArgumentException("account is null");
+ }
+ checkAuthenticateAccountsPermission(account);
+
+ final UserAccounts accounts = getUserAccountsForCaller();
+ int userId = Binder.getCallingUserHandle().getIdentifier();
+ if (!canUserModifyAccounts(userId) || !canUserModifyAccountsForType(userId, account.type)) {
+ return false;
+ }
+ synchronized (accounts.cacheLock) {
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ int i = db.update(
+ TABLE_ACCOUNTS,
+ values,
+ ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
+ new String[] {
+ account.name, account.type
+ });
+ if (i > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void completeCloningAccount(IAccountManagerResponse response,
final Bundle accountCredentials, final Account account, final UserAccounts targetUser) {
long id = clearCallingIdentity();
try {
new Session(targetUser, response, account.type, false,
- false /* stripAuthTokenFromResult */) {
+ false /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */) {
@Override
protected String toDebugString(long now) {
return super.toDebugString(now) + ", getAccountCredentialsForClone"
@@ -795,6 +829,7 @@ public class AccountManagerService
values.put(ACCOUNTS_NAME, account.name);
values.put(ACCOUNTS_TYPE, account.type);
values.put(ACCOUNTS_PASSWORD, password);
+ values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
if (accountId < 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account
@@ -885,7 +920,8 @@ public class AccountManagerService
public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response,
Account account, String[] features) {
super(accounts, response, account.type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
+ true /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */);
mFeatures = features;
mAccount = account;
}
@@ -1184,7 +1220,8 @@ public class AccountManagerService
public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response,
Account account, boolean expectActivityLaunch) {
super(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */);
+ true /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */);
mAccount = account;
}
@@ -1419,6 +1456,13 @@ public class AccountManagerService
try {
final ContentValues values = new ContentValues();
values.put(ACCOUNTS_PASSWORD, password);
+ long time = 0;
+ // Only set current time, if it is a valid password. For clear password case, it
+ // should not be set.
+ if (password != null) {
+ time = System.currentTimeMillis();
+ }
+ values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, time);
final long accountId = getAccountIdLocked(db, account);
if (accountId >= 0) {
final String[] argsAccountId = {String.valueOf(accountId)};
@@ -1547,8 +1591,9 @@ public class AccountManagerService
UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
long identityToken = clearCallingIdentity();
try {
- new Session(accounts, response, accountType, false,
- false /* stripAuthTokenFromResult */) {
+ new Session(accounts, response, accountType, false /* expectActivityLaunch */,
+ false /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */) {
@Override
protected String toDebugString(long now) {
return super.toDebugString(now) + ", getAuthTokenLabel"
@@ -1648,7 +1693,8 @@ public class AccountManagerService
}
new Session(accounts, response, account.type, expectActivityLaunch,
- false /* stripAuthTokenFromResult */) {
+ false /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */) {
@Override
protected String toDebugString(long now) {
if (loginOptions != null) loginOptions.keySet();
@@ -1736,7 +1782,7 @@ public class AccountManagerService
}
UserHandle user = new UserHandle(userId);
Context contextForUser = getContextForUser(user);
- n.color = contextForUser.getResources().getColor(
+ n.color = contextForUser.getColor(
com.android.internal.R.color.system_notification_accent_color);
n.setLatestEventInfo(contextForUser, title, subtitle,
PendingIntent.getActivityAsUser(mContext, 0, intent,
@@ -1842,7 +1888,8 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
@@ -1917,7 +1964,8 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
@@ -1973,7 +2021,8 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, account.name,
+ true /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.confirmCredentials(this, account, options);
@@ -2009,7 +2058,8 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, account.name,
+ false /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
@@ -2045,7 +2095,8 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
+ true /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */) {
@Override
public void run() throws RemoteException {
mAuthenticator.editProperties(this, mAccountType);
@@ -2071,7 +2122,8 @@ public class AccountManagerService
public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
IAccountManagerResponse response, String type, String[] features, int callingUid) {
super(accounts, response, type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
+ true /* stripAuthTokenFromResult */, null /* accountName */,
+ false /* authDetailsRequired */);
mCallingUid = callingUid;
mFeatures = features;
}
@@ -2437,6 +2489,9 @@ public class AccountManagerService
final String mAccountType;
final boolean mExpectActivityLaunch;
final long mCreationTime;
+ final String mAccountName;
+ // Indicates if we need to add auth details(like last credential time)
+ final boolean mAuthDetailsRequired;
public int mNumResults = 0;
private int mNumRequestContinued = 0;
@@ -2448,7 +2503,8 @@ public class AccountManagerService
protected final UserAccounts mAccounts;
public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
- boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
+ boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
+ boolean authDetailsRequired) {
super();
//if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
@@ -2458,6 +2514,9 @@ public class AccountManagerService
mAccountType = accountType;
mExpectActivityLaunch = expectActivityLaunch;
mCreationTime = SystemClock.elapsedRealtime();
+ mAccountName = accountName;
+ mAuthDetailsRequired = authDetailsRequired;
+
synchronized (mSessions) {
mSessions.put(toString(), this);
}
@@ -2592,6 +2651,16 @@ public class AccountManagerService
public void onResult(Bundle result) {
mNumResults++;
Intent intent = null;
+ if (result != null && mAuthDetailsRequired) {
+ long lastAuthenticatedTime = DatabaseUtils.longForQuery(
+ mAccounts.openHelper.getReadableDatabase(),
+ "select " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " from " +
+ TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND "
+ + ACCOUNTS_TYPE + "=?",
+ new String[]{mAccountName, mAccountType});
+ result.putLong(AccountManager.KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH,
+ lastAuthenticatedTime);
+ }
if (result != null
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
/*
@@ -2798,6 +2867,7 @@ public class AccountManagerService
+ ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ ACCOUNTS_PASSWORD + " TEXT, "
+ ACCOUNTS_PREVIOUS_NAME + " TEXT, "
+ + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, "
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
@@ -2833,6 +2903,11 @@ public class AccountManagerService
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
}
+ private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN "
+ + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0");
+ }
+
private void addOldAccountNameColumn(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
}
@@ -2892,6 +2967,11 @@ public class AccountManagerService
oldVersion++;
}
+ if (oldVersion == 6) {
+ addLastSuccessfullAuthenticatedTimeColumn(db);
+ oldVersion++;
+ }
+
if (oldVersion != newVersion) {
Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
}
@@ -3009,7 +3089,7 @@ public class AccountManagerService
Context contextForUser = getContextForUser(user);
final String notificationTitleFormat =
contextForUser.getText(R.string.notification_title).toString();
- n.color = contextForUser.getResources().getColor(
+ n.color = contextForUser.getColor(
com.android.internal.R.color.system_notification_accent_color);
n.setLatestEventInfo(contextForUser,
String.format(notificationTitleFormat, account.name),
@@ -3083,7 +3163,8 @@ public class AccountManagerService
try {
PackageInfo packageInfo = userPackageManager.getPackageInfo(name, 0 /* flags */);
if (packageInfo != null
- && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+ && (packageInfo.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index aefbf60..3dece49 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static com.android.server.am.ActivityManagerDebugConfig.*;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
@@ -31,8 +33,10 @@ import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
+import android.os.TransactionTooLargeException;
import android.util.ArrayMap;
import android.util.ArraySet;
+
import com.android.internal.app.ProcessStats;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.TransferPipe;
@@ -67,14 +71,15 @@ import android.util.SparseArray;
import android.util.TimeUtils;
public final class ActiveServices {
- static final boolean DEBUG_SERVICE = ActivityManagerService.DEBUG_SERVICE;
- static final boolean DEBUG_SERVICE_EXECUTING = ActivityManagerService.DEBUG_SERVICE_EXECUTING;
- static final boolean DEBUG_DELAYED_SERVICE = ActivityManagerService.DEBUG_SERVICE;
- static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE;
- static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU;
- static final boolean LOG_SERVICE_START_STOP = false;
- static final String TAG = ActivityManagerService.TAG;
- static final String TAG_MU = ActivityManagerService.TAG_MU;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ActiveServices" : TAG_AM;
+ private static final String TAG_MU = TAG + POSTFIX_MU;
+ private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
+ private static final String TAG_SERVICE_EXECUTING = TAG + POSTFIX_SERVICE_EXECUTING;
+
+ private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
+ private static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE;
+
+ private static final boolean LOG_SERVICE_START_STOP = false;
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
@@ -206,11 +211,12 @@ public final class ActiveServices {
void ensureNotStartingBackground(ServiceRecord r) {
if (mStartingBackground.remove(r)) {
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer background starting: " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
+ "No longer background starting: " + r);
rescheduleDelayedStarts();
}
if (mDelayedStartList.remove(r)) {
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer delaying start: " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "No longer delaying start: " + r);
}
}
@@ -229,26 +235,31 @@ public final class ActiveServices {
while (mDelayedStartList.size() > 0
&& mStartingBackground.size() < mMaxStartingBackground) {
ServiceRecord r = mDelayedStartList.remove(0);
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
+ "REM FR DELAY LIST (exec next): " + r);
if (r.pendingStarts.size() <= 0) {
Slog.w(TAG, "**** NO PENDING STARTS! " + r + " startReq=" + r.startRequested
+ " delayedStop=" + r.delayedStop);
}
if (DEBUG_DELAYED_SERVICE) {
if (mDelayedStartList.size() > 0) {
- Slog.v(TAG, "Remaining delayed list:");
+ Slog.v(TAG_SERVICE, "Remaining delayed list:");
for (int i=0; i<mDelayedStartList.size(); i++) {
- Slog.v(TAG, " #" + i + ": " + mDelayedStartList.get(i));
+ Slog.v(TAG_SERVICE, " #" + i + ": " + mDelayedStartList.get(i));
}
}
}
r.delayed = false;
- startServiceInnerLocked(this, r.pendingStarts.get(0).intent, r, false, true);
+ try {
+ startServiceInnerLocked(this, r.pendingStarts.get(0).intent, r, false, true);
+ } catch (TransactionTooLargeException e) {
+ // Ignore, nobody upstack cares.
+ }
}
if (mStartingBackground.size() > 0) {
ServiceRecord next = mStartingBackground.get(0);
long when = next.startingBgTimeout > now ? next.startingBgTimeout : now;
- if (DEBUG_DELAYED_SERVICE) Slog.v(TAG, "Top bg start is " + next
+ if (DEBUG_DELAYED_SERVICE) Slog.v(TAG_SERVICE, "Top bg start is " + next
+ ", can delay others up to " + when);
Message msg = obtainMessage(MSG_BG_START_TIMEOUT);
sendMessageAtTime(msg, when);
@@ -295,10 +306,10 @@ public final class ActiveServices {
return getServiceMap(callingUser).mServicesByName;
}
- ComponentName startServiceLocked(IApplicationThread caller,
- Intent service, String resolvedType,
- int callingPid, int callingUid, int userId) {
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "startService: " + service
+ ComponentName startServiceLocked(IApplicationThread caller, Intent service,
+ String resolvedType, int callingPid, int callingUid, int userId)
+ throws TransactionTooLargeException {
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
final boolean callerFg;
@@ -337,7 +348,7 @@ public final class ActiveServices {
NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
callingUid, r.packageName, service, service.getFlags(), null, r.userId);
if (unscheduleServiceRestartLocked(r, callingUid, false)) {
- if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "START SERVICE WHILE RESTART PENDING: " + r);
}
r.lastActivity = SystemClock.uptimeMillis();
r.startRequested = true;
@@ -360,29 +371,30 @@ public final class ActiveServices {
// service is started. This is especially the case for receivers, which
// may start a service in onReceive() to do some additional work and have
// initialized some global state as part of that.
- if (DEBUG_DELAYED_SERVICE) Slog.v(TAG, "Potential start delay of " + r + " in "
- + proc);
+ if (DEBUG_DELAYED_SERVICE) Slog.v(TAG_SERVICE, "Potential start delay of "
+ + r + " in " + proc);
if (r.delayed) {
// This service is already scheduled for a delayed start; just leave
// it still waiting.
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Continuing to delay: " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Continuing to delay: " + r);
return r.name;
}
if (smap.mStartingBackground.size() >= mMaxStartingBackground) {
// Something else is starting, delay!
- Slog.i(TAG, "Delaying start of: " + r);
+ Slog.i(TAG_SERVICE, "Delaying start of: " + r);
smap.mDelayedStartList.add(r);
r.delayed = true;
return r.name;
}
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Not delaying: " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Not delaying: " + r);
addToStarting = true;
} else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
// We slightly loosen when we will enqueue this new service as a background
// starting service we are waiting for, to also include processes that are
// currently running other services or receivers.
addToStarting = true;
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Not delaying, but counting as bg: " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
+ "Not delaying, but counting as bg: " + r);
} else if (DEBUG_DELAYED_STARTS) {
StringBuilder sb = new StringBuilder(128);
sb.append("Not potential delay (state=").append(proc.curProcState)
@@ -394,24 +406,25 @@ public final class ActiveServices {
}
sb.append("): ");
sb.append(r.toString());
- Slog.v(TAG, sb.toString());
+ Slog.v(TAG_SERVICE, sb.toString());
}
} else if (DEBUG_DELAYED_STARTS) {
if (callerFg) {
- Slog.v(TAG, "Not potential delay (callerFg=" + callerFg + " uid="
+ Slog.v(TAG_SERVICE, "Not potential delay (callerFg=" + callerFg + " uid="
+ callingUid + " pid=" + callingPid + "): " + r);
} else if (r.app != null) {
- Slog.v(TAG, "Not potential delay (cur app=" + r.app + "): " + r);
+ Slog.v(TAG_SERVICE, "Not potential delay (cur app=" + r.app + "): " + r);
} else {
- Slog.v(TAG, "Not potential delay (user " + r.userId + " not started): " + r);
+ Slog.v(TAG_SERVICE,
+ "Not potential delay (user " + r.userId + " not started): " + r);
}
}
return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}
- ComponentName startServiceInnerLocked(ServiceMap smap, Intent service,
- ServiceRecord r, boolean callerFg, boolean addToStarting) {
+ ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
+ boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
ProcessStats.ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
@@ -432,9 +445,9 @@ public final class ActiveServices {
if (DEBUG_DELAYED_SERVICE) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
- Slog.v(TAG, "Starting background (first=" + first + "): " + r, here);
+ Slog.v(TAG_SERVICE, "Starting background (first=" + first + "): " + r, here);
} else if (DEBUG_DELAYED_STARTS) {
- Slog.v(TAG, "Starting background (first=" + first + "): " + r);
+ Slog.v(TAG_SERVICE, "Starting background (first=" + first + "): " + r);
}
if (first) {
smap.rescheduleDelayedStarts();
@@ -451,7 +464,7 @@ public final class ActiveServices {
// If service isn't actually running, but is is being held in the
// delayed list, then we need to keep it started but note that it
// should be stopped once no longer delayed.
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Delaying stop of pending: " + service);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Delaying stop of pending: " + service);
service.delayedStop = true;
return;
}
@@ -469,7 +482,7 @@ public final class ActiveServices {
int stopServiceLocked(IApplicationThread caller, Intent service,
String resolvedType, int userId) {
- if (DEBUG_SERVICE) Slog.v(TAG, "stopService: " + service
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "stopService: " + service
+ " type=" + resolvedType);
final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
@@ -525,7 +538,7 @@ public final class ActiveServices {
boolean stopServiceTokenLocked(ComponentName className, IBinder token,
int startId) {
- if (DEBUG_SERVICE) Slog.v(TAG, "stopServiceToken: " + className
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "stopServiceToken: " + className
+ " " + token + " startId=" + startId);
ServiceRecord r = findServiceLocked(className, token, UserHandle.getCallingUserId());
if (r != null) {
@@ -684,10 +697,10 @@ public final class ActiveServices {
return false;
}
- int bindServiceLocked(IApplicationThread caller, IBinder token,
- Intent service, String resolvedType,
- IServiceConnection connection, int flags, int userId) {
- if (DEBUG_SERVICE) Slog.v(TAG, "bindService: " + service
+ int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
+ String resolvedType, IServiceConnection connection, int flags, int userId)
+ throws TransactionTooLargeException {
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
+ " flags=0x" + Integer.toHexString(flags));
final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
@@ -751,7 +764,7 @@ public final class ActiveServices {
try {
if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) {
- if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: "
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "BIND SERVICE WHILE RESTART PENDING: "
+ s);
}
@@ -819,7 +832,7 @@ public final class ActiveServices {
mAm.updateOomAdjLocked(s.app);
}
- if (DEBUG_SERVICE) Slog.v(TAG, "Bind " + s + " with " + b
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
+ ": received=" + b.intent.received
+ " apps=" + b.intent.apps.size()
+ " doRebind=" + b.intent.doRebind);
@@ -857,7 +870,7 @@ public final class ActiveServices {
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
final long origId = Binder.clearCallingIdentity();
try {
- if (DEBUG_SERVICE) Slog.v(TAG, "PUBLISHING " + r
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "PUBLISHING " + r
+ " " + intent + ": " + service);
if (r != null) {
Intent.FilterComparison filter
@@ -873,14 +886,14 @@ public final class ActiveServices {
ConnectionRecord c = clist.get(i);
if (!filter.equals(c.binding.intent.intent)) {
if (DEBUG_SERVICE) Slog.v(
- TAG, "Not publishing to: " + c);
+ TAG_SERVICE, "Not publishing to: " + c);
if (DEBUG_SERVICE) Slog.v(
- TAG, "Bound intent: " + c.binding.intent.intent);
+ TAG_SERVICE, "Bound intent: " + c.binding.intent.intent);
if (DEBUG_SERVICE) Slog.v(
- TAG, "Published intent: " + intent);
+ TAG_SERVICE, "Published intent: " + intent);
continue;
}
- if (DEBUG_SERVICE) Slog.v(TAG, "Publishing to: " + c);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Publishing to: " + c);
try {
c.conn.connected(r.name, service);
} catch (Exception e) {
@@ -901,7 +914,7 @@ public final class ActiveServices {
boolean unbindServiceLocked(IServiceConnection connection) {
IBinder binder = connection.asBinder();
- if (DEBUG_SERVICE) Slog.v(TAG, "unbindService: conn=" + binder);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "unbindService: conn=" + binder);
ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
if (clist == null) {
Slog.w(TAG, "Unbind failed: could not find connection for "
@@ -945,7 +958,7 @@ public final class ActiveServices {
Intent.FilterComparison filter
= new Intent.FilterComparison(intent);
IntentBindRecord b = r.bindings.get(filter);
- if (DEBUG_SERVICE) Slog.v(TAG, "unbindFinished in " + r
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "unbindFinished in " + r
+ " at " + b + ": apps="
+ (b != null ? b.apps.size() : 0));
@@ -963,7 +976,11 @@ public final class ActiveServices {
break;
}
}
- requestServiceBindingLocked(r, b, inFg, true);
+ try {
+ requestServiceBindingLocked(r, b, inFg, true);
+ } catch (TransactionTooLargeException e) {
+ // Don't pass this back to ActivityThread, it's unrelated.
+ }
} else {
// Note to tell the service the next time there is
// a new client.
@@ -1012,7 +1029,7 @@ public final class ActiveServices {
String resolvedType, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg) {
ServiceRecord r = null;
- if (DEBUG_SERVICE) Slog.v(TAG, "retrieveServiceLocked: " + service
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service
+ " type=" + resolvedType + " callingUid=" + callingUid);
userId = mAm.handleIncomingUser(callingPid, callingUid, userId,
@@ -1036,7 +1053,7 @@ public final class ActiveServices {
ServiceInfo sInfo =
rInfo != null ? rInfo.serviceInfo : null;
if (sInfo == null) {
- Slog.w(TAG, "Unable to start service " + service + " U=" + userId +
+ Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId +
": not found");
return null;
}
@@ -1110,9 +1127,9 @@ public final class ActiveServices {
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
- if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING "
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
+ why + " of " + r + " in app " + r.app);
- else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, ">>> EXECUTING "
+ else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
+ why + " of " + r.shortName);
long now = SystemClock.uptimeMillis();
if (r.executeNesting == 0) {
@@ -1137,8 +1154,8 @@ public final class ActiveServices {
r.executingStart = now;
}
- private final boolean requestServiceBindingLocked(ServiceRecord r,
- IntentBindRecord i, boolean execInFg, boolean rebind) {
+ private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
+ boolean execInFg, boolean rebind) throws TransactionTooLargeException {
if (r.app == null || r.app.thread == null) {
// If service is not currently running, can't yet bind.
return false;
@@ -1154,8 +1171,17 @@ public final class ActiveServices {
}
i.hasBound = true;
i.doRebind = false;
+ } catch (TransactionTooLargeException e) {
+ // Keep the executeNesting count accurate.
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
+ final boolean inDestroying = mDestroyingServices.contains(r);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+ throw e;
} catch (RemoteException e) {
- if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while binding " + r);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
+ // Keep the executeNesting count accurate.
+ final boolean inDestroying = mDestroyingServices.contains(r);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying);
return false;
}
}
@@ -1280,7 +1306,11 @@ public final class ActiveServices {
if (!mRestartingServices.contains(r)) {
return;
}
- bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
+ try {
+ bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
+ } catch (TransactionTooLargeException e) {
+ // Ignore, it's been logged and nothing upstack cares.
+ }
}
private final boolean unscheduleServiceRestartLocked(ServiceRecord r, int callingUid,
@@ -1321,8 +1351,8 @@ public final class ActiveServices {
}
}
- private final String bringUpServiceLocked(ServiceRecord r,
- int intentFlags, boolean execInFg, boolean whileRestarting) {
+ private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
+ boolean whileRestarting) throws TransactionTooLargeException {
//Slog.i(TAG, "Bring up service:");
//r.dump(" ");
@@ -1336,17 +1366,18 @@ public final class ActiveServices {
return null;
}
- if (DEBUG_SERVICE) Slog.v(TAG, "Bringing up " + r + " " + r.intent);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent);
// We are now bringing the service up, so no longer in the
// restarting state.
if (mRestartingServices.remove(r)) {
+ r.resetRestartCounter();
clearRestartingIfNeededLocked(r);
}
// Make sure this service is no longer considered delayed, we are starting it now.
if (r.delayed) {
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (bring up): " + r);
getServiceMap(r.userId).mDelayedStartList.remove(r);
r.delayed = false;
}
@@ -1386,6 +1417,8 @@ public final class ActiveServices {
app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
realStartServiceLocked(r, app, execInFg);
return null;
+ } catch (TransactionTooLargeException e) {
+ throw e;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting service " + r.shortName, e);
}
@@ -1429,7 +1462,8 @@ public final class ActiveServices {
// Oh and hey we've already been asked to stop!
r.delayedStop = false;
if (r.startRequested) {
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
+ "Applying delayed stop (in bring up): " + r);
stopServiceLocked(r);
}
}
@@ -1437,7 +1471,8 @@ public final class ActiveServices {
return null;
}
- private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) {
+ private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
+ throws TransactionTooLargeException {
for (int i=r.bindings.size()-1; i>=0; i--) {
IntentBindRecord ibr = r.bindings.valueAt(i);
if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
@@ -1457,7 +1492,7 @@ public final class ActiveServices {
r.app = app;
r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
- app.services.add(r);
+ final boolean newService = app.services.add(r);
bumpServiceExecutingLocked(r, execInFg, "create");
mAm.updateLruProcessLocked(app, false, null);
mAm.updateOomAdjLocked();
@@ -1484,12 +1519,23 @@ public final class ActiveServices {
} catch (DeadObjectException e) {
Slog.w(TAG, "Application dead when creating service " + r);
mAm.appDiedLocked(app);
+ throw e;
} finally {
if (!created) {
- app.services.remove(r);
- r.app = null;
- scheduleServiceRestartLocked(r, false);
- return;
+ // Keep the executeNesting count accurate.
+ final boolean inDestroying = mDestroyingServices.contains(r);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+
+ // Cleanup.
+ if (newService) {
+ app.services.remove(r);
+ r.app = null;
+ }
+
+ // Retry.
+ if (!inDestroying) {
+ scheduleServiceRestartLocked(r, false);
+ }
}
}
@@ -1508,7 +1554,7 @@ public final class ActiveServices {
sendServiceArgsLocked(r, execInFg, true);
if (r.delayed) {
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r);
getServiceMap(r.userId).mDelayedStartList.remove(r);
r.delayed = false;
}
@@ -1517,23 +1563,26 @@ public final class ActiveServices {
// Oh and hey we've already been asked to stop!
r.delayedStop = false;
if (r.startRequested) {
- if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (from start): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
+ "Applying delayed stop (from start): " + r);
stopServiceLocked(r);
}
}
}
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
- boolean oomAdjusted) {
+ boolean oomAdjusted) throws TransactionTooLargeException {
final int N = r.pendingStarts.size();
if (N == 0) {
return;
}
while (r.pendingStarts.size() > 0) {
+ Exception caughtException = null;
+ ServiceRecord.StartItem si;
try {
- ServiceRecord.StartItem si = r.pendingStarts.remove(0);
- if (DEBUG_SERVICE) Slog.v(TAG, "Sending arguments to: "
+ si = r.pendingStarts.remove(0);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Sending arguments to: "
+ r + " " + r.intent + " args=" + si.intent);
if (si.intent == null && N > 1) {
// If somehow we got a dummy null intent in the middle,
@@ -1562,13 +1611,26 @@ public final class ActiveServices {
flags |= Service.START_FLAG_REDELIVERY;
}
r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
+ } catch (TransactionTooLargeException e) {
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large: intent="
+ + si.intent);
+ caughtException = e;
} catch (RemoteException e) {
- // Remote process gone... we'll let the normal cleanup take
- // care of this.
- if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r);
- break;
+ // Remote process gone... we'll let the normal cleanup take care of this.
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r);
+ caughtException = e;
} catch (Exception e) {
Slog.w(TAG, "Unexpected exception", e);
+ caughtException = e;
+ }
+
+ if (caughtException != null) {
+ // Keep nesting count correct
+ final boolean inDestroying = mDestroyingServices.contains(r);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+ if (caughtException instanceof TransactionTooLargeException) {
+ throw (TransactionTooLargeException)caughtException;
+ }
break;
}
}
@@ -1635,7 +1697,7 @@ public final class ActiveServices {
if (r.app != null && r.app.thread != null) {
for (int i=r.bindings.size()-1; i>=0; i--) {
IntentBindRecord ibr = r.bindings.valueAt(i);
- if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down binding " + ibr
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down binding " + ibr
+ ": hasBound=" + ibr.hasBound);
if (ibr.hasBound) {
try {
@@ -1653,7 +1715,7 @@ public final class ActiveServices {
}
}
- if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down " + r + " " + r.intent);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down " + r + " " + r.intent);
r.destroyTime = SystemClock.uptimeMillis();
if (LOG_SERVICE_START_STOP) {
EventLogTags.writeAmDestroyService(
@@ -1670,7 +1732,7 @@ public final class ActiveServices {
for (int i=mPendingServices.size()-1; i>=0; i--) {
if (mPendingServices.get(i) == r) {
mPendingServices.remove(i);
- if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Removed pending: " + r);
}
}
@@ -1703,11 +1765,11 @@ public final class ActiveServices {
}
} else {
if (DEBUG_SERVICE) Slog.v(
- TAG, "Removed service that has no process: " + r);
+ TAG_SERVICE, "Removed service that has no process: " + r);
}
} else {
if (DEBUG_SERVICE) Slog.v(
- TAG, "Removed service that is not running: " + r);
+ TAG_SERVICE, "Removed service that is not running: " + r);
}
if (r.bindings.size() > 0) {
@@ -1774,7 +1836,7 @@ public final class ActiveServices {
}
if (!c.serviceDead) {
- if (DEBUG_SERVICE) Slog.v(TAG, "Disconnecting binding " + b.intent
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Disconnecting binding " + b.intent
+ ": shouldUnbind=" + b.intent.hasBound);
if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0
&& b.intent.hasBound) {
@@ -1876,6 +1938,7 @@ public final class ActiveServices {
} else if (r.executeNesting != 1) {
Slog.wtfStack(TAG, "Service done with onDestroy, but executeNesting="
+ r.executeNesting + ": " + r);
+ // Fake it to keep from ANR due to orphaned entry.
r.executeNesting = 1;
}
}
@@ -1901,19 +1964,20 @@ public final class ActiveServices {
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
boolean finishing) {
- if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
+ ": nesting=" + r.executeNesting
+ ", inDestroying=" + inDestroying + ", app=" + r.app);
- else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "<<< DONE EXECUTING " + r.shortName);
+ else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
+ "<<< DONE EXECUTING " + r.shortName);
r.executeNesting--;
if (r.executeNesting <= 0) {
if (r.app != null) {
- if (DEBUG_SERVICE) Slog.v(TAG,
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"Nesting at 0 of " + r.shortName);
r.app.execServicesFg = false;
r.app.executingServices.remove(r);
if (r.app.executingServices.size() == 0) {
- if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG,
+ if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
"No more executingServices of " + r.shortName);
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
} else if (r.executeFg) {
@@ -1926,7 +1990,7 @@ public final class ActiveServices {
}
}
if (inDestroying) {
- if (DEBUG_SERVICE) Slog.v(TAG,
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"doneExecuting remove destroying " + r);
mDestroyingServices.remove(r);
r.bindings.clear();
@@ -2090,7 +2154,11 @@ public final class ActiveServices {
if (sr.app != null && sr.app.thread != null) {
// We always run in the foreground, since this is called as
// part of the "remove task" UI operation.
- sendServiceArgsLocked(sr, true, false);
+ try {
+ sendServiceArgsLocked(sr, true, false);
+ } catch (TransactionTooLargeException e) {
+ // Ignore, keep going.
+ }
}
}
}
@@ -2126,8 +2194,16 @@ public final class ActiveServices {
}
}
- // First clear app state from services.
- for (int i=app.services.size()-1; i>=0; i--) {
+ // Clean up any connections this application has to other services.
+ for (int i = app.connections.size() - 1; i >= 0; i--) {
+ ConnectionRecord r = app.connections.valueAt(i);
+ removeConnectionLocked(r, app, null);
+ }
+ updateServiceConnectionActivitiesLocked(app);
+ app.connections.clear();
+
+ // Clear app state from services.
+ for (int i = app.services.size() - 1; i >= 0; i--) {
ServiceRecord sr = app.services.valueAt(i);
synchronized (sr.stats.getBatteryStats()) {
sr.stats.stopLaunchedLocked();
@@ -2140,13 +2216,13 @@ public final class ActiveServices {
sr.executeNesting = 0;
sr.forceClearTracker();
if (mDestroyingServices.remove(sr)) {
- if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "killServices remove destroying " + sr);
}
final int numClients = sr.bindings.size();
for (int bindingi=numClients-1; bindingi>=0; bindingi--) {
IntentBindRecord b = sr.bindings.valueAt(bindingi);
- if (DEBUG_SERVICE) Slog.v(TAG, "Killing binding " + b
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Killing binding " + b
+ ": shouldUnbind=" + b.hasBound);
b.binder = null;
b.requested = b.received = b.hasBound = false;
@@ -2187,14 +2263,6 @@ public final class ActiveServices {
}
}
- // Clean up any connections this application has to other services.
- for (int i=app.connections.size()-1; i>=0; i--) {
- ConnectionRecord r = app.connections.valueAt(i);
- removeConnectionLocked(r, app, null);
- }
- updateServiceConnectionActivitiesLocked(app);
- app.connections.clear();
-
ServiceMap smap = getServiceMap(app.userId);
// Now do remaining service cleanup.
@@ -2227,7 +2295,7 @@ public final class ActiveServices {
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
sr.userId, sr.crashCount, sr.shortName, app.pid);
bringDownServiceLocked(sr);
- } else if (!allowRestart) {
+ } else if (!allowRestart || !mAm.isUserRunningLocked(sr.userId, false)) {
bringDownServiceLocked(sr);
} else {
boolean canceled = scheduleServiceRestartLocked(sr, true);
@@ -2280,7 +2348,7 @@ public final class ActiveServices {
if (sr.app == app) {
sr.forceClearTracker();
mDestroyingServices.remove(i);
- if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "killServices remove destroying " + sr);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
new file mode 100644
index 0000000..d64e39f
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 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;
+
+/**
+ * Common class for the various debug {@link android.util.Log} output configuration in the activity
+ * manager package.
+ */
+class ActivityManagerDebugConfig {
+
+ // All output logs in the activity manager package use the {@link #TAG_AM} string for tagging
+ // their log output. This makes it easy to identify the origin of the log message when sifting
+ // through a large amount of log output from multiple sources. However, it also makes trying
+ // to figure-out the origin of a log message while debugging the activity manager a little
+ // painful. By setting this constant to true, log messages from the activity manager package
+ // will be tagged with their class names instead fot the generic tag.
+ static final boolean TAG_WITH_CLASS_NAME = false;
+
+ // While debugging it is sometimes useful to have the category name of the log appended to the
+ // base log tag to make sifting through logs with the same base tag easier. By setting this
+ // constant to true, the category name of the log point will be appended to the log tag.
+ static final boolean APPEND_CATEGORY_NAME = false;
+
+ // Default log tag for the activity manager package.
+ static final String TAG_AM = "ActivityManager";
+
+ // Enable all debug log categories.
+ static final boolean DEBUG_ALL = false;
+
+ // Available log categories in the activity manager package.
+ static final boolean DEBUG_BACKUP = DEBUG_ALL || false;
+ static final boolean DEBUG_BROADCAST = DEBUG_ALL || false;
+ static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false;
+ static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
+ static final boolean DEBUG_CLEANUP = DEBUG_ALL || false;
+ static final boolean DEBUG_CONFIGURATION = DEBUG_ALL || false;
+ static final boolean DEBUG_FOCUS = false;
+ static final boolean DEBUG_IMMERSIVE = DEBUG_ALL || false;
+ static final boolean DEBUG_LOCKSCREEN = DEBUG_ALL || false;
+ static final boolean DEBUG_LRU = DEBUG_ALL || false;
+ static final boolean DEBUG_MU = DEBUG_ALL || false;
+ static final boolean DEBUG_OOM_ADJ = DEBUG_ALL || false;
+ static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
+ static final boolean DEBUG_POWER = DEBUG_ALL || false;
+ static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
+ static final boolean DEBUG_PROCESS_OBSERVERS = DEBUG_ALL || false;
+ static final boolean DEBUG_PROCESSES = DEBUG_ALL || false;
+ static final boolean DEBUG_PROVIDER = DEBUG_ALL || false;
+ static final boolean DEBUG_PSS = DEBUG_ALL || false;
+ static final boolean DEBUG_RECENTS = DEBUG_ALL || false;
+ static final boolean DEBUG_RESULTS = DEBUG_ALL || false;
+ static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
+ static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false;
+ static final boolean DEBUG_STACK = DEBUG_ALL || false;
+ static final boolean DEBUG_SWITCH = DEBUG_ALL || false;
+ static final boolean DEBUG_TASKS = DEBUG_ALL || false;
+ static final boolean DEBUG_THUMBNAILS = DEBUG_ALL || false;
+ static final boolean DEBUG_TRANSITION = DEBUG_ALL || false;
+ static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false;
+ static final boolean DEBUG_USER_LEAVING = DEBUG_ALL || false;
+ static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false;
+ static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false;
+
+ static final String POSTFIX_BACKUP = (APPEND_CATEGORY_NAME) ? "_Backup" : "";
+ static final String POSTFIX_BROADCAST = (APPEND_CATEGORY_NAME) ? "_Broadcast" : "";
+ static final String POSTFIX_CLEANUP = (APPEND_CATEGORY_NAME) ? "_Cleanup" : "";
+ static final String POSTFIX_CONFIGURATION = (APPEND_CATEGORY_NAME) ? "_Configuration" : "";
+ static final String POSTFIX_FOCUS = (APPEND_CATEGORY_NAME) ? "_Focus" : "";
+ static final String POSTFIX_IMMERSIVE = (APPEND_CATEGORY_NAME) ? "_Immersive" : "";
+ static final String POSTFIX_LOCKSCREEN = (APPEND_CATEGORY_NAME) ? "_LOCKSCREEN" : "";
+ static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : "";
+ static final String POSTFIX_MU = "_MU";
+ static final String POSTFIX_OOM_ADJ = (APPEND_CATEGORY_NAME) ? "_OomAdj" : "";
+ static final String POSTFIX_PAUSE = (APPEND_CATEGORY_NAME) ? "_Pause" : "";
+ static final String POSTFIX_POWER = (APPEND_CATEGORY_NAME) ? "_Power" : "";
+ static final String POSTFIX_PROCESS_OBSERVERS = (APPEND_CATEGORY_NAME)
+ ? "_ProcessObservers" : "";
+ static final String POSTFIX_PROCESSES = (APPEND_CATEGORY_NAME) ? "_Processes" : "";
+ static final String POSTFIX_PROVIDER = (APPEND_CATEGORY_NAME) ? "_Provider" : "";
+ static final String POSTFIX_PSS = (APPEND_CATEGORY_NAME) ? "_Pss" : "";
+ static final String POSTFIX_RESULTS = (APPEND_CATEGORY_NAME) ? "_Results" : "";
+ static final String POSTFIX_RECENTS = (APPEND_CATEGORY_NAME) ? "_Recents" : "";
+ static final String POSTFIX_SERVICE = (APPEND_CATEGORY_NAME) ? "_Service" : "";
+ static final String POSTFIX_SERVICE_EXECUTING =
+ (APPEND_CATEGORY_NAME) ? "_ServiceExecuting" : "";
+ static final String POSTFIX_STACK = (APPEND_CATEGORY_NAME) ? "_Stack" : "";
+ static final String POSTFIX_SWITCH = (APPEND_CATEGORY_NAME) ? "_Switch" : "";
+ static final String POSTFIX_TASKS = (APPEND_CATEGORY_NAME) ? "_Tasks" : "";
+ static final String POSTFIX_THUMBNAILS = (APPEND_CATEGORY_NAME) ? "_Thumbnails" : "";
+ static final String POSTFIX_TRANSITION = (APPEND_CATEGORY_NAME) ? "_Transition" : "";
+ static final String POSTFIX_URI_PERMISSION = (APPEND_CATEGORY_NAME) ? "_UriPermission" : "";
+ static final String POSTFIX_USER_LEAVING = (APPEND_CATEGORY_NAME) ? "_UserLeaving" : "";
+ static final String POSTFIX_VISIBILITY = (APPEND_CATEGORY_NAME) ? "_Visibility" : "";
+
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e8f3757..4970e0f 100755..100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -27,10 +27,14 @@ 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.Watchdog.NATIVE_STACKS_OF_INTEREST;
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import static com.android.server.am.ActivityManagerDebugConfig.*;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.Manifest;
import android.app.AppOpsManager;
@@ -40,7 +44,6 @@ import android.app.IActivityContainerCallback;
import android.app.IAppTask;
import android.app.ITaskStackListener;
import android.app.ProfilerInfo;
-import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
@@ -50,24 +53,32 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.BatteryStats;
import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.os.TransactionTooLargeException;
+import android.os.WorkSource;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.SparseIntArray;
+import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.DumpHeapActivity;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.ProcessStats;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.TransferPipe;
import com.android.internal.os.Zygote;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.MemInfoReader;
@@ -91,6 +102,7 @@ import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -101,6 +113,7 @@ import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.SleepToken;
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.ActivityThread;
@@ -169,6 +182,7 @@ import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.IPermissionController;
+import android.os.IProcessInfoService;
import android.os.IRemoteCallback;
import android.os.IUserManager;
import android.os.Looper;
@@ -241,49 +255,37 @@ public final class ActivityManagerService extends ActivityManagerNative
// File that stores last updated system version and called preboot receivers
static final String CALLED_PRE_BOOTS_FILENAME = "called_pre_boots.dat";
- static final String TAG = "ActivityManager";
- static final String TAG_MU = "ActivityManagerServiceMU";
- static final boolean DEBUG = false;
- static final boolean localLOGV = DEBUG;
- static final boolean DEBUG_BACKUP = localLOGV || false;
- static final boolean DEBUG_BROADCAST = localLOGV || false;
- static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
- static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false;
- static final boolean DEBUG_CLEANUP = localLOGV || false;
- static final boolean DEBUG_CONFIGURATION = localLOGV || false;
- static final boolean DEBUG_FOCUS = false;
- static final boolean DEBUG_IMMERSIVE = localLOGV || false;
- static final boolean DEBUG_MU = localLOGV || false;
- static final boolean DEBUG_OOM_ADJ = localLOGV || false;
- static final boolean DEBUG_LRU = localLOGV || false;
- static final boolean DEBUG_PAUSE = localLOGV || false;
- static final boolean DEBUG_POWER = localLOGV || false;
- static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
- static final boolean DEBUG_PROCESS_OBSERVERS = localLOGV || false;
- static final boolean DEBUG_PROCESSES = localLOGV || false;
- static final boolean DEBUG_PROVIDER = localLOGV || false;
- static final boolean DEBUG_RESULTS = localLOGV || false;
- static final boolean DEBUG_SERVICE = localLOGV || false;
- static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false;
- static final boolean DEBUG_STACK = localLOGV || false;
- static final boolean DEBUG_SWITCH = localLOGV || false;
- static final boolean DEBUG_TASKS = localLOGV || false;
- static final boolean DEBUG_THUMBNAILS = localLOGV || false;
- static final boolean DEBUG_TRANSITION = localLOGV || false;
- static final boolean DEBUG_URI_PERMISSION = localLOGV || false;
- static final boolean DEBUG_USER_LEAVING = localLOGV || false;
- static final boolean DEBUG_VISBILITY = localLOGV || false;
- static final boolean DEBUG_PSS = localLOGV || false;
- static final boolean DEBUG_LOCKSCREEN = localLOGV || false;
- static final boolean DEBUG_RECENTS = localLOGV || false;
- static final boolean VALIDATE_TOKENS = false;
- static final boolean SHOW_ACTIVITY_START_TIME = true;
-
- // Control over CPU and battery monitoring.
- static final long BATTERY_STATS_TIME = 30*60*1000; // write battery stats every 30 minutes.
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
+ private static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
+ private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
+ private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
+ private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
+ private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
+ private static final String TAG_IMMERSIVE = TAG + POSTFIX_IMMERSIVE;
+ private static final String TAG_LOCKSCREEN = TAG + POSTFIX_LOCKSCREEN;
+ private static final String TAG_LRU = TAG + POSTFIX_LRU;
+ private static final String TAG_MU = TAG + POSTFIX_MU;
+ private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
+ private static final String TAG_POWER = TAG + POSTFIX_POWER;
+ private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
+ private static final String TAG_PROCESSES = TAG + POSTFIX_PROCESSES;
+ private static final String TAG_PROVIDER = TAG + POSTFIX_PROVIDER;
+ private static final String TAG_PSS = TAG + POSTFIX_PSS;
+ private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
+ private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
+ private static final String TAG_STACK = TAG + POSTFIX_STACK;
+ private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
+ private static final String TAG_URI_PERMISSION = TAG + POSTFIX_URI_PERMISSION;
+ private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
+
+ /** Control over CPU and battery monitoring */
+ // write battery stats every 30 minutes.
+ static final long BATTERY_STATS_TIME = 30 * 60 * 1000;
static final boolean MONITOR_CPU_USAGE = true;
- static final long MONITOR_CPU_MIN_TIME = 5*1000; // don't sample cpu less than every 5 seconds.
- static final long MONITOR_CPU_MAX_TIME = 0x0fffffff; // wait possibly forever for next cpu sample.
+ // don't sample cpu less than every 5 seconds.
+ static final long MONITOR_CPU_MIN_TIME = 5 * 1000;
+ // wait possibly forever for next cpu sample.
+ static final long MONITOR_CPU_MAX_TIME = 0x0fffffff;
static final boolean MONITOR_THREAD_CPU_USAGE = false;
// The flags that are set for all calls we make to the package manager.
@@ -293,9 +295,6 @@ public final class ActivityManagerService extends ActivityManagerNative
static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
- // Maximum number recent bitmaps to keep in memory.
- static final int MAX_RECENT_BITMAPS = 3;
-
// Amount of time after a call to stopAppSwitches() during which we will
// prevent further untrusted switches from happening.
static final long APP_SWITCH_DELAY_TIME = 5*1000;
@@ -402,24 +401,12 @@ public final class ActivityManagerService extends ActivityManagerNative
BroadcastQueue broadcastQueueForIntent(Intent intent) {
final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
- if (DEBUG_BACKGROUND_BROADCAST) {
- Slog.i(TAG, "Broadcast intent " + intent + " on "
- + (isFg ? "foreground" : "background")
- + " queue");
- }
+ if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
+ "Broadcast intent " + intent + " on "
+ + (isFg ? "foreground" : "background") + " queue");
return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
}
- BroadcastRecord broadcastRecordForReceiverLocked(IBinder receiver) {
- for (BroadcastQueue queue : mBroadcastQueues) {
- BroadcastRecord r = queue.getMatchingOrderedReceiver(receiver);
- if (r != null) {
- return r;
- }
- }
- return null;
- }
-
/**
* Activity we have told the window manager to have key focus.
*/
@@ -428,8 +415,7 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* List of intents that were used to start the most recent tasks.
*/
- ArrayList<TaskRecord> mRecentTasks;
- ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
+ private final RecentTasks mRecentTasks;
/**
* For addAppTask: cached of the last activity component that was added.
@@ -446,28 +432,43 @@ public final class ActivityManagerService extends ActivityManagerNative
*/
ActivityInfo mLastAddedTaskActivity;
+ /**
+ * List of packages whitelisted by DevicePolicyManager for locktask. Indexed by userId.
+ */
+ SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
+
+ /**
+ * The package name of the DeviceOwner. This package is not permitted to have its data cleared.
+ */
+ String mDeviceOwnerName;
+
public class PendingAssistExtras extends Binder implements Runnable {
public final ActivityRecord activity;
public final Bundle extras;
public final Intent intent;
public final String hint;
+ public final IResultReceiver receiver;
public final int userHandle;
public boolean haveResult = false;
public Bundle result = null;
public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
- String _hint, int _userHandle) {
+ String _hint, IResultReceiver _receiver, int _userHandle) {
activity = _activity;
extras = _extras;
intent = _intent;
hint = _hint;
+ receiver = _receiver;
userHandle = _userHandle;
}
@Override
public void run() {
Slog.w(TAG, "getAssistContextExtras failed: timeout retrieving from " + activity);
- synchronized (this) {
- haveResult = true;
- notifyAll();
+ synchronized (ActivityManagerService.this) {
+ synchronized (this) {
+ haveResult = true;
+ notifyAll();
+ }
+ pendingAssistExtrasTimedOutLocked(this);
}
}
}
@@ -994,16 +995,36 @@ public final class ActivityManagerService extends ActivityManagerNative
private boolean mSleeping = false;
/**
+ * The process state used for processes that are running the top activities.
+ * This changes between TOP and TOP_SLEEPING to following mSleeping.
+ */
+ int mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
+
+ /**
* Set while we are running a voice interaction. This overrides
* sleeping while it is active.
*/
- private boolean mRunningVoice = false;
+ private IVoiceInteractionSession mRunningVoice;
+
+ /**
+ * We want to hold a wake lock while running a voice interaction session, since
+ * this may happen with the screen off and we need to keep the CPU running to
+ * be able to continue to interact with the user.
+ */
+ PowerManager.WakeLock mVoiceWakeLock;
/**
* State of external calls telling us if the device is awake or asleep.
*/
private int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+ /**
+ * A list of tokens that cause the top activity to be put to sleep.
+ * They are used by components that may hide and block interaction with underlying
+ * activities.
+ */
+ final ArrayList<SleepToken> mSleepTokens = new ArrayList<SleepToken>();
+
static final int LOCK_SCREEN_HIDDEN = 0;
static final int LOCK_SCREEN_LEAVING = 1;
static final int LOCK_SCREEN_SHOWN = 2;
@@ -1129,6 +1150,11 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean mAutoStopProfiler = false;
int mProfileType = 0;
String mOpenGlTraceApp = null;
+ final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>();
+ String mMemWatchDumpProcName;
+ String mMemWatchDumpFile;
+ int mMemWatchDumpPid;
+ int mMemWatchDumpUid;
final long[] mTmpLong = new long[1];
@@ -1211,7 +1237,7 @@ public final class ActivityManagerService extends ActivityManagerNative
AppDeathRecipient(ProcessRecord app, int pid,
IApplicationThread thread) {
- if (localLOGV) Slog.v(
+ if (DEBUG_ALL) Slog.v(
TAG, "New death recipient " + this
+ " for thread " + thread.asBinder());
mApp = app;
@@ -1221,11 +1247,11 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public void binderDied() {
- if (localLOGV) Slog.v(
+ if (DEBUG_ALL) Slog.v(
TAG, "Death received in " + this
+ " for thread " + mAppThread.asBinder());
synchronized(ActivityManagerService.this) {
- appDiedLocked(mApp, mPid, mAppThread);
+ appDiedLocked(mApp, mPid, mAppThread, true);
}
}
}
@@ -1270,6 +1296,10 @@ public final class ActivityManagerService extends ActivityManagerNative
static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47;
static final int DISMISS_DIALOG_MSG = 48;
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG = 49;
+ static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 50;
+ static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 51;
+ static final int DELETE_DUMPHEAP_MSG = 52;
+ static final int FOREGROUND_PROFILE_CHANGED_MSG = 53;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1294,10 +1324,11 @@ public final class ActivityManagerService extends ActivityManagerNative
final ServiceThread mHandlerThread;
final MainHandler mHandler;
+ final UiHandler mUiHandler;
- final class MainHandler extends Handler {
- public MainHandler(Looper looper) {
- super(looper, null, true);
+ final class UiHandler extends Handler {
+ public UiHandler() {
+ super(com.android.server.UiThread.get().getLooper(), null, true);
}
@Override
@@ -1410,15 +1441,6 @@ public final class ActivityManagerService extends ActivityManagerNative
d.show();
ensureBootCompleted();
} break;
- case UPDATE_CONFIGURATION_MSG: {
- final ContentResolver resolver = mContext.getContentResolver();
- Settings.System.putConfiguration(resolver, (Configuration)msg.obj);
- } break;
- case GC_BACKGROUND_PROCESSES_MSG: {
- synchronized (ActivityManagerService.this) {
- performAppGcsIfAppropriateLocked();
- }
- } break;
case WAIT_FOR_DEBUGGER_MSG: {
synchronized (ActivityManagerService.this) {
ProcessRecord app = (ProcessRecord)msg.obj;
@@ -1439,6 +1461,88 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
} break;
+ case SHOW_UID_ERROR_MSG: {
+ if (mShowDialogs) {
+ AlertDialog d = new BaseErrorDialog(mContext);
+ d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+ d.setCancelable(false);
+ d.setTitle(mContext.getText(R.string.android_system_label));
+ d.setMessage(mContext.getText(R.string.system_error_wipe_data));
+ d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
+ obtainMessage(DISMISS_DIALOG_MSG, d));
+ d.show();
+ }
+ } break;
+ case SHOW_FINGERPRINT_ERROR_MSG: {
+ if (mShowDialogs) {
+ AlertDialog d = new BaseErrorDialog(mContext);
+ d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+ d.setCancelable(false);
+ d.setTitle(mContext.getText(R.string.android_system_label));
+ d.setMessage(mContext.getText(R.string.system_error_manufacturer));
+ d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
+ obtainMessage(DISMISS_DIALOG_MSG, d));
+ d.show();
+ }
+ } break;
+ case SHOW_COMPAT_MODE_DIALOG_MSG: {
+ synchronized (ActivityManagerService.this) {
+ ActivityRecord ar = (ActivityRecord) msg.obj;
+ if (mCompatModeDialog != null) {
+ if (mCompatModeDialog.mAppInfo.packageName.equals(
+ ar.info.applicationInfo.packageName)) {
+ return;
+ }
+ mCompatModeDialog.dismiss();
+ mCompatModeDialog = null;
+ }
+ if (ar != null && false) {
+ if (mCompatModePackages.getPackageAskCompatModeLocked(
+ ar.packageName)) {
+ int mode = mCompatModePackages.computeCompatModeLocked(
+ ar.info.applicationInfo);
+ if (mode == ActivityManager.COMPAT_MODE_DISABLED
+ || mode == ActivityManager.COMPAT_MODE_ENABLED) {
+ mCompatModeDialog = new CompatModeDialog(
+ ActivityManagerService.this, mContext,
+ ar.info.applicationInfo);
+ mCompatModeDialog.show();
+ }
+ }
+ }
+ }
+ break;
+ }
+ case START_USER_SWITCH_MSG: {
+ showUserSwitchDialog(msg.arg1, (String) msg.obj);
+ break;
+ }
+ case DISMISS_DIALOG_MSG: {
+ final Dialog d = (Dialog) msg.obj;
+ d.dismiss();
+ break;
+ }
+ }
+ }
+ }
+
+ final class MainHandler extends Handler {
+ public MainHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case UPDATE_CONFIGURATION_MSG: {
+ final ContentResolver resolver = mContext.getContentResolver();
+ Settings.System.putConfiguration(resolver, (Configuration) msg.obj);
+ } break;
+ case GC_BACKGROUND_PROCESSES_MSG: {
+ synchronized (ActivityManagerService.this) {
+ performAppGcsIfAppropriateLocked();
+ }
+ } break;
case SERVICE_TIMEOUT_MSG: {
if (mDidDexOpt) {
mDidDexOpt = false;
@@ -1503,30 +1607,6 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
} break;
- case SHOW_UID_ERROR_MSG: {
- if (mShowDialogs) {
- AlertDialog d = new BaseErrorDialog(mContext);
- d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
- d.setCancelable(false);
- d.setTitle(mContext.getText(R.string.android_system_label));
- d.setMessage(mContext.getText(R.string.system_error_wipe_data));
- d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
- mHandler.obtainMessage(DISMISS_DIALOG_MSG, d));
- d.show();
- }
- } break;
- case SHOW_FINGERPRINT_ERROR_MSG: {
- if (mShowDialogs) {
- AlertDialog d = new BaseErrorDialog(mContext);
- d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
- d.setCancelable(false);
- d.setTitle(mContext.getText(R.string.android_system_label));
- d.setMessage(mContext.getText(R.string.system_error_manufacturer));
- d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok),
- mHandler.obtainMessage(DISMISS_DIALOG_MSG, d));
- d.show();
- }
- } break;
case PROC_START_TIMEOUT_MSG: {
if (mDidDexOpt) {
mDidDexOpt = false;
@@ -1583,7 +1663,7 @@ public final class ActivityManagerService extends ActivityManagerNative
notification.defaults = 0; // please be quiet
notification.sound = null;
notification.vibrate = null;
- notification.color = mContext.getResources().getColor(
+ notification.color = mContext.getColor(
com.android.internal.R.color.system_notification_accent_color);
notification.setLatestEventInfo(context, text,
mContext.getText(R.string.heavy_weight_notification_detail),
@@ -1627,34 +1707,6 @@ public final class ActivityManagerService extends ActivityManagerNative
sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
}
} break;
- case SHOW_COMPAT_MODE_DIALOG_MSG: {
- synchronized (ActivityManagerService.this) {
- ActivityRecord ar = (ActivityRecord)msg.obj;
- if (mCompatModeDialog != null) {
- if (mCompatModeDialog.mAppInfo.packageName.equals(
- ar.info.applicationInfo.packageName)) {
- return;
- }
- mCompatModeDialog.dismiss();
- mCompatModeDialog = null;
- }
- if (ar != null && false) {
- if (mCompatModePackages.getPackageAskCompatModeLocked(
- ar.packageName)) {
- int mode = mCompatModePackages.computeCompatModeLocked(
- ar.info.applicationInfo);
- if (mode == ActivityManager.COMPAT_MODE_DISABLED
- || mode == ActivityManager.COMPAT_MODE_ENABLED) {
- mCompatModeDialog = new CompatModeDialog(
- ActivityManagerService.this, mContext,
- ar.info.applicationInfo);
- mCompatModeDialog.show();
- }
- }
- }
- }
- break;
- }
case DISPATCH_PROCESSES_CHANGED: {
dispatchProcessesChanged();
break;
@@ -1675,10 +1727,6 @@ public final class ActivityManagerService extends ActivityManagerNative
thread.start();
break;
}
- case START_USER_SWITCH_MSG: {
- showUserSwitchDialog(msg.arg1, (String) msg.obj);
- break;
- }
case REPORT_USER_SWITCH_MSG: {
dispatchUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2);
break;
@@ -1694,10 +1742,9 @@ public final class ActivityManagerService extends ActivityManagerNative
case IMMERSIVE_MODE_LOCK_MSG: {
final boolean nextState = (msg.arg1 != 0);
if (mUpdateLock.isHeld() != nextState) {
- if (DEBUG_IMMERSIVE) {
- final ActivityRecord r = (ActivityRecord) msg.obj;
- Slog.d(TAG, "Applying new update lock state '" + nextState + "' for " + r);
- }
+ if (DEBUG_IMMERSIVE) Slog.d(TAG_IMMERSIVE,
+ "Applying new update lock state '" + nextState
+ + "' for " + (ActivityRecord)msg.obj);
if (nextState) {
mUpdateLock.acquire();
} else {
@@ -1755,7 +1802,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
case ENTER_ANIMATION_COMPLETE_MSG: {
synchronized (ActivityManagerService.this) {
- ActivityRecord r = ActivityRecord.forToken((IBinder) msg.obj);
+ ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj);
if (r != null && r.app != null && r.app.thread != null) {
try {
r.app.thread.scheduleEnterAnimationComplete(r.appToken);
@@ -1786,11 +1833,6 @@ public final class ActivityManagerService extends ActivityManagerNative
}
break;
}
- case DISMISS_DIALOG_MSG: {
- final Dialog d = (Dialog) msg.obj;
- d.dismiss();
- break;
- }
case NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG: {
synchronized (ActivityManagerService.this) {
int i = mTaskStackListeners.beginBroadcast();
@@ -1807,6 +1849,111 @@ public final class ActivityManagerService extends ActivityManagerNative
}
break;
}
+ case NOTIFY_CLEARTEXT_NETWORK_MSG: {
+ final int uid = msg.arg1;
+ final byte[] firstPacket = (byte[]) msg.obj;
+
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0; i < mPidsSelfLocked.size(); i++) {
+ final ProcessRecord p = mPidsSelfLocked.valueAt(i);
+ if (p.uid == uid) {
+ try {
+ p.thread.notifyCleartextNetwork(firstPacket);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+ }
+ break;
+ }
+ case POST_DUMP_HEAP_NOTIFICATION_MSG: {
+ final String procName;
+ final int uid;
+ final long memLimit;
+ final String reportPackage;
+ synchronized (ActivityManagerService.this) {
+ procName = mMemWatchDumpProcName;
+ uid = mMemWatchDumpUid;
+ Pair<Long, String> val = mMemWatchProcesses.get(procName, uid);
+ if (val == null) {
+ val = mMemWatchProcesses.get(procName, 0);
+ }
+ if (val != null) {
+ memLimit = val.first;
+ reportPackage = val.second;
+ } else {
+ memLimit = 0;
+ reportPackage = null;
+ }
+ }
+ if (procName == null) {
+ return;
+ }
+
+ if (DEBUG_PSS) Slog.d(TAG_PSS,
+ "Showing dump heap notification from " + procName + "/" + uid);
+
+ INotificationManager inm = NotificationManager.getService();
+ if (inm == null) {
+ return;
+ }
+
+ String text = mContext.getString(R.string.dump_heap_notification, procName);
+ Notification notification = new Notification();
+ notification.icon = com.android.internal.R.drawable.stat_sys_adb;
+ notification.when = 0;
+ notification.flags = Notification.FLAG_ONGOING_EVENT|Notification.FLAG_AUTO_CANCEL;
+ notification.tickerText = text;
+ notification.defaults = 0; // please be quiet
+ notification.sound = null;
+ notification.vibrate = null;
+ notification.color = mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color);
+ Intent deleteIntent = new Intent();
+ deleteIntent.setAction(DumpHeapActivity.ACTION_DELETE_DUMPHEAP);
+ notification.deleteIntent = PendingIntent.getBroadcastAsUser(mContext, 0,
+ deleteIntent, 0, UserHandle.OWNER);
+ Intent intent = new Intent();
+ intent.setClassName("android", DumpHeapActivity.class.getName());
+ intent.putExtra(DumpHeapActivity.KEY_PROCESS, procName);
+ intent.putExtra(DumpHeapActivity.KEY_SIZE, memLimit);
+ if (reportPackage != null) {
+ intent.putExtra(DumpHeapActivity.KEY_DIRECT_LAUNCH, reportPackage);
+ }
+ int userId = UserHandle.getUserId(uid);
+ notification.setLatestEventInfo(mContext, text,
+ mContext.getText(R.string.dump_heap_notification_detail),
+ PendingIntent.getActivityAsUser(mContext, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null,
+ new UserHandle(userId)));
+
+ try {
+ int[] outId = new int[1];
+ inm.enqueueNotificationWithTag("android", "android", null,
+ R.string.dump_heap_notification,
+ notification, outId, userId);
+ } catch (RuntimeException e) {
+ Slog.w(ActivityManagerService.TAG,
+ "Error showing notification for dump heap", e);
+ } catch (RemoteException e) {
+ }
+ } break;
+ case DELETE_DUMPHEAP_MSG: {
+ revokeUriPermission(ActivityThread.currentActivityThread().getApplicationThread(),
+ DumpHeapActivity.JAVA_URI,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ UserHandle.myUserId());
+ synchronized (ActivityManagerService.this) {
+ mMemWatchDumpFile = null;
+ mMemWatchDumpProcName = null;
+ mMemWatchDumpPid = -1;
+ mMemWatchDumpUid = -1;
+ }
+ } break;
+ case FOREGROUND_PROFILE_CHANGED_MSG: {
+ dispatchForegroundProfileChanged(msg.arg1);
+ } break;
}
}
};
@@ -1848,11 +1995,16 @@ public final class ActivityManagerService extends ActivityManagerNative
}
memInfo.readMemInfo();
synchronized (ActivityManagerService.this) {
- if (DEBUG_PSS) Slog.d(TAG, "Collected native and kernel memory in "
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Collected native and kernel memory in "
+ (SystemClock.uptimeMillis()-start) + "ms");
- mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
- memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(),
- memInfo.getKernelUsedSizeKb(), nativeTotalPss);
+ final long cachedKb = memInfo.getCachedSizeKb();
+ final long freeKb = memInfo.getFreeSizeKb();
+ final long zramKb = memInfo.getZramTotalSizeKb();
+ final long kernelKb = memInfo.getKernelUsedSizeKb();
+ EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024,
+ kernelKb*1024, nativeTotalPss*1024);
+ mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb,
+ nativeTotalPss);
}
}
@@ -1865,8 +2017,9 @@ public final class ActivityManagerService extends ActivityManagerNative
long lastPssTime;
synchronized (ActivityManagerService.this) {
if (mPendingPssProcesses.size() <= 0) {
- if (mTestPssMode || DEBUG_PSS) Slog.d(TAG, "Collected PSS of " + num
- + " processes in " + (SystemClock.uptimeMillis()-start) + "ms");
+ if (mTestPssMode || DEBUG_PSS) Slog.d(TAG_PSS,
+ "Collected PSS of " + num + " processes in "
+ + (SystemClock.uptimeMillis() - start) + "ms");
mPendingPssProcesses.clear();
return;
}
@@ -1888,7 +2041,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (pss != 0 && proc.thread != null && proc.setProcState == procState
&& proc.pid == pid && proc.lastPssTime == lastPssTime) {
num++;
- recordPssSample(proc, procState, pss, tmp[0],
+ recordPssSampleLocked(proc, procState, pss, tmp[0],
SystemClock.uptimeMillis());
}
}
@@ -1910,6 +2063,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ServiceManager.addService("cpuinfo", new CpuBinder(this));
}
ServiceManager.addService("permission", new PermissionController(this));
+ ServiceManager.addService("processinfo", new ProcessInfoService(this));
ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
"android", STOCK_PM_FLAGS);
@@ -2067,6 +2221,7 @@ public final class ActivityManagerService extends ActivityManagerNative
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
mHandlerThread.start();
mHandler = new MainHandler(mHandlerThread.getLooper());
+ mUiHandler = new UiHandler();
mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
"foreground", BROADCAST_FG_TIMEOUT, false);
@@ -2084,7 +2239,7 @@ public final class ActivityManagerService extends ActivityManagerNative
systemDir.mkdirs();
mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
mBatteryStatsService.getActiveStatistics().readLocked();
- mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
+ mBatteryStatsService.scheduleWriteToDisk();
mOnBattery = DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
mBatteryStatsService.getActiveStatistics().setCallback(this);
@@ -2096,8 +2251,8 @@ public final class ActivityManagerService extends ActivityManagerNative
mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
// User 0 is the first and only user that runs at boot.
- mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true));
- mUserLru.add(Integer.valueOf(0));
+ mStartedUsers.put(UserHandle.USER_OWNER, new UserStartedState(UserHandle.OWNER, true));
+ mUserLru.add(UserHandle.USER_OWNER);
updateStartedUserArrayLocked();
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
@@ -2106,15 +2261,16 @@ public final class ActivityManagerService extends ActivityManagerNative
mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
mConfiguration.setToDefaults();
- mConfiguration.locale = Locale.getDefault();
+ mConfiguration.setLocale(Locale.getDefault());
mConfigurationSeq = mConfiguration.seq = 1;
mProcessCpuTracker.init();
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
- mStackSupervisor = new ActivityStackSupervisor(this);
- mTaskPersister = new TaskPersister(systemDir, mStackSupervisor);
+ mRecentTasks = new RecentTasks(this);
+ mStackSupervisor = new ActivityStackSupervisor(this, mRecentTasks);
+ mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, mRecentTasks);
mProcessCpuThread = new Thread("CpuTracker") {
@Override
@@ -2171,6 +2327,9 @@ public final class ActivityManagerService extends ActivityManagerNative
public void initPowerManagement() {
mStackSupervisor.initPowerManagement();
mBatteryStatsService.initPowerManagement();
+ PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ mVoiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*voice*");
+ mVoiceWakeLock.setReferenceCounted(false);
}
@Override
@@ -2236,31 +2395,33 @@ public final class ActivityManagerService extends ActivityManagerNative
if (MONITOR_CPU_USAGE &&
mLastCpuTime.get() < (now-MONITOR_CPU_MIN_TIME)) {
mLastCpuTime.set(now);
- haveNewCpuStats = true;
mProcessCpuTracker.update();
- //Slog.i(TAG, mProcessCpu.printCurrentState());
- //Slog.i(TAG, "Total CPU usage: "
- // + mProcessCpu.getTotalCpuPercent() + "%");
+ if (mProcessCpuTracker.hasGoodLastStats()) {
+ haveNewCpuStats = true;
+ //Slog.i(TAG, mProcessCpu.printCurrentState());
+ //Slog.i(TAG, "Total CPU usage: "
+ // + mProcessCpu.getTotalCpuPercent() + "%");
- // Slog the cpu usage if the property is set.
- if ("true".equals(SystemProperties.get("events.cpu"))) {
- int user = mProcessCpuTracker.getLastUserTime();
- int system = mProcessCpuTracker.getLastSystemTime();
- int iowait = mProcessCpuTracker.getLastIoWaitTime();
- int irq = mProcessCpuTracker.getLastIrqTime();
- int softIrq = mProcessCpuTracker.getLastSoftIrqTime();
- int idle = mProcessCpuTracker.getLastIdleTime();
+ // Slog the cpu usage if the property is set.
+ if ("true".equals(SystemProperties.get("events.cpu"))) {
+ int user = mProcessCpuTracker.getLastUserTime();
+ int system = mProcessCpuTracker.getLastSystemTime();
+ int iowait = mProcessCpuTracker.getLastIoWaitTime();
+ int irq = mProcessCpuTracker.getLastIrqTime();
+ int softIrq = mProcessCpuTracker.getLastSoftIrqTime();
+ int idle = mProcessCpuTracker.getLastIdleTime();
- int total = user + system + iowait + irq + softIrq + idle;
- if (total == 0) total = 1;
+ int total = user + system + iowait + irq + softIrq + idle;
+ if (total == 0) total = 1;
- EventLog.writeEvent(EventLogTags.CPU,
- ((user+system+iowait+irq+softIrq) * 100) / total,
- (user * 100) / total,
- (system * 100) / total,
- (iowait * 100) / total,
- (irq * 100) / total,
- (softIrq * 100) / total);
+ EventLog.writeEvent(EventLogTags.CPU,
+ ((user+system+iowait+irq+softIrq) * 100) / total,
+ (user * 100) / total,
+ (system * 100) / total,
+ (iowait * 100) / total,
+ (irq * 100) / total,
+ (softIrq * 100) / total);
+ }
}
}
@@ -2269,8 +2430,10 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized(bstats) {
synchronized(mPidsSelfLocked) {
if (haveNewCpuStats) {
- if (mOnBattery) {
- int perc = bstats.startAddingCpuLocked();
+ final int perc = bstats.startAddingCpuLocked();
+ if (perc >= 0) {
+ int remainUTime = 0;
+ int remainSTime = 0;
int totalUTime = 0;
int totalSTime = 0;
final int N = mProcessCpuTracker.countStats();
@@ -2282,38 +2445,45 @@ public final class ActivityManagerService extends ActivityManagerNative
ProcessRecord pr = mPidsSelfLocked.get(st.pid);
int otherUTime = (st.rel_utime*perc)/100;
int otherSTime = (st.rel_stime*perc)/100;
- totalUTime += otherUTime;
- totalSTime += otherSTime;
+ remainUTime += otherUTime;
+ remainSTime += otherSTime;
+ totalUTime += st.rel_utime;
+ totalSTime += st.rel_stime;
if (pr != null) {
BatteryStatsImpl.Uid.Proc ps = pr.curProcBatteryStats;
if (ps == null || !ps.isActive()) {
pr.curProcBatteryStats = ps = bstats.getProcessStatsLocked(
pr.info.uid, pr.processName);
}
- ps.addCpuTimeLocked(st.rel_utime-otherUTime,
- st.rel_stime-otherSTime);
- ps.addSpeedStepTimes(cpuSpeedTimes);
- pr.curCpuTime += (st.rel_utime+st.rel_stime) * 10;
+ ps.addCpuTimeLocked(st.rel_utime - otherUTime,
+ st.rel_stime - otherSTime, cpuSpeedTimes);
+ pr.curCpuTime += st.rel_utime + st.rel_stime;
} else {
BatteryStatsImpl.Uid.Proc ps = st.batteryStats;
if (ps == null || !ps.isActive()) {
st.batteryStats = ps = bstats.getProcessStatsLocked(
bstats.mapUid(st.uid), st.name);
}
- ps.addCpuTimeLocked(st.rel_utime-otherUTime,
- st.rel_stime-otherSTime);
- ps.addSpeedStepTimes(cpuSpeedTimes);
+ ps.addCpuTimeLocked(st.rel_utime - otherUTime,
+ st.rel_stime - otherSTime, cpuSpeedTimes);
}
}
- bstats.finishAddingCpuLocked(perc, totalUTime,
- totalSTime, cpuSpeedTimes);
+ final int userTime = mProcessCpuTracker.getLastUserTime();
+ final int systemTime = mProcessCpuTracker.getLastSystemTime();
+ final int iowaitTime = mProcessCpuTracker.getLastIoWaitTime();
+ final int irqTime = mProcessCpuTracker.getLastIrqTime();
+ final int softIrqTime = mProcessCpuTracker.getLastSoftIrqTime();
+ final int idleTime = mProcessCpuTracker.getLastIdleTime();
+ bstats.finishAddingCpuLocked(perc, remainUTime,
+ remainSTime, totalUTime, totalSTime, userTime, systemTime,
+ iowaitTime, irqTime, softIrqTime, idleTime, cpuSpeedTimes);
}
}
}
if (mLastWriteTime < (now-BATTERY_STATS_TIME)) {
mLastWriteTime = now;
- mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
+ mBatteryStatsService.scheduleWriteToDisk();
}
}
}
@@ -2336,6 +2506,13 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ @Override
+ public void batterySendBroadcast(Intent intent) {
+ broadcastIntentLocked(null, null, intent, null,
+ null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, -1,
+ Process.SYSTEM_UID, UserHandle.USER_ALL);
+ }
+
/**
* Initialize the application bind args. These are passed to each
* process when the bindApplication() IPC is sent to the process. They're
@@ -2359,19 +2536,31 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final void setFocusedActivityLocked(ActivityRecord r, String reason) {
- if (mFocusedActivity != r) {
- if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedActivityLocked: r=" + r);
+ if (r != null && mFocusedActivity != r) {
+ if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedActivityLocked: r=" + r);
+ ActivityRecord last = mFocusedActivity;
mFocusedActivity = r;
if (r.task != null && r.task.voiceInteractor != null) {
- startRunningVoiceLocked();
+ startRunningVoiceLocked(r.task.voiceSession, r.info.applicationInfo.uid);
} else {
finishRunningVoiceLocked();
+ if (last != null && last.task.voiceSession != null) {
+ // We had been in a voice interaction session, but now focused has
+ // move to something different. Just finish the session, we can't
+ // return to it and retain the proper state and synchronization with
+ // the voice interaction service.
+ finishVoiceTask(last.task.voiceSession);
+ }
}
- mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity");
- if (r != null) {
+ if (mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity")) {
mWindowManager.setFocusedApp(r.appToken, true);
}
applyUpdateLockStateLocked(r);
+ if (last != null && last.userId != mFocusedActivity.userId) {
+ mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG,
+ mFocusedActivity.userId, 0));
+ }
}
EventLog.writeEvent(EventLogTags.AM_FOCUSED_ACTIVITY, mCurrentUserId,
mFocusedActivity == null ? "NULL" : mFocusedActivity.shortComponentName);
@@ -2385,13 +2574,14 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public void setFocusedStack(int stackId) {
- if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: stackId=" + stackId);
+ if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedStack: stackId=" + stackId);
synchronized (ActivityManagerService.this) {
ActivityStack stack = mStackSupervisor.getStack(stackId);
if (stack != null) {
ActivityRecord r = stack.topRunningActivityLocked(null);
if (r != null) {
setFocusedActivityLocked(r, "setFocusedStack");
+ mStackSupervisor.resumeTopActivitiesLocked(stack, null, null);
}
}
}
@@ -2409,9 +2599,9 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public void notifyActivityDrawn(IBinder token) {
- if (DEBUG_VISBILITY) Slog.d(TAG, "notifyActivityDrawn: token=" + token);
+ if (DEBUG_VISIBILITY) Slog.d(TAG_VISIBILITY, "notifyActivityDrawn: token=" + token);
synchronized (this) {
- ActivityRecord r= mStackSupervisor.isInAnyStackLocked(token);
+ ActivityRecord r = mStackSupervisor.isInAnyStackLocked(token);
if (r != null) {
r.task.stack.notifyActivityDrawnLocked(r);
}
@@ -2432,7 +2622,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Message msg = Message.obtain();
msg.what = SHOW_COMPAT_MODE_DIALOG_MSG;
msg.obj = r.task.askedCompatMode ? null : r;
- mHandler.sendMessage(msg);
+ mUiHandler.sendMessage(msg);
}
private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
@@ -2466,7 +2656,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (index > 0) {
index--;
}
- if (DEBUG_LRU) Slog.d(TAG, "Moving dep from " + lrui + " to " + index
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
+ " in LRU list: " + app);
mLruProcesses.add(index, app);
return index;
@@ -2512,13 +2702,13 @@ public final class ActivityManagerService extends ActivityManagerNative
if (hasActivity) {
final int N = mLruProcesses.size();
if (N > 0 && mLruProcesses.get(N-1) == app) {
- if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top activity: " + app);
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top activity: " + app);
return;
}
} else {
if (mLruProcessServiceStart > 0
&& mLruProcesses.get(mLruProcessServiceStart-1) == app) {
- if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top other: " + app);
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top other: " + app);
return;
}
}
@@ -2528,7 +2718,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.persistent && lrui >= 0) {
// We don't care about the position of persistent processes, as long as
// they are in the list.
- if (DEBUG_LRU) Slog.d(TAG, "Not moving, persistent: " + app);
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, persistent: " + app);
return;
}
@@ -2597,27 +2787,29 @@ public final class ActivityManagerService extends ActivityManagerNative
int nextIndex;
if (hasActivity) {
final int N = mLruProcesses.size();
- if (app.activities.size() == 0 && mLruProcessActivityStart < (N-1)) {
+ if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
// Process doesn't have activities, but has clients with
// activities... move it up, but one below the top (the top
// should always have a real activity).
- if (DEBUG_LRU) Slog.d(TAG, "Adding to second-top of LRU activity list: " + app);
- mLruProcesses.add(N-1, app);
+ if (DEBUG_LRU) Slog.d(TAG_LRU,
+ "Adding to second-top of LRU activity list: " + app);
+ mLruProcesses.add(N - 1, app);
// To keep it from spamming the LRU list (by making a bunch of clients),
// we will push down any other entries owned by the app.
final int uid = app.info.uid;
- for (int i=N-2; i>mLruProcessActivityStart; i--) {
+ for (int i = N - 2; i > mLruProcessActivityStart; i--) {
ProcessRecord subProc = mLruProcesses.get(i);
if (subProc.info.uid == uid) {
// We want to push this one down the list. If the process after
// it is for the same uid, however, don't do so, because we don't
// want them internally to be re-ordered.
- if (mLruProcesses.get(i-1).info.uid != uid) {
- if (DEBUG_LRU) Slog.d(TAG, "Pushing uid " + uid + " swapping at " + i
- + ": " + mLruProcesses.get(i) + " : " + mLruProcesses.get(i-1));
+ if (mLruProcesses.get(i - 1).info.uid != uid) {
+ if (DEBUG_LRU) Slog.d(TAG_LRU,
+ "Pushing uid " + uid + " swapping at " + i + ": "
+ + mLruProcesses.get(i) + " : " + mLruProcesses.get(i - 1));
ProcessRecord tmp = mLruProcesses.get(i);
- mLruProcesses.set(i, mLruProcesses.get(i-1));
- mLruProcesses.set(i-1, tmp);
+ mLruProcesses.set(i, mLruProcesses.get(i - 1));
+ mLruProcesses.set(i - 1, tmp);
i--;
}
} else {
@@ -2627,13 +2819,13 @@ public final class ActivityManagerService extends ActivityManagerNative
}
} else {
// Process has activities, put it at the very tipsy-top.
- if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU activity list: " + app);
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU activity list: " + app);
mLruProcesses.add(app);
}
nextIndex = mLruProcessServiceStart;
} else if (hasService) {
// Process has services, put it at the top of the service list.
- if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU service list: " + app);
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU service list: " + app);
mLruProcesses.add(mLruProcessActivityStart, app);
nextIndex = mLruProcessServiceStart;
mLruProcessActivityStart++;
@@ -2644,7 +2836,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// If there is a client, don't allow the process to be moved up higher
// in the list than that client.
int clientIndex = mLruProcesses.lastIndexOf(client);
- if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG, "Unknown client " + client
+ if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG_LRU, "Unknown client " + client
+ " when updating " + app);
if (clientIndex <= lrui) {
// Don't allow the client index restriction to push it down farther in the
@@ -2655,7 +2847,7 @@ public final class ActivityManagerService extends ActivityManagerNative
index = clientIndex;
}
}
- if (DEBUG_LRU) Slog.d(TAG, "Adding at " + index + " of LRU list: " + app);
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding at " + index + " of LRU list: " + app);
mLruProcesses.add(index, app);
nextIndex = index-1;
mLruProcessActivityStart++;
@@ -2707,7 +2899,7 @@ public final class ActivityManagerService extends ActivityManagerNative
} else if (proc != null && !keepIfLarge
&& mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
&& proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
- if (DEBUG_PSS) Slog.d(TAG, "May not keep " + proc + ": pss=" + proc.lastCachedPss);
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "May not keep " + proc + ": pss=" + proc.lastCachedPss);
if (proc.lastCachedPss >= mProcessList.getCachedRestoreThresholdKb()) {
if (proc.baseProcessTracker != null) {
proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss);
@@ -2779,17 +2971,45 @@ public final class ActivityManagerService extends ActivityManagerNative
if (!isolated) {
app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
checkTime(startTime, "startProcess: after getProcessRecord");
+
+ if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) {
+ // If we are in the background, then check to see if this process
+ // is bad. If so, we will just silently fail.
+ if (mBadProcesses.get(info.processName, info.uid) != null) {
+ if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid
+ + "/" + info.processName);
+ return null;
+ }
+ } else {
+ // When the user is explicitly starting a process, then clear its
+ // crash count so that we won't make it bad until they see at
+ // least one crash dialog again, and make the process good again
+ // if it had been bad.
+ if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid
+ + "/" + info.processName);
+ mProcessCrashTimes.remove(info.processName, info.uid);
+ if (mBadProcesses.get(info.processName, info.uid) != null) {
+ EventLog.writeEvent(EventLogTags.AM_PROC_GOOD,
+ UserHandle.getUserId(info.uid), info.uid,
+ info.processName);
+ mBadProcesses.remove(info.processName, info.uid);
+ if (app != null) {
+ app.bad = false;
+ }
+ }
+ }
} else {
// If this is an isolated process, it can't re-use an existing process.
app = null;
}
+
// We don't have to do anything more if:
// (1) There is an existing application record; and
// (2) The caller doesn't think it is dead, OR there is no thread
// object attached to it so we know it couldn't have crashed; and
// (3) There is a pid assigned to it, so it is either starting or
// already running.
- if (DEBUG_PROCESSES) Slog.v(TAG, "startProcess: name=" + processName
+ if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "startProcess: name=" + processName
+ " app=" + app + " knownToBeDead=" + knownToBeDead
+ " thread=" + (app != null ? app.thread : null)
+ " pid=" + (app != null ? app.pid : -1));
@@ -2797,7 +3017,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (!knownToBeDead || app.thread == null) {
// We already have the app running, or are waiting for it to
// come up (we have a pid but not yet its thread), so keep it.
- if (DEBUG_PROCESSES) Slog.v(TAG, "App already running: " + app);
+ if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "App already running: " + app);
// If this is a new package in the process, add the package to the list
app.addPackage(info.packageName, info.versionCode, mProcessStats);
checkTime(startTime, "startProcess: done, added package to proc");
@@ -2806,7 +3026,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// An application record is attached to a previous process,
// clean it up now.
- if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG, "App died: " + app);
+ if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG_PROCESSES, "App died: " + app);
checkTime(startTime, "startProcess: bad proc running, killing");
Process.killProcessGroup(app.info.uid, app.pid);
handleAppDiedLocked(app, true, true);
@@ -2816,35 +3036,6 @@ public final class ActivityManagerService extends ActivityManagerNative
String hostingNameStr = hostingName != null
? hostingName.flattenToShortString() : null;
- if (!isolated) {
- if ((intentFlags&Intent.FLAG_FROM_BACKGROUND) != 0) {
- // If we are in the background, then check to see if this process
- // is bad. If so, we will just silently fail.
- if (mBadProcesses.get(info.processName, info.uid) != null) {
- if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid
- + "/" + info.processName);
- return null;
- }
- } else {
- // When the user is explicitly starting a process, then clear its
- // crash count so that we won't make it bad until they see at
- // least one crash dialog again, and make the process good again
- // if it had been bad.
- if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid
- + "/" + info.processName);
- mProcessCrashTimes.remove(info.processName, info.uid);
- if (mBadProcesses.get(info.processName, info.uid) != null) {
- EventLog.writeEvent(EventLogTags.AM_PROC_GOOD,
- UserHandle.getUserId(info.uid), info.uid,
- info.processName);
- mBadProcesses.remove(info.processName, info.uid);
- if (app != null) {
- app.bad = false;
- }
- }
- }
- }
-
if (app == null) {
checkTime(startTime, "startProcess: creating new process record");
app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
@@ -2873,7 +3064,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (!mProcessesOnHold.contains(app)) {
mProcessesOnHold.add(app);
}
- if (DEBUG_PROCESSES) Slog.v(TAG, "System not ready, putting on hold: " + app);
+ if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES,
+ "System not ready, putting on hold: " + app);
checkTime(startTime, "startProcess: returning with proc on hold");
return app;
}
@@ -2908,7 +3100,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.setPid(0);
}
- if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG,
+ if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
"startProcessLocked removing on hold: " + app);
mProcessesOnHold.remove(app);
@@ -2920,25 +3112,14 @@ public final class ActivityManagerService extends ActivityManagerNative
int uid = app.uid;
int[] gids = null;
- int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+ int mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
if (!app.isolated) {
int[] permGids = null;
try {
checkTime(startTime, "startProcess: getting gids from package manager");
- final PackageManager pm = mContext.getPackageManager();
- permGids = pm.getPackageGids(app.info.packageName);
-
- if (Environment.isExternalStorageEmulated()) {
- checkTime(startTime, "startProcess: checking external storage perm");
- if (pm.checkPermission(
- android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
- app.info.packageName) == PERMISSION_GRANTED) {
- mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
- } else {
- mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
+ permGids = AppGlobals.getPackageManager().getPackageGids(app.info.packageName,
+ app.userId);
+ } catch (RemoteException e) {
Slog.w(TAG, "Unable to retrieve gids", e);
}
@@ -2946,7 +3127,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* Add shared application and profile GIDs so applications can share some
* resources like shared libraries and access user-wide resources
*/
- if (permGids == null) {
+ if (ArrayUtils.isEmpty(permGids)) {
gids = new int[2];
} else {
gids = new int[permGids.length + 2];
@@ -2983,6 +3164,15 @@ public final class ActivityManagerService extends ActivityManagerNative
if ("1".equals(SystemProperties.get("debug.checkjni"))) {
debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
}
+ String jitDebugProperty = SystemProperties.get("debug.usejit");
+ if ("true".equals(jitDebugProperty)) {
+ debugFlags |= Zygote.DEBUG_ENABLE_JIT;
+ } else if (!"false".equals(jitDebugProperty)) {
+ // If we didn't force disable by setting false, defer to the dalvik vm options.
+ if ("true".equals(SystemProperties.get("dalvik.vm.usejit"))) {
+ debugFlags |= Zygote.DEBUG_ENABLE_JIT;
+ }
+ }
if ("1".equals(SystemProperties.get("debug.jni.logging"))) {
debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
}
@@ -3079,7 +3269,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
void updateUsageStats(ActivityRecord component, boolean resumed) {
- if (DEBUG_SWITCH) Slog.d(TAG, "updateUsageStats: comp=" + component + "res=" + resumed);
+ if (DEBUG_SWITCH) Slog.d(TAG_SWITCH,
+ "updateUsageStats: comp=" + component + "res=" + resumed);
final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
if (resumed) {
if (mUsageStatsService != null) {
@@ -3287,6 +3478,35 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ @Override
+ public int getPackageProcessState(String packageName) {
+ int procState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ synchronized (this) {
+ for (int i=mLruProcesses.size()-1; i>=0; i--) {
+ final ProcessRecord proc = mLruProcesses.get(i);
+ if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT
+ || procState > proc.setProcState) {
+ boolean found = false;
+ for (int j=proc.pkgList.size()-1; j>=0 && !found; j--) {
+ if (proc.pkgList.keyAt(j).equals(packageName)) {
+ procState = proc.setProcState;
+ found = true;
+ }
+ }
+ if (proc.pkgDeps != null && !found) {
+ for (int j=proc.pkgDeps.size()-1; j>=0; j--) {
+ if (proc.pkgDeps.valueAt(j).equals(packageName)) {
+ procState = proc.setProcState;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return procState;
+ }
+
private void dispatchProcessesChanged() {
int N;
synchronized (this) {
@@ -3297,7 +3517,8 @@ public final class ActivityManagerService extends ActivityManagerNative
mPendingProcessChanges.toArray(mActiveProcessChanges);
mAvailProcessChanges.addAll(mPendingProcessChanges);
mPendingProcessChanges.clear();
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "*** Delivering " + N + " process changes");
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "*** Delivering " + N + " process changes");
}
int i = mProcessObservers.beginBroadcast();
@@ -3309,15 +3530,16 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int j=0; j<N; j++) {
ProcessChangeItem item = mActiveProcessChanges[j];
if ((item.changes&ProcessChangeItem.CHANGE_ACTIVITIES) != 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "ACTIVITIES CHANGED pid="
- + item.pid + " uid=" + item.uid + ": "
- + item.foregroundActivities);
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "ACTIVITIES CHANGED pid=" + item.pid + " uid="
+ + item.uid + ": " + item.foregroundActivities);
observer.onForegroundActivitiesChanged(item.pid, item.uid,
item.foregroundActivities);
}
if ((item.changes&ProcessChangeItem.CHANGE_PROCESS_STATE) != 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "PROCSTATE CHANGED pid="
- + item.pid + " uid=" + item.uid + ": " + item.processState);
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "PROCSTATE CHANGED pid=" + item.pid + " uid=" + item.uid
+ + ": " + item.processState);
observer.onProcessStateChanged(item.pid, item.uid, item.processState);
}
}
@@ -3462,10 +3684,10 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public int startActivityIntentSender(IApplicationThread caller,
- IntentSender intent, Intent fillInIntent, String resolvedType,
- IBinder resultTo, String resultWho, int requestCode,
- int flagsMask, int flagsValues, Bundle options) {
+ public int startActivityIntentSender(IApplicationThread caller, IntentSender intent,
+ Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho,
+ int requestCode, int flagsMask, int flagsValues, Bundle options)
+ throws TransactionTooLargeException {
enforceNotIsolatedCaller("startActivityIntentSender");
// Refuse possible leaked file descriptors
if (fillInIntent != null && fillInIntent.hasFileDescriptors()) {
@@ -3519,6 +3741,19 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) {
+ synchronized (this) {
+ if (mRunningVoice != null && mRunningVoice.asBinder() == session.asBinder()) {
+ if (keepAwake) {
+ mVoiceWakeLock.acquire();
+ } else {
+ mVoiceWakeLock.release();
+ }
+ }
+ }
+ }
+
+ @Override
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) {
// Refuse possible leaked file descriptors
@@ -3642,7 +3877,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final Intent intent;
final int userId;
synchronized (this) {
- task = recentTaskForIdLocked(taskId);
+ task = mRecentTasks.taskForIdLocked(taskId);
if (task == null) {
throw new IllegalArgumentException("Task " + taskId + " not found.");
}
@@ -3700,518 +3935,6 @@ public final class ActivityManagerService extends ActivityManagerNative
return ret;
}
- //explicitly remove thd old information in mRecentTasks when removing existing user.
- private void removeRecentTasksForUserLocked(int userId) {
- if(userId <= 0) {
- Slog.i(TAG, "Can't remove recent task on user " + userId);
- return;
- }
-
- for (int i = mRecentTasks.size() - 1; i >= 0; --i) {
- TaskRecord tr = mRecentTasks.get(i);
- if (tr.userId == userId) {
- if(DEBUG_TASKS) Slog.i(TAG, "remove RecentTask " + tr
- + " when finishing user" + userId);
- mRecentTasks.remove(i);
- tr.removedFromRecents();
- }
- }
-
- // Remove tasks from persistent storage.
- notifyTaskPersisterLocked(null, true);
- }
-
- // Sort by taskId
- private Comparator<TaskRecord> mTaskRecordComparator = new Comparator<TaskRecord>() {
- @Override
- public int compare(TaskRecord lhs, TaskRecord rhs) {
- return rhs.taskId - lhs.taskId;
- }
- };
-
- // Extract the affiliates of the chain containing mRecentTasks[start].
- private int processNextAffiliateChainLocked(int start) {
- final TaskRecord startTask = mRecentTasks.get(start);
- final int affiliateId = startTask.mAffiliatedTaskId;
-
- // Quick identification of isolated tasks. I.e. those not launched behind.
- if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null &&
- startTask.mNextAffiliate == null) {
- // There is still a slim chance that there are other tasks that point to this task
- // and that the chain is so messed up that this task no longer points to them but
- // the gain of this optimization outweighs the risk.
- startTask.inRecents = true;
- return start + 1;
- }
-
- // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents.
- mTmpRecents.clear();
- for (int i = mRecentTasks.size() - 1; i >= start; --i) {
- final TaskRecord task = mRecentTasks.get(i);
- if (task.mAffiliatedTaskId == affiliateId) {
- mRecentTasks.remove(i);
- mTmpRecents.add(task);
- }
- }
-
- // Sort them all by taskId. That is the order they were create in and that order will
- // always be correct.
- Collections.sort(mTmpRecents, mTaskRecordComparator);
-
- // Go through and fix up the linked list.
- // The first one is the end of the chain and has no next.
- final TaskRecord first = mTmpRecents.get(0);
- first.inRecents = true;
- if (first.mNextAffiliate != null) {
- Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate);
- first.setNextAffiliate(null);
- notifyTaskPersisterLocked(first, false);
- }
- // Everything in the middle is doubly linked from next to prev.
- final int tmpSize = mTmpRecents.size();
- for (int i = 0; i < tmpSize - 1; ++i) {
- final TaskRecord next = mTmpRecents.get(i);
- final TaskRecord prev = mTmpRecents.get(i + 1);
- if (next.mPrevAffiliate != prev) {
- Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate +
- " setting prev=" + prev);
- next.setPrevAffiliate(prev);
- notifyTaskPersisterLocked(next, false);
- }
- if (prev.mNextAffiliate != next) {
- Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate +
- " setting next=" + next);
- prev.setNextAffiliate(next);
- notifyTaskPersisterLocked(prev, false);
- }
- prev.inRecents = true;
- }
- // The last one is the beginning of the list and has no prev.
- final TaskRecord last = mTmpRecents.get(tmpSize - 1);
- if (last.mPrevAffiliate != null) {
- Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate);
- last.setPrevAffiliate(null);
- notifyTaskPersisterLocked(last, false);
- }
-
- // Insert the group back into mRecentTasks at start.
- mRecentTasks.addAll(start, mTmpRecents);
-
- // Let the caller know where we left off.
- return start + tmpSize;
- }
-
- /**
- * Update the recent tasks lists: make sure tasks should still be here (their
- * applications / activities still exist), update their availability, fixup ordering
- * of affiliations.
- */
- void cleanupRecentTasksLocked(int userId) {
- if (mRecentTasks == null) {
- // Happens when called from the packagemanager broadcast before boot.
- return;
- }
-
- final HashMap<ComponentName, ActivityInfo> availActCache = new HashMap<>();
- final HashMap<String, ApplicationInfo> availAppCache = new HashMap<>();
- final IPackageManager pm = AppGlobals.getPackageManager();
- final ActivityInfo dummyAct = new ActivityInfo();
- final ApplicationInfo dummyApp = new ApplicationInfo();
-
- int N = mRecentTasks.size();
-
- int[] users = userId == UserHandle.USER_ALL
- ? getUsersLocked() : new int[] { userId };
- for (int user : users) {
- for (int i = 0; i < N; i++) {
- TaskRecord task = mRecentTasks.get(i);
- if (task.userId != user) {
- // Only look at tasks for the user ID of interest.
- continue;
- }
- if (task.autoRemoveRecents && task.getTopActivity() == null) {
- // This situation is broken, and we should just get rid of it now.
- mRecentTasks.remove(i);
- task.removedFromRecents();
- i--;
- N--;
- Slog.w(TAG, "Removing auto-remove without activity: " + task);
- continue;
- }
- // Check whether this activity is currently available.
- if (task.realActivity != null) {
- ActivityInfo ai = availActCache.get(task.realActivity);
- if (ai == null) {
- try {
- ai = pm.getActivityInfo(task.realActivity,
- PackageManager.GET_UNINSTALLED_PACKAGES
- | PackageManager.GET_DISABLED_COMPONENTS, user);
- } catch (RemoteException e) {
- // Will never happen.
- continue;
- }
- if (ai == null) {
- ai = dummyAct;
- }
- availActCache.put(task.realActivity, ai);
- }
- if (ai == dummyAct) {
- // This could be either because the activity no longer exists, or the
- // app is temporarily gone. For the former we want to remove the recents
- // entry; for the latter we want to mark it as unavailable.
- ApplicationInfo app = availAppCache.get(task.realActivity.getPackageName());
- if (app == null) {
- try {
- app = pm.getApplicationInfo(task.realActivity.getPackageName(),
- PackageManager.GET_UNINSTALLED_PACKAGES
- | PackageManager.GET_DISABLED_COMPONENTS, user);
- } catch (RemoteException e) {
- // Will never happen.
- continue;
- }
- if (app == null) {
- app = dummyApp;
- }
- availAppCache.put(task.realActivity.getPackageName(), app);
- }
- if (app == dummyApp || (app.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
- // Doesn't exist any more! Good-bye.
- mRecentTasks.remove(i);
- task.removedFromRecents();
- i--;
- N--;
- Slog.w(TAG, "Removing no longer valid recent: " + task);
- continue;
- } else {
- // Otherwise just not available for now.
- if (task.isAvailable) {
- if (DEBUG_RECENTS) Slog.d(TAG, "Making recent unavailable: "
- + task);
- }
- task.isAvailable = false;
- }
- } else {
- if (!ai.enabled || !ai.applicationInfo.enabled
- || (ai.applicationInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
- if (task.isAvailable) {
- if (DEBUG_RECENTS) Slog.d(TAG, "Making recent unavailable: "
- + task + " (enabled=" + ai.enabled + "/"
- + ai.applicationInfo.enabled + " flags="
- + Integer.toHexString(ai.applicationInfo.flags) + ")");
- }
- task.isAvailable = false;
- } else {
- if (!task.isAvailable) {
- if (DEBUG_RECENTS) Slog.d(TAG, "Making recent available: "
- + task);
- }
- task.isAvailable = true;
- }
- }
- }
- }
- }
-
- // Verify the affiliate chain for each task.
- for (int i = 0; i < N; i = processNextAffiliateChainLocked(i)) {
- }
-
- mTmpRecents.clear();
- // mRecentTasks is now in sorted, affiliated order.
- }
-
- private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) {
- int N = mRecentTasks.size();
- TaskRecord top = task;
- int topIndex = taskIndex;
- while (top.mNextAffiliate != null && topIndex > 0) {
- top = top.mNextAffiliate;
- topIndex--;
- }
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: adding affilliates starting at "
- + topIndex + " from intial " + taskIndex);
- // Find the end of the chain, doing a sanity check along the way.
- boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
- int endIndex = topIndex;
- TaskRecord prev = top;
- while (endIndex < N) {
- TaskRecord cur = mRecentTasks.get(endIndex);
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: looking at next chain @"
- + endIndex + " " + cur);
- if (cur == top) {
- // Verify start of the chain.
- if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": first task has next affiliate: " + prev);
- sane = false;
- break;
- }
- } else {
- // Verify middle of the chain's next points back to the one before.
- if (cur.mNextAffiliate != prev
- || cur.mNextAffiliateTaskId != prev.taskId) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": middle task " + cur + " @" + endIndex
- + " has bad next affiliate "
- + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId
- + ", expected " + prev);
- sane = false;
- break;
- }
- }
- if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) {
- // Chain ends here.
- if (cur.mPrevAffiliate != null) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": last task " + cur + " has previous affiliate "
- + cur.mPrevAffiliate);
- sane = false;
- }
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: end of chain @" + endIndex);
- break;
- } else {
- // Verify middle of the chain's prev points to a valid item.
- if (cur.mPrevAffiliate == null) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": task " + cur + " has previous affiliate "
- + cur.mPrevAffiliate + " but should be id "
- + cur.mPrevAffiliate);
- sane = false;
- break;
- }
- }
- if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": task " + cur + " has affiliated id "
- + cur.mAffiliatedTaskId + " but should be "
- + task.mAffiliatedTaskId);
- sane = false;
- break;
- }
- prev = cur;
- endIndex++;
- if (endIndex >= N) {
- Slog.wtf(TAG, "Bad chain ran off index " + endIndex
- + ": last task " + prev);
- sane = false;
- break;
- }
- }
- if (sane) {
- if (endIndex < taskIndex) {
- Slog.wtf(TAG, "Bad chain @" + endIndex
- + ": did not extend to task " + task + " @" + taskIndex);
- sane = false;
- }
- }
- if (sane) {
- // All looks good, we can just move all of the affiliated tasks
- // to the top.
- for (int i=topIndex; i<=endIndex; i++) {
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: moving affiliated " + task
- + " from " + i + " to " + (i-topIndex));
- TaskRecord cur = mRecentTasks.remove(i);
- mRecentTasks.add(i-topIndex, cur);
- }
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: done moving tasks " + topIndex
- + " to " + endIndex);
- return true;
- }
-
- // Whoops, couldn't do it.
- return false;
- }
-
- final void addRecentTaskLocked(TaskRecord task) {
- final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
- || task.mNextAffiliateTaskId != INVALID_TASK_ID
- || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
-
- int N = mRecentTasks.size();
- // Quick case: check if the top-most recent task is the same.
- if (!isAffiliated && N > 0 && mRecentTasks.get(0) == task) {
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: already at top: " + task);
- return;
- }
- // Another quick case: check if this is part of a set of affiliated
- // tasks that are at the top.
- if (isAffiliated && N > 0 && task.inRecents
- && task.mAffiliatedTaskId == mRecentTasks.get(0).mAffiliatedTaskId) {
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: affiliated " + mRecentTasks.get(0)
- + " at top when adding " + task);
- return;
- }
- // Another quick case: never add voice sessions.
- if (task.voiceSession != null) {
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: not adding voice interaction " + task);
- return;
- }
-
- boolean needAffiliationFix = false;
-
- // Slightly less quick case: the task is already in recents, so all we need
- // to do is move it.
- if (task.inRecents) {
- int taskIndex = mRecentTasks.indexOf(task);
- if (taskIndex >= 0) {
- if (!isAffiliated) {
- // Simple case: this is not an affiliated task, so we just move it to the front.
- mRecentTasks.remove(taskIndex);
- mRecentTasks.add(0, task);
- notifyTaskPersisterLocked(task, false);
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: moving to top " + task
- + " from " + taskIndex);
- return;
- } else {
- // More complicated: need to keep all affiliated tasks together.
- if (moveAffiliatedTasksToFront(task, taskIndex)) {
- // All went well.
- return;
- }
-
- // Uh oh... something bad in the affiliation chain, try to rebuild
- // everything and then go through our general path of adding a new task.
- needAffiliationFix = true;
- }
- } else {
- Slog.wtf(TAG, "Task with inRecent not in recents: " + task);
- needAffiliationFix = true;
- }
- }
-
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: trimming tasks for " + task);
- trimRecentsForTaskLocked(task, true);
-
- N = mRecentTasks.size();
- while (N >= ActivityManager.getMaxRecentTasksStatic()) {
- final TaskRecord tr = mRecentTasks.remove(N - 1);
- tr.removedFromRecents();
- N--;
- }
- task.inRecents = true;
- if (!isAffiliated || needAffiliationFix) {
- // If this is a simple non-affiliated task, or we had some failure trying to
- // handle it as part of an affilated task, then just place it at the top.
- mRecentTasks.add(0, task);
- } else if (isAffiliated) {
- // If this is a new affiliated task, then move all of the affiliated tasks
- // to the front and insert this new one.
- TaskRecord other = task.mNextAffiliate;
- if (other == null) {
- other = task.mPrevAffiliate;
- }
- if (other != null) {
- int otherIndex = mRecentTasks.indexOf(other);
- if (otherIndex >= 0) {
- // Insert new task at appropriate location.
- int taskIndex;
- if (other == task.mNextAffiliate) {
- // We found the index of our next affiliation, which is who is
- // before us in the list, so add after that point.
- taskIndex = otherIndex+1;
- } else {
- // We found the index of our previous affiliation, which is who is
- // after us in the list, so add at their position.
- taskIndex = otherIndex;
- }
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: new affiliated task added at "
- + taskIndex + ": " + task);
- mRecentTasks.add(taskIndex, task);
-
- // Now move everything to the front.
- if (moveAffiliatedTasksToFront(task, taskIndex)) {
- // All went well.
- return;
- }
-
- // Uh oh... something bad in the affiliation chain, try to rebuild
- // everything and then go through our general path of adding a new task.
- needAffiliationFix = true;
- } else {
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: couldn't find other affiliation "
- + other);
- needAffiliationFix = true;
- }
- } else {
- if (DEBUG_RECENTS) Slog.d(TAG,
- "addRecent: adding affiliated task without next/prev:" + task);
- needAffiliationFix = true;
- }
- }
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: adding " + task);
-
- if (needAffiliationFix) {
- if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: regrouping affiliations");
- cleanupRecentTasksLocked(task.userId);
- }
- }
-
- /**
- * If needed, remove oldest existing entries in recents that are for the same kind
- * of task as the given one.
- */
- int trimRecentsForTaskLocked(TaskRecord task, boolean doTrim) {
- int N = mRecentTasks.size();
- final Intent intent = task.intent;
- final boolean document = intent != null && intent.isDocument();
-
- int maxRecents = task.maxRecents - 1;
- for (int i=0; i<N; i++) {
- final TaskRecord tr = mRecentTasks.get(i);
- if (task != tr) {
- if (task.userId != tr.userId) {
- continue;
- }
- if (i > MAX_RECENT_BITMAPS) {
- tr.freeLastThumbnail();
- }
- final Intent trIntent = tr.intent;
- if ((task.affinity == null || !task.affinity.equals(tr.affinity)) &&
- (intent == null || !intent.filterEquals(trIntent))) {
- continue;
- }
- final boolean trIsDocument = trIntent != null && trIntent.isDocument();
- if (document && trIsDocument) {
- // These are the same document activity (not necessarily the same doc).
- if (maxRecents > 0) {
- --maxRecents;
- continue;
- }
- // Hit the maximum number of documents for this task. Fall through
- // and remove this document from recents.
- } else if (document || trIsDocument) {
- // Only one of these is a document. Not the droid we're looking for.
- continue;
- }
- }
-
- if (!doTrim) {
- // If the caller is not actually asking for a trim, just tell them we reached
- // a point where the trim would happen.
- return i;
- }
-
- // Either task and tr are the same or, their affinities match or their intents match
- // and neither of them is a document, or they are documents using the same activity
- // and their maxRecents has been reached.
- tr.disposeThumbnail();
- mRecentTasks.remove(i);
- if (task != tr) {
- tr.removedFromRecents();
- }
- i--;
- N--;
- if (task.intent == null) {
- // If the new recent task we are adding is not fully
- // specified, then replace it with the existing recent task.
- task = tr;
- }
- notifyTaskPersisterLocked(tr, false);
- }
-
- return -1;
- }
-
@Override
public void reportActivityFullyDrawn(IBinder token) {
synchronized (this) {
@@ -4230,6 +3953,10 @@ public final class ActivityManagerService extends ActivityManagerNative
if (r == null) {
return;
}
+ if (r.task != null && r.task.mResizeable) {
+ // Fixed screen orientation isn't supported with resizeable activities.
+ return;
+ }
final long origId = Binder.clearCallingIdentity();
mWindowManager.setAppOrientation(r.appToken, requestedOrientation);
Configuration config = mWindowManager.updateOrientationFromAppTokens(
@@ -4285,13 +4012,13 @@ public final class ActivityManagerService extends ActivityManagerNative
if (rootR == null) {
Slog.w(TAG, "Finishing task with all activities already finished");
}
- // Do not allow task to finish in Lock Task mode.
- if (tr == mStackSupervisor.mLockTaskModeTask) {
- if (rootR == r) {
- Slog.i(TAG, "Not finishing task in lock task mode");
- mStackSupervisor.showLockTaskToast();
- return false;
- }
+ // Do not allow task to finish if last task in lockTask mode. Launchable apps can
+ // finish themselves.
+ if (tr.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE && rootR == r &&
+ mStackSupervisor.isLastLockedTask(tr)) {
+ Slog.i(TAG, "Not finishing task in lock task mode");
+ mStackSupervisor.showLockTaskToast();
+ return false;
}
if (mController != null) {
// Find the first activity that is not finishing.
@@ -4354,11 +4081,10 @@ public final class ActivityManagerService extends ActivityManagerNative
return;
}
- ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(
- mHeavyWeightProcess.activities);
- for (int i=0; i<activities.size(); i++) {
+ ArrayList<ActivityRecord> activities = new ArrayList<>(mHeavyWeightProcess.activities);
+ for (int i = 0; i < activities.size(); i++) {
ActivityRecord r = activities.get(i);
- if (!r.finishing) {
+ if (!r.finishing && r.isInStackLocked()) {
r.task.stack.finishActivityLocked(r, Activity.RESULT_CANCELED,
null, "finish-heavy", true);
}
@@ -4446,20 +4172,18 @@ public final class ActivityManagerService extends ActivityManagerNative
final long origId = Binder.clearCallingIdentity();
try {
ActivityRecord r = ActivityRecord.isInStackLocked(token);
-
- ActivityRecord rootR = r.task.getRootActivity();
- // Do not allow task to finish in Lock Task mode.
- if (r.task == mStackSupervisor.mLockTaskModeTask) {
- if (rootR == r) {
- mStackSupervisor.showLockTaskToast();
- return false;
- }
+ if (r == null) {
+ return false;
}
- boolean res = false;
- if (r != null) {
- res = r.task.stack.finishActivityAffinityLocked(r);
+
+ // Do not allow the last non-launchable task to finish in Lock Task mode.
+ final TaskRecord task = r.task;
+ if (task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE &&
+ mStackSupervisor.isLastLockedTask(task) && task.getRootActivity() == r) {
+ mStackSupervisor.showLockTaskToast();
+ return false;
}
- return res;
+ return task.stack.finishActivityAffinityLocked(r);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -4485,7 +4209,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final long origId = Binder.clearCallingIdentity();
try {
ActivityRecord r = ActivityRecord.isInStackLocked(token);
- if (r.task == null || r.task.stack == null) {
+ if (r == null) {
return false;
}
return r.task.stack.safelyDestroyActivityLocked(r, "app-req");
@@ -4573,17 +4297,13 @@ public final class ActivityManagerService extends ActivityManagerNative
finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info);
}
- if (!restarting) {
- if (!mStackSupervisor.resumeTopActivitiesLocked()) {
- // If there was nothing to resume, and we are not already
- // restarting this process, but there is a visible activity that
- // is hosted by the process... then make sure all visible
- // activities are running, taking care of restarting this
- // process.
- if (hasVisibleActivities) {
- mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
- }
- }
+ if (!restarting && hasVisibleActivities && !mStackSupervisor.resumeTopActivitiesLocked()) {
+ // If there was nothing to resume, and we are not already
+ // restarting this process, but there is a visible activity that
+ // is hosted by the process... then make sure all visible
+ // activities are running, taking care of restarting this
+ // process.
+ mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
}
}
@@ -4670,10 +4390,11 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final void appDiedLocked(ProcessRecord app) {
- appDiedLocked(app, app.pid, app.thread);
+ appDiedLocked(app, app.pid, app.thread, false);
}
- final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) {
+ final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,
+ boolean fromBinderDied) {
// First check if this ProcessRecord is actually active for the pid.
synchronized (mPidsSelfLocked) {
ProcessRecord curProc = mPidsSelfLocked.get(pid);
@@ -4689,7 +4410,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (!app.killed) {
- Process.killProcessQuiet(pid);
+ if (!fromBinderDied) {
+ Process.killProcessQuiet(pid);
+ }
Process.killProcessGroup(app.info.uid, pid);
app.killed = true;
}
@@ -4710,9 +4433,8 @@ public final class ActivityManagerService extends ActivityManagerNative
doLowMem = false;
}
EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.userId, app.pid, app.processName);
- if (DEBUG_CLEANUP) Slog.v(
- TAG, "Dying app: " + app + ", pid: " + pid
- + ", thread: " + thread.asBinder());
+ if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
+ "Dying app: " + app + ", pid: " + pid + ", thread: " + thread.asBinder());
handleAppDiedLocked(app, false, true);
if (doOomAdj) {
@@ -4727,7 +4449,7 @@ public final class ActivityManagerService extends ActivityManagerNative
+ ") has died and restarted (pid " + app.pid + ").");
EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.userId, app.pid, app.processName);
} else if (DEBUG_PROCESSES) {
- Slog.d(TAG, "Received spurious death notification for thread "
+ Slog.d(TAG_PROCESSES, "Received spurious death notification for thread "
+ thread.asBinder());
}
}
@@ -5082,20 +4804,20 @@ public final class ActivityManagerService extends ActivityManagerNative
map.put("activity", activity);
}
- mHandler.sendMessage(msg);
+ mUiHandler.sendMessage(msg);
}
}
final void showLaunchWarningLocked(final ActivityRecord cur, final ActivityRecord next) {
if (!mLaunchWarningShown) {
mLaunchWarningShown = true;
- mHandler.post(new Runnable() {
+ mUiHandler.post(new Runnable() {
@Override
public void run() {
synchronized (ActivityManagerService.this) {
final Dialog d = new LaunchWarningWindow(mContext, cur, next);
d.show();
- mHandler.postDelayed(new Runnable() {
+ mUiHandler.postDelayed(new Runnable() {
@Override
public void run() {
synchronized (ActivityManagerService.this) {
@@ -5114,6 +4836,9 @@ public final class ActivityManagerService extends ActivityManagerNative
public boolean clearApplicationUserData(final String packageName,
final IPackageDataObserver observer, int userId) {
enforceNotIsolatedCaller("clearApplicationUserData");
+ if (packageName != null && packageName.equals(mDeviceOwnerName)) {
+ throw new SecurityException("Clearing DeviceOwner data is forbidden.");
+ }
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
userId = handleIncomingUser(pid, uid,
@@ -5762,9 +5487,8 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean callerWillRestart, boolean allowRestart, String reason) {
final String name = app.processName;
final int uid = app.uid;
- if (DEBUG_PROCESSES) Slog.d(
- TAG, "Force removing proc " + app.toShortString() + " (" + name
- + "/" + uid + ")");
+ if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES,
+ "Force removing proc " + app.toShortString() + " (" + name + "/" + uid + ")");
mProcessNames.remove(name, uid);
mIsolatedProcesses.remove(app.uid);
@@ -5784,17 +5508,20 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.isolated) {
mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
}
- app.kill(reason, true);
- handleAppDiedLocked(app, true, allowRestart);
- removeLruProcessLocked(app);
-
+ boolean willRestart = false;
if (app.persistent && !app.isolated) {
if (!callerWillRestart) {
- addAppLocked(app.info, false, null /* ABI override */);
+ willRestart = true;
} else {
needRestart = true;
}
}
+ app.kill(reason, true);
+ handleAppDiedLocked(app, willRestart, allowRestart);
+ if (willRestart) {
+ removeLruProcessLocked(app);
+ addAppLocked(app.info, false, null /* ABI override */);
+ }
} else {
mRemovedProcesses.add(app);
}
@@ -5833,6 +5560,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Take care of any services that are waiting for the process.
mServices.processStartTimedOutLocked(app);
app.kill("start timeout", true);
+ removeLruProcessLocked(app);
if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
Slog.w(TAG, "Unattached app died before backup, skipping");
try {
@@ -5892,7 +5620,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Tell the process all about itself.
- if (localLOGV) Slog.v(
+ if (DEBUG_ALL) Slog.v(
TAG, "Binding process pid " + pid + " to record " + app);
final String processName = app.processName;
@@ -5928,7 +5656,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.i(TAG, "Launching preboot mode app: " + app);
}
- if (localLOGV) Slog.v(
+ if (DEBUG_ALL) Slog.v(
TAG, "New app record " + app
+ " thread=" + thread.asBinder() + " pid=" + pid);
try {
@@ -5974,7 +5702,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.instrumentationClass != null) {
ensurePackageDexOpt(app.instrumentationClass.getPackageName());
}
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Binding proc "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc "
+ processName + " with config " + mConfiguration);
ApplicationInfo appInfo = app.instrumentationInfo != null
? app.instrumentationInfo : app.info;
@@ -6007,7 +5735,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Remove this record from the list of starting applications.
mPersistentStartingProcesses.remove(app);
- if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG,
+ if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
"Attach application locked removing on hold: " + app);
mProcessesOnHold.remove(app);
@@ -6049,7 +5777,8 @@ public final class ActivityManagerService extends ActivityManagerNative
// Check whether the next backup agent is in this process...
if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.uid) {
- if (DEBUG_BACKUP) Slog.v(TAG, "New app is backup target, launching agent for " + app);
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
+ "New app is backup target, launching agent for " + app);
ensurePackageDexOpt(mBackupTarget.appInfo.packageName);
try {
thread.scheduleCreateBackupAgent(mBackupTarget.appInfo,
@@ -6123,7 +5852,10 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public void showBootMessage(final CharSequence msg, final boolean always) {
- enforceNotIsolatedCaller("showBootMessage");
+ if (Binder.getCallingUid() != Process.myUid()) {
+ // These days only the core system can call this, so apps can't get in
+ // the way of what we show about running them.
+ }
mWindowManager.showBootMessage(msg, always);
}
@@ -6177,7 +5909,7 @@ public final class ActivityManagerService extends ActivityManagerNative
for (String pkg : pkgs) {
synchronized (ActivityManagerService.this) {
if (forceStopPackageLocked(pkg, -1, false, false, false, false, false,
- 0, "finished booting")) {
+ 0, "query restart")) {
setResultCode(Activity.RESULT_OK);
return;
}
@@ -6187,6 +5919,19 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}, pkgFilter);
+ IntentFilter dumpheapFilter = new IntentFilter();
+ dumpheapFilter.addAction(DumpHeapActivity.ACTION_DELETE_DUMPHEAP);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getBooleanExtra(DumpHeapActivity.EXTRA_DELAY_DELETE, false)) {
+ mHandler.sendEmptyMessageDelayed(POST_DUMP_HEAP_NOTIFICATION_MSG, 5*60*1000);
+ } else {
+ mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG);
+ }
+ }
+ }, dumpheapFilter);
+
// Let system services know.
mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -6198,7 +5943,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ArrayList<ProcessRecord> procs =
new ArrayList<ProcessRecord>(mProcessesOnHold);
for (int ip=0; ip<NP; ip++) {
- if (DEBUG_PROCESSES) Slog.v(TAG, "Starting process on hold: "
+ if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "Starting process on hold: "
+ procs.get(ip));
startProcessLocked(procs.get(ip), "on-hold", null);
}
@@ -6316,7 +6061,7 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public final void activityStopped(IBinder token, Bundle icicle,
PersistableBundle persistentState, CharSequence description) {
- if (localLOGV) Slog.v(TAG, "Activity stopped: token=" + token);
+ if (DEBUG_ALL) Slog.v(TAG, "Activity stopped: token=" + token);
// Refuse possible leaked file descriptors
if (icicle != null && icicle.hasFileDescriptors()) {
@@ -6339,7 +6084,7 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public final void activityDestroyed(IBinder token) {
- if (DEBUG_SWITCH) Slog.v(TAG, "ACTIVITY DESTROYED: " + token);
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token);
synchronized (this) {
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
@@ -6495,8 +6240,7 @@ public final class ActivityManagerService extends ActivityManagerNative
int callingUid, int userId, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
Bundle options) {
- if (DEBUG_MU)
- Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid);
+ if (DEBUG_MU) Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid);
ActivityRecord activity = null;
if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
activity = ActivityRecord.isInStackLocked(token);
@@ -6673,31 +6417,38 @@ public final class ActivityManagerService extends ActivityManagerNative
}
try {
PendingIntentRecord res = (PendingIntentRecord)pendingResult;
- Intent intent = res.key.requestIntent;
- if (intent != null) {
- if (res.lastTag != null && res.lastTagPrefix == prefix && (res.lastTagPrefix == null
- || res.lastTagPrefix.equals(prefix))) {
- return res.lastTag;
- }
- res.lastTagPrefix = prefix;
- StringBuilder sb = new StringBuilder(128);
- if (prefix != null) {
- sb.append(prefix);
- }
- if (intent.getAction() != null) {
- sb.append(intent.getAction());
- } else if (intent.getComponent() != null) {
- intent.getComponent().appendShortString(sb);
- } else {
- sb.append("?");
- }
- return res.lastTag = sb.toString();
+ synchronized (this) {
+ return getTagForIntentSenderLocked(res, prefix);
}
} catch (ClassCastException e) {
}
return null;
}
+ String getTagForIntentSenderLocked(PendingIntentRecord res, String prefix) {
+ final Intent intent = res.key.requestIntent;
+ if (intent != null) {
+ if (res.lastTag != null && res.lastTagPrefix == prefix && (res.lastTagPrefix == null
+ || res.lastTagPrefix.equals(prefix))) {
+ return res.lastTag;
+ }
+ res.lastTagPrefix = prefix;
+ final StringBuilder sb = new StringBuilder(128);
+ if (prefix != null) {
+ sb.append(prefix);
+ }
+ if (intent.getAction() != null) {
+ sb.append(intent.getAction());
+ } else if (intent.getComponent() != null) {
+ intent.getComponent().appendShortString(sb);
+ } else {
+ sb.append("?");
+ }
+ return res.lastTag = sb.toString();
+ }
+ return null;
+ }
+
@Override
public void setProcessLimit(int max) {
enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
@@ -6742,7 +6493,7 @@ public final class ActivityManagerService extends ActivityManagerNative
"setProcessForeground()");
synchronized(this) {
boolean changed = false;
-
+
synchronized (mPidsSelfLocked) {
ProcessRecord pr = mPidsSelfLocked.get(pid);
if (pr == null && isForeground) {
@@ -6778,13 +6529,52 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+
if (changed) {
updateOomAdjLocked();
}
}
}
-
+
+ // =========================================================
+ // PROCESS INFO
+ // =========================================================
+
+ static class ProcessInfoService extends IProcessInfoService.Stub {
+ final ActivityManagerService mActivityManagerService;
+ ProcessInfoService(ActivityManagerService activityManagerService) {
+ mActivityManagerService = activityManagerService;
+ }
+
+ @Override
+ public void getProcessStatesFromPids(/*in*/ int[] pids, /*out*/ int[] states) {
+ mActivityManagerService.getProcessStatesForPIDs(/*in*/ pids, /*out*/ states);
+ }
+ }
+
+ /**
+ * For each PID in the given input array, write the current process state
+ * for that process into the output array, or -1 to indicate that no
+ * process with the given PID exists.
+ */
+ public void getProcessStatesForPIDs(/*in*/ int[] pids, /*out*/ int[] states) {
+ if (pids == null) {
+ throw new NullPointerException("pids");
+ } else if (states == null) {
+ throw new NullPointerException("states");
+ } else if (pids.length != states.length) {
+ throw new IllegalArgumentException("input and output arrays have different lengths!");
+ }
+
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0; i < pids.length; i++) {
+ ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
+ states[i] = (pr == null) ? ActivityManager.PROCESS_STATE_NONEXISTENT :
+ pr.curProcState;
+ }
+ }
+ }
+
// =========================================================
// PERMISSIONS
// =========================================================
@@ -6834,7 +6624,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* permission is automatically denied. (Internally a null permission
* string is used when calling {@link #checkComponentPermission} in cases
* when only uid-based security is needed.)
- *
+ *
* This can be called with or without the global lock held.
*/
@Override
@@ -6842,7 +6632,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
- return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true);
+ return checkComponentPermission(permission, pid, uid, -1, true);
}
@Override
@@ -6862,7 +6652,7 @@ public final class ActivityManagerService extends ActivityManagerNative
pid = tlsIdentity.pid;
}
- return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true);
+ return checkComponentPermission(permission, pid, uid, -1, true);
}
/**
@@ -6899,7 +6689,7 @@ public final class ActivityManagerService extends ActivityManagerNative
*/
private final boolean checkHoldingPermissionsLocked(
IPackageManager pm, ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"checkHoldingPermissionsLocked: uri=" + grantUri + " uid=" + uid);
if (UserHandle.getUserId(uid) != grantUri.sourceUserId) {
if (ActivityManager.checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true)
@@ -6947,8 +6737,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (pp.match(path)) {
if (!readMet) {
final String pprperm = pp.getReadPermission();
- if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking read perm for "
- + pprperm + " for " + pp.getPath()
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
+ "Checking read perm for " + pprperm + " for " + pp.getPath()
+ ": match=" + pp.match(path)
+ " check=" + pm.checkUidPermission(pprperm, uid));
if (pprperm != null) {
@@ -6962,8 +6752,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (!writeMet) {
final String ppwperm = pp.getWritePermission();
- if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking write perm "
- + ppwperm + " for " + pp.getPath()
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
+ "Checking write perm " + ppwperm + " for " + pp.getPath()
+ ": match=" + pp.match(path)
+ " check=" + pm.checkUidPermission(ppwperm, uid));
if (ppwperm != null) {
@@ -7108,15 +6898,15 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (targetPkg != null) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Checking grant " + targetPkg + " permission to " + grantUri);
}
-
+
final IPackageManager pm = AppGlobals.getPackageManager();
// If this is not a content: uri, we can't do anything with it.
if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Can't grant URI permission for non-content URI: " + grantUri);
return -1;
}
@@ -7134,7 +6924,7 @@ public final class ActivityManagerService extends ActivityManagerNative
try {
targetUid = pm.getPackageUid(targetPkg, UserHandle.getUserId(callingUid));
if (targetUid < 0) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Can't grant URI permission no uid for: " + targetPkg);
return -1;
}
@@ -7147,7 +6937,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// First... does the target actually need this permission?
if (checkHoldingPermissionsLocked(pm, pi, grantUri, targetUid, modeFlags)) {
// No need to grant the target this permission.
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Target " + targetPkg + " already has full permission to " + grantUri);
return -1;
}
@@ -7244,7 +7034,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// to the uri, and the target doesn't. Let's now give this to
// the target.
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Granting " + targetPkg + "/" + targetUid + " permission to " + grantUri);
final String authority = grantUri.uri.getAuthority();
@@ -7302,7 +7092,7 @@ public final class ActivityManagerService extends ActivityManagerNative
*/
NeededUriGrants checkGrantUriPermissionFromIntentLocked(int callingUid,
String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Checking URI perm to data=" + (intent != null ? intent.getData() : null)
+ " clip=" + (intent != null ? intent.getClipData() : null)
+ " from " + intent + "; flags=0x"
@@ -7336,10 +7126,9 @@ public final class ActivityManagerService extends ActivityManagerNative
return null;
}
if (targetUid < 0) {
- if (DEBUG_URI_PERMISSION) {
- Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg
- + " on user " + targetUserId);
- }
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
+ "Can't grant URI permission no uid for: " + targetPkg
+ + " on user " + targetUserId);
return null;
}
}
@@ -7446,7 +7235,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(
perm.targetUid);
if (perms != null) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Removing " + perm.targetUid + " permission to " + perm.uri);
perms.remove(perm.uri);
@@ -7458,7 +7247,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private void revokeUriPermissionLocked(int callingUid, GrantUri grantUri, final int modeFlags) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + grantUri);
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
+ "Revoking all granted permissions to " + grantUri);
final IPackageManager pm = AppGlobals.getPackageManager();
final String authority = grantUri.uri.getAuthority();
@@ -7480,9 +7270,9 @@ public final class ActivityManagerService extends ActivityManagerNative
final UriPermission perm = it.next();
if (perm.uri.sourceUserId == grantUri.sourceUserId
&& perm.uri.uri.isPathPrefixMatch(grantUri.uri)) {
- if (DEBUG_URI_PERMISSION)
- Slog.v(TAG, "Revoking non-owned " + perm.targetUid +
- " permission to " + perm.uri);
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
+ "Revoking non-owned " + perm.targetUid
+ + " permission to " + perm.uri);
persistChanged |= perm.revokeModes(
modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
if (perm.modeFlags == 0) {
@@ -7512,8 +7302,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final UriPermission perm = it.next();
if (perm.uri.sourceUserId == grantUri.sourceUserId
&& perm.uri.uri.isPathPrefixMatch(grantUri.uri)) {
- if (DEBUG_URI_PERMISSION)
- Slog.v(TAG,
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
"Revoking " + perm.targetUid + " permission to " + perm.uri);
persistChanged |= perm.revokeModes(
modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, true);
@@ -7559,7 +7348,6 @@ public final class ActivityManagerService extends ActivityManagerNative
return;
}
- final IPackageManager pm = AppGlobals.getPackageManager();
final String authority = uri.getAuthority();
final ProviderInfo pi = getProviderInfoLocked(authority, userId);
if (pi == null) {
@@ -7699,7 +7487,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private void writeGrantedUriPermissions() {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG, "writeGrantedUriPermissions()");
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "writeGrantedUriPermissions()");
// Snapshot permissions so we can persist without lock
ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList();
@@ -7747,7 +7535,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private void readGrantedUriPermissionsLocked() {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG, "readGrantedUriPermissions()");
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "readGrantedUriPermissions()");
final long now = System.currentTimeMillis();
@@ -7925,9 +7713,8 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int i = 0; i < trimCount; i++) {
final UriPermission perm = persisted.get(i);
- if (DEBUG_URI_PERMISSION) {
- Slog.v(TAG, "Trimming grant created at " + perm.persistedCreateTime);
- }
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION,
+ "Trimming grant created at " + perm.persistedCreateTime);
perm.releasePersistableModes(~0);
removeUriPermissionIfNeededLocked(perm);
@@ -7995,7 +7782,7 @@ public final class ActivityManagerService extends ActivityManagerNative
msg.what = WAIT_FOR_DEBUGGER_MSG;
msg.obj = app;
msg.arg1 = waiting ? 1 : 0;
- mHandler.sendMessage(msg);
+ mUiHandler.sendMessage(msg);
}
}
@@ -8015,7 +7802,7 @@ public final class ActivityManagerService extends ActivityManagerNative
outInfo.foregroundAppThreshold = mProcessList.getMemLevel(
ProcessList.FOREGROUND_APP_ADJ);
}
-
+
// =========================================================
// TASK MANAGEMENT
// =========================================================
@@ -8028,7 +7815,7 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized(this) {
ArrayList<IAppTask> list = new ArrayList<IAppTask>();
try {
- if (localLOGV) Slog.v(TAG, "getAppTasks");
+ if (DEBUG_ALL) Slog.v(TAG, "getAppTasks");
final int N = mRecentTasks.size();
for (int i = 0; i < N; i++) {
@@ -8062,7 +7849,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();
synchronized(this) {
- if (localLOGV) Slog.v(
+ if (DEBUG_ALL) Slog.v(
TAG, "getTasks: max=" + maxNum + ", flags=" + flags);
final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(),
@@ -8096,6 +7883,27 @@ public final class ActivityManagerService extends ActivityManagerNative
rti.lastActiveTime = tr.lastActiveTime;
rti.affiliatedTaskId = tr.mAffiliatedTaskId;
rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
+ rti.numActivities = 0;
+
+ ActivityRecord base = null;
+ ActivityRecord top = null;
+ ActivityRecord tmp;
+
+ for (int i = tr.mActivities.size() - 1; i >= 0; --i) {
+ tmp = tr.mActivities.get(i);
+ if (tmp.finishing) {
+ continue;
+ }
+ base = tmp;
+ if (top == null || (top.state == ActivityState.INITIALIZING)) {
+ top = base;
+ }
+ rti.numActivities++;
+ }
+
+ rti.baseActivity = (base != null) ? base.intent.getComponent() : null;
+ rti.topActivity = (top != null) ? top.intent.getComponent() : null;
+
return rti;
}
@@ -8121,7 +7929,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (!allowed) {
Slog.w(TAG, caller + ": caller " + callingUid
- + " does not hold GET_TASKS; limiting output");
+ + " does not hold REAL_GET_TASKS; limiting output");
}
return allowed;
}
@@ -8141,24 +7949,23 @@ public final class ActivityManagerService extends ActivityManagerNative
android.Manifest.permission.GET_DETAILED_TASKS)
== PackageManager.PERMISSION_GRANTED;
- final int N = mRecentTasks.size();
- ArrayList<ActivityManager.RecentTaskInfo> res
- = new ArrayList<ActivityManager.RecentTaskInfo>(
- maxNum < N ? maxNum : N);
+ final int recentsCount = mRecentTasks.size();
+ ArrayList<ActivityManager.RecentTaskInfo> res =
+ new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount);
final Set<Integer> includedUsers;
if (includeProfiles) {
includedUsers = getProfileIdsLocked(userId);
} else {
- includedUsers = new HashSet<Integer>();
+ includedUsers = new HashSet<>();
}
includedUsers.add(Integer.valueOf(userId));
- for (int i=0; i<N && maxNum > 0; i++) {
+ for (int i = 0; i < recentsCount && maxNum > 0; i++) {
TaskRecord tr = mRecentTasks.get(i);
// Only add calling user or related users recent tasks
if (!includedUsers.contains(Integer.valueOf(tr.userId))) {
- if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, not user: " + tr);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr);
continue;
}
@@ -8177,25 +7984,27 @@ public final class ActivityManagerService extends ActivityManagerNative
// If the caller doesn't have the GET_TASKS permission, then only
// allow them to see a small subset of tasks -- their own and home.
if (!tr.isHomeTask() && tr.effectiveUid != callingUid) {
- if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, not allowed: " + tr);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr);
continue;
}
}
if ((flags & ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS) != 0) {
if (tr.stack != null && tr.stack.isHomeStack()) {
- if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, home stack task: " + tr);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "Skipping, home stack task: " + tr);
continue;
}
}
if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
// Don't include auto remove tasks that are finished or finishing.
- if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, auto-remove without activity: "
- + tr);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "Skipping, auto-remove without activity: " + tr);
continue;
}
if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0
&& !tr.isAvailable) {
- if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, unavail real act: " + tr);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "Skipping, unavail real act: " + tr);
continue;
}
@@ -8212,23 +8021,12 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- TaskRecord recentTaskForIdLocked(int id) {
- final int N = mRecentTasks.size();
- for (int i=0; i<N; i++) {
- TaskRecord tr = mRecentTasks.get(i);
- if (tr.taskId == id) {
- return tr;
- }
- }
- return null;
- }
-
@Override
public ActivityManager.TaskThumbnail getTaskThumbnail(int id) {
synchronized (this) {
enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
"getTaskThumbnail()");
- TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id);
+ TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id, false);
if (tr != null) {
return tr.getTaskThumbnailLocked();
}
@@ -8293,7 +8091,7 @@ public final class ActivityManagerService extends ActivityManagerNative
TaskRecord task = new TaskRecord(this, mStackSupervisor.getNextTaskId(), ainfo,
intent, description);
- int trimIdx = trimRecentsForTaskLocked(task, false);
+ int trimIdx = mRecentTasks.trimForTaskLocked(task, false);
if (trimIdx >= 0) {
// If this would have caused a trim, then we'll abort because that
// means it would be added at the end of the list but then just removed.
@@ -8339,6 +8137,41 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public void setTaskResizeable(int taskId, boolean resizeable) {
+ synchronized (this) {
+ TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, false);
+ if (task == null) {
+ Slog.w(TAG, "setTaskResizeable: taskId=" + taskId + " not found");
+ return;
+ }
+ if (task.mResizeable != resizeable) {
+ task.mResizeable = resizeable;
+ mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
+ mStackSupervisor.resumeTopActivitiesLocked();
+ }
+ }
+ }
+
+ @Override
+ public void resizeTask(int taskId, Rect bounds) {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "resizeTask()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task == null) {
+ Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
+ return;
+ }
+ mStackSupervisor.resizeTaskLocked(task, bounds);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public Bitmap getTaskDescriptionIcon(String filename) {
if (!FileUtils.isValidExtFilename(filename)
|| !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
@@ -8473,7 +8306,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* @return Returns true if the given task was found and removed.
*/
private boolean removeTaskByIdLocked(int taskId, boolean killProcess) {
- TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId);
+ TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId, false);
if (tr != null) {
tr.removeTaskActivitiesLocked();
cleanUpRemovedTaskLocked(tr, killProcess);
@@ -8505,10 +8338,9 @@ public final class ActivityManagerService extends ActivityManagerNative
*/
@Override
public void moveTaskToFront(int taskId, int flags, Bundle options) {
- enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
- "moveTaskToFront()");
+ enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToFront()");
- if (DEBUG_STACK) Slog.d(TAG, "moveTaskToFront: moving taskId=" + taskId);
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToFront: moving taskId=" + taskId);
synchronized(this) {
moveTaskToFrontLocked(taskId, flags, options);
}
@@ -8543,40 +8375,10 @@ public final class ActivityManagerService extends ActivityManagerNative
ActivityOptions.abort(options);
}
- @Override
- public void moveTaskToBack(int taskId) {
- enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
- "moveTaskToBack()");
-
- synchronized(this) {
- TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId);
- if (tr != null) {
- if (tr == mStackSupervisor.mLockTaskModeTask) {
- mStackSupervisor.showLockTaskToast();
- return;
- }
- if (DEBUG_STACK) Slog.d(TAG, "moveTaskToBack: moving task=" + tr);
- ActivityStack stack = tr.stack;
- if (stack.mResumedActivity != null && stack.mResumedActivity.task == tr) {
- if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
- Binder.getCallingUid(), -1, -1, "Task to back")) {
- return;
- }
- }
- final long origId = Binder.clearCallingIdentity();
- try {
- stack.moveTaskToBackLocked(taskId);
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
- }
- }
-
/**
* Moves an activity, and all of the other activities within the same task, to the bottom
* of the history stack. The activity's order within the task is unchanged.
- *
+ *
* @param token A reference to the activity we wish to move
* @param nonRoot If false then this only works if the activity is the root
* of a task; if true it will work for any activity in a task.
@@ -8589,9 +8391,9 @@ public final class ActivityManagerService extends ActivityManagerNative
final long origId = Binder.clearCallingIdentity();
try {
int taskId = ActivityRecord.getTaskForActivityLocked(token, !nonRoot);
- if (taskId >= 0) {
- if ((mStackSupervisor.mLockTaskModeTask != null)
- && (mStackSupervisor.mLockTaskModeTask.taskId == taskId)) {
+ final TaskRecord task = mRecentTasks.taskForIdLocked(taskId);
+ if (task != null) {
+ if (mStackSupervisor.isLockedTask(task)) {
mStackSupervisor.showLockTaskToast();
return false;
}
@@ -8634,7 +8436,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public IActivityContainer createActivityContainer(IBinder parentActivityToken,
+ public IActivityContainer createVirtualActivityContainer(IBinder parentActivityToken,
IActivityContainerCallback callback) throws RemoteException {
enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
"createActivityContainer()");
@@ -8642,14 +8444,14 @@ public final class ActivityManagerService extends ActivityManagerNative
if (parentActivityToken == null) {
throw new IllegalArgumentException("parent token must not be null");
}
- ActivityRecord r = ActivityRecord.forToken(parentActivityToken);
+ ActivityRecord r = ActivityRecord.forTokenLocked(parentActivityToken);
if (r == null) {
return null;
}
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
- return mStackSupervisor.createActivityContainer(r, callback);
+ return mStackSupervisor.createVirtualActivityContainer(r, callback);
}
}
@@ -8663,14 +8465,27 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public IActivityContainer getEnclosingActivityContainer(IBinder activityToken)
- throws RemoteException {
+ public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "createStackOnDisplay()");
+ synchronized (this) {
+ final int stackId = mStackSupervisor.getNextStackId();
+ final ActivityStack stack = mStackSupervisor.createStackOnDisplay(stackId, displayId);
+ if (stack == null) {
+ return null;
+ }
+ return stack.mActivityContainer;
+ }
+ }
+
+ @Override
+ public int getActivityDisplayId(IBinder activityToken) throws RemoteException {
synchronized (this) {
ActivityStack stack = ActivityRecord.getStackLocked(activityToken);
- if (stack != null) {
- return stack.mActivityContainer;
+ if (stack != null && stack.mActivityContainer.isAttachedLocked()) {
+ return stack.mActivityContainer.getDisplayId();
}
- return null;
+ return Display.DEFAULT_DISPLAY;
}
}
@@ -8685,8 +8500,8 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
- if (DEBUG_STACK) Slog.d(TAG, "moveTaskToStack: moving task=" + taskId + " to stackId="
- + stackId + " toTop=" + toTop);
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
+ + " to stackId=" + stackId + " toTop=" + toTop);
mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -8695,12 +8510,14 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public void resizeStack(int stackBoxId, Rect bounds) {
+ public void resizeStack(int stackId, Rect bounds) {
enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
- "resizeStackBox()");
+ "resizeStack()");
long ident = Binder.clearCallingIdentity();
try {
- mWindowManager.resizeStack(stackBoxId, bounds);
+ synchronized (this) {
+ mStackSupervisor.resizeStackLocked(stackId, bounds);
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -8741,7 +8558,7 @@ public final class ActivityManagerService extends ActivityManagerNative
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
- TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId);
+ TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId, false);
return tr != null && tr.stack != null && tr.stack.isHomeStack();
}
} finally {
@@ -8756,47 +8573,63 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- private boolean isLockTaskAuthorized(String pkg) {
- final DevicePolicyManager dpm = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- try {
- int uid = mContext.getPackageManager().getPackageUid(pkg,
- Binder.getCallingUserHandle().getIdentifier());
- return (uid == Binder.getCallingUid()) && dpm != null && dpm.isLockTaskPermitted(pkg);
- } catch (NameNotFoundException e) {
- return false;
+ @Override
+ public void updateDeviceOwner(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException("updateDeviceOwner called from non-system process");
+ }
+ synchronized (this) {
+ mDeviceOwnerName = packageName;
}
}
- void startLockTaskMode(TaskRecord task) {
- final String pkg;
+ @Override
+ public void updateLockTaskPackages(int userId, String[] packages) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+ throw new SecurityException("updateLockTaskPackage called from non-system process");
+ }
synchronized (this) {
- pkg = task.intent.getComponent().getPackageName();
+ mLockTaskPackages.put(userId, packages);
+ mStackSupervisor.onLockTaskPackagesUpdatedLocked();
}
- boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID;
- if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) {
- StatusBarManagerInternal statusBarManager = LocalServices.getService(
- StatusBarManagerInternal.class);
- if (statusBarManager != null) {
- statusBarManager.showScreenPinningRequest();
- }
+ }
+
+
+ void startLockTaskModeLocked(TaskRecord task) {
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
return;
}
+
+ // isSystemInitiated is used to distinguish between locked and pinned mode, as pinned mode
+ // is initiated by system after the pinning request was shown and locked mode is initiated
+ // by an authorized app directly
+ final int callingUid = Binder.getCallingUid();
+ boolean isSystemInitiated = callingUid == Process.SYSTEM_UID;
long ident = Binder.clearCallingIdentity();
try {
- synchronized (this) {
- // Since we lost lock on task, make sure it is still there.
- task = mStackSupervisor.anyTaskForIdLocked(task.taskId);
- if (task != null) {
- if (!isSystemInitiated
- && ((mStackSupervisor.getFocusedStack() == null)
- || (task != mStackSupervisor.getFocusedStack().topTask()))) {
- throw new IllegalArgumentException("Invalid task, not in foreground");
+ final ActivityStack stack = mStackSupervisor.getFocusedStack();
+ if (!isSystemInitiated) {
+ task.mLockTaskUid = callingUid;
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
+ // startLockTask() called by app and task mode is lockTaskModeDefault.
+ StatusBarManagerInternal statusBarManager =
+ LocalServices.getService(StatusBarManagerInternal.class);
+ if (statusBarManager != null) {
+ statusBarManager.showScreenPinningRequest();
}
- mStackSupervisor.setLockTaskModeLocked(task, !isSystemInitiated,
- "startLockTask");
+ return;
+ }
+
+ if (stack == null || task != stack.topTask()) {
+ throw new IllegalArgumentException("Invalid task, not in foreground");
}
}
+ mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated ?
+ ActivityManager.LOCK_TASK_MODE_PINNED :
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
+ "startLockTask");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -8804,37 +8637,25 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public void startLockTaskMode(int taskId) {
- final TaskRecord task;
- long ident = Binder.clearCallingIdentity();
- try {
- synchronized (this) {
- task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ synchronized (this) {
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task != null) {
+ startLockTaskModeLocked(task);
}
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- if (task != null) {
- startLockTaskMode(task);
}
}
@Override
public void startLockTaskMode(IBinder token) {
- final TaskRecord task;
- long ident = Binder.clearCallingIdentity();
- try {
- synchronized (this) {
- final ActivityRecord r = ActivityRecord.forToken(token);
- if (r == null) {
- return;
- }
- task = r.task;
+ synchronized (this) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (r == null) {
+ return;
+ }
+ final TaskRecord task = r.task;
+ if (task != null) {
+ startLockTaskModeLocked(task);
}
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- if (task != null) {
- startLockTaskMode(task);
}
}
@@ -8844,11 +8665,12 @@ public final class ActivityManagerService extends ActivityManagerNative
"startLockTaskModeOnCurrent");
long ident = Binder.clearCallingIdentity();
try {
- ActivityRecord r = null;
synchronized (this) {
- r = mStackSupervisor.topRunningActivityLocked();
+ ActivityRecord r = mStackSupervisor.topRunningActivityLocked();
+ if (r != null) {
+ startLockTaskModeLocked(r.task);
+ }
}
- startLockTaskMode(r.task);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -8856,30 +8678,23 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public void stopLockTaskMode() {
- // Verify that the user matches the package of the intent for the TaskRecord
- // we are locked to or systtem. This will ensure the same caller for startLockTaskMode
- // and stopLockTaskMode.
- final int callingUid = Binder.getCallingUid();
- if (callingUid != Process.SYSTEM_UID) {
- try {
- String pkg =
- mStackSupervisor.mLockTaskModeTask.intent.getComponent().getPackageName();
- int uid = mContext.getPackageManager().getPackageUid(pkg,
- Binder.getCallingUserHandle().getIdentifier());
- if (uid != callingUid) {
- throw new SecurityException("Invalid uid, expected " + uid);
- }
- } catch (NameNotFoundException e) {
- Log.d(TAG, "stopLockTaskMode " + e);
- return;
- }
+ final TaskRecord lockTask = mStackSupervisor.getLockedTaskLocked();
+ if (lockTask == null) {
+ // Our work here is done.
+ return;
+ }
+ // Ensure the same caller for startLockTaskMode and stopLockTaskMode.
+ if (getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED &&
+ Binder.getCallingUid() != lockTask.mLockTaskUid) {
+ throw new SecurityException("Invalid uid, expected " + lockTask.mLockTaskUid);
}
long ident = Binder.clearCallingIdentity();
try {
Log.d(TAG, "stopLockTaskMode");
// Stop lock task
synchronized (this) {
- mStackSupervisor.setLockTaskModeLocked(null, false, "stopLockTask");
+ mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE,
+ "stopLockTask");
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -8900,8 +8715,24 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public boolean isInLockTaskMode() {
+ return getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE;
+ }
+
+ @Override
+ public int getLockTaskModeState() {
+ synchronized (this) {
+ return mStackSupervisor.getLockTaskModeState();
+ }
+ }
+
+ @Override
+ public void showLockTaskEscapeMessage(IBinder token) {
synchronized (this) {
- return mStackSupervisor.isInLockTaskMode();
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (r == null) {
+ return;
+ }
+ mStackSupervisor.showLockTaskEscapeMessageLocked(r.task);
}
}
@@ -8917,8 +8748,8 @@ public final class ActivityManagerService extends ActivityManagerNative
STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS);
} catch (RemoteException ex) {
}
- if (DEBUG_MU)
- Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.uid);
+ if (DEBUG_MU) Slog.v(TAG_MU,
+ "generateApplicationProvidersLocked, app.info.uid = " + app.uid);
int userId = app.userId;
if (providers != null) {
int N = providers.size();
@@ -8928,7 +8759,7 @@ public final class ActivityManagerService extends ActivityManagerNative
(ProviderInfo)providers.get(i);
boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags);
- if (singleton && UserHandle.getUserId(app.uid) != 0) {
+ if (singleton && UserHandle.getUserId(app.uid) != UserHandle.USER_OWNER) {
// This is a singleton provider, but a user besides the
// default user is asking to initialize a process it runs
// in... well, no, it doesn't actually run in this process,
@@ -8945,8 +8776,8 @@ public final class ActivityManagerService extends ActivityManagerNative
cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton);
mProviderMap.putProviderByClass(comp, cpr);
}
- if (DEBUG_MU)
- Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid);
+ if (DEBUG_MU) Slog.v(TAG_MU,
+ "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid);
app.pubProviders.put(cpi.name, cpr);
if (!cpi.multiprocess || !"android".equals(cpi.packageName)) {
// Don't add this if it is a platform component that is marked
@@ -9002,7 +8833,7 @@ public final class ActivityManagerService extends ActivityManagerNative
== PackageManager.PERMISSION_GRANTED) {
return null;
}
-
+
PathPermission[] pps = cpi.pathPermissions;
if (pps != null) {
int i = pps.length;
@@ -9084,7 +8915,7 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int i=0; i<r.conProviders.size(); i++) {
ContentProviderConnection conn = r.conProviders.get(i);
if (conn.provider == cpr) {
- if (DEBUG_PROVIDER) Slog.v(TAG,
+ if (DEBUG_PROVIDER) Slog.v(TAG_PROVIDER,
"Adding provider requested by "
+ r.processName + " from process "
+ cpr.info.processName + ": " + cpr.name.flattenToShortString()
@@ -9120,7 +8951,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable) {
if (conn != null) {
cpr = conn.provider;
- if (DEBUG_PROVIDER) Slog.v(TAG,
+ if (DEBUG_PROVIDER) Slog.v(TAG_PROVIDER,
"Removing provider requested by "
+ conn.client.processName + " from process "
+ cpr.info.processName + ": " + cpr.name.flattenToShortString()
@@ -9248,7 +9079,7 @@ public final class ActivityManagerService extends ActivityManagerNative
checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
boolean success = updateOomAdjLocked(cpr.proc);
checkTime(startTime, "getContentProviderImpl: after updateOomAdj");
- if (DEBUG_PROVIDER) Slog.i(TAG, "Adjust success: " + success);
+ if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success);
// NOTE: there is still a race here where a signal could be
// pending on the process even though we managed to update its
// adj level. Not sure what to do about this, but at least
@@ -9258,8 +9089,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// has been killed on us. We need to wait for a new
// process to be started, and make sure its death
// doesn't kill our process.
- Slog.i(TAG,
- "Existing provider " + cpr.name.flattenToShortString()
+ Slog.i(TAG, "Existing provider " + cpr.name.flattenToShortString()
+ " is crashing; detaching " + r);
boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);
checkTime(startTime, "getContentProviderImpl: before appDied");
@@ -9370,18 +9200,16 @@ public final class ActivityManagerService extends ActivityManagerNative
return cpr.newHolder(null);
}
- if (DEBUG_PROVIDER) {
- RuntimeException e = new RuntimeException("here");
- Slog.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + (r != null ? r.uid : null)
- + " pruid " + cpr.appInfo.uid + "): " + cpr.info.name, e);
- }
+ if (DEBUG_PROVIDER) Slog.w(TAG_PROVIDER, "LAUNCHING REMOTE PROVIDER (myuid "
+ + (r != null ? r.uid : null) + " pruid " + cpr.appInfo.uid + "): "
+ + cpr.info.name + " callers=" + Debug.getCallers(6));
// This is single process, and our app is now connecting to it.
// See if we are already in the process of launching this
// provider.
final int N = mLaunchingProviders.size();
int i;
- for (i=0; i<N; i++) {
+ for (i = 0; i < N; i++) {
if (mLaunchingProviders.get(i) == cpr) {
break;
}
@@ -9410,14 +9238,15 @@ public final class ActivityManagerService extends ActivityManagerNative
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null) {
- if (DEBUG_PROVIDER) {
- Slog.d(TAG, "Installing in existing process " + proc);
- }
- checkTime(startTime, "getContentProviderImpl: scheduling install");
- proc.pubProviders.put(cpi.name, cpr);
- try {
- proc.thread.scheduleInstallProvider(cpi);
- } catch (RemoteException e) {
+ if (DEBUG_PROVIDER) Slog.d(TAG_PROVIDER,
+ "Installing in existing process " + proc);
+ if (!proc.pubProviders.containsKey(cpi.name)) {
+ checkTime(startTime, "getContentProviderImpl: scheduling install");
+ proc.pubProviders.put(cpi.name, cpr);
+ try {
+ proc.thread.scheduleInstallProvider(cpi);
+ } catch (RemoteException e) {
+ }
}
} else {
checkTime(startTime, "getContentProviderImpl: before start process");
@@ -9473,10 +9302,9 @@ public final class ActivityManagerService extends ActivityManagerNative
return null;
}
try {
- if (DEBUG_MU) {
- Slog.v(TAG_MU, "Waiting to start provider " + cpr + " launchingApp="
- + cpr.launchingApp);
- }
+ if (DEBUG_MU) Slog.v(TAG_MU,
+ "Waiting to start provider " + cpr
+ + " launchingApp=" + cpr.launchingApp);
if (conn != null) {
conn.waiting = true;
}
@@ -9567,7 +9395,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ContentProviderRecord cpr = mProviderMap.getProviderByName(name, userId);
if(cpr == null) {
//remove from mProvidersByClass
- if(localLOGV) Slog.v(TAG, name+" content provider not found in providers list");
+ if(DEBUG_ALL) Slog.v(TAG, name+" content provider not found in providers list");
return;
}
@@ -9588,7 +9416,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+
public final void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) {
if (providers == null) {
@@ -9598,8 +9426,7 @@ public final class ActivityManagerService extends ActivityManagerNative
enforceNotIsolatedCaller("publishContentProviders");
synchronized (this) {
final ProcessRecord r = getRecordForAppLocked(caller);
- if (DEBUG_MU)
- Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid);
+ if (DEBUG_MU) Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid);
if (r == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
@@ -9616,8 +9443,7 @@ public final class ActivityManagerService extends ActivityManagerNative
continue;
}
ContentProviderRecord dst = r.pubProviders.get(src.info.name);
- if (DEBUG_MU)
- Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid);
+ if (DEBUG_MU) Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid);
if (dst != null) {
ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
mProviderMap.putProviderByClass(comp, dst);
@@ -9982,10 +9808,9 @@ public final class ActivityManagerService extends ActivityManagerNative
} finally {
// Ensure that whatever happens, we clean up the identity state
sCallerIdentity.remove();
+ // Ensure we're done with the provider.
+ removeContentProviderExternalUnchecked(name, null, userId);
}
-
- // We've got the fd now, so we're done with the provider.
- removeContentProviderExternalUnchecked(name, null, userId);
} else {
Slog.d(TAG, "Failed to get provider for authority '" + name + "'");
}
@@ -10010,8 +9835,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
void finishRunningVoiceLocked() {
- if (mRunningVoice) {
- mRunningVoice = false;
+ if (mRunningVoice != null) {
+ mRunningVoice = null;
updateSleepIfNeededLocked();
}
}
@@ -10019,10 +9844,14 @@ public final class ActivityManagerService extends ActivityManagerNative
void updateSleepIfNeededLocked() {
if (mSleeping && !shouldSleepLocked()) {
mSleeping = false;
+ mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
mStackSupervisor.comeOutOfSleepIfNeededLocked();
+ updateOomAdjLocked();
} else if (!mSleeping && shouldSleepLocked()) {
mSleeping = true;
+ mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
mStackSupervisor.goingToSleepLocked();
+ updateOomAdjLocked();
// Initialize the wake times of all processes.
checkExcessivePowerUsageLocked(false);
@@ -10034,19 +9863,18 @@ public final class ActivityManagerService extends ActivityManagerNative
private boolean shouldSleepLocked() {
// Resume applications while running a voice interactor.
- if (mRunningVoice) {
+ if (mRunningVoice != null) {
return false;
}
+ // TODO: Transform the lock screen state into a sleep token instead.
switch (mWakefulness) {
case PowerManagerInternal.WAKEFULNESS_AWAKE:
case PowerManagerInternal.WAKEFULNESS_DREAMING:
- // If we're interactive but applications are already paused then defer
- // resuming them until the lock screen is hidden.
- return mSleeping && mLockScreenShown != LOCK_SCREEN_HIDDEN;
case PowerManagerInternal.WAKEFULNESS_DOZING:
- // If we're dozing then pause applications whenever the lock screen is shown.
- return mLockScreenShown != LOCK_SCREEN_HIDDEN;
+ // Pause applications whenever the lock screen is shown or any sleep
+ // tokens have been acquired.
+ return (mLockScreenShown != LOCK_SCREEN_HIDDEN || !mSleepTokens.isEmpty());
case PowerManagerInternal.WAKEFULNESS_ASLEEP:
default:
// If we're asleep then pause applications unconditionally.
@@ -10071,6 +9899,11 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public void notifyCleartextNetwork(int uid, byte[] firstPacket) {
+ mHandler.obtainMessage(NOTIFY_CLEARTEXT_NETWORK_MSG, uid, 0, firstPacket).sendToTarget();
+ }
+
+ @Override
public boolean shutdown(int timeout) {
if (checkCallingPermission(android.Manifest.permission.SHUTDOWN)
!= PackageManager.PERMISSION_GRANTED) {
@@ -10100,7 +9933,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
public final void activitySlept(IBinder token) {
- if (localLOGV) Slog.v(TAG, "Activity slept: token=" + token);
+ if (DEBUG_ALL) Slog.v(TAG, "Activity slept: token=" + token);
final long origId = Binder.clearCallingIdentity();
@@ -10124,16 +9957,20 @@ public final class ActivityManagerService extends ActivityManagerNative
}
void logLockScreen(String msg) {
- if (DEBUG_LOCKSCREEN) Slog.d(TAG, Debug.getCallers(2) + ":" + msg
+ if (DEBUG_LOCKSCREEN) Slog.d(TAG_LOCKSCREEN, Debug.getCallers(2) + ":" + msg
+ " mLockScreenShown=" + lockScreenShownToString() + " mWakefulness="
+ PowerManagerInternal.wakefulnessToString(mWakefulness)
+ " mSleeping=" + mSleeping);
}
- void startRunningVoiceLocked() {
- if (!mRunningVoice) {
- mRunningVoice = true;
- updateSleepIfNeededLocked();
+ void startRunningVoiceLocked(IVoiceInteractionSession session, int targetUid) {
+ mVoiceWakeLock.setWorkSource(new WorkSource(targetUid));
+ if (mRunningVoice == null || mRunningVoice.asBinder() != session.asBinder()) {
+ if (mRunningVoice == null) {
+ mVoiceWakeLock.acquire();
+ updateSleepIfNeededLocked();
+ }
+ mRunningVoice = session;
}
}
@@ -10167,7 +10004,7 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException("Requires permission "
+ android.Manifest.permission.STOP_APP_SWITCHES);
}
-
+
synchronized(this) {
mAppSwitchesAllowedTime = SystemClock.uptimeMillis()
+ APP_SWITCH_DELAY_TIME;
@@ -10177,14 +10014,14 @@ public final class ActivityManagerService extends ActivityManagerNative
mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
}
}
-
+
public void resumeAppSwitches() {
if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission "
+ android.Manifest.permission.STOP_APP_SWITCHES);
}
-
+
synchronized(this) {
// Note that we don't execute any pending app switches... we will
// let those wait until either the timeout, or the next start
@@ -10192,7 +10029,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mAppSwitchesAllowedTime = 0;
}
}
-
+
boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid,
int callingPid, int callingUid, String name) {
if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
@@ -10220,7 +10057,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.w(TAG, name + " request from " + sourceUid + " stopped");
return false;
}
-
+
public void setDebugApp(String packageName, boolean waitForDebugger,
boolean persistent) {
enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP,
@@ -10436,8 +10273,9 @@ public final class ActivityManagerService extends ActivityManagerNative
return true;
}
+ @Override
public Bundle getAssistContextExtras(int requestType) {
- PendingAssistExtras pae = enqueueAssistContext(requestType, null, null,
+ PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null,
UserHandle.getCallingUserId());
if (pae == null) {
return null;
@@ -10449,30 +10287,30 @@ public final class ActivityManagerService extends ActivityManagerNative
} catch (InterruptedException e) {
}
}
- if (pae.result != null) {
- pae.extras.putBundle(Intent.EXTRA_ASSIST_CONTEXT, pae.result);
- }
}
synchronized (this) {
+ buildAssistBundleLocked(pae, pae.result);
mPendingAssistExtras.remove(pae);
mHandler.removeCallbacks(pae);
}
return pae.extras;
}
+ @Override
+ public void requestAssistContextExtras(int requestType, IResultReceiver receiver) {
+ enqueueAssistContext(requestType, null, null, receiver, UserHandle.getCallingUserId());
+ }
+
private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
- int userHandle) {
+ IResultReceiver receiver, int userHandle) {
enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
- "getAssistContextExtras()");
- PendingAssistExtras pae;
- Bundle extras = new Bundle();
+ "enqueueAssistContext()");
synchronized (this) {
- ActivityRecord activity = getFocusedStack().mResumedActivity;
+ ActivityRecord activity = getFocusedStack().topActivity();
if (activity == null) {
- Slog.w(TAG, "getAssistContextExtras failed: no resumed activity");
+ Slog.w(TAG, "getAssistContextExtras failed: no top activity");
return null;
}
- extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName);
if (activity.app == null || activity.app.thread == null) {
Slog.w(TAG, "getAssistContextExtras failed: no process for " + activity);
return null;
@@ -10481,7 +10319,11 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.w(TAG, "getAssistContextExtras failed: request process same as " + activity);
return null;
}
- pae = new PendingAssistExtras(activity, extras, intent, hint, userHandle);
+ PendingAssistExtras pae;
+ Bundle extras = new Bundle();
+ extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName);
+ extras.putInt(Intent.EXTRA_ASSIST_UID, activity.app.uid);
+ pae = new PendingAssistExtras(activity, extras, intent, hint, receiver, userHandle);
try {
activity.app.thread.requestAssistContextExtras(activity.appToken, pae,
requestType);
@@ -10495,13 +10337,33 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ void pendingAssistExtrasTimedOutLocked(PendingAssistExtras pae) {
+ mPendingAssistExtras.remove(pae);
+ if (pae.receiver != null) {
+ // Caller wants result sent back to them.
+ try {
+ pae.receiver.send(0, null);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ private void buildAssistBundleLocked(PendingAssistExtras pae, Bundle result) {
+ if (result != null) {
+ pae.extras.putBundle(Intent.EXTRA_ASSIST_CONTEXT, result);
+ }
+ if (pae.hint != null) {
+ pae.extras.putBoolean(pae.hint, true);
+ }
+ }
+
public void reportAssistContextExtras(IBinder token, Bundle extras) {
PendingAssistExtras pae = (PendingAssistExtras)token;
synchronized (pae) {
pae.result = extras;
pae.haveResult = true;
pae.notifyAll();
- if (pae.intent == null) {
+ if (pae.intent == null && pae.receiver == null) {
// Caller is just waiting for the result.
return;
}
@@ -10509,17 +10371,23 @@ public final class ActivityManagerService extends ActivityManagerNative
// We are now ready to launch the assist activity.
synchronized (this) {
+ buildAssistBundleLocked(pae, extras);
boolean exists = mPendingAssistExtras.remove(pae);
mHandler.removeCallbacks(pae);
if (!exists) {
// Timed out.
return;
}
+ if (pae.receiver != null) {
+ // Caller wants result sent back to them.
+ try {
+ pae.receiver.send(0, pae.extras);
+ } catch (RemoteException e) {
+ }
+ return;
+ }
}
- pae.intent.replaceExtras(extras);
- if (pae.hint != null) {
- pae.intent.putExtra(pae.hint, true);
- }
+ pae.intent.replaceExtras(pae.extras);
pae.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -10532,7 +10400,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
public boolean launchAssistIntent(Intent intent, int requestType, String hint, int userHandle) {
- return enqueueAssistContext(requestType, intent, hint, userHandle) != null;
+ return enqueueAssistContext(requestType, intent, hint, null, userHandle) != null;
}
public void registerProcessObserver(IProcessObserver observer) {
@@ -10561,7 +10429,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final boolean translucentChanged = r.changeWindowTranslucency(true);
if (translucentChanged) {
- r.task.stack.releaseBackgroundResources();
+ r.task.stack.releaseBackgroundResources(r);
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
}
mWindowManager.setAppFullscreen(token, true);
@@ -10588,7 +10456,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final boolean translucentChanged = r.changeWindowTranslucency(false);
if (translucentChanged) {
- r.task.stack.convertToTranslucent(r);
+ r.task.stack.convertActivityToTranslucent(r);
}
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
mWindowManager.setAppFullscreen(token, false);
@@ -10660,9 +10528,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// update associated state if we're frontmost
if (r == mFocusedActivity) {
- if (DEBUG_IMMERSIVE) {
- Slog.d(TAG, "Frontmost changed immersion: "+ r);
- }
+ if (DEBUG_IMMERSIVE) Slog.d(TAG_IMMERSIVE, "Frontmost changed immersion: "+ r);
applyUpdateLockStateLocked(r);
}
}
@@ -10729,25 +10595,53 @@ public final class ActivityManagerService extends ActivityManagerNative
Context.WINDOW_SERVICE)).addView(v, lp);
}
- public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg) {
+ public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) {
if (!(sender instanceof PendingIntentRecord)) {
return;
}
- BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+ final PendingIntentRecord rec = (PendingIntentRecord)sender;
+ final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
synchronized (stats) {
if (mBatteryStatsService.isOnBattery()) {
mBatteryStatsService.enforceCallingPermission();
- PendingIntentRecord rec = (PendingIntentRecord)sender;
int MY_UID = Binder.getCallingUid();
int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
BatteryStatsImpl.Uid.Pkg pkg =
stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
sourcePkg != null ? sourcePkg : rec.key.packageName);
- pkg.incWakeupsLocked();
+ pkg.noteWakeupAlarmLocked(tag);
}
}
}
+ public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) {
+ if (!(sender instanceof PendingIntentRecord)) {
+ return;
+ }
+ final PendingIntentRecord rec = (PendingIntentRecord)sender;
+ final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+ synchronized (stats) {
+ mBatteryStatsService.enforceCallingPermission();
+ int MY_UID = Binder.getCallingUid();
+ int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid);
+ }
+ }
+
+ public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) {
+ if (!(sender instanceof PendingIntentRecord)) {
+ return;
+ }
+ final PendingIntentRecord rec = (PendingIntentRecord)sender;
+ final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+ synchronized (stats) {
+ mBatteryStatsService.enforceCallingPermission();
+ int MY_UID = Binder.getCallingUid();
+ int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid);
+ }
+ }
+
public boolean killPids(int[] pids, String pReason, boolean secure) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("killPids only available to the system");
@@ -10951,7 +10845,8 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord proc = mLruProcesses.get(i);
if (proc.notCachedSinceIdle) {
- if (proc.setProcState > ActivityManager.PROCESS_STATE_TOP
+ if (proc.setProcState != ActivityManager.PROCESS_STATE_TOP
+ && proc.setProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
&& proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
if (doKilling && proc.initialIdlePss != 0
&& proc.lastPss > ((proc.initialIdlePss*3)/2)) {
@@ -11011,7 +10906,8 @@ public final class ActivityManagerService extends ActivityManagerNative
// This happens before any activities are started, so we can
// change mConfiguration in-place.
updateConfigurationLocked(configuration, null, false, true);
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Initial config: " + mConfiguration);
+ if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Initial config: " + mConfiguration);
}
}
@@ -11105,9 +11001,68 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ final class PreBootContinuation extends IIntentReceiver.Stub {
+ final Intent intent;
+ final Runnable onFinishCallback;
+ final ArrayList<ComponentName> doneReceivers;
+ final List<ResolveInfo> ris;
+ final int[] users;
+ int lastRi = -1;
+ int curRi = 0;
+ int curUser = 0;
+
+ PreBootContinuation(Intent _intent, Runnable _onFinishCallback,
+ ArrayList<ComponentName> _doneReceivers, List<ResolveInfo> _ris, int[] _users) {
+ intent = _intent;
+ onFinishCallback = _onFinishCallback;
+ doneReceivers = _doneReceivers;
+ ris = _ris;
+ users = _users;
+ }
+
+ void go() {
+ if (lastRi != curRi) {
+ ActivityInfo ai = ris.get(curRi).activityInfo;
+ ComponentName comp = new ComponentName(ai.packageName, ai.name);
+ intent.setComponent(comp);
+ doneReceivers.add(comp);
+ lastRi = curRi;
+ CharSequence label = ai.loadLabel(mContext.getPackageManager());
+ showBootMessage(mContext.getString(R.string.android_preparing_apk, label), false);
+ }
+ Slog.i(TAG, "Pre-boot of " + intent.getComponent().toShortString()
+ + " for user " + users[curUser]);
+ EventLogTags.writeAmPreBoot(users[curUser], intent.getComponent().getPackageName());
+ broadcastIntentLocked(null, null, intent, null, this,
+ 0, null, null, null, AppOpsManager.OP_NONE,
+ true, false, MY_PID, Process.SYSTEM_UID,
+ users[curUser]);
+ }
+
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered,
+ boolean sticky, int sendingUser) {
+ curUser++;
+ if (curUser >= users.length) {
+ curUser = 0;
+ curRi++;
+ if (curRi >= ris.size()) {
+ // All done sending broadcasts!
+ if (onFinishCallback != null) {
+ // The raw IIntentReceiver interface is called
+ // with the AM lock held, so redispatch to
+ // execute our code without the lock.
+ mHandler.post(onFinishCallback);
+ }
+ return;
+ }
+ }
+ go();
+ }
+ }
+
private boolean deliverPreBootCompleted(final Runnable onFinishCallback,
ArrayList<ComponentName> doneReceivers, int userId) {
- boolean waitingUpdate = false;
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
List<ResolveInfo> ris = null;
try {
@@ -11115,71 +11070,51 @@ public final class ActivityManagerService extends ActivityManagerNative
intent, null, 0, userId);
} catch (RemoteException e) {
}
- if (ris != null) {
- for (int i=ris.size()-1; i>=0; i--) {
- if ((ris.get(i).activityInfo.applicationInfo.flags
- &ApplicationInfo.FLAG_SYSTEM) == 0) {
- ris.remove(i);
- }
- }
- intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
-
- // For User 0, load the version number. When delivering to a new user, deliver
- // to all receivers.
- if (userId == UserHandle.USER_OWNER) {
- ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
- for (int i=0; i<ris.size(); i++) {
- ActivityInfo ai = ris.get(i).activityInfo;
- ComponentName comp = new ComponentName(ai.packageName, ai.name);
- if (lastDoneReceivers.contains(comp)) {
- // We already did the pre boot receiver for this app with the current
- // platform version, so don't do it again...
- ris.remove(i);
- i--;
- // ...however, do keep it as one that has been done, so we don't
- // forget about it when rewriting the file of last done receivers.
- doneReceivers.add(comp);
- }
- }
+ if (ris == null) {
+ return false;
+ }
+ for (int i=ris.size()-1; i>=0; i--) {
+ if ((ris.get(i).activityInfo.applicationInfo.flags
+ &ApplicationInfo.FLAG_SYSTEM) == 0) {
+ ris.remove(i);
}
+ }
+ intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
- // If primary user, send broadcast to all available users, else just to userId
- final int[] users = userId == UserHandle.USER_OWNER ? getUsersLocked()
- : new int[] { userId };
- for (int i = 0; i < ris.size(); i++) {
+ // For User 0, load the version number. When delivering to a new user, deliver
+ // to all receivers.
+ if (userId == UserHandle.USER_OWNER) {
+ ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
+ for (int i=0; i<ris.size(); i++) {
ActivityInfo ai = ris.get(i).activityInfo;
ComponentName comp = new ComponentName(ai.packageName, ai.name);
- doneReceivers.add(comp);
- intent.setComponent(comp);
- for (int j=0; j<users.length; j++) {
- IIntentReceiver finisher = null;
- // On last receiver and user, set up a completion callback
- if (i == ris.size() - 1 && j == users.length - 1 && onFinishCallback != null) {
- finisher = new IIntentReceiver.Stub() {
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser) {
- // The raw IIntentReceiver interface is called
- // with the AM lock held, so redispatch to
- // execute our code without the lock.
- mHandler.post(onFinishCallback);
- }
- };
- }
- Slog.i(TAG, "Sending system update to " + intent.getComponent()
- + " for user " + users[j]);
- broadcastIntentLocked(null, null, intent, null, finisher,
- 0, null, null, null, AppOpsManager.OP_NONE,
- true, false, MY_PID, Process.SYSTEM_UID,
- users[j]);
- if (finisher != null) {
- waitingUpdate = true;
- }
+ if (false && lastDoneReceivers.contains(comp)) {
+ // We already did the pre boot receiver for this app with the current
+ // platform version, so don't do it again...
+ ris.remove(i);
+ i--;
+ // ...however, do keep it as one that has been done, so we don't
+ // forget about it when rewriting the file of last done receivers.
+ doneReceivers.add(comp);
}
}
}
- return waitingUpdate;
+ if (ris.size() <= 0) {
+ return false;
+ }
+
+ // If primary user, send broadcast to all available users, else just to userId
+ final int[] users = userId == UserHandle.USER_OWNER ? getUsersLocked()
+ : new int[] { userId };
+ if (users.length <= 0) {
+ return false;
+ }
+
+ PreBootContinuation cont = new PreBootContinuation(intent, onFinishCallback, doneReceivers,
+ ris, users);
+ cont.go();
+ return true;
}
public void systemReady(final Runnable goingCallback) {
@@ -11197,12 +11132,11 @@ public final class ActivityManagerService extends ActivityManagerNative
// security checks.
updateCurrentProfileIdsLocked();
- if (mRecentTasks == null) {
- mRecentTasks = mTaskPersister.restoreTasksLocked();
- mTaskPersister.restoreTasksFromOtherDeviceLocked();
- cleanupRecentTasksLocked(UserHandle.USER_ALL);
- mTaskPersister.startPersisting();
- }
+ mRecentTasks.clear();
+ mRecentTasks.addAll(mTaskPersister.restoreTasksLocked());
+ mTaskPersister.restoreTasksFromOtherDeviceLocked();
+ mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
+ mTaskPersister.startPersisting();
// Check to see if there are any update receivers to run.
if (!mDidUpdate) {
@@ -11215,9 +11149,10 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (ActivityManagerService.this) {
mDidUpdate = true;
}
- writeLastDonePreBootReceivers(doneReceivers);
- showBootMessage(mContext.getText(R.string.android_upgrading_complete),
+ showBootMessage(mContext.getText(
+ R.string.android_upgrading_complete),
false);
+ writeLastDonePreBootReceivers(doneReceivers);
systemReady(goingCallback);
}
}, doneReceivers, UserHandle.USER_OWNER);
@@ -11244,7 +11179,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+
synchronized(this) {
if (procsToKill != null) {
for (int i=procsToKill.size()-1; i>=0; i--) {
@@ -11253,20 +11188,20 @@ public final class ActivityManagerService extends ActivityManagerNative
removeProcessLocked(proc, true, false, "system update done");
}
}
-
+
// Now that we have cleaned up any update processes, we
// are ready to start launching real processes and know that
// we won't trample on them any more.
mProcessesReady = true;
}
-
+
Slog.i(TAG, "System now ready");
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_AMS_READY,
SystemClock.uptimeMillis());
synchronized(this) {
// Make sure we have no pre-ready processes sitting around.
-
+
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
ResolveInfo ri = mContext.getPackageManager()
.resolveActivity(new Intent(Intent.ACTION_FACTORY_TEST),
@@ -11295,7 +11230,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Message msg = Message.obtain();
msg.what = SHOW_FACTORY_ERROR_MSG;
msg.getData().putCharSequence("msg", errorMsg);
- mHandler.sendMessage(msg);
+ mUiHandler.sendMessage(msg);
}
}
}
@@ -11345,14 +11280,14 @@ public final class ActivityManagerService extends ActivityManagerNative
if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
Slog.e(TAG, "UIDs on the system are inconsistent, you need to wipe your"
+ " data partition or your device will be unstable.");
- mHandler.obtainMessage(SHOW_UID_ERROR_MSG).sendToTarget();
+ mUiHandler.obtainMessage(SHOW_UID_ERROR_MSG).sendToTarget();
}
} catch (RemoteException e) {
}
- if (!Build.isFingerprintConsistent()) {
+ if (!Build.isBuildConsistent()) {
Slog.e(TAG, "Build fingerprint is not consistent, warning user");
- mHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_MSG).sendToTarget();
+ mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_MSG).sendToTarget();
}
long ident = Binder.clearCallingIdentity();
@@ -11394,7 +11329,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
startAppProblemLocked(app);
app.stopFreezingAllLocked();
- return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace);
+ return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace);
}
private void makeAppNotRespondingLocked(ProcessRecord app,
@@ -11406,12 +11341,12 @@ public final class ActivityManagerService extends ActivityManagerNative
startAppProblemLocked(app);
app.stopFreezingAllLocked();
}
-
+
/**
* Generate a process error record, suitable for attachment to a ProcessRecord.
- *
+ *
* @param app The ProcessRecord in which the error occurred.
- * @param condition Crashing, Application Not Responding, etc. Values are defined in
+ * @param condition Crashing, Application Not Responding, etc. Values are defined in
* ActivityManager.AppErrorStateInfo
* @param activity The activity associated with the crash, if known.
* @param shortMsg Short message describing the crash.
@@ -11420,7 +11355,7 @@ public final class ActivityManagerService extends ActivityManagerNative
*
* @return Returns a fully-formed AppErrorStateInfo record.
*/
- private ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app,
+ private ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app,
int condition, String activity, String shortMsg, String longMsg, String stackTrace) {
ActivityManager.ProcessErrorStateInfo report = new ActivityManager.ProcessErrorStateInfo();
@@ -11449,14 +11384,15 @@ public final class ActivityManagerService extends ActivityManagerNative
app.waitDialog = null;
}
if (app.pid > 0 && app.pid != MY_PID) {
- handleAppCrashLocked(app, null, null, null);
+ handleAppCrashLocked(app, "user-terminated" /*reason*/,
+ null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/);
app.kill("user request after error", true);
}
}
}
- private boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg,
- String stackTrace) {
+ private boolean handleAppCrashLocked(ProcessRecord app, String reason,
+ String shortMsg, String longMsg, String stackTrace) {
long now = SystemClock.uptimeMillis();
Long crashTime;
@@ -11497,7 +11433,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mStackSupervisor.resumeTopActivitiesLocked();
} else {
- mStackSupervisor.finishTopRunningActivityLocked(app);
+ mStackSupervisor.finishTopRunningActivityLocked(app, reason);
}
// Bump up the crash count of any services currently running in the proc.
@@ -11638,7 +11574,7 @@ public final class ActivityManagerService extends ActivityManagerNative
data.put("violationMask", violationMask);
data.put("info", info);
msg.obj = data;
- mHandler.sendMessage(msg);
+ mUiHandler.sendMessage(msg);
Binder.restoreCallingIdentity(origId);
}
@@ -11700,8 +11636,12 @@ public final class ActivityManagerService extends ActivityManagerNative
sb.append("\n");
if (info.crashInfo != null && info.crashInfo.stackTrace != null) {
sb.append(info.crashInfo.stackTrace);
+ sb.append("\n");
+ }
+ if (info.message != null) {
+ sb.append(info.message);
+ sb.append("\n");
}
- sb.append("\n");
// Only buffer up to ~64k. Various logging bits truncate
// things at 128k.
@@ -12089,7 +12029,7 @@ public final class ActivityManagerService extends ActivityManagerNative
data.put("result", result);
data.put("app", r);
msg.obj = data;
- mHandler.sendMessage(msg);
+ mUiHandler.sendMessage(msg);
Binder.restoreCallingIdentity(origId);
}
@@ -12189,14 +12129,14 @@ public final class ActivityManagerService extends ActivityManagerNative
} else if (app.notResponding) {
report = app.notRespondingReport;
}
-
+
if (report != null) {
if (errList == null) {
errList = new ArrayList<ActivityManager.ProcessErrorStateInfo>(1);
}
errList.add(report);
} else {
- Slog.w(TAG, "Missing app error report, app = " + app.processName +
+ Slog.w(TAG, "Missing app error report, app = " + app.processName +
" crashing = " + app.crashing +
" notResponding = " + app.notResponding);
}
@@ -12241,21 +12181,28 @@ public final class ActivityManagerService extends ActivityManagerNative
public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() {
enforceNotIsolatedCaller("getRunningAppProcesses");
+
+ final int callingUid = Binder.getCallingUid();
+
// Lazy instantiation of list
List<ActivityManager.RunningAppProcessInfo> runList = null;
final boolean allUsers = ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL,
- Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED;
- int userId = UserHandle.getUserId(Binder.getCallingUid());
+ callingUid) == PackageManager.PERMISSION_GRANTED;
+ final int userId = UserHandle.getUserId(callingUid);
+ final boolean allUids = isGetTasksAllowed(
+ "getRunningAppProcesses", Binder.getCallingPid(), callingUid);
+
synchronized (this) {
// Iterate across all processes
- for (int i=mLruProcesses.size()-1; i>=0; i--) {
+ for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);
- if (!allUsers && app.userId != userId) {
+ if ((!allUsers && app.userId != userId)
+ || (!allUids && app.uid != callingUid)) {
continue;
}
if ((app.thread != null) && (!app.crashing && !app.notResponding)) {
// Generate process state info for running application
- ActivityManager.RunningAppProcessInfo currApp =
+ ActivityManager.RunningAppProcessInfo currApp =
new ActivityManager.RunningAppProcessInfo(app.processName,
app.pid, app.getPackageList());
fillInProcMemInfo(app, currApp);
@@ -12274,7 +12221,7 @@ public final class ActivityManagerService extends ActivityManagerNative
//Slog.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance
// + " lru=" + currApp.lru);
if (runList == null) {
- runList = new ArrayList<ActivityManager.RunningAppProcessInfo>();
+ runList = new ArrayList<>();
}
runList.add(currApp);
}
@@ -12337,7 +12284,7 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean dumpAll = false;
boolean dumpClient = false;
String dumpPackage = null;
-
+
int opti = 0;
while (opti < args.length) {
String opt = args[opti];
@@ -12827,12 +12774,12 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+
if (mForegroundProcesses.size() > 0) {
synchronized (mPidsSelfLocked) {
boolean printed = false;
for (int i=0; i<mForegroundProcesses.size(); i++) {
- ProcessRecord r = mPidsSelfLocked.get(
+ ProcessRecord r = mPidsSelfLocked.get(
mForegroundProcesses.valueAt(i).pid);
if (dumpPackage != null && (r == null
|| !r.pkgList.containsKey(dumpPackage))) {
@@ -12850,7 +12797,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+
if (mPersistentStartingProcesses.size() > 0) {
if (needSep) pw.println();
needSep = true;
@@ -12868,7 +12815,7 @@ public final class ActivityManagerService extends ActivityManagerNative
dumpProcessList(pw, this, mRemovedProcesses, " ",
"Removed Norm", "Removed PERS", dumpPackage);
}
-
+
if (mProcessesOnHold.size() > 0) {
if (needSep) pw.println();
needSep = true;
@@ -12879,7 +12826,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
-
+
if (mProcessCrashTimes.getMap().size() > 0) {
boolean printed = false;
long now = SystemClock.uptimeMillis();
@@ -13056,10 +13003,14 @@ public final class ActivityManagerService extends ActivityManagerNative
if (dumpPackage == null) {
pw.println(" mWakefulness="
+ PowerManagerInternal.wakefulnessToString(mWakefulness));
+ pw.println(" mSleepTokens=" + mSleepTokens);
pw.println(" mSleeping=" + mSleeping + " mLockScreenShown="
+ lockScreenShownToString());
- pw.println(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice
- + " mTestPssMode=" + mTestPssMode);
+ pw.println(" mShuttingDown=" + mShuttingDown + " mTestPssMode=" + mTestPssMode);
+ if (mRunningVoice != null) {
+ pw.println(" mRunningVoice=" + mRunningVoice);
+ pw.println(" mVoiceWakeLock" + mVoiceWakeLock);
+ }
}
if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
|| mOrigWaitForDebugger) {
@@ -13074,6 +13025,34 @@ public final class ActivityManagerService extends ActivityManagerNative
+ " mOrigWaitForDebugger=" + mOrigWaitForDebugger);
}
}
+ if (mMemWatchProcesses.getMap().size() > 0) {
+ pw.println(" Mem watch processes:");
+ final ArrayMap<String, SparseArray<Pair<Long, String>>> procs
+ = mMemWatchProcesses.getMap();
+ for (int i=0; i<procs.size(); i++) {
+ final String proc = procs.keyAt(i);
+ final SparseArray<Pair<Long, String>> uids = procs.valueAt(i);
+ for (int j=0; j<uids.size(); j++) {
+ if (needSep) {
+ pw.println();
+ needSep = false;
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(" ").append(proc).append('/');
+ UserHandle.formatUid(sb, uids.keyAt(j));
+ Pair<Long, String> val = uids.valueAt(j);
+ sb.append(": "); DebugUtils.sizeValueToString(val.first, sb);
+ if (val.second != null) {
+ sb.append(", report to ").append(val.second);
+ }
+ pw.println(sb.toString());
+ }
+ }
+ pw.print(" mMemWatchDumpProcName="); pw.println(mMemWatchDumpProcName);
+ pw.print(" mMemWatchDumpFile="); pw.println(mMemWatchDumpFile);
+ pw.print(" mMemWatchDumpPid="); pw.print(mMemWatchDumpPid);
+ pw.print(" mMemWatchDumpUid="); pw.println(mMemWatchDumpUid);
+ }
if (mOpenGlTraceApp != null) {
if (dumpPackage == null || dumpPackage.equals(mOpenGlTraceApp)) {
if (needSep) {
@@ -13251,7 +13230,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ArrayList<String> strings;
ArrayList<Integer> objects;
boolean all;
-
+
ItemMatcher() {
all = true;
}
@@ -13336,7 +13315,7 @@ public final class ActivityManagerService extends ActivityManagerNative
protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
int opti, boolean dumpAll) {
ArrayList<ActivityRecord> activities;
-
+
synchronized (this) {
activities = mStackSupervisor.getDumpActivitiesLocked(name);
}
@@ -13459,7 +13438,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
needSep = true;
-
+
if (!onlyHistory && mStickyBroadcasts != null && dumpPackage == null) {
for (int user=0; user<mStickyBroadcasts.size(); user++) {
if (needSep) {
@@ -13494,7 +13473,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+
if (!onlyHistory && dumpAll) {
pw.println();
for (BroadcastQueue queue : mBroadcastQueues) {
@@ -13506,7 +13485,7 @@ public final class ActivityManagerService extends ActivityManagerNative
needSep = true;
printedAnything = true;
}
-
+
if (!printedAnything) {
pw.println(" (nothing)");
}
@@ -13756,8 +13735,9 @@ public final class ActivityManagerService extends ActivityManagerNative
pw.print(" ");
pw.print("state: cur="); pw.print(ProcessList.makeProcStateString(r.curProcState));
pw.print(" set="); pw.print(ProcessList.makeProcStateString(r.setProcState));
- pw.print(" lastPss="); pw.print(r.lastPss);
- pw.print(" lastCachedPss="); pw.println(r.lastCachedPss);
+ pw.print(" lastPss="); DebugUtils.printSizeValue(pw, r.lastPss*1024);
+ pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, r.lastCachedPss*1024);
+ pw.println();
pw.print(prefix);
pw.print(" ");
pw.print("cached="); pw.print(r.cached);
@@ -13846,7 +13826,7 @@ public final class ActivityManagerService extends ActivityManagerNative
long realtime = SystemClock.elapsedRealtime();
pw.println("Applications Graphics Acceleration Info:");
pw.println("Uptime: " + uptime + " Realtime: " + realtime);
-
+
for (int i = procs.size() - 1 ; i >= 0 ; i--) {
ProcessRecord r = procs.get(i);
if (r.thread != null) {
@@ -14071,7 +14051,7 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean isCompact = false;
boolean localOnly = false;
boolean packages = false;
-
+
int opti = 0;
while (opti < args.length) {
String opt = args[opti];
@@ -14096,7 +14076,7 @@ public final class ActivityManagerService extends ActivityManagerNative
} else if ("-h".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]");
pw.println(" -a: include all available information for each process.");
- pw.println(" -d: include dalvik details when dumping process details.");
+ pw.println(" -d: include dalvik details.");
pw.println(" -c: dump in a compact machine-parseable representation.");
pw.println(" --oom: only show processes organized by oom adj.");
pw.println(" --local: only collect details locally, don't call process.");
@@ -14109,7 +14089,7 @@ public final class ActivityManagerService extends ActivityManagerNative
pw.println("Unknown argument: " + opt + "; use -h for help");
}
}
-
+
final boolean isCheckinRequest = scanArgs(args, "--checkin");
long uptime = SystemClock.uptimeMillis();
long realtime = SystemClock.elapsedRealtime();
@@ -14183,6 +14163,8 @@ public final class ActivityManagerService extends ActivityManagerNative
final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
long nativePss = 0;
long dalvikPss = 0;
+ long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ EmptyArray.LONG;
long otherPss = 0;
long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
@@ -14260,6 +14242,9 @@ public final class ActivityManagerService extends ActivityManagerNative
nativePss += mi.nativePss;
dalvikPss += mi.dalvikPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
otherPss += mi.otherPss;
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
long mem = mi.getOtherPss(j);
@@ -14318,6 +14303,10 @@ public final class ActivityManagerService extends ActivityManagerNative
nativePss += mi.nativePss;
dalvikPss += mi.dalvikPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(
+ Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
otherPss += mi.otherPss;
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
long mem = mi.getOtherPss(j);
@@ -14336,7 +14325,16 @@ public final class ActivityManagerService extends ActivityManagerNative
ArrayList<MemItem> catMems = new ArrayList<MemItem>();
catMems.add(new MemItem("Native", "Native", nativePss, -1));
- catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, -2));
+ final MemItem dalvikItem = new MemItem("Dalvik", "Dalvik", dalvikPss, -2);
+ if (dalvikSubitemPss.length > 0) {
+ dalvikItem.subitems = new ArrayList<MemItem>();
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ final String name = Debug.MemoryInfo.getOtherLabel(
+ Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ dalvikItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j], j));
+ }
+ }
+ catMems.add(dalvikItem);
catMems.add(new MemItem("Unknown", "Unknown", otherPss, -3));
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
String label = Debug.MemoryInfo.getOtherLabel(j);
@@ -14380,9 +14378,14 @@ public final class ActivityManagerService extends ActivityManagerNative
memInfo.readMemInfo();
if (nativeProcTotalPss > 0) {
synchronized (this) {
- mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
- memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(),
- memInfo.getKernelUsedSizeKb(), nativeProcTotalPss);
+ final long cachedKb = memInfo.getCachedSizeKb();
+ final long freeKb = memInfo.getFreeSizeKb();
+ final long zramKb = memInfo.getZramTotalSizeKb();
+ final long kernelKb = memInfo.getKernelUsedSizeKb();
+ EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024,
+ kernelKb*1024, nativeProcTotalPss*1024);
+ mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb,
+ nativeProcTotalPss);
}
}
if (!brief) {
@@ -14805,7 +14808,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- for (int i=0; i<cpr.connections.size(); i++) {
+ for (int i = cpr.connections.size() - 1; i >= 0; i--) {
ContentProviderConnection conn = cpr.connections.get(i);
if (conn.waiting) {
// If this connection is waiting for the provider, then we don't
@@ -14897,10 +14900,11 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean restart = false;
// Remove published content providers.
- for (int i=app.pubProviders.size()-1; i>=0; i--) {
+ for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
ContentProviderRecord cpr = app.pubProviders.valueAt(i);
final boolean always = app.bad || !allowRestart;
- if (removeDyingProviderLocked(app, cpr, always) || always) {
+ boolean inLaunching = removeDyingProviderLocked(app, cpr, always);
+ if ((inLaunching || always) && !cpr.connections.isEmpty()) {
// We left the provider in the launching list, need to
// restart it.
restart = true;
@@ -14918,7 +14922,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Unregister from connected content providers.
if (!app.conProviders.isEmpty()) {
- for (int i=0; i<app.conProviders.size(); i++) {
+ for (int i = app.conProviders.size() - 1; i >= 0; i--) {
ContentProviderConnection conn = app.conProviders.get(i);
conn.provider.connections.remove(conn);
stopAssociationLocked(app.uid, app.processName, conn.provider.uid,
@@ -14933,9 +14937,8 @@ public final class ActivityManagerService extends ActivityManagerNative
// XXX Commented out for now. Trying to figure out a way to reproduce
// the actual situation to identify what is actually going on.
if (false) {
- for (int i=0; i<mLaunchingProviders.size(); i++) {
- ContentProviderRecord cpr = (ContentProviderRecord)
- mLaunchingProviders.get(i);
+ for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) {
+ ContentProviderRecord cpr = mLaunchingProviders.get(i);
if (cpr.connections.size() <= 0 && !cpr.hasExternalProcessHandles()) {
synchronized (cpr) {
cpr.launchingApp = null;
@@ -14948,14 +14951,14 @@ public final class ActivityManagerService extends ActivityManagerNative
skipCurrentReceiverLocked(app);
// Unregister any receivers.
- for (int i=app.receivers.size()-1; i>=0; i--) {
+ for (int i = app.receivers.size() - 1; i >= 0; i--) {
removeReceiverLocked(app.receivers.valueAt(i));
}
app.receivers.clear();
// If the app is undergoing backup, tell the backup manager about it
if (mBackupTarget != null && app.pid == mBackupTarget.app.pid) {
- if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG, "App "
+ if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
+ mBackupTarget.appInfo + " died during backup");
try {
IBackupManager bm = IBackupManager.Stub.asInterface(
@@ -14966,7 +14969,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- for (int i = mPendingProcessChanges.size()-1; i>=0; i--) {
+ for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {
ProcessChangeItem item = mPendingProcessChanges.get(i);
if (item.pid == app.pid) {
mPendingProcessChanges.remove(i);
@@ -14982,7 +14985,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (!app.persistent || app.isolated) {
- if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG,
+ if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
"Removing non-persistent process during cleanup: " + app);
mProcessNames.remove(app.processName, app.uid);
mIsolatedProcesses.remove(app.uid);
@@ -15000,8 +15003,8 @@ public final class ActivityManagerService extends ActivityManagerNative
restart = true;
}
}
- if ((DEBUG_PROCESSES || DEBUG_CLEANUP) && mProcessesOnHold.contains(app)) Slog.v(TAG,
- "Clean-up removing on hold: " + app);
+ if ((DEBUG_PROCESSES || DEBUG_CLEANUP) && mProcessesOnHold.contains(app)) Slog.v(
+ TAG_CLEANUP, "Clean-up removing on hold: " + app);
mProcessesOnHold.remove(app);
if (app == mHomeProcess) {
@@ -15041,24 +15044,20 @@ public final class ActivityManagerService extends ActivityManagerNative
// and if any run in this process then either schedule a restart of
// the process or kill the client waiting for it if this process has
// gone bad.
- int NL = mLaunchingProviders.size();
boolean restart = false;
- for (int i=0; i<NL; i++) {
+ for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) {
ContentProviderRecord cpr = mLaunchingProviders.get(i);
if (cpr.launchingApp == app) {
- if (!alwaysBad && !app.bad) {
+ if (!alwaysBad && !app.bad && !cpr.connections.isEmpty()) {
restart = true;
} else {
removeDyingProviderLocked(app, cpr, true);
- // cpr should have been removed from mLaunchingProviders
- NL = mLaunchingProviders.size();
- i--;
}
}
}
return restart;
}
-
+
// =========================================================
// SERVICES
// =========================================================
@@ -15082,15 +15081,15 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
- String resolvedType, int userId) {
+ String resolvedType, int userId) throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
- if (DEBUG_SERVICE)
- Slog.v(TAG, "startService: " + service + " type=" + resolvedType);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
+ "startService: " + service + " type=" + resolvedType);
synchronized(this) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
@@ -15102,11 +15101,11 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- ComponentName startServiceInPackage(int uid,
- Intent service, String resolvedType, int userId) {
+ ComponentName startServiceInPackage(int uid, Intent service, String resolvedType, int userId)
+ throws TransactionTooLargeException {
synchronized(this) {
- if (DEBUG_SERVICE)
- Slog.v(TAG, "startServiceInPackage: " + service + " type=" + resolvedType);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
+ "startServiceInPackage: " + service + " type=" + resolvedType);
final long origId = Binder.clearCallingIdentity();
ComponentName res = mServices.startServiceLocked(null, service,
resolvedType, -1, uid, userId);
@@ -15140,7 +15139,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return mServices.peekServiceLocked(service, resolvedType);
}
}
-
+
@Override
public boolean stopServiceToken(ComponentName className, IBinder token,
int startId) {
@@ -15285,10 +15284,9 @@ public final class ActivityManagerService extends ActivityManagerNative
result = UserHandle.isSameApp(aInfo.uid, Process.PHONE_UID)
|| (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
}
- if (DEBUG_MU) {
- Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo
- + ", " + className + ", 0x" + Integer.toHexString(flags) + ") = " + result);
- }
+ if (DEBUG_MU) Slog.v(TAG_MU,
+ "isSingleton(" + componentProcessName + ", " + aInfo + ", " + className + ", 0x"
+ + Integer.toHexString(flags) + ") = " + result);
return result;
}
@@ -15307,9 +15305,9 @@ public final class ActivityManagerService extends ActivityManagerNative
== PackageManager.PERMISSION_GRANTED;
}
- public int bindService(IApplicationThread caller, IBinder token,
- Intent service, String resolvedType,
- IServiceConnection connection, int flags, int userId) {
+ public int bindService(IApplicationThread caller, IBinder token, Intent service,
+ String resolvedType, IServiceConnection connection, int flags, int userId)
+ throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
@@ -15363,16 +15361,17 @@ public final class ActivityManagerService extends ActivityManagerNative
mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res);
}
}
-
+
// =========================================================
// BACKUP AND RESTORE
// =========================================================
-
+
// Cause the target app to be launched if necessary and its backup agent
// instantiated. The backup agent will invoke backupAgentCreated() on the
// activity manager to announce its creation.
public boolean bindBackupAgent(ApplicationInfo app, int backupMode) {
- if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + app + " mode=" + backupMode);
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
+ "bindBackupAgent: app=" + app + " mode=" + backupMode);
enforceCallingPermission("android.permission.CONFIRM_FULL_BACKUP", "bindBackupAgent");
synchronized(this) {
@@ -15415,7 +15414,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// If the process is already attached, schedule the creation of the backup agent now.
// If it is not yet live, this will be done when it attaches to the framework.
if (proc.thread != null) {
- if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc already running: " + proc);
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc already running: " + proc);
try {
proc.thread.scheduleCreateBackupAgent(app,
compatibilityInfoForPackageLocked(app), backupMode);
@@ -15423,20 +15422,20 @@ public final class ActivityManagerService extends ActivityManagerNative
// Will time out on the backup manager side
}
} else {
- if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc not running, waiting for attach");
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc not running, waiting for attach");
}
// Invariants: at this point, the target app process exists and the application
// is either already running or in the process of coming up. mBackupTarget and
// mBackupAppName describe the app, so that when it binds back to the AM we
// know that it's scheduled for a backup-agent operation.
}
-
+
return true;
}
@Override
public void clearPendingBackup() {
- if (DEBUG_BACKUP) Slog.v(TAG, "clearPendingBackup");
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "clearPendingBackup");
enforceCallingPermission("android.permission.BACKUP", "clearPendingBackup");
synchronized (this) {
@@ -15447,7 +15446,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// A backup agent has just come up
public void backupAgentCreated(String agentPackageName, IBinder agent) {
- if (DEBUG_BACKUP) Slog.v(TAG, "backupAgentCreated: " + agentPackageName
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName
+ " = " + agent);
synchronized(this) {
@@ -15474,7 +15473,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// done with this agent
public void unbindBackupAgent(ApplicationInfo appInfo) {
- if (DEBUG_BACKUP) Slog.v(TAG, "unbindBackupAgent: " + appInfo);
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "unbindBackupAgent: " + appInfo);
if (appInfo == null) {
Slog.w(TAG, "unbind backup agent for null app");
return;
@@ -15516,30 +15515,6 @@ public final class ActivityManagerService extends ActivityManagerNative
// BROADCASTS
// =========================================================
- private final List getStickiesLocked(String action, IntentFilter filter,
- List cur, int userId) {
- final ContentResolver resolver = mContext.getContentResolver();
- ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
- if (stickies == null) {
- return cur;
- }
- final ArrayList<Intent> list = stickies.get(action);
- if (list == null) {
- return cur;
- }
- int N = list.size();
- for (int i=0; i<N; i++) {
- Intent intent = list.get(i);
- if (filter.match(resolver, intent, true, TAG) >= 0) {
- if (cur == null) {
- cur = new ArrayList<Intent>();
- }
- cur.add(intent);
- }
- }
- return cur;
- }
-
boolean isPendingBroadcastProcessLocked(int pid) {
return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid)
|| mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid);
@@ -15564,10 +15539,11 @@ public final class ActivityManagerService extends ActivityManagerNative
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
enforceNotIsolatedCaller("registerReceiver");
+ ArrayList<Intent> stickyIntents = null;
+ ProcessRecord callerApp = null;
int callingUid;
int callingPid;
synchronized(this) {
- ProcessRecord callerApp = null;
if (caller != null) {
callerApp = getRecordForAppLocked(caller);
if (callerApp == null) {
@@ -15590,39 +15566,67 @@ public final class ActivityManagerService extends ActivityManagerNative
callingPid = Binder.getCallingPid();
}
- userId = this.handleIncomingUser(callingPid, callingUid, userId,
+ userId = handleIncomingUser(callingPid, callingUid, userId,
true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
- List allSticky = null;
+ Iterator<String> actions = filter.actionsIterator();
+ if (actions == null) {
+ ArrayList<String> noAction = new ArrayList<String>(1);
+ noAction.add(null);
+ actions = noAction.iterator();
+ }
+
+ // Collect stickies of users
+ int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
+ while (actions.hasNext()) {
+ String action = actions.next();
+ for (int id : userIds) {
+ ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
+ if (stickies != null) {
+ ArrayList<Intent> intents = stickies.get(action);
+ if (intents != null) {
+ if (stickyIntents == null) {
+ stickyIntents = new ArrayList<Intent>();
+ }
+ stickyIntents.addAll(intents);
+ }
+ }
+ }
+ }
+ }
+ ArrayList<Intent> allSticky = null;
+ if (stickyIntents != null) {
+ final ContentResolver resolver = mContext.getContentResolver();
// Look for any matching sticky broadcasts...
- Iterator actions = filter.actionsIterator();
- if (actions != null) {
- while (actions.hasNext()) {
- String action = (String)actions.next();
- allSticky = getStickiesLocked(action, filter, allSticky,
- UserHandle.USER_ALL);
- allSticky = getStickiesLocked(action, filter, allSticky,
- UserHandle.getUserId(callingUid));
+ for (int i = 0, N = stickyIntents.size(); i < N; i++) {
+ Intent intent = stickyIntents.get(i);
+ // If intent has scheme "content", it will need to acccess
+ // provider that needs to lock mProviderMap in ActivityThread
+ // and also it may need to wait application response, so we
+ // cannot lock ActivityManagerService here.
+ if (filter.match(resolver, intent, true, TAG) >= 0) {
+ if (allSticky == null) {
+ allSticky = new ArrayList<Intent>();
+ }
+ allSticky.add(intent);
}
- } else {
- allSticky = getStickiesLocked(null, filter, allSticky,
- UserHandle.USER_ALL);
- allSticky = getStickiesLocked(null, filter, allSticky,
- UserHandle.getUserId(callingUid));
}
+ }
- // The first sticky in the list is returned directly back to
- // the client.
- Intent sticky = allSticky != null ? (Intent)allSticky.get(0) : null;
-
- if (DEBUG_BROADCAST) Slog.v(TAG, "Register receiver " + filter
- + ": " + sticky);
+ // The first sticky in the list is returned directly back to the client.
+ Intent sticky = allSticky != null ? allSticky.get(0) : null;
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
+ if (receiver == null) {
+ return sticky;
+ }
- if (receiver == null) {
- return sticky;
+ synchronized (this) {
+ if (callerApp != null && (callerApp.thread == null
+ || callerApp.thread.asBinder() != caller.asBinder())) {
+ // Original caller already died
+ return null;
}
-
ReceiverList rl
= (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
if (rl == null) {
@@ -15683,7 +15687,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
public void unregisterReceiver(IIntentReceiver receiver) {
- if (DEBUG_BROADCAST) Slog.v(TAG, "Unregister receiver: " + receiver);
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
final long origId = Binder.clearCallingIdentity();
try {
@@ -15692,11 +15696,11 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized(this) {
ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
if (rl != null) {
- if (rl.curBroadcast != null) {
- BroadcastRecord r = rl.curBroadcast;
- final boolean doNext = finishReceiverLocked(
- receiver.asBinder(), r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort);
+ final BroadcastRecord r = rl.curBroadcast;
+ if (r != null && r == r.queue.getMatchingOrderedReceiver(r)) {
+ final boolean doNext = r.queue.finishReceiverLocked(
+ r, r.resultCode, r.resultData, r.resultExtras,
+ r.resultAbort, false);
if (doNext) {
doTrim = true;
r.queue.processNextBroadcast(false);
@@ -15733,7 +15737,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mReceiverResolver.removeFilter(rl.get(i));
}
}
-
+
private final void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord r = mLruProcesses.get(i);
@@ -15761,7 +15765,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
.queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user);
- if (user != 0 && newReceivers != null) {
+ if (user != UserHandle.USER_OWNER && newReceivers != null) {
// If this is not the primary user, we need to check for
// any receivers that should be filtered out.
for (int i=0; i<newReceivers.size(); i++) {
@@ -15834,9 +15838,9 @@ public final class ActivityManagerService extends ActivityManagerNative
// By default broadcasts do not go to stopped apps.
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
- if (DEBUG_BROADCAST_LIGHT) Slog.v(
- TAG, (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
- + " ordered=" + ordered + " userid=" + userId);
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+ (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
+ + " ordered=" + ordered + " userid=" + userId);
if ((resultTo != null) && !ordered) {
Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
}
@@ -15955,14 +15959,14 @@ public final class ActivityManagerService extends ActivityManagerNative
forceStopPackageLocked(list[i], -1, false, true, true,
false, false, userId, "storage unmount");
}
- cleanupRecentTasksLocked(UserHandle.USER_ALL);
+ mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
sendPackageBroadcastLocked(
IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list,
userId);
}
break;
case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
- cleanupRecentTasksLocked(UserHandle.USER_ALL);
+ mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
break;
case Intent.ACTION_PACKAGE_REMOVED:
case Intent.ACTION_PACKAGE_CHANGED:
@@ -15992,6 +15996,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (userId == UserHandle.USER_OWNER) {
mTaskPersister.removeFromPackageCache(ssp);
}
+ mBatteryStatsService.notePackageUninstalled(ssp);
}
} else {
removeTasksByRemovedPackageComponentsLocked(ssp, userId);
@@ -16018,6 +16023,13 @@ public final class ActivityManagerService extends ActivityManagerNative
if (userId == UserHandle.USER_OWNER) {
mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
}
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager().
+ getApplicationInfo(ssp, 0, 0);
+ mBatteryStatsService.notePackageInstalled(ssp,
+ ai != null ? ai.versionCode : 0);
+ } catch (RemoteException e) {
+ }
}
break;
case Intent.ACTION_TIMEZONE_CHANGED:
@@ -16157,10 +16169,10 @@ public final class ActivityManagerService extends ActivityManagerNative
final boolean replacePending =
(intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
- if (DEBUG_BROADCAST) Slog.v(TAG, "Enqueing broadcast: " + intent.getAction()
+
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueing broadcast: " + intent.getAction()
+ " replacePending=" + replacePending);
-
+
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
if (!ordered && NR > 0) {
// If we are not serializing this broadcast, then send the
@@ -16171,8 +16183,7 @@ public final class ActivityManagerService extends ActivityManagerNative
callerPackage, callingPid, callingUid, resolvedType, requiredPermission,
appOp, registeredReceivers, resultTo, resultCode, resultData, map,
ordered, sticky, false, userId);
- if (DEBUG_BROADCAST) Slog.v(
- TAG, "Enqueueing parallel broadcast " + r);
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
if (!replaced) {
queue.enqueueParallelBroadcastLocked(r);
@@ -16261,14 +16272,13 @@ public final class ActivityManagerService extends ActivityManagerNative
callerPackage, callingPid, callingUid, resolvedType,
requiredPermission, appOp, receivers, resultTo, resultCode,
resultData, map, ordered, sticky, false, userId);
- if (DEBUG_BROADCAST) Slog.v(
- TAG, "Enqueueing ordered broadcast " + r
+
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r
+ ": prev had " + queue.mOrderedBroadcasts.size());
- if (DEBUG_BROADCAST) {
- int seq = r.intent.getIntExtra("seq", -1);
- Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq);
- }
- boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
+ if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
+ "Enqueueing broadcast " + r.intent.getAction());
+
+ boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
if (!replaced) {
queue.enqueueOrderedBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
@@ -16387,17 +16397,6 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- private final boolean finishReceiverLocked(IBinder receiver, int resultCode,
- String resultData, Bundle resultExtras, boolean resultAbort) {
- final BroadcastRecord r = broadcastRecordForReceiverLocked(receiver);
- if (r == null) {
- Slog.w(TAG, "finishReceiver called but not found on queue");
- return false;
- }
-
- return r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, false);
- }
-
void backgroundServicesFinishedLocked(int userId) {
for (BroadcastQueue queue : mBroadcastQueues) {
queue.backgroundServicesFinishedLocked(userId);
@@ -16405,8 +16404,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
public void finishReceiver(IBinder who, int resultCode, String resultData,
- Bundle resultExtras, boolean resultAbort) {
- if (DEBUG_BROADCAST) Slog.v(TAG, "Finish receiver: " + who);
+ Bundle resultExtras, boolean resultAbort, int flags) {
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + who);
// Refuse possible leaked file descriptors
if (resultExtras != null && resultExtras.hasFileDescriptors()) {
@@ -16419,7 +16418,9 @@ public final class ActivityManagerService extends ActivityManagerNative
BroadcastRecord r;
synchronized(this) {
- r = broadcastRecordForReceiverLocked(who);
+ BroadcastQueue queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0
+ ? mFgBroadcastQueue : mBgBroadcastQueue;
+ r = queue.getMatchingOrderedReceiver(who);
if (r != null) {
doNext = r.queue.finishReceiverLocked(r, resultCode,
resultData, resultExtras, resultAbort, true);
@@ -16434,7 +16435,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Binder.restoreCallingIdentity(origId);
}
}
-
+
// =========================================================
// INSTRUMENTATION
// =========================================================
@@ -16504,17 +16505,17 @@ public final class ActivityManagerService extends ActivityManagerNative
return true;
}
-
+
/**
- * Report errors that occur while attempting to start Instrumentation. Always writes the
+ * Report errors that occur while attempting to start Instrumentation. Always writes the
* error to the logs, but if somebody is watching, send the report there too. This enables
* the "am" command to report errors with more information.
- *
+ *
* @param watcher The IInstrumentationWatcher. Null if there isn't one.
* @param cn The component name of the instrumentation.
* @param report The error report.
*/
- private void reportStartInstrumentationFailure(IInstrumentationWatcher watcher,
+ private void reportStartInstrumentationFailure(IInstrumentationWatcher watcher,
ComponentName cn, String report) {
Slog.w(TAG, report);
try {
@@ -16584,7 +16585,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// =========================================================
// CONFIGURATION
// =========================================================
-
+
public ConfigurationInfo getDeviceConfigurationInfo() {
ConfigurationInfo config = new ConfigurationInfo();
synchronized (this) {
@@ -16608,6 +16609,15 @@ public final class ActivityManagerService extends ActivityManagerNative
return mStackSupervisor.getFocusedStack();
}
+ @Override
+ public int getFocusedStackId() throws RemoteException {
+ ActivityStack focusedStack = getFocusedStack();
+ if (focusedStack != null) {
+ return focusedStack.getStackId();
+ }
+ return -1;
+ }
+
public Configuration getConfiguration() {
Configuration ci;
synchronized(this) {
@@ -16672,16 +16682,16 @@ public final class ActivityManagerService extends ActivityManagerNative
Configuration newConfig = new Configuration(mConfiguration);
changes = newConfig.updateFrom(values);
if (changes != 0) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {
- Slog.i(TAG, "Updating configuration to: " + values);
- }
-
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION,
+ "Updating configuration to: " + values);
+
EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
- if (values.locale != null && !initLocale) {
- saveLocaleLocked(values.locale,
- !values.locale.equals(mConfiguration.locale),
- values.userSetLocale);
+ if (!initLocale && values.locale != null && values.userSetLocale) {
+ final String languageTag = values.locale.toLanguageTag();
+ SystemProperties.set("persist.sys.locale", languageTag);
+ mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
+ values.locale));
}
mConfigurationSeq++;
@@ -16695,7 +16705,7 @@ public final class ActivityManagerService extends ActivityManagerNative
//mUsageStatsService.noteStartConfig(newConfig);
final Configuration configCopy = new Configuration(mConfiguration);
-
+
// TODO: If our config changes, should we auto dismiss any currently
// showing dialogs?
mShowDialogs = shouldShowDialogs(newConfig);
@@ -16724,7 +16734,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ProcessRecord app = mLruProcesses.get(i);
try {
if (app.thread != null) {
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
+ app.processName + " new config " + mConfiguration);
app.thread.scheduleConfigurationChanged(configCopy);
}
@@ -16784,32 +16794,15 @@ public final class ActivityManagerService extends ActivityManagerNative
*/
private static final boolean shouldShowDialogs(Configuration config) {
return !(config.keyboard == Configuration.KEYBOARD_NOKEYS
- && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH);
- }
-
- /**
- * Save the locale. You must be inside a synchronized (this) block.
- */
- private void saveLocaleLocked(Locale l, boolean isDiff, boolean isPersist) {
- if(isDiff) {
- SystemProperties.set("user.language", l.getLanguage());
- SystemProperties.set("user.region", l.getCountry());
- }
-
- if(isPersist) {
- SystemProperties.set("persist.sys.language", l.getLanguage());
- SystemProperties.set("persist.sys.country", l.getCountry());
- SystemProperties.set("persist.sys.localevar", l.getVariant());
-
- mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG, l));
- }
+ && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
+ && config.navigation == Configuration.NAVIGATION_NONAV);
}
@Override
public boolean shouldUpRecreateTask(IBinder token, String destAffinity) {
synchronized (this) {
- ActivityRecord srec = ActivityRecord.forToken(token);
- if (srec.task != null && srec.task.stack != null) {
+ ActivityRecord srec = ActivityRecord.forTokenLocked(token);
+ if (srec != null) {
return srec.task.stack.shouldUpRecreateTaskLocked(srec, destAffinity);
}
}
@@ -16820,16 +16813,19 @@ public final class ActivityManagerService extends ActivityManagerNative
Intent resultData) {
synchronized (this) {
- final ActivityStack stack = ActivityRecord.getStackLocked(token);
- if (stack != null) {
- return stack.navigateUpToLocked(token, destIntent, resultCode, resultData);
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (r != null) {
+ return r.task.stack.navigateUpToLocked(r, destIntent, resultCode, resultData);
}
return false;
}
}
public int getLaunchedFromUid(IBinder activityToken) {
- ActivityRecord srec = ActivityRecord.forToken(activityToken);
+ ActivityRecord srec;
+ synchronized (this) {
+ srec = ActivityRecord.forTokenLocked(activityToken);
+ }
if (srec == null) {
return -1;
}
@@ -16837,7 +16833,10 @@ public final class ActivityManagerService extends ActivityManagerNative
}
public String getLaunchedFromPackage(IBinder activityToken) {
- ActivityRecord srec = ActivityRecord.forToken(activityToken);
+ ActivityRecord srec;
+ synchronized (this) {
+ srec = ActivityRecord.forTokenLocked(activityToken);
+ }
if (srec == null) {
return null;
}
@@ -16987,6 +16986,8 @@ public final class ActivityManagerService extends ActivityManagerNative
app.systemNoUi = false;
+ final int PROCESS_STATE_TOP = mTopProcessState;
+
// Determine the importance of the process, starting with most
// important to least, and assign an appropriate OOM adjustment.
int adj;
@@ -17000,7 +17001,7 @@ public final class ActivityManagerService extends ActivityManagerNative
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "top-activity";
foregroundActivities = true;
- procState = ActivityManager.PROCESS_STATE_TOP;
+ procState = PROCESS_STATE_TOP;
} else if (app.instrumentationClass != null) {
// Don't want to kill running instrumentation.
adj = ProcessList.FOREGROUND_APP_ADJ;
@@ -17053,8 +17054,8 @@ public final class ActivityManagerService extends ActivityManagerNative
adj = ProcessList.VISIBLE_APP_ADJ;
app.adjType = "visible";
}
- if (procState > ActivityManager.PROCESS_STATE_TOP) {
- procState = ActivityManager.PROCESS_STATE_TOP;
+ if (procState > PROCESS_STATE_TOP) {
+ procState = PROCESS_STATE_TOP;
}
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.cached = false;
@@ -17066,8 +17067,8 @@ public final class ActivityManagerService extends ActivityManagerNative
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
app.adjType = "pausing";
}
- if (procState > ActivityManager.PROCESS_STATE_TOP) {
- procState = ActivityManager.PROCESS_STATE_TOP;
+ if (procState > PROCESS_STATE_TOP) {
+ procState = PROCESS_STATE_TOP;
}
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.cached = false;
@@ -17106,7 +17107,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.foregroundServices) {
// The user is aware of this app, so make it visible.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
app.cached = false;
app.adjType = "fg-service";
schedGroup = Process.THREAD_GROUP_DEFAULT;
@@ -17177,7 +17178,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (mBackupTarget != null && app == mBackupTarget.app) {
// If possible we want to avoid killing apps while they're being backed up
if (adj > ProcessList.BACKUP_APP_ADJ) {
- if (DEBUG_BACKUP) Slog.v(TAG, "oom BACKUP_APP_ADJ for " + app);
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
adj = ProcessList.BACKUP_APP_ADJ;
if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
procState = ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
@@ -17546,7 +17547,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
app.curRawAdj = adj;
-
+
//Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
// " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
if (adj > app.maxAdj) {
@@ -17572,11 +17573,12 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* Record new PSS sample for a process.
*/
- void recordPssSample(ProcessRecord proc, int procState, long pss, long uss, long now) {
+ void recordPssSampleLocked(ProcessRecord proc, int procState, long pss, long uss, long now) {
+ EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss * 1024, uss * 1024);
proc.lastPssTime = now;
proc.baseProcessTracker.addPss(pss, uss, true, proc.pkgList);
- if (DEBUG_PSS) Slog.d(TAG, "PSS of " + proc.toShortString()
- + ": " + pss + " lastPss=" + proc.lastPss
+ if (DEBUG_PSS) Slog.d(TAG_PSS,
+ "PSS of " + proc.toShortString() + ": " + pss + " lastPss=" + proc.lastPss
+ " state=" + ProcessList.makeProcStateString(procState));
if (proc.initialIdlePss == 0) {
proc.initialIdlePss = pss;
@@ -17585,6 +17587,80 @@ public final class ActivityManagerService extends ActivityManagerNative
if (procState >= ActivityManager.PROCESS_STATE_HOME) {
proc.lastCachedPss = pss;
}
+
+ final SparseArray<Pair<Long, String>> watchUids
+ = mMemWatchProcesses.getMap().get(proc.processName);
+ Long check = null;
+ if (watchUids != null) {
+ Pair<Long, String> val = watchUids.get(proc.uid);
+ if (val == null) {
+ val = watchUids.get(0);
+ }
+ if (val != null) {
+ check = val.first;
+ }
+ }
+ if (check != null) {
+ if ((pss * 1024) >= check && proc.thread != null && mMemWatchDumpProcName == null) {
+ boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+ if (!isDebuggable) {
+ if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ isDebuggable = true;
+ }
+ }
+ if (isDebuggable) {
+ Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting");
+ final ProcessRecord myProc = proc;
+ final File heapdumpFile = DumpHeapProvider.getJavaFile();
+ mMemWatchDumpProcName = proc.processName;
+ mMemWatchDumpFile = heapdumpFile.toString();
+ mMemWatchDumpPid = proc.pid;
+ mMemWatchDumpUid = proc.uid;
+ BackgroundThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ revokeUriPermission(ActivityThread.currentActivityThread()
+ .getApplicationThread(),
+ DumpHeapActivity.JAVA_URI,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ UserHandle.myUserId());
+ ParcelFileDescriptor fd = null;
+ try {
+ heapdumpFile.delete();
+ fd = ParcelFileDescriptor.open(heapdumpFile,
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE |
+ ParcelFileDescriptor.MODE_WRITE_ONLY |
+ ParcelFileDescriptor.MODE_APPEND);
+ IApplicationThread thread = myProc.thread;
+ if (thread != null) {
+ try {
+ if (DEBUG_PSS) Slog.d(TAG_PSS,
+ "Requesting dump heap from "
+ + myProc + " to " + heapdumpFile);
+ thread.dumpHeap(true, heapdumpFile.toString(), fd);
+ } catch (RemoteException e) {
+ }
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } finally {
+ if (fd != null) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ });
+ } else {
+ Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check
+ + ", but debugging not enabled");
+ }
+ }
+ }
}
/**
@@ -17597,7 +17673,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (mPendingPssProcesses.size() == 0) {
mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
}
- if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of: " + proc);
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting PSS of: " + proc);
proc.pssProcState = procState;
mPendingPssProcesses.add(proc);
}
@@ -17612,13 +17688,17 @@ public final class ActivityManagerService extends ActivityManagerNative
return;
}
}
- if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of all procs! memLowered=" + memLowered);
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting PSS of all procs! memLowered=" + memLowered);
mLastFullPssTime = now;
mFullPssPending = true;
mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
mPendingPssProcesses.clear();
- for (int i=mLruProcesses.size()-1; i>=0; i--) {
+ for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);
+ if (app.thread == null
+ || app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ continue;
+ }
if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
app.pssProcState = app.setProcState;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
@@ -17658,7 +17738,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// whatever.
}
}
-
+
/**
* Returns true if things are idle enough to perform GCs.
*/
@@ -17672,7 +17752,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return !processingBroadcasts
&& (isSleeping() || mStackSupervisor.allResumedActivitiesIdle());
}
-
+
/**
* Perform GCs on all processes that are waiting for it, but only
* if things are idle.
@@ -17701,11 +17781,11 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+
scheduleAppGcsLocked();
}
}
-
+
/**
* If all looks good, perform GCs on all processes waiting for them.
*/
@@ -17723,12 +17803,12 @@ public final class ActivityManagerService extends ActivityManagerNative
*/
final void scheduleAppGcsLocked() {
mHandler.removeMessages(GC_BACKGROUND_PROCESSES_MSG);
-
+
if (mProcessesToGc.size() > 0) {
// Schedule a GC for the time to the next process.
ProcessRecord proc = mProcessesToGc.get(0);
Message msg = mHandler.obtainMessage(GC_BACKGROUND_PROCESSES_MSG);
-
+
long when = proc.lastRequestedGc + GC_MIN_INTERVAL;
long now = SystemClock.uptimeMillis();
if (when < (now+GC_TIMEOUT)) {
@@ -17737,7 +17817,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mHandler.sendMessageAtTime(msg, when);
}
}
-
+
/**
* Add a process to the array of processes waiting to be GCed. Keeps the
* list in sorted order by the last GC time. The process can't already be
@@ -17757,7 +17837,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mProcessesToGc.add(0, proc);
}
}
-
+
/**
* Set up to ask a process to GC itself. This will either do it
* immediately, or put it on the list of processes to gc the next
@@ -17824,7 +17904,7 @@ public final class ActivityManagerService extends ActivityManagerNative
sb.append(" (");
sb.append((wtimeUsed*100)/realtimeSince);
sb.append("%)");
- Slog.i(TAG, sb.toString());
+ Slog.i(TAG_POWER, sb.toString());
sb.setLength(0);
sb.append("CPU for ");
app.toShortString(sb);
@@ -17835,7 +17915,7 @@ public final class ActivityManagerService extends ActivityManagerNative
sb.append(" (");
sb.append((cputimeUsed*100)/uptimeSince);
sb.append("%)");
- Slog.i(TAG, sb.toString());
+ Slog.i(TAG_POWER, sb.toString());
}
// If a process has held a wake lock for more
// than 50% of the time during this period,
@@ -17876,15 +17956,15 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.curAdj != app.setAdj) {
ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(
- TAG, "Set " + app.pid + " " + app.processName +
- " adj " + app.curAdj + ": " + app.adjType);
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
+ "Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": "
+ + app.adjType);
app.setAdj = app.curAdj;
}
if (app.setSchedGroup != app.curSchedGroup) {
app.setSchedGroup = app.curSchedGroup;
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Setting process group of " + app.processName
+ " to " + app.curSchedGroup);
if (app.waitingToKill != null &&
@@ -17934,8 +18014,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
- if (app.setProcState < 0 || ProcessList.procStatesDifferForMem(app.curProcState,
- app.setProcState)) {
+ if (app.setProcState == ActivityManager.PROCESS_STATE_NONEXISTENT
+ || ProcessList.procStatesDifferForMem(app.curProcState, app.setProcState)) {
if (false && mTestPssMode && app.setProcState >= 0 && app.lastStateTime <= (now-200)) {
// Experimental code to more aggressively collect pss while
// running test... the problem is that this tends to collect
@@ -17943,7 +18023,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// states, which well tend to give noisy data.
long start = SystemClock.uptimeMillis();
long pss = Debug.getPss(app.pid, mTmpLong, null);
- recordPssSample(app, app.curProcState, pss, mTmpLong[0], now);
+ recordPssSampleLocked(app, app.curProcState, pss, mTmpLong[0], now);
mPendingPssProcesses.remove(app);
Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState
+ " to " + app.curProcState + ": "
@@ -17952,7 +18032,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.lastStateTime = now;
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
mTestPssMode, isSleeping(), now);
- if (DEBUG_PSS) Slog.d(TAG, "Process state change from "
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from "
+ ProcessList.makeProcStateString(app.setProcState) + " to "
+ ProcessList.makeProcStateString(app.curProcState) + " next pss in "
+ (app.nextPssTime-now) + ": " + app);
@@ -17963,12 +18043,11 @@ public final class ActivityManagerService extends ActivityManagerNative
requestPssLocked(app, app.setProcState);
app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false,
mTestPssMode, isSleeping(), now);
- } else if (false && DEBUG_PSS) {
- Slog.d(TAG, "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now));
- }
+ } else if (false && DEBUG_PSS) Slog.d(TAG_PSS,
+ "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now));
}
if (app.setProcState != app.curProcState) {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Proc state change of " + app.processName
+ " to " + app.curProcState);
boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
@@ -17986,6 +18065,10 @@ public final class ActivityManagerService extends ActivityManagerNative
app.lastCpuTime = app.curCpuTime;
}
+ // Inform UsageStats of important process state change
+ // Must be called before updating setProcState
+ maybeUpdateUsageStats(app);
+
app.setProcState = app.curProcState;
if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
app.notCachedSinceIdle = false;
@@ -17998,13 +18081,15 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (changes != 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Changes in " + app + ": " + changes);
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Changes in " + app + ": " + changes);
int i = mPendingProcessChanges.size()-1;
ProcessChangeItem item = null;
while (i >= 0) {
item = mPendingProcessChanges.get(i);
if (item.pid == app.pid) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Re-using existing item: " + item);
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Re-using existing item: " + item);
break;
}
i--;
@@ -18014,16 +18099,18 @@ public final class ActivityManagerService extends ActivityManagerNative
final int NA = mAvailProcessChanges.size();
if (NA > 0) {
item = mAvailProcessChanges.remove(NA-1);
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Retreiving available item: " + item);
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Retreiving available item: " + item);
} else {
item = new ProcessChangeItem();
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Allocating new item: " + item);
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Allocating new item: " + item);
}
item.changes = 0;
item.pid = app.pid;
item.uid = app.info.uid;
if (mPendingProcessChanges.size() == 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG,
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"*** Enqueueing dispatch processes changed!");
mHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
}
@@ -18032,8 +18119,8 @@ public final class ActivityManagerService extends ActivityManagerNative
item.changes |= changes;
item.processState = app.repProcState;
item.foregroundActivities = app.repForegroundActivities;
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Item "
- + Integer.toHexString(System.identityHashCode(item))
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Item " + Integer.toHexString(System.identityHashCode(item))
+ " " + app.toShortString() + ": changes=" + item.changes
+ " procState=" + item.processState
+ " foreground=" + item.foregroundActivities
@@ -18044,6 +18131,28 @@ public final class ActivityManagerService extends ActivityManagerNative
return success;
}
+ private void maybeUpdateUsageStats(ProcessRecord app) {
+ if (DEBUG_USAGE_STATS) {
+ Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
+ + "] state changes: old = " + app.setProcState + ", new = "
+ + app.curProcState);
+ }
+ if (mUsageStatsService == null) {
+ return;
+ }
+ if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+ && (app.setProcState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+ || app.setProcState < 0)) {
+ String[] packages = app.getPackageList();
+ if (packages != null) {
+ for (int i = 0; i < packages.length; i++) {
+ mUsageStatsService.reportEvent(packages[i], app.userId,
+ UsageEvents.Event.INTERACTION);
+ }
+ }
+ }
+ }
+
private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
if (proc.thread != null) {
if (proc.baseProcessTracker != null) {
@@ -18233,7 +18342,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// step that cached level.
app.curRawAdj = curCachedAdj;
app.curAdj = app.modifyRawOomAdj(curCachedAdj);
- if (DEBUG_LRU && false) Slog.d(TAG, "Assigning activity LRU #" + i
+ if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i
+ " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
+ ")");
if (curCachedAdj != nextCachedAdj) {
@@ -18256,7 +18365,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// state is still as a service), which is what we want.
app.curRawAdj = curEmptyAdj;
app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
- if (DEBUG_LRU && false) Slog.d(TAG, "Assigning empty LRU #" + i
+ if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i
+ " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
+ ")");
if (curEmptyAdj != nextEmptyAdj) {
@@ -18346,13 +18455,13 @@ public final class ActivityManagerService extends ActivityManagerNative
// We always allow the memory level to go up (better). We only allow it to go
// down if we are in a state where that is allowed, *and* the total number of processes
// has gone down since last time.
- if (DEBUG_OOM_ADJ) Slog.d(TAG, "oom: memFactor=" + memFactor + " last=" + mLastMemoryLevel
- + " allowLow=" + mAllowLowerMemLevel + " numProcs=" + mLruProcesses.size()
- + " last=" + mLastNumProcesses);
+ if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor
+ + " last=" + mLastMemoryLevel + " allowLow=" + mAllowLowerMemLevel
+ + " numProcs=" + mLruProcesses.size() + " last=" + mLastNumProcesses);
if (memFactor > mLastMemoryLevel) {
if (!mAllowLowerMemLevel || mLruProcesses.size() >= mLastNumProcesses) {
memFactor = mLastMemoryLevel;
- if (DEBUG_OOM_ADJ) Slog.d(TAG, "Keeping last mem factor!");
+ if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!");
}
}
mLastMemoryLevel = memFactor;
@@ -18392,9 +18501,8 @@ public final class ActivityManagerService extends ActivityManagerNative
&& !app.killedByAm) {
if (app.trimMemoryLevel < curLevel && app.thread != null) {
try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
- "Trimming memory of " + app.processName
- + " to " + curLevel);
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
+ "Trimming memory of " + app.processName + " to " + curLevel);
app.thread.scheduleTrimMemory(curLevel);
} catch (RemoteException e) {
}
@@ -18429,7 +18537,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
&& app.thread != null) {
try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Trimming memory of heavy-weight " + app.processName
+ " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
app.thread.scheduleTrimMemory(
@@ -18447,7 +18555,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
if (app.trimMemoryLevel < level && app.thread != null) {
try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Trimming memory of bg-ui " + app.processName
+ " to " + level);
app.thread.scheduleTrimMemory(level);
@@ -18458,7 +18566,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) {
try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Trimming memory of fg " + app.processName
+ " to " + fgTrimLevel);
app.thread.scheduleTrimMemory(fgTrimLevel);
@@ -18484,7 +18592,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
&& app.thread != null) {
try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
"Trimming memory of ui hidden " + app.processName
+ " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
app.thread.scheduleTrimMemory(
@@ -18519,12 +18627,12 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (DEBUG_OOM_ADJ) {
+ final long duration = SystemClock.uptimeMillis() - now;
if (false) {
- RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
- Slog.d(TAG, "Did OOM ADJ in " + (SystemClock.uptimeMillis()-now) + "ms", here);
+ Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms",
+ new RuntimeException("here").fillInStackTrace());
} else {
- Slog.d(TAG, "Did OOM ADJ in " + (SystemClock.uptimeMillis()-now) + "ms");
+ Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
}
}
}
@@ -18763,6 +18871,61 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ @Override
+ public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize,
+ String reportPackage) {
+ if (processName != null) {
+ enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP,
+ "setDumpHeapDebugLimit()");
+ } else {
+ if (!Build.IS_DEBUGGABLE) {
+ throw new SecurityException("Not running a debuggable build");
+ }
+ synchronized (mPidsSelfLocked) {
+ ProcessRecord proc = mPidsSelfLocked.get(Binder.getCallingPid());
+ if (proc == null) {
+ throw new SecurityException("No process found for calling pid "
+ + Binder.getCallingPid());
+ }
+ processName = proc.processName;
+ uid = proc.uid;
+ if (reportPackage != null && !proc.pkgList.containsKey(reportPackage)) {
+ throw new SecurityException("Package " + reportPackage + " is not running in "
+ + proc);
+ }
+ }
+ }
+ synchronized (this) {
+ if (maxMemSize > 0) {
+ mMemWatchProcesses.put(processName, uid, new Pair(maxMemSize, reportPackage));
+ } else {
+ if (uid != 0) {
+ mMemWatchProcesses.remove(processName, uid);
+ } else {
+ mMemWatchProcesses.getMap().remove(processName);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void dumpHeapFinished(String path) {
+ synchronized (this) {
+ if (Binder.getCallingPid() != mMemWatchDumpPid) {
+ Slog.w(TAG, "dumpHeapFinished: Calling pid " + Binder.getCallingPid()
+ + " does not match last pid " + mMemWatchDumpPid);
+ return;
+ }
+ if (mMemWatchDumpFile == null || !mMemWatchDumpFile.equals(path)) {
+ Slog.w(TAG, "dumpHeapFinished: Calling path " + path
+ + " does not match last path " + mMemWatchDumpFile);
+ return;
+ }
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path);
+ mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG);
+ }
+ }
+
/** In this method we try to acquire our lock to make sure that we have not deadlocked */
public void monitor() {
synchronized (this) { }
@@ -18826,8 +18989,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- private Set getProfileIdsLocked(int userId) {
- Set userIds = new HashSet<Integer>();
+ private Set<Integer> getProfileIdsLocked(int userId) {
+ Set<Integer> userIds = new HashSet<Integer>();
final List<UserInfo> profiles = getUserManagerLocked().getProfiles(
userId, false /* enabledOnly */);
for (UserInfo user : profiles) {
@@ -18853,8 +19016,8 @@ public final class ActivityManagerService extends ActivityManagerNative
userName = userInfo.name;
mTargetUserId = userId;
}
- mHandler.removeMessages(START_USER_SWITCH_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
+ mUiHandler.removeMessages(START_USER_SWITCH_MSG);
+ mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName));
return true;
}
@@ -18886,7 +19049,8 @@ public final class ActivityManagerService extends ActivityManagerNative
return true;
}
- mStackSupervisor.setLockTaskModeLocked(null, false, "startUser");
+ mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE,
+ "startUser");
final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
if (userInfo == null) {
@@ -19032,6 +19196,18 @@ public final class ActivityManagerService extends ActivityManagerNative
return true;
}
+ void dispatchForegroundProfileChanged(int userId) {
+ final int N = mUserSwitchObservers.beginBroadcast();
+ for (int i = 0; i < N; i++) {
+ try {
+ mUserSwitchObservers.getBroadcastItem(i).onForegroundProfileSwitch(userId);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+ mUserSwitchObservers.finishBroadcast();
+ }
+
void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
long ident = Binder.clearCallingIdentity();
try {
@@ -19302,7 +19478,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
- if (userId <= 0) {
+ if (userId < 0 || userId == UserHandle.USER_OWNER) {
throw new IllegalArgumentException("Can't stop primary user " + userId);
}
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
@@ -19420,7 +19596,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
// Explicitly remove the old information in mRecentTasks.
- removeRecentTasksForUserLocked(userId);
+ mRecentTasks.removeTasksForUserLocked(userId);
}
for (int i=0; i<callbacks.size(); i++) {
@@ -19548,14 +19724,6 @@ public final class ActivityManagerService extends ActivityManagerNative
mUserSwitchObservers.unregister(observer);
}
- private boolean userExists(int userId) {
- if (userId == 0) {
- return true;
- }
- UserManagerService ums = getUserManagerLocked();
- return ums != null ? (ums.getUserInfo(userId) != null) : false;
- }
-
int[] getUsersLocked() {
UserManagerService ums = getUserManagerLocked();
return ums != null ? ums.getUserIds() : new int[] { 0 };
@@ -19577,8 +19745,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (info == null) return null;
ApplicationInfo newInfo = new ApplicationInfo(info);
newInfo.uid = applyUserId(info.uid, userId);
- newInfo.dataDir = USER_DATA_DIR + userId + "/"
- + info.packageName;
+ newInfo.dataDir = PackageManager.getDataDirForUser(info.volumeUuid, info.packageName,
+ userId).getAbsolutePath();
return newInfo;
}
@@ -19605,6 +19773,42 @@ public final class ActivityManagerService extends ActivityManagerNative
return ActivityManagerService.this.startIsolatedProcess(entryPoint, entryPointArgs,
processName, abiOverride, uid, crashHandler);
}
+
+ @Override
+ public SleepToken acquireSleepToken(String tag) {
+ Preconditions.checkNotNull(tag);
+
+ synchronized (ActivityManagerService.this) {
+ SleepTokenImpl token = new SleepTokenImpl(tag);
+ mSleepTokens.add(token);
+ updateSleepIfNeededLocked();
+ return token;
+ }
+ }
+ }
+
+ private final class SleepTokenImpl extends SleepToken {
+ private final String mTag;
+ private final long mAcquireTime;
+
+ public SleepTokenImpl(String tag) {
+ mTag = tag;
+ mAcquireTime = SystemClock.uptimeMillis();
+ }
+
+ @Override
+ public void release() {
+ synchronized (ActivityManagerService.this) {
+ if (mSleepTokens.remove(this)) {
+ updateSleepIfNeededLocked();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "{\"" + mTag + "\", acquire at " + TimeUtils.formatUptime(mAcquireTime) + "}";
+ }
}
/**
@@ -19651,7 +19855,7 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (ActivityManagerService.this) {
long origId = Binder.clearCallingIdentity();
try {
- TaskRecord tr = recentTaskForIdLocked(mTaskId);
+ TaskRecord tr = mRecentTasks.taskForIdLocked(mTaskId);
if (tr == null) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
@@ -19678,7 +19882,7 @@ public final class ActivityManagerService extends ActivityManagerNative
TaskRecord tr;
IApplicationThread appThread;
synchronized (ActivityManagerService.this) {
- tr = recentTaskForIdLocked(mTaskId);
+ tr = mRecentTasks.taskForIdLocked(mTaskId);
if (tr == null) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
@@ -19699,7 +19903,7 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (ActivityManagerService.this) {
long origId = Binder.clearCallingIdentity();
try {
- TaskRecord tr = recentTaskForIdLocked(mTaskId);
+ TaskRecord tr = mRecentTasks.taskForIdLocked(mTaskId);
if (tr == null) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index b1b2a5c..f3b18f5 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static com.android.server.am.ActivityManagerDebugConfig.*;
import static com.android.server.am.TaskPersister.DEBUG_PERSISTER;
import static com.android.server.am.TaskPersister.DEBUG_RESTORER;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
@@ -71,11 +72,14 @@ import java.util.Objects;
* An entry in the history stack, representing an activity.
*/
final class ActivityRecord {
- static final String TAG = ActivityManagerService.TAG;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_AM;
+ private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
+ private static final String TAG_THUMBNAILS = TAG + POSTFIX_THUMBNAILS;
+
+ private static final boolean SHOW_ACTIVITY_START_TIME = true;
static final boolean DEBUG_SAVED_STATE = ActivityStackSupervisor.DEBUG_SAVED_STATE;
final public static String RECENTS_PACKAGE_NAME = "com.android.systemui.recents";
- private static final String TAG_ACTIVITY = "activity";
private static final String ATTR_ID = "id";
private static final String TAG_INTENT = "intent";
private static final String ATTR_USERID = "user_id";
@@ -127,6 +131,10 @@ final class ActivityRecord {
long pauseTime; // last time we started pausing the activity
long launchTickTime; // base time for launch tick messages
Configuration configuration; // configuration activity was last running in
+ // Overridden configuration by the activity stack
+ // WARNING: Reference points to {@link ActivityStack#mOverrideConfig}, so its internal state
+ // should never be altered directly.
+ Configuration stackConfigOverride;
CompatibilityInfo compat;// last used compatibility mode
ActivityRecord resultTo; // who started this entry, so will get our reply
final String resultWho; // additional identifier for use by resultTo.
@@ -154,7 +162,6 @@ final class ActivityRecord {
int launchMode; // the launch mode activity attribute.
boolean visible; // does this activity's window need to be shown?
boolean sleeping; // have we told the activity to sleep?
- boolean waitingVisible; // true if waiting for a new act to become vis
boolean nowVisible; // is this activity's window visible?
boolean idle; // has the activity gone idle?
boolean hasBeenLaunched;// has this activity ever been launched?
@@ -205,6 +212,7 @@ final class ActivityRecord {
pw.print(" icon=0x"); pw.print(Integer.toHexString(icon));
pw.print(" theme=0x"); pw.println(Integer.toHexString(theme));
pw.print(prefix); pw.print("config="); pw.println(configuration);
+ pw.print(prefix); pw.print("stackConfigOverride="); pw.println(stackConfigOverride);
if (resultTo != null || resultWho != null) {
pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
pw.print(" resultWho="); pw.print(resultWho);
@@ -293,6 +301,7 @@ final class ActivityRecord {
else TimeUtils.formatDuration(startTime, now, pw);
pw.println();
}
+ final boolean waitingVisible = mStackSupervisor.mWaitingVisibleActivities.contains(this);
if (lastVisibleTime != 0 || waitingVisible || nowVisible) {
pw.print(prefix); pw.print("waitingVisible="); pw.print(waitingVisible);
pw.print(" nowVisible="); pw.print(nowVisible);
@@ -312,44 +321,83 @@ final class ActivityRecord {
}
static class Token extends IApplicationToken.Stub {
- final WeakReference<ActivityRecord> weakActivity;
+ private final WeakReference<ActivityRecord> weakActivity;
+ private final ActivityManagerService mService;
- Token(ActivityRecord activity) {
- weakActivity = new WeakReference<ActivityRecord>(activity);
+ Token(ActivityRecord activity, ActivityManagerService service) {
+ weakActivity = new WeakReference<>(activity);
+ mService = service;
}
- @Override public void windowsDrawn() {
- ActivityRecord activity = weakActivity.get();
- if (activity != null) {
- activity.windowsDrawn();
+ @Override
+ public void windowsDrawn() {
+ synchronized (mService) {
+ ActivityRecord r = tokenToActivityRecordLocked(this);
+ if (r != null) {
+ r.windowsDrawnLocked();
+ }
}
}
- @Override public void windowsVisible() {
- ActivityRecord activity = weakActivity.get();
- if (activity != null) {
- activity.windowsVisible();
+ @Override
+ public void windowsVisible() {
+ synchronized (mService) {
+ ActivityRecord r = tokenToActivityRecordLocked(this);
+ if (r != null) {
+ r.windowsVisibleLocked();
+ }
}
}
- @Override public void windowsGone() {
- ActivityRecord activity = weakActivity.get();
- if (activity != null) {
- activity.windowsGone();
+ @Override
+ public void windowsGone() {
+ synchronized (mService) {
+ ActivityRecord r = tokenToActivityRecordLocked(this);
+ if (r != null) {
+ if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "windowsGone(): " + r);
+ r.nowVisible = false;
+ return;
+ }
}
}
- @Override public boolean keyDispatchingTimedOut(String reason) {
- ActivityRecord activity = weakActivity.get();
- return activity != null && activity.keyDispatchingTimedOut(reason);
+ @Override
+ public boolean keyDispatchingTimedOut(String reason) {
+ ActivityRecord r;
+ ActivityRecord anrActivity;
+ ProcessRecord anrApp;
+ synchronized (mService) {
+ r = tokenToActivityRecordLocked(this);
+ if (r == null) {
+ return false;
+ }
+ anrActivity = r.getWaitingHistoryRecordLocked();
+ anrApp = r != null ? r.app : null;
+ }
+ return mService.inputDispatchingTimedOut(anrApp, anrActivity, r, false, reason);
+ }
+
+ @Override
+ public long getKeyDispatchingTimeout() {
+ synchronized (mService) {
+ ActivityRecord r = tokenToActivityRecordLocked(this);
+ if (r == null) {
+ return 0;
+ }
+ r = r.getWaitingHistoryRecordLocked();
+ return ActivityManagerService.getInputDispatchingTimeoutLocked(r);
+ }
}
- @Override public long getKeyDispatchingTimeout() {
- ActivityRecord activity = weakActivity.get();
- if (activity != null) {
- return activity.getKeyDispatchingTimeout();
+ private static final ActivityRecord tokenToActivityRecordLocked(Token token) {
+ if (token == null) {
+ return null;
}
- return 0;
+ ActivityRecord r = token.weakActivity.get();
+ if (r == null || r.task == null || r.task.stack == null) {
+ return null;
+ }
+ return r;
}
@Override
@@ -364,11 +412,11 @@ final class ActivityRecord {
}
}
- static ActivityRecord forToken(IBinder token) {
+ static ActivityRecord forTokenLocked(IBinder token) {
try {
- return token != null ? ((Token)token).weakActivity.get() : null;
+ return Token.tokenToActivityRecordLocked((Token)token);
} catch (ClassCastException e) {
- Slog.w(ActivityManagerService.TAG, "Bad activity token: " + token, e);
+ Slog.w(TAG, "Bad activity token: " + token, e);
return null;
}
}
@@ -384,7 +432,7 @@ final class ActivityRecord {
boolean _componentSpecified, ActivityStackSupervisor supervisor,
ActivityContainer container, Bundle options) {
service = _service;
- appToken = new Token(this);
+ appToken = new Token(this, service);
info = aInfo;
launchedFromUid = _launchedFromUid;
launchedFromPackage = _launchedFromPackage;
@@ -394,6 +442,8 @@ final class ActivityRecord {
resolvedType = _resolvedType;
componentSpecified = _componentSpecified;
configuration = _configuration;
+ stackConfigOverride = (container != null)
+ ? container.mStack.mOverrideConfig : Configuration.EMPTY;
resultTo = _resultTo;
resultWho = _resultWho;
requestCode = _reqCode;
@@ -407,7 +457,6 @@ final class ActivityRecord {
keysPaused = false;
inHistory = false;
visible = true;
- waitingVisible = false;
nowVisible = false;
idle = false;
hasBeenLaunched = false;
@@ -474,10 +523,16 @@ final class ActivityRecord {
AttributeCache.Entry ent = AttributeCache.instance().get(packageName,
realTheme, com.android.internal.R.styleable.Window, userId);
+ final boolean translucent = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowIsTranslucent, false)
+ || (!ent.array.hasValue(
+ com.android.internal.R.styleable.Window_windowIsTranslucent)
+ && ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowSwipeToDismiss,
+ false));
fullscreen = ent != null && !ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsFloating, false)
- && !ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+ && !translucent;
noDisplay = ent != null && ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
@@ -514,13 +569,8 @@ final class ActivityRecord {
}
void setTask(TaskRecord newTask, TaskRecord taskToAffiliateWith) {
- if (task != null && task.removeActivity(this)) {
- if (task != newTask) {
- task.stack.removeTask(task, "setTask");
- } else {
- Slog.d(TAG, "!!! REMOVE THIS LOG !!! setTask: nearly removed stack=" +
- (newTask == null ? null : newTask.stack));
- }
+ if (task != null && task.removeActivity(this) && task != newTask && task.stack != null) {
+ task.stack.removeTask(task, "setTask");
}
task = newTask;
setTaskToAffiliateWith(taskToAffiliateWith);
@@ -566,6 +616,10 @@ final class ActivityRecord {
return inHistory;
}
+ boolean isInStackLocked() {
+ return task != null && task.stack != null && task.stack.isInStackLocked(this) != null;
+ }
+
boolean isHomeActivity() {
return mActivityType == HOME_ACTIVITY_TYPE;
}
@@ -585,9 +639,10 @@ final class ActivityRecord {
(intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
}
- void makeFinishing() {
+ void makeFinishingLocked() {
if (!finishing) {
- if (this == task.stack.getVisibleBehindActivity()) {
+ if (task != null && task.stack != null
+ && this == task.stack.getVisibleBehindActivity()) {
// A finishing activity should not remain as visible in the background
mStackSupervisor.requestVisibleBehindLocked(this, false);
}
@@ -656,8 +711,9 @@ final class ActivityRecord {
// stack.
final ReferrerIntent rintent = new ReferrerIntent(intent, referrer);
boolean unsent = true;
- if ((state == ActivityState.RESUMED || (service.isSleeping()
- && task.stack.topRunningActivityLocked(null) == this))
+ if ((state == ActivityState.RESUMED
+ || (service.isSleeping() && task.stack != null
+ && task.stack.topRunningActivityLocked(null) == this))
&& app != null && app.thread != null) {
try {
ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
@@ -665,11 +721,9 @@ final class ActivityRecord {
app.thread.scheduleNewIntent(ar, appToken);
unsent = false;
} catch (RemoteException e) {
- Slog.w(ActivityManagerService.TAG,
- "Exception thrown sending new intent to " + this, e);
+ Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
} catch (NullPointerException e) {
- Slog.w(ActivityManagerService.TAG,
- "Exception thrown sending new intent to " + this, e);
+ Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
}
}
if (unsent) {
@@ -707,6 +761,17 @@ final class ActivityRecord {
pendingOptions.getCustomExitResId(),
pendingOptions.getOnAnimationStartListener());
break;
+ case ActivityOptions.ANIM_CLIP_REVEAL:
+ service.mWindowManager.overridePendingAppTransitionClipReveal(
+ pendingOptions.getStartX(), pendingOptions.getStartY(),
+ pendingOptions.getWidth(), pendingOptions.getHeight());
+ if (intent.getSourceBounds() == null) {
+ intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
+ pendingOptions.getStartY(),
+ pendingOptions.getStartX()+pendingOptions.getWidth(),
+ pendingOptions.getStartY()+pendingOptions.getHeight()));
+ }
+ break;
case ActivityOptions.ANIM_SCALE_UP:
service.mWindowManager.overridePendingAppTransitionScaleUp(
pendingOptions.getStartX(), pendingOptions.getStartY(),
@@ -798,7 +863,7 @@ final class ActivityRecord {
void updateThumbnailLocked(Bitmap newThumbnail, CharSequence description) {
if (newThumbnail != null) {
- if (ActivityManagerService.DEBUG_THUMBNAILS) Slog.i(ActivityManagerService.TAG,
+ if (DEBUG_THUMBNAILS) Slog.i(TAG_THUMBNAILS,
"Setting thumbnail of " + this + " to " + newThumbnail);
boolean thumbnailUpdated = task.setLastThumbnail(newThumbnail);
if (thumbnailUpdated && isPersistable()) {
@@ -819,19 +884,27 @@ final class ActivityRecord {
}
boolean continueLaunchTickingLocked() {
- if (launchTickTime != 0) {
- final ActivityStack stack = task.stack;
- Message msg = stack.mHandler.obtainMessage(ActivityStack.LAUNCH_TICK_MSG, this);
- stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG);
- stack.mHandler.sendMessageDelayed(msg, ActivityStack.LAUNCH_TICK);
- return true;
+ if (launchTickTime == 0) {
+ return false;
}
- return false;
+
+ final ActivityStack stack = task.stack;
+ if (stack == null) {
+ return false;
+ }
+
+ Message msg = stack.mHandler.obtainMessage(ActivityStack.LAUNCH_TICK_MSG, this);
+ stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG);
+ stack.mHandler.sendMessageDelayed(msg, ActivityStack.LAUNCH_TICK);
+ return true;
}
void finishLaunchTickingLocked() {
launchTickTime = 0;
- task.stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG);
+ final ActivityStack stack = task.stack;
+ if (stack != null) {
+ stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG);
+ }
}
// IApplicationToken
@@ -862,12 +935,12 @@ final class ActivityRecord {
if (displayStartTime != 0) {
reportLaunchTimeLocked(curTime);
}
- if (fullyDrawnStartTime != 0) {
- final ActivityStack stack = task.stack;
+ final ActivityStack stack = task.stack;
+ if (fullyDrawnStartTime != 0 && stack != null) {
final long thisTime = curTime - fullyDrawnStartTime;
final long totalTime = stack.mFullyDrawnStartTime != 0
? (curTime - stack.mFullyDrawnStartTime) : thisTime;
- if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
+ if (SHOW_ACTIVITY_START_TIME) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0);
EventLog.writeEvent(EventLogTags.AM_ACTIVITY_FULLY_DRAWN_TIME,
userId, System.identityHashCode(this), shortComponentName,
@@ -883,22 +956,25 @@ final class ActivityRecord {
TimeUtils.formatDuration(totalTime, sb);
sb.append(")");
}
- Log.i(ActivityManagerService.TAG, sb.toString());
+ Log.i(TAG, sb.toString());
}
if (totalTime > 0) {
//service.mUsageStatsService.noteFullyDrawnTime(realActivity, (int) totalTime);
}
- fullyDrawnStartTime = 0;
stack.mFullyDrawnStartTime = 0;
}
+ fullyDrawnStartTime = 0;
}
private void reportLaunchTimeLocked(final long curTime) {
final ActivityStack stack = task.stack;
+ if (stack == null) {
+ return;
+ }
final long thisTime = curTime - displayStartTime;
final long totalTime = stack.mLaunchStartTime != 0
? (curTime - stack.mLaunchStartTime) : thisTime;
- if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
+ if (SHOW_ACTIVITY_START_TIME) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching", 0);
EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME,
userId, System.identityHashCode(this), shortComponentName,
@@ -914,7 +990,7 @@ final class ActivityRecord {
TimeUtils.formatDuration(totalTime, sb);
sb.append(")");
}
- Log.i(ActivityManagerService.TAG, sb.toString());
+ Log.i(TAG, sb.toString());
}
mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
if (totalTime > 0) {
@@ -924,100 +1000,62 @@ final class ActivityRecord {
stack.mLaunchStartTime = 0;
}
- public void windowsDrawn() {
- synchronized(service) {
- if (displayStartTime != 0) {
- reportLaunchTimeLocked(SystemClock.uptimeMillis());
- }
- mStackSupervisor.sendWaitingVisibleReportLocked(this);
- startTime = 0;
- finishLaunchTickingLocked();
- if (task != null) {
- task.hasBeenVisible = true;
- }
+ void windowsDrawnLocked() {
+ if (displayStartTime != 0) {
+ reportLaunchTimeLocked(SystemClock.uptimeMillis());
+ }
+ mStackSupervisor.sendWaitingVisibleReportLocked(this);
+ startTime = 0;
+ finishLaunchTickingLocked();
+ if (task != null) {
+ task.hasBeenVisible = true;
}
}
- public void windowsVisible() {
- synchronized(service) {
- mStackSupervisor.reportActivityVisibleLocked(this);
- if (ActivityManagerService.DEBUG_SWITCH) Log.v(
- ActivityManagerService.TAG, "windowsVisible(): " + this);
- if (!nowVisible) {
- nowVisible = true;
- lastVisibleTime = SystemClock.uptimeMillis();
- if (!idle) {
- // Instead of doing the full stop routine here, let's just
- // hide any activities we now can, and let them stop when
- // the normal idle happens.
- mStackSupervisor.processStoppingActivitiesLocked(false);
- } else {
- // If this activity was already idle, then we now need to
- // make sure we perform the full stop of any activities
- // that are waiting to do so. This is because we won't
- // do that while they are still waiting for this one to
- // become visible.
- final int N = mStackSupervisor.mWaitingVisibleActivities.size();
- if (N > 0) {
- for (int i=0; i<N; i++) {
- ActivityRecord r = mStackSupervisor.mWaitingVisibleActivities.get(i);
- r.waitingVisible = false;
- if (ActivityManagerService.DEBUG_SWITCH) Log.v(
- ActivityManagerService.TAG,
- "Was waiting for visible: " + r);
- }
- mStackSupervisor.mWaitingVisibleActivities.clear();
- mStackSupervisor.scheduleIdleLocked();
+ void windowsVisibleLocked() {
+ mStackSupervisor.reportActivityVisibleLocked(this);
+ if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "windowsVisibleLocked(): " + this);
+ if (!nowVisible) {
+ nowVisible = true;
+ lastVisibleTime = SystemClock.uptimeMillis();
+ if (!idle) {
+ // Instead of doing the full stop routine here, let's just hide any activities
+ // we now can, and let them stop when the normal idle happens.
+ mStackSupervisor.processStoppingActivitiesLocked(false);
+ } else {
+ // If this activity was already idle, then we now need to make sure we perform
+ // the full stop of any activities that are waiting to do so. This is because
+ // we won't do that while they are still waiting for this one to become visible.
+ final int size = mStackSupervisor.mWaitingVisibleActivities.size();
+ if (size > 0) {
+ for (int i = 0; i < size; i++) {
+ ActivityRecord r = mStackSupervisor.mWaitingVisibleActivities.get(i);
+ if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "Was waiting for visible: " + r);
}
+ mStackSupervisor.mWaitingVisibleActivities.clear();
+ mStackSupervisor.scheduleIdleLocked();
}
- service.scheduleAppGcsLocked();
}
+ service.scheduleAppGcsLocked();
}
}
- public void windowsGone() {
- if (ActivityManagerService.DEBUG_SWITCH) Log.v(
- ActivityManagerService.TAG, "windowsGone(): " + this);
- nowVisible = false;
- }
-
- private ActivityRecord getWaitingHistoryRecordLocked() {
- // First find the real culprit... if we are waiting
- // for another app to start, then we have paused dispatching
- // for this activity.
- ActivityRecord r = this;
- if (r.waitingVisible) {
+ ActivityRecord getWaitingHistoryRecordLocked() {
+ // First find the real culprit... if this activity is waiting for
+ // another activity to start or has stopped, then the key dispatching
+ // timeout should not be caused by this.
+ if (mStackSupervisor.mWaitingVisibleActivities.contains(this) || stopped) {
final ActivityStack stack = mStackSupervisor.getFocusedStack();
- // Hmmm, who might we be waiting for?
- r = stack.mResumedActivity;
+ // Try to use the one which is closest to top.
+ ActivityRecord r = stack.mResumedActivity;
if (r == null) {
r = stack.mPausingActivity;
}
- // Both of those null? Fall back to 'this' again
- if (r == null) {
- r = this;
+ if (r != null) {
+ return r;
}
}
-
- return r;
- }
-
- public boolean keyDispatchingTimedOut(String reason) {
- ActivityRecord r;
- ProcessRecord anrApp;
- synchronized(service) {
- r = getWaitingHistoryRecordLocked();
- anrApp = r != null ? r.app : null;
- }
- return service.inputDispatchingTimedOut(anrApp, r, this, false, reason);
- }
-
- /** Returns the key dispatching timeout for this application token. */
- public long getKeyDispatchingTimeout() {
- synchronized(service) {
- ActivityRecord r = getWaitingHistoryRecordLocked();
- return ActivityManagerService.getInputDispatchingTimeoutLocked(r);
- }
+ return this;
}
/**
@@ -1047,14 +1085,14 @@ final class ActivityRecord {
}
static void activityResumedLocked(IBinder token) {
- final ActivityRecord r = ActivityRecord.forToken(token);
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (DEBUG_SAVED_STATE) Slog.i(TAG, "Resumed activity; dropping state of: " + r);
r.icicle = null;
r.haveState = false;
}
static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) {
- final ActivityRecord r = ActivityRecord.forToken(token);
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
return INVALID_TASK_ID;
}
@@ -1067,11 +1105,8 @@ final class ActivityRecord {
}
static ActivityRecord isInStackLocked(IBinder token) {
- final ActivityRecord r = ActivityRecord.forToken(token);
- if (r != null) {
- return r.task.stack.isInStackLocked(token);
- }
- return null;
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ return (r != null) ? r.task.stack.isInStackLocked(r) : null;
}
static ActivityStack getStackLocked(IBinder token) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 7908da0..33f915f 100755..100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,19 +16,9 @@
package com.android.server.am;
-import static com.android.server.am.ActivityManagerService.TAG;
-import static com.android.server.am.ActivityManagerService.localLOGV;
-import static com.android.server.am.ActivityManagerService.DEBUG_CLEANUP;
-import static com.android.server.am.ActivityManagerService.DEBUG_CONFIGURATION;
-import static com.android.server.am.ActivityManagerService.DEBUG_PAUSE;
-import static com.android.server.am.ActivityManagerService.DEBUG_RESULTS;
-import static com.android.server.am.ActivityManagerService.DEBUG_STACK;
-import static com.android.server.am.ActivityManagerService.DEBUG_SWITCH;
-import static com.android.server.am.ActivityManagerService.DEBUG_TASKS;
-import static com.android.server.am.ActivityManagerService.DEBUG_TRANSITION;
-import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING;
-import static com.android.server.am.ActivityManagerService.DEBUG_VISBILITY;
-import static com.android.server.am.ActivityManagerService.VALIDATE_TOKENS;
+import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+
+import static com.android.server.am.ActivityManagerDebugConfig.*;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
@@ -97,6 +87,20 @@ import java.util.Objects;
*/
final class ActivityStack {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM;
+ private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
+ private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
+ private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
+ private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
+ private static final String TAG_STACK = TAG + POSTFIX_STACK;
+ private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
+ private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
+ private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION;
+ private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
+ private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
+
+ private static final boolean VALIDATE_TOKENS = false;
+
// Ticks during which we check progress while waiting for an app to launch.
static final int LAUNCH_TICK = 500;
@@ -130,8 +134,6 @@ final class ActivityStack {
// convertToTranslucent().
static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000;
- static final boolean SCREENSHOT_FORCE_565 = ActivityManager.isLowRamDeviceStatic();
-
enum ActivityState {
INITIALIZING,
RESUMED,
@@ -146,30 +148,31 @@ final class ActivityStack {
final ActivityManagerService mService;
final WindowManagerService mWindowManager;
+ private final RecentTasks mRecentTasks;
/**
* The back history of all previous (and possibly still
* running) activities. It contains #TaskRecord objects.
*/
- private ArrayList<TaskRecord> mTaskHistory = new ArrayList<TaskRecord>();
+ private ArrayList<TaskRecord> mTaskHistory = new ArrayList<>();
/**
* Used for validating app tokens with window manager.
*/
- final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<TaskGroup>();
+ final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<>();
/**
* List of running activities, sorted by recent usage.
* The first entry in the list is the least recently used.
* It contains HistoryRecord objects.
*/
- final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>();
+ final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>();
/**
* Animations that for the current transition have requested not to
* be considered for the transition animation.
*/
- final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<ActivityRecord>();
+ final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<>();
/**
* When we are in the process of pausing an activity, before starting the
@@ -219,6 +222,9 @@ final class ActivityStack {
*/
boolean mConfigWillChange;
+ // Whether or not this stack covers the entire screen; by default stacks are full screen
+ boolean mFullscreen = true;
+
long mLaunchStartTime = 0;
long mFullyDrawnStartTime = 0;
@@ -234,6 +240,11 @@ final class ActivityStack {
/** Run all ActivityStacks through this */
final ActivityStackSupervisor mStackSupervisor;
+ Configuration mOverrideConfig;
+ /** True if the stack was forced to full screen because {@link TaskRecord#mResizeable} is false
+ * and the stack was previously resized. */
+ private boolean mForcedFullscreen = false;
+
static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1;
static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2;
static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3;
@@ -255,9 +266,7 @@ final class ActivityStack {
final Handler mHandler;
final class ActivityStackHandler extends Handler {
- //public Handler() {
- // if (localLOGV) Slog.v(TAG, "Handler started!");
- //}
+
ActivityStackHandler(Looper looper) {
super(looper);
}
@@ -337,7 +346,12 @@ final class ActivityStack {
return count;
}
- ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer) {
+ int numTasks() {
+ return mTaskHistory.size();
+ }
+
+ ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
+ RecentTasks recentTasks) {
mActivityContainer = activityContainer;
mStackSupervisor = activityContainer.getOuter();
mService = mStackSupervisor.mService;
@@ -345,22 +359,13 @@ final class ActivityStack {
mWindowManager = mService.mWindowManager;
mStackId = activityContainer.mStackId;
mCurrentUser = mService.mCurrentUserId;
- }
-
- /**
- * Checks whether the userid is a profile of the current user.
- */
- private boolean isCurrentProfileLocked(int userId) {
- if (userId == mCurrentUser) return true;
- for (int i = 0; i < mService.mCurrentProfileIds.length; i++) {
- if (mService.mCurrentProfileIds[i] == userId) return true;
- }
- return false;
+ mRecentTasks = recentTasks;
+ mOverrideConfig = Configuration.EMPTY;
}
boolean okToShowLocked(ActivityRecord r) {
- return isCurrentProfileLocked(r.userId)
- || (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0;
+ return mStackSupervisor.isCurrentProfileLocked(r.userId)
+ || (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0;
}
final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
@@ -446,14 +451,20 @@ final class ActivityStack {
}
ActivityRecord isInStackLocked(IBinder token) {
- final ActivityRecord r = ActivityRecord.forToken(token);
- if (r != null) {
- final TaskRecord task = r.task;
- if (task != null && task.mActivities.contains(r) && mTaskHistory.contains(task)) {
- if (task.stack != this) Slog.w(TAG,
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ return isInStackLocked(r);
+ }
+
+ ActivityRecord isInStackLocked(ActivityRecord r) {
+ if (r == null) {
+ return null;
+ }
+ final TaskRecord task = r.task;
+ if (task != null && task.stack != null
+ && task.mActivities.contains(r) && mTaskHistory.contains(task)) {
+ if (task.stack != this) Slog.w(TAG,
"Illegal state! task does not point to stack it is in.");
- return r;
- }
+ return r;
}
return null;
}
@@ -475,11 +486,22 @@ final class ActivityStack {
final void moveToFront(String reason) {
if (isAttached()) {
+ final boolean homeStack = isHomeStack()
+ || (mActivityContainer.mParentActivity != null
+ && mActivityContainer.mParentActivity.isHomeActivity());
+ ActivityStack lastFocusStack = null;
+ if (!homeStack) {
+ // Need to move this stack to the front before calling
+ // {@link ActivityStackSupervisor#moveHomeStack} below.
+ lastFocusStack = mStacks.get(mStacks.size() - 1);
+ mStacks.remove(this);
+ mStacks.add(this);
+ }
+ // TODO(multi-display): Focus stack currently adjusted in call to move home stack.
+ // Needs to also work if focus is moving to the non-home display.
if (isOnHomeDisplay()) {
- mStackSupervisor.moveHomeStack(isHomeStack(), reason);
+ mStackSupervisor.moveHomeStack(homeStack, reason, lastFocusStack);
}
- mStacks.remove(this);
- mStacks.add(this);
final TaskRecord task = topTask();
if (task != null) {
mWindowManager.moveTaskToTop(task.taskId);
@@ -507,23 +529,23 @@ final class ActivityStack {
// If documentData is non-null then it must match the existing task data.
Uri documentData = isDocument ? intent.getData() : null;
- if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this);
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + target + " in " + this);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
if (task.voiceSession != null) {
// We never match voice sessions; those always run independently.
- if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": voice session");
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": voice session");
continue;
}
if (task.userId != userId) {
// Looking for a different task.
- if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": different user");
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": different user");
continue;
}
final ActivityRecord r = task.getTopActivity();
if (r == null || r.finishing || r.userId != userId ||
r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
- if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": mismatch root " + r);
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": mismatch root " + r);
continue;
}
@@ -542,34 +564,32 @@ final class ActivityStack {
taskDocumentData = null;
}
- if (DEBUG_TASKS) Slog.d(TAG, "Comparing existing cls="
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Comparing existing cls="
+ taskIntent.getComponent().flattenToShortString()
+ "/aff=" + r.task.rootAffinity + " to new cls="
+ intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity);
if (!isDocument && !taskIsDocument && task.rootAffinity != null) {
if (task.rootAffinity.equals(target.taskAffinity)) {
- if (DEBUG_TASKS) Slog.d(TAG, "Found matching affinity!");
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching affinity!");
return r;
}
} else if (taskIntent != null && taskIntent.getComponent() != null &&
taskIntent.getComponent().compareTo(cls) == 0 &&
Objects.equals(documentData, taskDocumentData)) {
- if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching class!");
//dump();
- if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
- + r.intent);
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS,
+ "For Intent " + intent + " bringing to top: " + r.intent);
return r;
} else if (affinityIntent != null && affinityIntent.getComponent() != null &&
affinityIntent.getComponent().compareTo(cls) == 0 &&
Objects.equals(documentData, taskDocumentData)) {
- if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching class!");
//dump();
- if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
- + r.intent);
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS,
+ "For Intent " + intent + " bringing to top: " + r.intent);
return r;
- } else if (DEBUG_TASKS) {
- Slog.d(TAG, "Not a match: " + task);
- }
+ } else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task);
}
return null;
@@ -588,13 +608,16 @@ final class ActivityStack {
final int userId = UserHandle.getUserId(info.applicationInfo.uid);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- TaskRecord task = mTaskHistory.get(taskNdx);
- if (!isCurrentProfileLocked(task.userId)) {
- return null;
- }
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ final boolean notCurrentUserTask =
+ !mStackSupervisor.isCurrentProfileLocked(task.userId);
final ArrayList<ActivityRecord> activities = task.mActivities;
+
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = activities.get(activityNdx);
+ if (notCurrentUserTask && (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) == 0) {
+ return null;
+ }
if (!r.finishing && r.intent.getComponent().equals(cls) && r.userId == userId) {
//Slog.i(TAG, "Found matching class!");
//dump();
@@ -619,9 +642,13 @@ final class ActivityStack {
// Move userId's tasks to the top.
int index = mTaskHistory.size();
for (int i = 0; i < index; ) {
- TaskRecord task = mTaskHistory.get(i);
- if (isCurrentProfileLocked(task.userId)) {
- if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() +
+ final TaskRecord task = mTaskHistory.get(i);
+
+ // NOTE: If {@link TaskRecord#topRunningActivityLocked} return is not null then it is
+ // okay to show the activity when locked.
+ if (mStackSupervisor.isCurrentProfileLocked(task.userId)
+ || task.topRunningActivityLocked(null) != null) {
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "switchUserLocked: stack=" + getStackId() +
" moving " + task + " to top");
mTaskHistory.remove(i);
mTaskHistory.add(task);
@@ -643,7 +670,7 @@ final class ActivityStack {
r.stopped = false;
mResumedActivity = r;
r.task.touchActiveTime();
- mService.addRecentTaskLocked(r.task);
+ mRecentTasks.addLocked(r.task);
completeResumeLocked(r);
mStackSupervisor.checkReadyForSleepLocked();
setLaunchTime(r);
@@ -708,14 +735,15 @@ final class ActivityStack {
boolean checkReadyForSleepLocked() {
if (mResumedActivity != null) {
// Still have something resumed; can't sleep until it is paused.
- if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause " + mResumedActivity);
- if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false");
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep needs to pause " + mResumedActivity);
+ if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
+ "Sleep => pause with userLeaving=false");
startPausingLocked(false, true, false, false);
return true;
}
if (mPausingActivity != null) {
// Still waiting for something to pause; can't sleep yet.
- if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still waiting to pause " + mPausingActivity);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep still waiting to pause " + mPausingActivity);
return true;
}
@@ -757,7 +785,7 @@ final class ActivityStack {
if (w > 0) {
if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tTaking screenshot");
return mWindowManager.screenshotApplications(who.appToken, Display.DEFAULT_DISPLAY,
- w, h, SCREENSHOT_FORCE_565);
+ w, h);
}
Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h);
return null;
@@ -780,8 +808,14 @@ final class ActivityStack {
final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, boolean resuming,
boolean dontWait) {
if (mPausingActivity != null) {
- Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity);
- completePauseLocked(false);
+ Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
+ + " state=" + mPausingActivity.state);
+ if (!mService.isSleeping()) {
+ // Avoid recursion among check for sleep and complete pause during sleeping.
+ // Because activity will be paused immediately after resume, just let pause
+ // be completed by the order of activity paused from clients.
+ completePauseLocked(false);
+ }
}
ActivityRecord prev = mResumedActivity;
if (prev == null) {
@@ -798,7 +832,7 @@ final class ActivityStack {
}
if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSING: " + prev);
- else if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev);
+ else if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Start pausing: " + prev);
mResumedActivity = null;
mPausingActivity = prev;
mLastPausedActivity = prev;
@@ -816,7 +850,7 @@ final class ActivityStack {
mService.updateCpuStats();
if (prev.app != null && prev.app.thread != null) {
- if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending pause: " + prev);
try {
EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY,
prev.userId, System.identityHashCode(prev),
@@ -839,7 +873,7 @@ final class ActivityStack {
// If we are not going to sleep, we want to ensure the device is
// awake until the next activity is started.
- if (!mService.isSleepingOrShuttingDown()) {
+ if (!uiSleeping && !mService.isSleepingOrShuttingDown()) {
mStackSupervisor.acquireLaunchWakelock();
}
@@ -850,8 +884,8 @@ final class ActivityStack {
// key dispatch; the same activity will pick it up again on wakeup.
if (!uiSleeping) {
prev.pauseKeyDispatchingLocked();
- } else {
- if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off");
+ } else if (DEBUG_PAUSE) {
+ Slog.v(TAG_PAUSE, "Key dispatch not paused for screen off");
}
if (dontWait) {
@@ -868,14 +902,14 @@ final class ActivityStack {
msg.obj = prev;
prev.pauseTime = SystemClock.uptimeMillis();
mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT);
- if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete...");
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete...");
return true;
}
} else {
// This activity failed to schedule the
// pause, so just treat it as being paused now.
- if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next.");
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Activity not running, resuming next.");
if (!resuming) {
mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null);
}
@@ -884,8 +918,8 @@ final class ActivityStack {
}
final void activityPausedLocked(IBinder token, boolean timeout) {
- if (DEBUG_PAUSE) Slog.v(
- TAG, "Activity paused: token=" + token + ", timeout=" + timeout);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE,
+ "Activity paused: token=" + token + ", timeout=" + timeout);
final ActivityRecord r = isInStackLocked(token);
if (r != null) {
@@ -899,6 +933,11 @@ final class ActivityStack {
r.userId, System.identityHashCode(r), r.shortComponentName,
mPausingActivity != null
? mPausingActivity.shortComponentName : "(none)");
+ if (r.finishing && r.state == ActivityState.PAUSING) {
+ if (DEBUG_PAUSE) Slog.v(TAG,
+ "Executing finish of failed to pause activity: " + r);
+ finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false);
+ }
}
}
}
@@ -946,20 +985,18 @@ final class ActivityStack {
private void completePauseLocked(boolean resumeNext) {
ActivityRecord prev = mPausingActivity;
- if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev);
if (prev != null) {
prev.state = ActivityState.PAUSED;
if (prev.finishing) {
- if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev);
prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);
} else if (prev.app != null) {
- if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev);
- if (prev.waitingVisible) {
- prev.waitingVisible = false;
- mStackSupervisor.mWaitingVisibleActivities.remove(prev);
- if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(
- TAG, "Complete pause, no longer waiting: " + prev);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending stop: " + prev);
+ if (mStackSupervisor.mWaitingVisibleActivities.remove(prev)) {
+ if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(TAG_PAUSE,
+ "Complete pause, no longer waiting: " + prev);
}
if (prev.configDestroy) {
// The previous is being paused because the configuration
@@ -967,7 +1004,7 @@ final class ActivityStack {
// To juggle the fact that we are also starting a new
// instance right now, we need to first completely stop
// the current instance before starting the new one.
- if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Destroying after pause: " + prev);
destroyActivityLocked(prev, true, "pause-config");
} else if (!hasVisibleBehindActivity()) {
// If we were visible then resumeTopActivities will release resources before
@@ -979,16 +1016,20 @@ final class ActivityStack {
// then give up on things going idle and start clearing
// them out. Or if r is the last of activity of the last task the stack
// will be empty and must be cleared immediately.
- if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcing idle");
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "To many pending stops, forcing idle");
mStackSupervisor.scheduleIdleLocked();
} else {
mStackSupervisor.checkReadyForSleepLocked();
}
}
} else {
- if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping: " + prev);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "App died during pause, not stopping: " + prev);
prev = null;
}
+ // It is possible the activity was freezing the screen before it was paused.
+ // In that case go ahead and remove the freeze this activity has on the screen
+ // since it is no longer visible.
+ prev.stopFreezingScreenLocked(true /*force*/);
mPausingActivity = null;
}
@@ -1083,7 +1124,7 @@ final class ActivityStack {
}
}
- private void setVisibile(ActivityRecord r, boolean visible) {
+ private void setVisible(ActivityRecord r, boolean visible) {
r.visible = visible;
mWindowManager.setAppVisibility(r.appToken, visible);
final ArrayList<ActivityContainer> containers = r.mChildContainers;
@@ -1116,7 +1157,8 @@ final class ActivityStack {
final int numStacks = mStacks.size();
while (stackNdx < numStacks) {
- tasks = mStacks.get(stackNdx).mTaskHistory;
+ ActivityStack historyStack = mStacks.get(stackNdx);
+ tasks = historyStack.mTaskHistory;
final int numTasks = tasks.size();
while (taskNdx < numTasks) {
activities = tasks.get(taskNdx).mActivities;
@@ -1124,7 +1166,7 @@ final class ActivityStack {
while (activityNdx < numActivities) {
final ActivityRecord activity = activities.get(activityNdx);
if (!activity.finishing) {
- return activity.fullscreen ? null : activity;
+ return historyStack.mFullscreen && activity.fullscreen ? null : activity;
}
++activityNdx;
}
@@ -1138,9 +1180,26 @@ final class ActivityStack {
return null;
}
+ private ActivityStack getNextVisibleStackLocked() {
+ ArrayList<ActivityStack> stacks = mStacks;
+ final ActivityRecord parent = mActivityContainer.mParentActivity;
+ if (parent != null) {
+ stacks = parent.task.stack.mStacks;
+ }
+ if (stacks != null) {
+ for (int i = stacks.size() - 1; i >= 0; --i) {
+ ActivityStack stack = stacks.get(i);
+ if (stack != this && stack.isStackVisibleLocked()) {
+ return stack;
+ }
+ }
+ }
+ return null;
+ }
+
// Checks if any of the stacks above this one has a fullscreen activity behind it.
// If so, this stack is hidden, otherwise it is visible.
- private boolean isStackVisible() {
+ private boolean isStackVisibleLocked() {
if (!isAttached()) {
return false;
}
@@ -1155,11 +1214,18 @@ final class ActivityStack {
* wallpaper to be shown behind it.
*/
for (int i = mStacks.indexOf(this) + 1; i < mStacks.size(); i++) {
- final ArrayList<TaskRecord> tasks = mStacks.get(i).getAllTasks();
- for (int taskNdx = 0; taskNdx < tasks.size(); taskNdx++) {
+ ActivityStack stack = mStacks.get(i);
+ // stack above isn't full screen, so, we assume we're still visible. at some point
+ // we should look at the stack bounds to see if we're occluded even if the stack
+ // isn't fullscreen
+ if (!stack.mFullscreen) {
+ continue;
+ }
+ final ArrayList<TaskRecord> tasks = stack.getAllTasks();
+ for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = tasks.get(taskNdx);
final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = 0; activityNdx < activities.size(); activityNdx++) {
+ for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
// Conditions for an activity to obscure the stack we're
@@ -1188,8 +1254,8 @@ final class ActivityStack {
if (top == null) {
return;
}
- if (DEBUG_VISBILITY) Slog.v(
- TAG, "ensureActivitiesVisible behind " + top
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "ensureActivitiesVisible behind " + top
+ " configChanges=0x" + Integer.toHexString(configChanges));
if (mTranslucentActivityWaiting != top) {
@@ -1205,7 +1271,8 @@ final class ActivityStack {
// If the top activity is not fullscreen, then we need to
// make sure any activities under it are now visible.
boolean aboveTop = true;
- boolean behindFullscreen = !isStackVisible();
+ boolean behindFullscreen = !isStackVisibleLocked();
+ boolean noStackActivityResumed = (isInStackLocked(starting) == null);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
@@ -1222,8 +1289,8 @@ final class ActivityStack {
// mLaunchingBehind: Activities launching behind are at the back of the task stack
// but must be drawn initially for the animation as though they were visible.
if (!behindFullscreen || r.mLaunchTaskBehind) {
- if (DEBUG_VISBILITY) Slog.v(
- TAG, "Make visible? " + r + " finishing=" + r.finishing
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Make visible? " + r + " finishing=" + r.finishing
+ " state=" + r.state);
// First: if this is not the current activity being started, make
@@ -1233,26 +1300,29 @@ final class ActivityStack {
}
if (r.app == null || r.app.thread == null) {
- // This activity needs to be visible, but isn't even
- // running... get it started, but don't resume it
- // at this point.
- if (DEBUG_VISBILITY) Slog.v(TAG, "Start and freeze screen for " + r);
+ // This activity needs to be visible, but isn't even running...
+ // get it started and resume if no other stack in this stack is resumed.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Start and freeze screen for " + r);
if (r != starting) {
r.startFreezingScreenLocked(r.app, configChanges);
}
if (!r.visible || r.mLaunchTaskBehind) {
- if (DEBUG_VISBILITY) Slog.v(
- TAG, "Starting and making visible: " + r);
- setVisibile(r, true);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Starting and making visible: " + r);
+ setVisible(r, true);
}
if (r != starting) {
- mStackSupervisor.startSpecificActivityLocked(r, false, false);
+ mStackSupervisor.startSpecificActivityLocked(
+ r, noStackActivityResumed, false);
+ noStackActivityResumed = false;
}
} else if (r.visible) {
// If this activity is already visible, then there is nothing
// else to do here.
- if (DEBUG_VISBILITY) Slog.v(TAG, "Skipping: already visible at " + r);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Skipping: already visible at " + r);
r.stopFreezingScreenLocked(false);
try {
if (r.returningOptions != null) {
@@ -1268,14 +1338,14 @@ final class ActivityStack {
if (r.state != ActivityState.RESUMED && r != starting) {
// If this activity is paused, tell it
// to now show its window.
- if (DEBUG_VISBILITY) Slog.v(
- TAG, "Making visible and scheduling visibility: " + r);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Making visible and scheduling visibility: " + r);
try {
if (mTranslucentActivityWaiting != null) {
r.updateOptionsLocked(r.returningOptions);
mUndrawnActivitiesBelowTopTranslucent.add(r);
}
- setVisibile(r, true);
+ setVisible(r, true);
r.sleeping = false;
r.app.pendingUiClean = true;
r.app.thread.scheduleWindowVisibility(r.appToken, true);
@@ -1294,29 +1364,28 @@ final class ActivityStack {
if (r.fullscreen) {
// At this point, nothing else needs to be shown
- if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r);
behindFullscreen = true;
} else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
- if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r);
behindFullscreen = true;
}
} else {
- if (DEBUG_VISBILITY) Slog.v(
- TAG, "Make invisible? " + r + " finishing=" + r.finishing
- + " state=" + r.state
- + " behindFullscreen=" + behindFullscreen);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Make invisible? " + r + " finishing=" + r.finishing
+ + " state=" + r.state + " behindFullscreen=" + behindFullscreen);
// Now for any activities that aren't visible to the user, make
// sure they no longer are keeping the screen frozen.
if (r.visible) {
- if (DEBUG_VISBILITY) Slog.v(TAG, "Making invisible: " + r);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r);
try {
- setVisibile(r, false);
+ setVisible(r, false);
switch (r.state) {
case STOPPING:
case STOPPED:
if (r.app != null && r.app.thread != null) {
- if (DEBUG_VISBILITY) Slog.v(
- TAG, "Scheduling invisibility: " + r);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Scheduling invisibility: " + r);
r.app.thread.scheduleWindowVisibility(r.appToken, false);
}
break;
@@ -1328,7 +1397,7 @@ final class ActivityStack {
// This case created for transitioning activities from
// translucent to opaque {@link Activity#convertToOpaque}.
if (getVisibleBehindActivity() == r) {
- releaseBackgroundResources();
+ releaseBackgroundResources(r);
} else {
if (!mStackSupervisor.mStoppingActivities.contains(r)) {
mStackSupervisor.mStoppingActivities.add(r);
@@ -1347,7 +1416,7 @@ final class ActivityStack {
+ r.intent.getComponent(), e);
}
} else {
- if (DEBUG_VISBILITY) Slog.v(TAG, "Already invisible: " + r);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
}
}
}
@@ -1360,7 +1429,7 @@ final class ActivityStack {
}
}
- void convertToTranslucent(ActivityRecord r) {
+ void convertActivityToTranslucent(ActivityRecord r) {
mTranslucentActivityWaiting = r;
mUndrawnActivitiesBelowTopTranslucent.clear();
mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT);
@@ -1417,7 +1486,8 @@ final class ActivityStack {
}
if (r.state == ActivityState.INITIALIZING && r.mStartingWindowShown) {
- if (DEBUG_VISBILITY) Slog.w(TAG, "Found orphaned starting window " + r);
+ if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY,
+ "Found orphaned starting window " + r);
r.mStartingWindowShown = false;
mWindowManager.removeAppStartingWindow(r.appToken);
}
@@ -1459,8 +1529,8 @@ final class ActivityStack {
return result;
}
- final boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
- if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen("");
+ private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
+ if (DEBUG_LOCKSCREEN) mService.logLockScreen("");
if (!mService.mBooting && !mService.mBooted) {
// Not ready yet!
@@ -1487,8 +1557,17 @@ final class ActivityStack {
final TaskRecord prevTask = prev != null ? prev.task : null;
if (next == null) {
- // There are no more activities! Let's just start up the
- // Launcher...
+ // There are no more activities!
+ final String reason = "noMoreActivities";
+ if (!mFullscreen) {
+ // Try to move focus to the next visible stack with a running activity if this
+ // stack is not covering the entire screen.
+ final ActivityStack stack = getNextVisibleStackLocked();
+ if (adjustFocusToNextVisibleStackLocked(stack, reason)) {
+ return mStackSupervisor.resumeTopActivitiesLocked(stack, prev, null);
+ }
+ }
+ // Let's just start up the Launcher...
ActivityOptions.abort(options);
if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home");
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
@@ -1496,7 +1575,7 @@ final class ActivityStack {
final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
return isOnHomeDisplay() &&
- mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, "noMoreActivities");
+ mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, reason);
}
next.delayedResume = false;
@@ -1525,10 +1604,11 @@ final class ActivityStack {
// Now the task above it has to return to the home task instead.
final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
mTaskHistory.get(taskNdx).setTaskToReturnTo(HOME_ACTIVITY_TYPE);
- } else {
- if (DEBUG_STATES && isOnHomeDisplay()) Slog.d(TAG,
+ } else if (!isOnHomeDisplay()) {
+ return false;
+ } else if (!isHomeStack()){
+ if (DEBUG_STATES) Slog.d(TAG,
"resumeTopActivityLocked: Launching home next");
- // Only resume home if on home display
final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
return isOnHomeDisplay() &&
@@ -1568,12 +1648,12 @@ final class ActivityStack {
next.sleeping = false;
mStackSupervisor.mWaitingVisibleActivities.remove(next);
- if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next);
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);
// If we are currently pausing an activity, then don't do anything
// until that is done.
if (!mStackSupervisor.allPausedActivitiesComplete()) {
- if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
"resumeTopActivityLocked: Skip resume: some activity pausing.");
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
return false;
@@ -1608,6 +1688,8 @@ final class ActivityStack {
}
}
+ mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid);
+
// We need to start pausing the current activity so the top one
// can be resumed...
boolean dontWaitForPause = (next.info.flags&ActivityInfo.FLAG_RESUME_WHILE_PAUSING) != 0;
@@ -1638,16 +1720,16 @@ final class ActivityStack {
if (DEBUG_STATES) Slog.d(TAG, "no-history finish of " + mLastNoHistoryActivity +
" on new resume");
requestFinishActivityLocked(mLastNoHistoryActivity.appToken, Activity.RESULT_CANCELED,
- null, "no-history", false);
+ null, "resume-no-history", false);
mLastNoHistoryActivity = null;
}
if (prev != null && prev != next) {
- if (!prev.waitingVisible && next != null && !next.nowVisible) {
- prev.waitingVisible = true;
+ if (!mStackSupervisor.mWaitingVisibleActivities.contains(prev)
+ && next != null && !next.nowVisible) {
mStackSupervisor.mWaitingVisibleActivities.add(prev);
- if (DEBUG_SWITCH) Slog.v(
- TAG, "Resuming top, waiting visible to hide: " + prev);
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
+ "Resuming top, waiting visible to hide: " + prev);
} else {
// The next activity is already visible, so hide the previous
// activity's windows right now so we can show the new one ASAP.
@@ -1659,15 +1741,16 @@ final class ActivityStack {
// new one is found to be full-screen or not.
if (prev.finishing) {
mWindowManager.setAppVisibility(prev.appToken, false);
- if (DEBUG_SWITCH) Slog.v(TAG, "Not waiting for visible to hide: "
- + prev + ", waitingVisible="
- + (prev != null ? prev.waitingVisible : null)
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
+ "Not waiting for visible to hide: " + prev + ", waitingVisible="
+ + mStackSupervisor.mWaitingVisibleActivities.contains(prev)
+ ", nowVisible=" + next.nowVisible);
} else {
- if (DEBUG_SWITCH) Slog.v(TAG, "Previous already visible but still waiting to hide: "
- + prev + ", waitingVisible="
- + (prev != null ? prev.waitingVisible : null)
- + ", nowVisible=" + next.nowVisible);
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
+ "Previous already visible but still waiting to hide: " + prev
+ + ", waitingVisible="
+ + mStackSupervisor.mWaitingVisibleActivities.contains(prev)
+ + ", nowVisible=" + next.nowVisible);
}
}
}
@@ -1689,7 +1772,7 @@ final class ActivityStack {
boolean anim = true;
if (prev != null) {
if (prev.finishing) {
- if (DEBUG_TRANSITION) Slog.v(TAG,
+ if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
"Prepare close transition: prev=" + prev);
if (mNoAnimActivities.contains(prev)) {
anim = false;
@@ -1702,7 +1785,8 @@ final class ActivityStack {
mWindowManager.setAppWillBeHidden(prev.appToken);
mWindowManager.setAppVisibility(prev.appToken, false);
} else {
- if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: prev=" + prev);
+ if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
+ "Prepare open transition: prev=" + prev);
if (mNoAnimActivities.contains(next)) {
anim = false;
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
@@ -1719,7 +1803,7 @@ final class ActivityStack {
mWindowManager.setAppVisibility(prev.appToken, false);
}
} else {
- if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: no previous");
+ if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
if (mNoAnimActivities.contains(next)) {
anim = false;
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
@@ -1741,7 +1825,7 @@ final class ActivityStack {
ActivityStack lastStack = mStackSupervisor.getLastStack();
if (next.app != null && next.app.thread != null) {
- if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next);
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next);
// This activity is now becoming visible.
mWindowManager.setAppVisibility(next.appToken, true);
@@ -1759,7 +1843,7 @@ final class ActivityStack {
next.state = ActivityState.RESUMED;
mResumedActivity = next;
next.task.touchActiveTime();
- mService.addRecentTaskLocked(next.task);
+ mRecentTasks.addLocked(next.task);
mService.updateLruProcessLocked(next.app, true, null);
updateLRUListLocked(next);
mService.updateOomAdjLocked();
@@ -1806,9 +1890,8 @@ final class ActivityStack {
if (a != null) {
final int N = a.size();
if (!next.finishing && N > 0) {
- if (DEBUG_RESULTS) Slog.v(
- TAG, "Delivering results to " + next
- + ": " + a);
+ if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
+ "Delivering results to " + next + ": " + a);
next.app.thread.scheduleSendResult(next.appToken, a);
}
}
@@ -1823,7 +1906,7 @@ final class ActivityStack {
next.sleeping = false;
mService.showAskCompatModeDialogLocked(next);
next.app.pendingUiClean = true;
- next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+ next.app.forceProcessStateUpTo(mService.mTopProcessState);
next.clearOptionsLocked();
next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
mService.isNextTransitionForward(), resumeAnimOptions);
@@ -1885,7 +1968,7 @@ final class ActivityStack {
next.labelRes, next.icon, next.logo, next.windowFlags,
null, true);
}
- if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next);
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
}
if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Restarting " + next);
mStackSupervisor.startSpecificActivityLocked(next, true, true);
@@ -1895,9 +1978,31 @@ final class ActivityStack {
return true;
}
- private void insertTaskAtTop(TaskRecord task) {
+ private TaskRecord getNextTask(TaskRecord targetTask) {
+ final int index = mTaskHistory.indexOf(targetTask);
+ if (index >= 0) {
+ final int numTasks = mTaskHistory.size();
+ for (int i = index + 1; i < numTasks; ++i) {
+ TaskRecord task = mTaskHistory.get(i);
+ if (task.userId == targetTask.userId) {
+ return task;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void insertTaskAtTop(TaskRecord task, ActivityRecord newActivity) {
+ // If the moving task is over home stack, transfer its return type to next task
+ if (task.isOverHomeStack()) {
+ final TaskRecord nextTask = getNextTask(task);
+ if (nextTask != null) {
+ nextTask.setTaskToReturnTo(task.getTaskToReturnTo());
+ }
+ }
+
// If this is being moved to the top by another activity or being launched from the home
- // activity, set mOnTopOfHome accordingly.
+ // activity, set mTaskToReturnTo accordingly.
if (isOnHomeDisplay()) {
ActivityStack lastStack = mStackSupervisor.getLastStack();
final boolean fromHome = lastStack.isHomeStack();
@@ -1915,10 +2020,15 @@ final class ActivityStack {
mTaskHistory.remove(task);
// Now put task at top.
int taskNdx = mTaskHistory.size();
- if (!isCurrentProfileLocked(task.userId)) {
+ final boolean notShownWhenLocked =
+ (newActivity != null && (newActivity.info.flags & FLAG_SHOW_FOR_ALL_USERS) == 0)
+ || (newActivity == null && task.topRunningActivityLocked(null) == null);
+ if (!mStackSupervisor.isCurrentProfileLocked(task.userId) && notShownWhenLocked) {
// Put non-current user tasks below current user tasks.
while (--taskNdx >= 0) {
- if (!isCurrentProfileLocked(mTaskHistory.get(taskNdx).userId)) {
+ final TaskRecord tmpTask = mTaskHistory.get(taskNdx);
+ if (!mStackSupervisor.isCurrentProfileLocked(tmpTask.userId)
+ || tmpTask.topRunningActivityLocked(null) == null) {
break;
}
}
@@ -1937,7 +2047,7 @@ final class ActivityStack {
// Last activity in task had been removed or ActivityManagerService is reusing task.
// Insert or replace.
// Might not even be in.
- insertTaskAtTop(rTask);
+ insertTaskAtTop(rTask, r);
mWindowManager.moveTaskToTop(taskId);
}
TaskRecord task = null;
@@ -1961,7 +2071,7 @@ final class ActivityStack {
r.putInHistory();
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
+ (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0,
r.userId, r.info.configChanges, task.voiceSession != null,
r.mLaunchTaskBehind);
if (VALIDATE_TOKENS) {
@@ -1985,7 +2095,7 @@ final class ActivityStack {
// activity
if (task == r.task && mTaskHistory.indexOf(task) != (mTaskHistory.size() - 1)) {
mStackSupervisor.mUserLeaving = false;
- if (DEBUG_USER_LEAVING) Slog.v(TAG,
+ if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
"startActivity() behind front, mUserLeaving=false");
}
@@ -2010,9 +2120,9 @@ final class ActivityStack {
if (proc == null || proc.thread == null) {
showStartingIcon = true;
}
- if (DEBUG_TRANSITION) Slog.v(TAG,
+ if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
"Prepare open transition: starting " + r);
- if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+ if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition);
mNoAnimActivities.add(r);
} else {
@@ -2025,7 +2135,7 @@ final class ActivityStack {
}
mWindowManager.addAppToken(task.mActivities.indexOf(r),
r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
+ (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
boolean doShow = true;
if (newTask) {
@@ -2077,7 +2187,7 @@ final class ActivityStack {
// because there is nothing for it to animate on top of.
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
+ (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
ActivityOptions.abort(options);
options = null;
@@ -2182,18 +2292,18 @@ final class ActivityStack {
// same task affinity as the one we are moving,
// then merge it into the same task.
targetTask = bottom.task;
- if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target
+ " out to bottom task " + bottom.task);
} else {
targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
null, null, null, false);
targetTask.affinityIntent = target.intent;
- if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target
+ " out to new task " + target.task);
}
final int targetTaskId = targetTask.taskId;
- mWindowManager.setAppGroupId(target.appToken, targetTaskId);
+ mWindowManager.setAppTask(target.appToken, targetTaskId);
boolean noOptions = canMoveOptions;
final int start = replyChainEnd < 0 ? i : replyChainEnd;
@@ -2213,12 +2323,12 @@ final class ActivityStack {
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing activity " + p + " from task="
+ task + " adding to task=" + targetTask
+ " Callers=" + Debug.getCallers(4));
- if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p
- + " out to target's task " + target.task);
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS,
+ "Pushing next activity " + p + " out to target's task " + target.task);
p.setTask(targetTask, null);
targetTask.addActivityAtBottom(p);
- mWindowManager.setAppGroupId(p.appToken, targetTaskId);
+ mWindowManager.setAppTask(p.appToken, targetTaskId);
}
mWindowManager.moveTaskToBottom(targetTaskId);
@@ -2237,7 +2347,7 @@ final class ActivityStack {
// In this case, we want to finish this activity
// and everything above it, so be sneaky and pretend
// like these are all in the reply chain.
- end = numActivities - 1;
+ end = activities.size() - 1;
} else if (replyChainEnd < 0) {
end = i;
} else {
@@ -2256,9 +2366,10 @@ final class ActivityStack {
noOptions = false;
}
}
- if (DEBUG_TASKS) Slog.w(TAG,
+ if (DEBUG_TASKS) Slog.w(TAG_TASKS,
"resetTaskIntendedTask: calling finishActivity on " + p);
- if (finishActivityLocked(p, Activity.RESULT_CANCELED, null, "reset", false)) {
+ if (finishActivityLocked(
+ p, Activity.RESULT_CANCELED, null, "reset-task", false)) {
end--;
srcPos--;
}
@@ -2329,13 +2440,15 @@ final class ActivityStack {
// in a task that is not currently on top.)
if (forceReset || finishOnTaskLaunch) {
final int start = replyChainEnd >= 0 ? replyChainEnd : i;
- if (DEBUG_TASKS) Slog.v(TAG, "Finishing task at index " + start + " to " + i);
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS,
+ "Finishing task at index " + start + " to " + i);
for (int srcPos = start; srcPos >= i; --srcPos) {
final ActivityRecord p = activities.get(srcPos);
if (p.finishing) {
continue;
}
- finishActivityLocked(p, Activity.RESULT_CANCELED, null, "reset", false);
+ finishActivityLocked(
+ p, Activity.RESULT_CANCELED, null, "move-affinity", false);
}
} else {
if (taskInsertionPoint < 0) {
@@ -2344,8 +2457,9 @@ final class ActivityStack {
}
final int start = replyChainEnd >= 0 ? replyChainEnd : i;
- if (DEBUG_TASKS) Slog.v(TAG, "Reparenting from task=" + affinityTask + ":"
- + start + "-" + i + " to task=" + task + ":" + taskInsertionPoint);
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS,
+ "Reparenting from task=" + affinityTask + ":" + start + "-" + i
+ + " to task=" + task + ":" + taskInsertionPoint);
for (int srcPos = start; srcPos >= i; --srcPos) {
final ActivityRecord p = activities.get(srcPos);
p.setTask(task, null);
@@ -2354,9 +2468,9 @@ final class ActivityStack {
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + p
+ " to stack at " + task,
new RuntimeException("here").fillInStackTrace());
- if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p + " from " + srcPos
- + " in to resetting task " + task);
- mWindowManager.setAppGroupId(p.appToken, taskId);
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Pulling activity " + p
+ + " from " + srcPos + " in to resetting task " + task);
+ mWindowManager.setAppTask(p.appToken, taskId);
}
mWindowManager.moveTaskToTop(taskId);
if (VALIDATE_TOKENS) {
@@ -2422,9 +2536,11 @@ final class ActivityStack {
}
int taskNdx = mTaskHistory.indexOf(task);
- do {
- taskTop = mTaskHistory.get(taskNdx--).getTopActivity();
- } while (taskTop == null && taskNdx >= 0);
+ if (taskNdx >= 0) {
+ do {
+ taskTop = mTaskHistory.get(taskNdx--).getTopActivity();
+ } while (taskTop == null && taskNdx >= 0);
+ }
if (topOptions != null) {
// If we got some ActivityOptions from an activity on top that
@@ -2468,22 +2584,50 @@ final class ActivityStack {
private void adjustFocusedActivityLocked(ActivityRecord r, String reason) {
if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) {
ActivityRecord next = topRunningActivityLocked(null);
+ final String myReason = reason + " adjustFocus";
if (next != r) {
final TaskRecord task = r.task;
if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) {
- mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(),
- reason + " adjustFocus");
+ // For non-fullscreen stack, we want to move the focus to the next visible
+ // stack to prevent the home screen from moving to the top and obscuring
+ // other visible stacks.
+ if (!mFullscreen
+ && adjustFocusToNextVisibleStackLocked(null, myReason)) {
+ return;
+ }
+ // Move the home stack to the top if this stack is fullscreen or there is no
+ // other visible stack.
+ if (mStackSupervisor.moveHomeStackTaskToTop(
+ task.getTaskToReturnTo(), myReason)) {
+ // Activity focus was already adjusted. Nothing else to do...
+ return;
+ }
}
}
- ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
+
+ final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
if (top != null) {
- mService.setFocusedActivityLocked(top, reason + " adjustTopFocus");
+ mService.setFocusedActivityLocked(top, myReason);
}
}
}
+ private boolean adjustFocusToNextVisibleStackLocked(ActivityStack inStack, String reason) {
+ final ActivityStack stack = (inStack != null) ? inStack : getNextVisibleStackLocked();
+ final String myReason = reason + " adjustFocusToNextVisibleStack";
+ if (stack == null) {
+ return false;
+ }
+ final ActivityRecord top = stack.topRunningActivityLocked(null);
+ if (top == null) {
+ return false;
+ }
+ mService.setFocusedActivityLocked(top, myReason);
+ return true;
+ }
+
final void stopActivityLocked(ActivityRecord r) {
- if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r);
+ if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + r);
if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
|| (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) {
if (!r.finishing) {
@@ -2492,7 +2636,7 @@ final class ActivityStack {
Slog.d(TAG, "no-history finish of " + r);
}
requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
- "no-history", false);
+ "stop-no-history", false);
} else {
if (DEBUG_STATES) Slog.d(TAG, "Not finishing noHistory " + r
+ " on stop because we're just sleeping");
@@ -2508,8 +2652,8 @@ final class ActivityStack {
if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r
+ " (stop requested)");
r.state = ActivityState.STOPPING;
- if (DEBUG_VISBILITY) Slog.v(
- TAG, "Stopping visible=" + r.visible + " for " + r);
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Stopping visible=" + r.visible + " for " + r);
if (!r.visible) {
mWindowManager.setAppVisibility(r.appToken, false);
}
@@ -2571,16 +2715,16 @@ final class ActivityStack {
mService.updateOomAdjLocked();
}
- final void finishTopRunningActivityLocked(ProcessRecord app) {
+ final void finishTopRunningActivityLocked(ProcessRecord app, String reason) {
ActivityRecord r = topRunningActivityLocked(null);
if (r != null && r.app == app) {
// If the top running activity is from this crashing
// process, then terminate it to avoid getting in a loop.
- Slog.w(TAG, " Force finishing activity 1 "
+ Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
int taskNdx = mTaskHistory.indexOf(r.task);
int activityNdx = r.task.mActivities.indexOf(r);
- finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false);
+ finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false);
// Also terminate any activities below it that aren't yet
// stopped, to avoid a situation where one will get
// re-start our crashing activity once it gets resumed again.
@@ -2600,9 +2744,9 @@ final class ActivityStack {
|| r.state == ActivityState.PAUSING
|| r.state == ActivityState.PAUSED) {
if (!r.isHomeActivity() || mService.mHomeProcess != r.app) {
- Slog.w(TAG, " Force finishing activity 2 "
+ Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
- finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false);
+ finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false);
}
}
}
@@ -2646,7 +2790,7 @@ final class ActivityStack {
// send the result
ActivityRecord resultTo = r.resultTo;
if (resultTo != null) {
- if (DEBUG_RESULTS) Slog.v(TAG, "Adding result to " + resultTo
+ if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "Adding result to " + resultTo
+ " who=" + r.resultWho + " req=" + r.requestCode
+ " res=" + resultCode + " data=" + resultData);
if (resultTo.userId != r.userId) {
@@ -2663,7 +2807,7 @@ final class ActivityStack {
resultData);
r.resultTo = null;
}
- else if (DEBUG_RESULTS) Slog.v(TAG, "No result destination from " + r);
+ else if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "No result destination from " + r);
// Make sure this HistoryRecord is not holding on to other resources,
// because clients have remote IPC references to this object so we
@@ -2685,7 +2829,7 @@ final class ActivityStack {
return false;
}
- r.makeFinishing();
+ r.makeFinishingLocked();
final TaskRecord task = r.task;
EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
r.userId, System.identityHashCode(r),
@@ -2711,7 +2855,7 @@ final class ActivityStack {
if (mResumedActivity == r) {
boolean endTask = index <= 0;
- if (DEBUG_VISBILITY || DEBUG_TRANSITION) Slog.v(TAG,
+ if (DEBUG_VISIBILITY || DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
"Prepare close transition: finishing " + r);
mWindowManager.prepareAppTransition(endTask
? AppTransition.TRANSIT_TASK_CLOSE
@@ -2721,21 +2865,22 @@ final class ActivityStack {
mWindowManager.setAppVisibility(r.appToken, false);
if (mPausingActivity == null) {
- if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r);
- if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false");
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish needs to pause: " + r);
+ if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
+ "finish() => pause with userLeaving=false");
startPausingLocked(false, false, false, false);
}
if (endTask) {
- mStackSupervisor.endLockTaskModeIfTaskEnding(task);
+ mStackSupervisor.removeLockedTaskLocked(task);
}
} else if (r.state != ActivityState.PAUSING) {
// If the activity is PAUSING, we will complete the finish once
// it is done pausing; else we can just directly finish it here.
- if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + r);
return finishCurrentActivityLocked(r, FINISH_AFTER_PAUSE, oomAdj) == null;
} else {
- if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r);
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + r);
}
return false;
@@ -2788,12 +2933,12 @@ final class ActivityStack {
|| prevState == ActivityState.INITIALIZING) {
// If this activity is already stopped, we can just finish
// it right now.
- r.makeFinishing();
+ r.makeFinishingLocked();
boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");
if (activityRemoved) {
mStackSupervisor.resumeTopActivitiesLocked();
}
- if (DEBUG_CONTAINERS) Slog.d(TAG,
+ if (DEBUG_CONTAINERS) Slog.d(TAG,
"destroyActivityLocked: finishCurrentActivityLocked r=" + r +
" destroy returned removed=" + activityRemoved);
return activityRemoved ? null : r;
@@ -2801,7 +2946,7 @@ final class ActivityStack {
// Need to go through the full pause cycle to get this
// activity into the stopped state and then finish it.
- if (localLOGV) Slog.v(TAG, "Enqueueing pending finish: " + r);
+ if (DEBUG_ALL) Slog.v(TAG, "Enqueueing pending finish: " + r);
mStackSupervisor.mFinishingActivities.add(r);
r.resumeKeyDispatchingLocked();
mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null);
@@ -2864,9 +3009,8 @@ final class ActivityStack {
return false;
}
- final boolean navigateUpToLocked(IBinder token, Intent destIntent, int resultCode,
+ final boolean navigateUpToLocked(ActivityRecord srec, Intent destIntent, int resultCode,
Intent resultData) {
- final ActivityRecord srec = ActivityRecord.forToken(token);
final TaskRecord task = srec.task;
final ArrayList<ActivityRecord> activities = task.mActivities;
final int start = activities.indexOf(srec);
@@ -2939,7 +3083,7 @@ final class ActivityStack {
foundParentInTask = false;
}
requestFinishActivityLocked(parent.appToken, resultCode,
- resultData, "navigate-up", true);
+ resultData, "navigate-top", true);
}
}
Binder.restoreCallingIdentity(origId);
@@ -2951,6 +3095,8 @@ final class ActivityStack {
* representation) and cleaning things up as a result of its hosting
* processing going away, in which case there is no remaining client-side
* state to destroy so only the cleanup here is needed.
+ *
+ * Note: Call before #removeActivityFromHistoryLocked.
*/
final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices,
boolean setState) {
@@ -3011,7 +3157,7 @@ final class ActivityStack {
private void removeActivityFromHistoryLocked(ActivityRecord r, String reason) {
mStackSupervisor.removeChildActivityContainers(r);
finishActivityResultsLocked(r, Activity.RESULT_CANCELED, null);
- r.makeFinishing();
+ r.makeFinishingLocked();
if (DEBUG_ADD_REMOVE) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
@@ -3029,7 +3175,7 @@ final class ActivityStack {
}
final TaskRecord task = r.task;
if (task != null && task.removeActivity(r)) {
- if (DEBUG_STACK) Slog.i(TAG,
+ if (DEBUG_STACK) Slog.i(TAG_STACK,
"removeActivityFromHistoryLocked: last activity removed from " + this);
if (mStackSupervisor.isFrontStack(this) && task == topTask() &&
task.isOverHomeStack()) {
@@ -3082,7 +3228,7 @@ final class ActivityStack {
continue;
}
if (r.isDestroyable()) {
- if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Destroying " + r + " in state " + r.state
+ " resumed=" + mResumedActivity
+ " pausing=" + mPausingActivity + " for reason " + reason);
if (destroyActivityLocked(r, true, reason)) {
@@ -3098,8 +3244,8 @@ final class ActivityStack {
final boolean safelyDestroyActivityLocked(ActivityRecord r, String reason) {
if (r.isDestroyable()) {
- if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state
- + " resumed=" + mResumedActivity
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
+ "Destroying " + r + " in state " + r.state + " resumed=" + mResumedActivity
+ " pausing=" + mPausingActivity + " for reason " + reason);
return destroyActivityLocked(r, true, reason);
}
@@ -3157,9 +3303,9 @@ final class ActivityStack {
* but then create a new client-side object for this same HistoryRecord.
*/
final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) {
- if (DEBUG_SWITCH || DEBUG_CLEANUP) Slog.v(
- TAG, "Removing activity from " + reason + ": token=" + r
- + ", app=" + (r.app != null ? r.app.processName : "(null)"));
+ if (DEBUG_SWITCH || DEBUG_CLEANUP) Slog.v(TAG_SWITCH,
+ "Removing activity from " + reason + ": token=" + r
+ + ", app=" + (r.app != null ? r.app.processName : "(null)"));
EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY,
r.userId, System.identityHashCode(r),
r.task.taskId, r.shortComponentName, reason);
@@ -3191,7 +3337,7 @@ final class ActivityStack {
boolean skipDestroy = false;
try {
- if (DEBUG_SWITCH) Slog.i(TAG, "Destroying: " + r);
+ if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r);
r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
r.configChangeFlags);
} catch (Exception e) {
@@ -3252,13 +3398,13 @@ final class ActivityStack {
final void activityDestroyedLocked(IBinder token, String reason) {
final long origId = Binder.clearCallingIdentity();
try {
- ActivityRecord r = ActivityRecord.forToken(token);
+ ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r != null) {
mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
}
if (DEBUG_CONTAINERS) Slog.d(TAG, "activityDestroyedLocked: r=" + r);
- if (isInStackLocked(token) != null) {
+ if (isInStackLocked(r) != null) {
if (r.state == ActivityState.DESTROYING) {
cleanUpActivityLocked(r, true, false);
removeActivityFromHistoryLocked(r, reason);
@@ -3270,10 +3416,9 @@ final class ActivityStack {
}
}
- void releaseBackgroundResources() {
+ void releaseBackgroundResources(ActivityRecord r) {
if (hasVisibleBehindActivity() &&
!mHandler.hasMessages(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG)) {
- final ActivityRecord r = getVisibleBehindActivity();
if (r == topRunningActivityLocked(null)) {
// Don't release the top activity if it has requested to run behind the next
// activity.
@@ -3323,15 +3468,14 @@ final class ActivityStack {
private void removeHistoryRecordsForAppLocked(ArrayList<ActivityRecord> list,
ProcessRecord app, String listName) {
int i = list.size();
- if (DEBUG_CLEANUP) Slog.v(
- TAG, "Removing app " + app + " from list " + listName
- + " with " + i + " entries");
+ if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
+ "Removing app " + app + " from list " + listName + " with " + i + " entries");
while (i > 0) {
i--;
ActivityRecord r = list.get(i);
- if (DEBUG_CLEANUP) Slog.v(TAG, "Record #" + i + " " + r);
+ if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "Record #" + i + " " + r);
if (r.app == app) {
- if (DEBUG_CLEANUP) Slog.v(TAG, "---> REMOVING this entry!");
+ if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "---> REMOVING this entry!");
list.remove(i);
removeTimeoutsForActivityLocked(r);
}
@@ -3353,17 +3497,20 @@ final class ActivityStack {
// Clean out the history list.
int i = numActivities();
- if (DEBUG_CLEANUP) Slog.v(
- TAG, "Removing app " + app + " from history with " + i + " entries");
+ if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
+ "Removing app " + app + " from history with " + i + " entries");
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
--i;
- if (DEBUG_CLEANUP) Slog.v(
- TAG, "Record #" + i + " " + r + ": app=" + r.app);
+ if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
+ "Record #" + i + " " + r + ": app=" + r.app);
if (r.app == app) {
- boolean remove;
+ if (r.visible) {
+ hasVisibleActivities = true;
+ }
+ final boolean remove;
if ((!r.haveState && !r.stateNotNeeded) || r.finishing) {
// Don't currently have state for the activity, or
// it is finishing -- always remove it.
@@ -3397,16 +3544,11 @@ final class ActivityStack {
mService.updateUsageStats(r, false);
}
}
- removeActivityFromHistoryLocked(r, "appDied");
-
} else {
// We have the current state for this activity, so
// it can be restarted later when needed.
- if (localLOGV) Slog.v(
+ if (DEBUG_ALL) Slog.v(
TAG, "Keeping entry, setting app to null");
- if (r.visible) {
- hasVisibleActivities = true;
- }
if (DEBUG_APP) Slog.v(TAG, "Clearing app during removeHistory for activity "
+ r);
r.app = null;
@@ -3417,8 +3559,10 @@ final class ActivityStack {
r.icicle = null;
}
}
-
cleanUpActivityLocked(r, true, true);
+ if (remove) {
+ removeActivityFromHistoryLocked(r, "appDied");
+ }
}
}
}
@@ -3455,27 +3599,25 @@ final class ActivityStack {
for (int taskNdx = top; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
if (task.taskType == homeStackTaskType) {
- if (DEBUG_TASKS || DEBUG_STACK)
- Slog.d(TAG, "moveHomeStackTaskToTop: moving " + task);
+ if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG_STACK,
+ "moveHomeStackTaskToTop: moving " + task);
mTaskHistory.remove(taskNdx);
mTaskHistory.add(top, task);
updateTaskMovement(task, true);
- mWindowManager.moveTaskToTop(task.taskId);
return;
}
}
}
- final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord source, Bundle options,
+ final void moveTaskToFrontLocked(TaskRecord tr, boolean noAnimation, Bundle options,
String reason) {
- if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr);
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
final int numTasks = mTaskHistory.size();
final int index = mTaskHistory.indexOf(tr);
if (numTasks == 0 || index < 0) {
// nothing to do!
- if (source != null &&
- (source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+ if (noAnimation) {
ActivityOptions.abort(options);
} else {
updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options);
@@ -3485,14 +3627,15 @@ final class ActivityStack {
// Shift all activities with this task up to the top
// of the stack, keeping them in the same internal order.
- insertTaskAtTop(tr);
- moveToFront(reason);
+ insertTaskAtTop(tr, null);
- if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to front transition: task=" + tr);
- if (source != null &&
- (source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+ // Set focus to the top running activity of this stack.
+ ActivityRecord r = topRunningActivityLocked(null);
+ mService.setFocusedActivityLocked(r, reason);
+
+ if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr);
+ if (noAnimation) {
mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
- ActivityRecord r = topRunningActivityLocked(null);
if (r != null) {
mNoAnimActivities.add(r);
}
@@ -3528,8 +3671,7 @@ final class ActivityStack {
}
Slog.i(TAG, "moveTaskToBack: " + tr);
-
- mStackSupervisor.endLockTaskModeIfTaskEnding(tr);
+ mStackSupervisor.removeLockedTaskLocked(tr);
// If we have a watcher, preflight the move before committing to it. First check
// for *other* available tasks, but if none are available, then try again allowing the
@@ -3554,9 +3696,17 @@ final class ActivityStack {
}
}
- if (DEBUG_TRANSITION) Slog.v(TAG,
- "Prepare to back transition: task=" + taskId);
+ if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId);
+ boolean prevIsHome = false;
+ if (tr.isOverHomeStack()) {
+ final TaskRecord nextTask = getNextTask(tr);
+ if (nextTask != null) {
+ nextTask.setTaskToReturnTo(tr.getTaskToReturnTo());
+ } else {
+ prevIsHome = true;
+ }
+ }
mTaskHistory.remove(tr);
mTaskHistory.add(0, tr);
updateTaskMovement(tr, false);
@@ -3583,7 +3733,8 @@ final class ActivityStack {
}
final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
- if (task == tr && tr.isOverHomeStack() || numTasks <= 1 && isOnHomeDisplay()) {
+ if (prevIsHome || task == tr && tr.isOverHomeStack()
+ || numTasks <= 1 && isOnHomeDisplay()) {
if (!mService.mBooting && !mService.mBooted) {
// Not ready yet!
return false;
@@ -3618,26 +3769,46 @@ final class ActivityStack {
final boolean ensureActivityConfigurationLocked(ActivityRecord r,
int globalChanges) {
if (mConfigWillChange) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Skipping config check (will change): " + r);
return true;
}
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Ensuring correct configuration: " + r);
+ // Make sure the current stack override configuration is supported by the top task
+ // before continuing.
+ final TaskRecord topTask = topTask();
+ if (topTask != null && ((topTask.mResizeable && mForcedFullscreen)
+ || (!topTask.mResizeable && !mFullscreen))) {
+ final boolean prevFullscreen = mFullscreen;
+ final Configuration newOverrideConfig =
+ mWindowManager.forceStackToFullscreen(mStackId, !topTask.mResizeable);
+ updateOverrideConfiguration(newOverrideConfig);
+ mForcedFullscreen = !prevFullscreen && mFullscreen;
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Updated stack config to support task=" + topTask
+ + " resizeable=" + topTask.mResizeable
+ + " mForcedFullscreen=" + mForcedFullscreen
+ + " prevFullscreen=" + prevFullscreen
+ + " mFullscreen=" + mFullscreen);
+ }
+
// Short circuit: if the two configurations are the exact same
// object (the common case), then there is nothing to do.
Configuration newConfig = mService.mConfiguration;
- if (r.configuration == newConfig && !r.forceNewConfig) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (r.configuration == newConfig
+ && r.stackConfigOverride == mOverrideConfig
+ && !r.forceNewConfig) {
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration unchanged in " + r);
return true;
}
// We don't worry about activities that are finishing.
if (r.finishing) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration doesn't matter in finishing " + r);
r.stopFreezingScreenLocked(false);
return true;
@@ -3645,16 +3816,33 @@ final class ActivityStack {
// Okay we now are going to make this activity have the new config.
// But then we need to figure out how it needs to deal with that.
- Configuration oldConfig = r.configuration;
+ final Configuration oldConfig = r.configuration;
+ final Configuration oldStackOverride = r.stackConfigOverride;
r.configuration = newConfig;
+ r.stackConfigOverride = mOverrideConfig;
// Determine what has changed. May be nothing, if this is a config
// that has come back from the app after going idle. In that case
// we just want to leave the official config object now in the
// activity and do nothing else.
- final int changes = oldConfig.diff(newConfig);
+ int stackChanges = oldStackOverride.diff(mOverrideConfig);
+ if (stackChanges == 0) {
+ // {@link Configuration#diff} doesn't catch changes from unset values.
+ // Check for changes we care about.
+ if (oldStackOverride.orientation != mOverrideConfig.orientation) {
+ stackChanges |= ActivityInfo.CONFIG_ORIENTATION;
+ }
+ if (oldStackOverride.screenHeightDp != mOverrideConfig.screenHeightDp
+ || oldStackOverride.screenWidthDp != mOverrideConfig.screenWidthDp) {
+ stackChanges |= ActivityInfo.CONFIG_SCREEN_SIZE;
+ }
+ if (oldStackOverride.smallestScreenWidthDp != mOverrideConfig.smallestScreenWidthDp) {
+ stackChanges |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+ }
+ }
+ final int changes = oldConfig.diff(newConfig) | stackChanges;
if (changes == 0 && !r.forceNewConfig) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration no differences in " + r);
return true;
}
@@ -3662,7 +3850,7 @@ final class ActivityStack {
// If the activity isn't currently running, just leave the new
// configuration and it will pick that up next time it starts.
if (r.app == null || r.app.thread == null) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration doesn't matter not running " + r);
r.stopFreezingScreenLocked(false);
r.forceNewConfig = false;
@@ -3670,26 +3858,25 @@ final class ActivityStack {
}
// Figure out how to handle the changes between the configurations.
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {
- Slog.v(TAG, "Checking to restart " + r.info.name + ": changed=0x"
- + Integer.toHexString(changes) + ", handles=0x"
- + Integer.toHexString(r.info.getRealConfigChanged())
- + ", newConfig=" + newConfig);
- }
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
+ "Checking to restart " + r.info.name + ": changed=0x"
+ + Integer.toHexString(changes) + ", handles=0x"
+ + Integer.toHexString(r.info.getRealConfigChanged()) + ", newConfig=" + newConfig);
+
if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
r.configChangeFlags |= changes;
r.startFreezingScreenLocked(r.app, globalChanges);
r.forceNewConfig = false;
if (r.app == null || r.app.thread == null) {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is destroying non-running " + r);
destroyActivityLocked(r, true, "config");
} else if (r.state == ActivityState.PAUSING) {
// A little annoying: we are waiting for this activity to
// finish pausing. Let's not do anything now, but just
// flag that it needs to be restarted when done pausing.
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is skipping already pausing " + r);
r.configDestroy = true;
return true;
@@ -3698,12 +3885,12 @@ final class ActivityStack {
// and we need to restart the top, resumed activity.
// Instead of doing the normal handshaking, just say
// "restart!".
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is relaunching resumed " + r);
relaunchActivityLocked(r, r.configChangeFlags, true);
r.configChangeFlags = 0;
} else {
- if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Config is relaunching non-resumed " + r);
relaunchActivityLocked(r, r.configChangeFlags, false);
r.configChangeFlags = 0;
@@ -3714,15 +3901,15 @@ final class ActivityStack {
return false;
}
- // Default case: the activity can handle this new configuration, so
- // hand it over. Note that we don't need to give it the new
- // configuration, since we always send configuration changes to all
- // process when they happen so it can just use whatever configuration
- // it last got.
+ // Default case: the activity can handle this new configuration, so hand it over.
+ // NOTE: We only forward the stack override configuration as the system level configuration
+ // changes is always sent to all processes when they happen so it can just use whatever
+ // system level configuration it last got.
if (r.app != null && r.app.thread != null) {
try {
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + r);
- r.app.thread.scheduleActivityConfigurationChanged(r.appToken);
+ if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending new config to " + r);
+ r.app.thread.scheduleActivityConfigurationChanged(
+ r.appToken, new Configuration(mOverrideConfig));
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -3732,16 +3919,15 @@ final class ActivityStack {
return true;
}
- private boolean relaunchActivityLocked(ActivityRecord r,
- int changes, boolean andResume) {
+ private boolean relaunchActivityLocked(ActivityRecord r, int changes, boolean andResume) {
List<ResultInfo> results = null;
List<ReferrerIntent> newIntents = null;
if (andResume) {
results = r.results;
newIntents = r.newIntents;
}
- if (DEBUG_SWITCH) Slog.v(TAG, "Relaunching: " + r
- + " with results=" + results + " newIntents=" + newIntents
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
+ "Relaunching: " + r + " with results=" + results + " newIntents=" + newIntents
+ " andResume=" + andResume);
EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY
: EventLogTags.AM_RELAUNCH_ACTIVITY, r.userId, System.identityHashCode(r),
@@ -3752,17 +3938,17 @@ final class ActivityStack {
mStackSupervisor.removeChildActivityContainers(r);
try {
- if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG,
- (andResume ? "Relaunching to RESUMED " : "Relaunching to PAUSED ")
- + r);
+ if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH,
+ "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + r);
r.forceNewConfig = false;
- r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents,
- changes, !andResume, new Configuration(mService.mConfiguration));
+ r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
+ !andResume, new Configuration(mService.mConfiguration),
+ new Configuration(mOverrideConfig));
// Note: don't need to call pauseIfSleepingLocked() here, because
// the caller will only pass in 'andResume' if this activity is
// currently resumed, which implies we aren't sleeping.
} catch (RemoteException e) {
- if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG, "Relaunch failed", e);
+ if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, "Relaunch failed", e);
}
if (andResume) {
@@ -3790,7 +3976,7 @@ final class ActivityStack {
}
}
}
- final ActivityRecord r = ActivityRecord.forToken(token);
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
return false;
}
@@ -3842,7 +4028,7 @@ final class ActivityStack {
}
}
didSomething = true;
- Slog.i(TAG, " Force finishing activity 3 " + r);
+ Slog.i(TAG, " Force finishing activity " + r);
if (samePackage) {
if (r.app != null) {
r.app.removed = true;
@@ -3863,21 +4049,28 @@ final class ActivityStack {
}
void getTasksLocked(List<RunningTaskInfo> list, int callingUid, boolean allowed) {
+ boolean focusedStack = mStackSupervisor.getFocusedStack() == this;
+ boolean topTask = true;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
+ if (task.getTopActivity() == null) {
+ continue;
+ }
ActivityRecord r = null;
ActivityRecord top = null;
+ ActivityRecord tmp;
int numActivities = 0;
int numRunning = 0;
final ArrayList<ActivityRecord> activities = task.mActivities;
- if (activities.isEmpty()) {
- continue;
- }
if (!allowed && !task.isHomeTask() && task.effectiveUid != callingUid) {
continue;
}
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- r = activities.get(activityNdx);
+ tmp = activities.get(activityNdx);
+ if (tmp.finishing) {
+ continue;
+ }
+ r = tmp;
// Initialize state for next task if needed.
if (top == null || (top.state == ActivityState.INITIALIZING)) {
@@ -3891,7 +4084,7 @@ final class ActivityStack {
numRunning++;
}
- if (localLOGV) Slog.v(
+ if (DEBUG_ALL) Slog.v(
TAG, r.intent.getComponent().flattenToShortString()
+ ": task=" + r.task);
}
@@ -3901,22 +4094,25 @@ final class ActivityStack {
ci.baseActivity = r.intent.getComponent();
ci.topActivity = top.intent.getComponent();
ci.lastActiveTime = task.lastActiveTime;
+ if (focusedStack && topTask) {
+ // Give the latest time to ensure foreground task can be sorted
+ // at the first, because lastActiveTime of creating task is 0.
+ ci.lastActiveTime = System.currentTimeMillis();
+ topTask = false;
+ }
if (top.task != null) {
ci.description = top.task.lastDescription;
}
ci.numActivities = numActivities;
ci.numRunning = numRunning;
- //System.out.println(
- // "#" + maxNum + ": " + " descr=" + ci.description);
list.add(ci);
}
}
public void unhandledBackLocked() {
final int top = mTaskHistory.size() - 1;
- if (DEBUG_SWITCH) Slog.d(
- TAG, "Performing unhandledBack(): top activity at " + top);
+ if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Performing unhandledBack(): top activity at " + top);
if (top >= 0) {
final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities;
int activityTop = activities.size() - 1;
@@ -3934,7 +4130,7 @@ final class ActivityStack {
*/
boolean handleAppDiedLocked(ProcessRecord app) {
if (mPausingActivity != null && mPausingActivity.app == app) {
- if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG,
+ if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG_PAUSE,
"App died while pausing: " + mPausingActivity);
mPausingActivity = null;
}
@@ -3952,7 +4148,7 @@ final class ActivityStack {
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
if (r.app == app) {
- Slog.w(TAG, " Force finishing activity 4 "
+ Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
// Force the destroy to skip right to removal.
r.app = null;
@@ -4033,8 +4229,22 @@ final class ActivityStack {
}
void removeTask(TaskRecord task, String reason) {
- mStackSupervisor.endLockTaskModeIfTaskEnding(task);
- mWindowManager.removeTask(task.taskId);
+ removeTask(task, reason, true /* notMoving */);
+ }
+
+ /**
+ * Removes the input task from this stack.
+ * @param task to remove.
+ * @param reason for removal.
+ * @param notMoving task to another stack. In the case we are moving we don't want to perform
+ * some operations on the task like removing it from window manager or recents.
+ */
+ void removeTask(TaskRecord task, String reason, boolean notMoving) {
+ if (notMoving) {
+ mStackSupervisor.removeLockedTaskLocked(task);
+ mWindowManager.removeTask(task.taskId);
+ }
+
final ActivityRecord r = mResumedActivity;
if (r != null && r.task == task) {
mResumedActivity = null;
@@ -4051,7 +4261,7 @@ final class ActivityStack {
mTaskHistory.remove(task);
updateTaskMovement(task, true);
- if (task.mActivities.isEmpty()) {
+ if (notMoving && task.mActivities.isEmpty()) {
final boolean isVoiceSession = task.voiceSession != null;
if (isVoiceSession) {
try {
@@ -4062,22 +4272,30 @@ final class ActivityStack {
if (task.autoRemoveFromRecents() || isVoiceSession) {
// Task creator asked to remove this when done, or this task was a voice
// interaction, so it should not remain on the recent tasks list.
- mService.mRecentTasks.remove(task);
+ mRecentTasks.remove(task);
task.removedFromRecents();
}
}
if (mTaskHistory.isEmpty()) {
- if (DEBUG_STACK) Slog.i(TAG, "removeTask: moving to back stack=" + this);
+ if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
+ final boolean notHomeStack = !isHomeStack();
if (isOnHomeDisplay()) {
- mStackSupervisor.moveHomeStack(!isHomeStack(), reason + " leftTaskHistoryEmpty");
+ String myReason = reason + " leftTaskHistoryEmpty";
+ if (mFullscreen || !adjustFocusToNextVisibleStackLocked(null, myReason)) {
+ mStackSupervisor.moveHomeStack(notHomeStack, myReason);
+ }
}
if (mStacks != null) {
mStacks.remove(this);
mStacks.add(0, this);
}
- mActivityContainer.onTaskListEmptyLocked();
+ if (notHomeStack) {
+ mActivityContainer.onTaskListEmptyLocked();
+ }
}
+
+ task.stack = null;
}
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
@@ -4090,13 +4308,13 @@ final class ActivityStack {
}
ArrayList<TaskRecord> getAllTasks() {
- return new ArrayList<TaskRecord>(mTaskHistory);
+ return new ArrayList<>(mTaskHistory);
}
void addTask(final TaskRecord task, final boolean toTop, boolean moving) {
task.stack = this;
if (toTop) {
- insertTaskAtTop(task);
+ insertTaskAtTop(task, null);
} else {
mTaskHistory.add(0, task);
updateTaskMovement(task, false);
@@ -4118,4 +4336,20 @@ final class ActivityStack {
return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
+ " stackId=" + mStackId + ", " + mTaskHistory.size() + " tasks}";
}
+
+ boolean updateOverrideConfiguration(Configuration newConfig) {
+ Configuration oldConfig = mOverrideConfig;
+ mOverrideConfig = (newConfig == null) ? Configuration.EMPTY : newConfig;
+ // We override the configuration only when the stack's dimensions are different from
+ // the display. In this manner, we know that if the override configuration is empty,
+ // the stack is necessarily full screen.
+ mFullscreen = Configuration.EMPTY.equals(mOverrideConfig);
+ return !mOverrideConfig.equals(oldConfig);
+ }
+
+ void onLockTaskPackagesUpdatedLocked() {
+ for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+ mTaskHistory.get(taskNdx).setLockTaskAuth();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 32787d8..8c98f9f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -17,24 +17,26 @@
package com.android.server.am;
import static android.Manifest.permission.START_ANY_ACTIVITY;
+import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
+import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static com.android.server.am.ActivityManagerService.localLOGV;
-import static com.android.server.am.ActivityManagerService.DEBUG_CONFIGURATION;
-import static com.android.server.am.ActivityManagerService.DEBUG_FOCUS;
-import static com.android.server.am.ActivityManagerService.DEBUG_PAUSE;
-import static com.android.server.am.ActivityManagerService.DEBUG_RECENTS;
-import static com.android.server.am.ActivityManagerService.DEBUG_RESULTS;
-import static com.android.server.am.ActivityManagerService.DEBUG_STACK;
-import static com.android.server.am.ActivityManagerService.DEBUG_SWITCH;
-import static com.android.server.am.ActivityManagerService.DEBUG_TASKS;
-import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING;
+import static com.android.server.am.ActivityManagerDebugConfig.*;
import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
-import static com.android.server.am.ActivityManagerService.TAG;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityStack.ActivityState.*;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
import android.app.Activity;
import android.app.ActivityManager;
@@ -63,12 +65,14 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Point;
+import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -82,7 +86,9 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.TransactionTooLargeException;
import android.os.UserHandle;
+import android.os.WorkSource;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.service.voice.IVoiceInteractionSession;
@@ -114,7 +120,18 @@ import java.util.ArrayList;
import java.util.List;
public final class ActivityStackSupervisor implements DisplayListener {
- static final boolean DEBUG = ActivityManagerService.DEBUG || false;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM;
+ private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
+ private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
+ private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
+ private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
+ private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
+ private static final String TAG_STACK = TAG + POSTFIX_STACK;
+ private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
+ private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
+ private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
+
+ static final boolean DEBUG = DEBUG_ALL || false;
static final boolean DEBUG_ADD_REMOVE = DEBUG || false;
static final boolean DEBUG_APP = DEBUG || false;
static final boolean DEBUG_CONTAINERS = DEBUG || false;
@@ -148,8 +165,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
static final int LOCK_TASK_START_MSG = FIRST_SUPERVISOR_STACK_MSG + 9;
static final int LOCK_TASK_END_MSG = FIRST_SUPERVISOR_STACK_MSG + 10;
static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 11;
- static final int CONTAINER_TASK_LIST_EMPTY_TIMEOUT = FIRST_SUPERVISOR_STACK_MSG + 12;
- static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 13;
+ static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
+ static final int SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG = FIRST_SUPERVISOR_STACK_MSG + 13;
private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
@@ -166,6 +183,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
final ActivityManagerService mService;
+ private final RecentTasks mRecentTasks;
+
final ActivityStackSupervisorHandler mHandler;
/** Short cut */
@@ -196,32 +215,30 @@ public final class ActivityStackSupervisor implements DisplayListener {
/** List of activities that are waiting for a new activity to become visible before completing
* whatever operation they are supposed to do. */
- final ArrayList<ActivityRecord> mWaitingVisibleActivities = new ArrayList<ActivityRecord>();
+ final ArrayList<ActivityRecord> mWaitingVisibleActivities = new ArrayList<>();
/** List of processes waiting to find out about the next visible activity. */
- final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible =
- new ArrayList<IActivityManager.WaitResult>();
+ final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible = new ArrayList<>();
/** List of processes waiting to find out about the next launched activity. */
- final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched =
- new ArrayList<IActivityManager.WaitResult>();
+ final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched = new ArrayList<>();
/** List of activities that are ready to be stopped, but waiting for the next activity to
* settle down before doing so. */
- final ArrayList<ActivityRecord> mStoppingActivities = new ArrayList<ActivityRecord>();
+ final ArrayList<ActivityRecord> mStoppingActivities = new ArrayList<>();
/** List of activities that are ready to be finished, but waiting for the previous activity to
* settle down before doing so. It contains ActivityRecord objects. */
- final ArrayList<ActivityRecord> mFinishingActivities = new ArrayList<ActivityRecord>();
+ final ArrayList<ActivityRecord> mFinishingActivities = new ArrayList<>();
/** List of activities that are in the process of going to sleep. */
- final ArrayList<ActivityRecord> mGoingToSleepActivities = new ArrayList<ActivityRecord>();
+ final ArrayList<ActivityRecord> mGoingToSleepActivities = new ArrayList<>();
/** Used on user changes */
- final ArrayList<UserStartedState> mStartingUsers = new ArrayList<UserStartedState>();
+ final ArrayList<UserStartedState> mStartingUsers = new ArrayList<>();
/** Used to queue up any background users being started */
- final ArrayList<UserStartedState> mStartingBackgroundUsers = new ArrayList<UserStartedState>();
+ final ArrayList<UserStartedState> mStartingBackgroundUsers = new ArrayList<>();
/** Set to indicate whether to issue an onUserLeaving callback when a newly launched activity
* is being brought in front of us. */
@@ -254,27 +271,28 @@ public final class ActivityStackSupervisor implements DisplayListener {
// TODO: Add listener for removal of references.
/** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
- private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<ActivityContainer>();
+ private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
/** Mapping from displayId to display current state */
- private final SparseArray<ActivityDisplay> mActivityDisplays =
- new SparseArray<ActivityDisplay>();
+ private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
InputManagerInternal mInputManagerInternal;
- /** If non-null then the task specified remains in front and no other tasks may be started
- * until the task exits or #stopLockTaskMode() is called. */
- TaskRecord mLockTaskModeTask;
- /** Whether lock task has been entered by an authorized app and cannot
- * be exited. */
- private boolean mLockTaskIsLocked;
+ /** The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks
+ * may be finished until there is only one entry left. If this is empty the system is not
+ * in lockTask mode. */
+ ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>();
+ /** Store the current lock task mode. Possible values:
+ * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
+ * {@link ActivityManager#LOCK_TASK_MODE_PINNED}
+ */
+ private int mLockTaskModeState;
/**
* Notifies the user when entering/exiting lock-task.
*/
private LockTaskNotify mLockTaskNotify;
- final ArrayList<PendingActivityLaunch> mPendingActivityLaunches
- = new ArrayList<PendingActivityLaunch>();
+ final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>();
/** Used to keep resumeTopActivityLocked() from being entered recursively */
boolean inResumeTopActivity;
@@ -298,8 +316,9 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
- public ActivityStackSupervisor(ActivityManagerService service) {
+ public ActivityStackSupervisor(ActivityManagerService service, RecentTasks recentTasks) {
mService = service;
+ mRecentTasks = recentTasks;
mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper());
}
@@ -310,8 +329,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
void initPowerManagement() {
PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE);
mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep");
- mLaunchingActivity =
- pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch");
+ mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*launch*");
mLaunchingActivity.setReferenceCounted(false);
}
@@ -372,7 +390,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
void notifyActivityDrawnForKeyguard() {
- if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen("");
+ if (DEBUG_LOCKSCREEN) mService.logLockScreen("");
mWindowManager.notifyActivityDrawnForKeyguard();
}
@@ -384,36 +402,47 @@ public final class ActivityStackSupervisor implements DisplayListener {
return mLastFocusedStack;
}
- // TODO: Split into two methods isFrontStack for any visible stack and isFrontmostStack for the
- // top of all visible stacks.
+ /** Top of all visible stacks is/should always be equal to the focused stack.
+ * Use {@link ActivityStack#isStackVisibleLocked} to determine if a specific
+ * stack is visible or not. */
boolean isFrontStack(ActivityStack stack) {
+ if (stack == null) {
+ return false;
+ }
+
final ActivityRecord parent = stack.mActivityContainer.mParentActivity;
if (parent != null) {
stack = parent.task.stack;
}
- ArrayList<ActivityStack> stacks = stack.mStacks;
- if (stacks != null && !stacks.isEmpty()) {
- return stack == stacks.get(stacks.size() - 1);
- }
- return false;
+ return stack == mFocusedStack;
}
void moveHomeStack(boolean toFront, String reason) {
+ moveHomeStack(toFront, reason, null);
+ }
+
+ void moveHomeStack(boolean toFront, String reason, ActivityStack lastFocusedStack) {
ArrayList<ActivityStack> stacks = mHomeStack.mStacks;
final int topNdx = stacks.size() - 1;
if (topNdx <= 0) {
return;
}
- ActivityStack topStack = stacks.get(topNdx);
- final boolean homeInFront = topStack == mHomeStack;
- if (homeInFront != toFront) {
- mLastFocusedStack = topStack;
+
+ // The home stack should either be at the top or bottom of the stack list.
+ if ((toFront && (stacks.get(topNdx) != mHomeStack))
+ || (!toFront && (stacks.get(0) != mHomeStack))) {
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "moveHomeTask: topStack old="
+ + ((lastFocusedStack != null) ? lastFocusedStack : stacks.get(topNdx))
+ + " new=" + mFocusedStack);
stacks.remove(mHomeStack);
stacks.add(toFront ? topNdx : 0, mHomeStack);
- mFocusedStack = stacks.get(topNdx);
- if (DEBUG_STACK) Slog.d(TAG, "moveHomeTask: topStack old=" + topStack + " new="
- + mFocusedStack);
}
+
+ if (lastFocusedStack != null) {
+ mLastFocusedStack = lastFocusedStack;
+ }
+ mFocusedStack = stacks.get(topNdx);
+
EventLog.writeEvent(EventLogTags.AM_HOME_STACK_MOVED,
mCurrentUser, toFront ? 1 : 0, stacks.get(topNdx).getStackId(),
mFocusedStack == null ? -1 : mFocusedStack.getStackId(), reason);
@@ -426,13 +455,21 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
- void moveHomeStackTaskToTop(int homeStackTaskType, String reason) {
+ /** Returns true if the focus activity was adjusted to the home stack top activity. */
+ boolean moveHomeStackTaskToTop(int homeStackTaskType, String reason) {
if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) {
mWindowManager.showRecentApps();
- return;
+ return false;
}
- moveHomeStack(true, reason);
+
mHomeStack.moveHomeStackTaskToTop(homeStackTaskType);
+
+ final ActivityRecord top = getHomeActivity();
+ if (top == null) {
+ return false;
+ }
+ mService.setFocusedActivityLocked(top, reason);
+ return true;
}
boolean resumeHomeStackTask(int homeStackTaskType, ActivityRecord prev, String reason) {
@@ -445,14 +482,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
mWindowManager.showRecentApps();
return false;
}
- moveHomeStackTaskToTop(homeStackTaskType, reason);
+
if (prev != null) {
prev.task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
}
- ActivityRecord r = mHomeStack.topRunningActivityLocked(null);
- // if (r != null && (r.isHomeActivity() || r.isRecentsActivity())) {
- if (r != null && r.isHomeActivity()) {
+ mHomeStack.moveHomeStackTaskToTop(homeStackTaskType);
+ ActivityRecord r = getHomeActivity();
+ if (r != null) {
mService.setFocusedActivityLocked(r, reason);
return resumeTopActivitiesLocked(mHomeStack, prev, null);
}
@@ -460,6 +497,16 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
TaskRecord anyTaskForIdLocked(int id) {
+ return anyTaskForIdLocked(id, true);
+ }
+
+ /**
+ * Returns a {@link TaskRecord} for the input id if available. Null otherwise.
+ * @param id Id of the task we would like returned.
+ * @param restoreFromRecents If the id was not in the active list, but was found in recents,
+ * restore the task from recents to the active list.
+ */
+ TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents) {
int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
@@ -473,18 +520,23 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
// Don't give up! Look in recents.
- if (DEBUG_RECENTS) Slog.v(TAG, "Looking for task id=" + id + " in recents");
- TaskRecord task = mService.recentTaskForIdLocked(id);
+ if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents");
+ TaskRecord task = mRecentTasks.taskForIdLocked(id);
if (task == null) {
- if (DEBUG_RECENTS) Slog.d(TAG, "\tDidn't find task id=" + id + " in recents");
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "\tDidn't find task id=" + id + " in recents");
return null;
}
+ if (!restoreFromRecents) {
+ return task;
+ }
+
if (!restoreRecentTaskLocked(task)) {
- if (DEBUG_RECENTS) Slog.w(TAG, "Couldn't restore task id=" + id + " found in recents");
+ if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
+ "Couldn't restore task id=" + id + " found in recents");
return null;
}
- if (DEBUG_RECENTS) Slog.w(TAG, "Restored task id=" + id + " from in recents");
+ if (DEBUG_RECENTS) Slog.w(TAG_RECENTS, "Restored task id=" + id + " from in recents");
return task;
}
@@ -514,12 +566,12 @@ public final class ActivityStackSupervisor implements DisplayListener {
if (mCurTaskId <= 0) {
mCurTaskId = 1;
}
- } while (anyTaskForIdLocked(mCurTaskId) != null);
+ } while (anyTaskForIdLocked(mCurTaskId, false) != null);
return mCurTaskId;
}
ActivityRecord resumedAppLocked() {
- ActivityStack stack = getFocusedStack();
+ ActivityStack stack = mFocusedStack;
if (stack == null) {
return null;
}
@@ -592,14 +644,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
final ActivityStack stack = stacks.get(stackNdx);
if (isFrontStack(stack)) {
final ActivityRecord r = stack.mResumedActivity;
- if (r != null && r.state != ActivityState.RESUMED) {
+ if (r != null && r.state != RESUMED) {
return false;
}
}
}
}
// TODO: Not sure if this should check if all Paused are complete too.
- if (DEBUG_STACK) Slog.d(TAG,
+ if (DEBUG_STACK) Slog.d(TAG_STACK,
"allResumedActivitiesComplete: mLastFocusedStack changing from=" +
mLastFocusedStack + " to=" + mFocusedStack);
mLastFocusedStack = mFocusedStack;
@@ -607,17 +659,21 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
boolean allResumedActivitiesVisible() {
+ boolean foundResumed = false;
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
final ActivityRecord r = stack.mResumedActivity;
- if (r != null && (!r.nowVisible || r.waitingVisible)) {
- return false;
+ if (r != null) {
+ if (!r.nowVisible || mWaitingVisibleActivities.contains(r)) {
+ return false;
+ }
+ foundResumed = true;
}
}
}
- return true;
+ return foundResumed;
}
/**
@@ -649,9 +705,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
final ActivityRecord r = stack.mPausingActivity;
- if (r != null && r.state != ActivityState.PAUSED
- && r.state != ActivityState.STOPPED
- && r.state != ActivityState.STOPPING) {
+ if (r != null && r.state != PAUSED && r.state != STOPPED && r.state != STOPPING) {
if (DEBUG_STATES) {
Slog.d(TAG, "allPausedActivitiesComplete: r=" + r + " state=" + r.state);
pausing = false;
@@ -723,7 +777,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
ActivityRecord topRunningActivityLocked() {
- final ActivityStack focusedStack = getFocusedStack();
+ final ActivityStack focusedStack = mFocusedStack;
ActivityRecord r = focusedStack.topRunningActivityLocked(null);
if (r != null) {
return r;
@@ -752,7 +806,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
- ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<RunningTaskInfo>();
+ ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>();
runningTaskLists.add(stackTaskList);
stack.getTasksLocked(stackTaskList, callingUid, allowed);
}
@@ -850,11 +904,16 @@ public final class ActivityStackSupervisor implements DisplayListener {
intent = new Intent(intent);
// Collect information about the target of the Intent.
- ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags,
- profilerInfo, userId);
+ ActivityInfo aInfo =
+ resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
ActivityContainer container = (ActivityContainer)iContainer;
synchronized (mService) {
+ if (container != null && container.mParentActivity != null &&
+ container.mParentActivity.state != RESUMED) {
+ // Cannot start a child activity if the parent is not resumed.
+ return ActivityManager.START_CANCELED;
+ }
final int realCallingPid = Binder.getCallingPid();
final int realCallingUid = Binder.getCallingUid();
int callingPid;
@@ -869,19 +928,19 @@ public final class ActivityStackSupervisor implements DisplayListener {
final ActivityStack stack;
if (container == null || container.mStack.isOnHomeDisplay()) {
- stack = getFocusedStack();
+ stack = mFocusedStack;
} else {
stack = container.mStack;
}
- stack.mConfigWillChange = config != null
- && mService.mConfiguration.diff(config) != 0;
- if (DEBUG_CONFIGURATION) Slog.v(TAG,
+ stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0;
+ if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Starting activity when config will change = " + stack.mConfigWillChange);
final long origId = Binder.clearCallingIdentity();
if (aInfo != null &&
- (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
+ (aInfo.applicationInfo.privateFlags
+ &ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Check to see if we already
// have another, different heavy-weight process running.
if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) {
@@ -964,7 +1023,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
"updateConfiguration()");
stack.mConfigWillChange = false;
- if (DEBUG_CONFIGURATION) Slog.v(TAG,
+ if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Updating to new configuration after starting activity.");
mService.updateConfigurationLocked(config, null, false, false);
}
@@ -981,7 +1040,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
} while (!outResult.timeout && outResult.who == null);
} else if (res == ActivityManager.START_TASK_TO_FRONT) {
ActivityRecord r = stack.topRunningActivityLocked(null);
- if (r.nowVisible && r.state == ActivityState.RESUMED) {
+ if (r.nowVisible && r.state == RESUMED) {
outResult.timeout = false;
outResult.who = new ComponentName(r.info.packageName, r.info.name);
outResult.totalTime = 0;
@@ -1052,8 +1111,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
aInfo = mService.getActivityInfoForUser(aInfo, userId);
if (aInfo != null &&
- (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_CANT_SAVE_STATE)
- != 0) {
+ (aInfo.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
throw new IllegalArgumentException(
"FLAG_CANT_SAVE_STATE not supported here");
}
@@ -1086,12 +1145,13 @@ public final class ActivityStackSupervisor implements DisplayListener {
ProcessRecord app, boolean andResume, boolean checkConfig)
throws RemoteException {
- r.startFreezingScreenLocked(app, 0);
- if (false) Slog.d(TAG, "realStartActivity: setting app visibility true");
- mWindowManager.setAppVisibility(r.appToken, true);
+ if (andResume) {
+ r.startFreezingScreenLocked(app, 0);
+ mWindowManager.setAppVisibility(r.appToken, true);
- // schedule launch ticks to collect information about slow apps.
- r.startLaunchTickingLocked();
+ // schedule launch ticks to collect information about slow apps.
+ r.startLaunchTickingLocked();
+ }
// Have the window manager re-evaluate the orientation of
// the screen based on the new activity order. Note that
@@ -1111,7 +1171,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
r.launchCount++;
r.lastLaunchTime = SystemClock.uptimeMillis();
- if (localLOGV) Slog.v(TAG, "Launching: " + r);
+ if (DEBUG_ALL) Slog.v(TAG, "Launching: " + r);
int idx = app.activities.indexOf(r);
if (idx < 0) {
@@ -1120,7 +1180,12 @@ public final class ActivityStackSupervisor implements DisplayListener {
mService.updateLruProcessLocked(app, true, null);
mService.updateOomAdjLocked();
- final ActivityStack stack = r.task.stack;
+ final TaskRecord task = r.task;
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) {
+ setLockTaskModeLocked(task, LOCK_TASK_MODE_LOCKED, "lockTaskLaunchMode attribute");
+ }
+
+ final ActivityStack stack = task.stack;
try {
if (app.thread == null) {
throw new RemoteException();
@@ -1131,60 +1196,62 @@ public final class ActivityStackSupervisor implements DisplayListener {
results = r.results;
newIntents = r.newIntents;
}
- if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r
- + " icicle=" + r.icicle
- + " with results=" + results + " newIntents=" + newIntents
- + " andResume=" + andResume);
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
+ "Launching: " + r + " icicle=" + r.icicle + " with results=" + results
+ + " newIntents=" + newIntents + " andResume=" + andResume);
if (andResume) {
EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY,
r.userId, System.identityHashCode(r),
- r.task.taskId, r.shortComponentName);
+ task.taskId, r.shortComponentName);
}
if (r.isHomeActivity() && r.isNotResolverActivity()) {
// Home process is the root process of the task.
- mService.mHomeProcess = r.task.mActivities.get(0).app;
+ mService.mHomeProcess = task.mActivities.get(0).app;
}
mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
r.sleeping = false;
r.forceNewConfig = false;
mService.showAskCompatModeDialogLocked(r);
r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
- String profileFile = null;
- ParcelFileDescriptor profileFd = null;
+ ProfilerInfo profilerInfo = null;
if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) {
if (mService.mProfileProc == null || mService.mProfileProc == app) {
mService.mProfileProc = app;
- profileFile = mService.mProfileFile;
- profileFd = mService.mProfileFd;
- }
- }
- app.hasShownUi = true;
- app.pendingUiClean = true;
- if (profileFd != null) {
- try {
- profileFd = profileFd.dup();
- } catch (IOException e) {
- if (profileFd != null) {
- try {
- profileFd.close();
- } catch (IOException o) {
+ final String profileFile = mService.mProfileFile;
+ if (profileFile != null) {
+ ParcelFileDescriptor profileFd = mService.mProfileFd;
+ if (profileFd != null) {
+ try {
+ profileFd = profileFd.dup();
+ } catch (IOException e) {
+ if (profileFd != null) {
+ try {
+ profileFd.close();
+ } catch (IOException o) {
+ }
+ profileFd = null;
+ }
+ }
}
- profileFd = null;
+
+ profilerInfo = new ProfilerInfo(profileFile, profileFd,
+ mService.mSamplingInterval, mService.mAutoStopProfiler);
}
}
}
- ProfilerInfo profilerInfo = profileFile != null
- ? new ProfilerInfo(profileFile, profileFd, mService.mSamplingInterval,
- mService.mAutoStopProfiler) : null;
- app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+ if (andResume) {
+ app.hasShownUi = true;
+ app.pendingUiClean = true;
+ }
+ app.forceProcessStateUpTo(mService.mTopProcessState);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
- r.compat, r.launchedFromPackage, r.task.voiceInteractor, app.repProcState,
- r.icicle, r.persistentState, results, newIntents, !andResume,
- mService.isNextTransitionForward(), profilerInfo);
+ new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
+ task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
+ newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
- if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
+ if ((app.info.privateFlags&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Note that the package
// manager will ensure that only activity can run in the main
// process of the .apk, which is the only thing that will be
@@ -1240,7 +1307,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
// other state.
if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r
+ " (starting in stopped state)");
- r.state = ActivityState.STOPPED;
+ r.state = STOPPED;
r.stopped = true;
}
@@ -1317,8 +1384,9 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
+ final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
+
if (err == ActivityManager.START_SUCCESS) {
- final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
+ "} from uid " + callingUid
+ " on display " + (container == null ? (mFocusedStack == null ?
@@ -1331,8 +1399,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
ActivityRecord resultRecord = null;
if (resultTo != null) {
sourceRecord = isInAnyStackLocked(resultTo);
- if (DEBUG_RESULTS) Slog.v(
- TAG, "Will send result to " + resultTo + " " + sourceRecord);
+ if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
+ "Will send result to " + resultTo + " " + sourceRecord);
if (sourceRecord != null) {
if (requestCode >= 0 && !sourceRecord.finishing) {
resultRecord = sourceRecord;
@@ -1342,7 +1410,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
final int launchFlags = intent.getFlags();
- if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
+ if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
// Transfer the result target from the source activity to the new
// one being started, including any failures.
if (requestCode >= 0) {
@@ -1350,6 +1418,9 @@ public final class ActivityStackSupervisor implements DisplayListener {
return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
}
resultRecord = sourceRecord.resultTo;
+ if (resultRecord != null && !resultRecord.isInStackLocked()) {
+ resultRecord = null;
+ }
resultWho = sourceRecord.resultWho;
requestCode = sourceRecord.requestCode;
sourceRecord.resultTo = null;
@@ -1383,6 +1454,13 @@ public final class ActivityStackSupervisor implements DisplayListener {
err = ActivityManager.START_CLASS_NOT_FOUND;
}
+ if (err == ActivityManager.START_SUCCESS
+ && !isCurrentProfileLocked(userId)
+ && (aInfo.flags & FLAG_SHOW_FOR_ALL_USERS) == 0) {
+ // Trying to launch a background activity that doesn't show for all users.
+ err = ActivityManager.START_NOT_CURRENT_USER_ACTIVITY;
+ }
+
if (err == ActivityManager.START_SUCCESS && sourceRecord != null
&& sourceRecord.task.voiceSession != null) {
// If this activity is being launched as part of a voice session, we need
@@ -1486,7 +1564,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
outActivity[0] = r;
}
- final ActivityStack stack = getFocusedStack();
+ final ActivityStack stack = mFocusedStack;
if (voiceSession == null && (stack.mResumedActivity == null
|| stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
@@ -1525,25 +1603,28 @@ public final class ActivityStackSupervisor implements DisplayListener {
return err;
}
- ActivityStack adjustStackFocus(ActivityRecord r, boolean newTask) {
+ ActivityStack computeStackFocus(ActivityRecord r, boolean newTask) {
final TaskRecord task = r.task;
// On leanback only devices we should keep all activities in the same stack.
if (!mLeanbackOnlyDevice &&
(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
- if (task != null) {
- final ActivityStack taskStack = task.stack;
- if (taskStack.isOnHomeDisplay()) {
- if (mFocusedStack != taskStack) {
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: Setting " +
- "focused stack to r=" + r + " task=" + task);
- mFocusedStack = taskStack;
+
+ ActivityStack stack;
+
+ if (task != null && task.stack != null) {
+ stack = task.stack;
+ if (stack.isOnHomeDisplay()) {
+ if (mFocusedStack != stack) {
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+ "computeStackFocus: Setting " + "focused stack to r=" + r
+ + " task=" + task);
} else {
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
- "adjustStackFocus: Focused stack already=" + mFocusedStack);
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+ "computeStackFocus: Focused stack already=" + mFocusedStack);
}
}
- return taskStack;
+ return stack;
}
final ActivityContainer container = r.mInitialActivityContainer;
@@ -1555,48 +1636,45 @@ public final class ActivityStackSupervisor implements DisplayListener {
if (mFocusedStack != mHomeStack && (!newTask ||
mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
- "adjustStackFocus: Have a focused stack=" + mFocusedStack);
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+ "computeStackFocus: Have a focused stack=" + mFocusedStack);
return mFocusedStack;
}
final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = homeDisplayStacks.get(stackNdx);
+ stack = homeDisplayStacks.get(stackNdx);
if (!stack.isHomeStack()) {
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
- "adjustStackFocus: Setting focused stack=" + stack);
- mFocusedStack = stack;
- return mFocusedStack;
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+ "computeStackFocus: Setting focused stack=" + stack);
+ return stack;
}
}
// Need to create an app stack for this user.
- int stackId = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY);
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: New stack r=" + r +
- " stackId=" + stackId);
- mFocusedStack = getStack(stackId);
- return mFocusedStack;
+ stack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY);
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
+ + r + " stackId=" + stack.mStackId);
+ return stack;
}
return mHomeStack;
}
- void setFocusedStack(ActivityRecord r, String reason) {
- if (r != null) {
- final TaskRecord task = r.task;
- boolean isHomeActivity = !r.isApplicationActivity();
- if (!isHomeActivity && task != null) {
- isHomeActivity = !task.isApplicationTask();
- }
- if (!isHomeActivity && task != null) {
- final ActivityRecord parent = task.stack.mActivityContainer.mParentActivity;
- isHomeActivity = parent != null && parent.isHomeActivity();
- }
- moveHomeStack(isHomeActivity, reason);
+ boolean setFocusedStack(ActivityRecord r, String reason) {
+ if (r == null) {
+ // Not sure what you are trying to do, but it is not going to work...
+ return false;
+ }
+ final TaskRecord task = r.task;
+ if (task == null || task.stack == null) {
+ Slog.w(TAG, "Can't set focus stack for r=" + r + " task=" + task);
+ return false;
}
+ task.stack.moveToFront(reason);
+ return true;
}
- final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord,
+ final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
boolean doResume, Bundle options, TaskRecord inTask) {
final Intent intent = r.intent;
@@ -1643,7 +1721,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
&& !launchSingleTask && !launchSingleInstance
&& (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
- if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0
+ && r.resultTo.task.stack != null) {
// For whatever reason this activity is being launched into a new
// task... yet the caller has requested a result back. Well, that
// is pretty messed up, so instead immediately send back a cancel
@@ -1672,7 +1751,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
// We'll invoke onUserLeaving before onPause only if the launching
// activity did not explicitly state that this is an automated launch.
mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
- if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() => mUserLeaving=" + mUserLeaving);
+ if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
+ "startActivity() => mUserLeaving=" + mUserLeaving);
// If the caller has asked not to resume at this point, we make note
// of this in the record so that we can skip it when trying to find
@@ -1691,7 +1771,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
ActivityRecord checkedCaller = sourceRecord;
if (checkedCaller == null) {
- checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop);
+ checkedCaller = mFocusedStack.topRunningNonDelayedActivityLocked(notTop);
}
if (!checkedCaller.realActivity.equals(r.realActivity)) {
// Caller is not the same as launcher, so always needed.
@@ -1807,6 +1887,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
ActivityStack targetStack;
intent.setFlags(launchFlags);
+ final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0;
// We may want to try to place the new activity in to an existing task. We always
// do this if the target activity is singleTask or singleInstance; we will also do
@@ -1836,11 +1917,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
if (r.task == null) {
r.task = intentActivity.task;
}
- targetStack = intentActivity.task.stack;
- targetStack.mLastPausedActivity = null;
- if (DEBUG_TASKS) Slog.d(TAG, "Bring to front target: " + targetStack
- + " from " + intentActivity);
- targetStack.moveToFront("intentActivityFound");
if (intentActivity.task.intent == null) {
// This task was started because of movement of
// the activity based on affinity... now that we
@@ -1848,29 +1924,31 @@ public final class ActivityStackSupervisor implements DisplayListener {
// base intent.
intentActivity.task.setIntent(r);
}
+ targetStack = intentActivity.task.stack;
+ targetStack.mLastPausedActivity = null;
// If the target task is not in the front, then we need
// to bring it to the front... except... well, with
// SINGLE_TASK_LAUNCH it's not entirely clear. We'd like
// to have the same behavior as if a new instance was
// being started, which means not bringing it to the front
// if the caller is not itself in the front.
- final ActivityStack lastStack = getLastStack();
- ActivityRecord curTop = lastStack == null?
- null : lastStack.topRunningNonDelayedActivityLocked(notTop);
+ final ActivityStack focusStack = getFocusedStack();
+ ActivityRecord curTop = (focusStack == null)
+ ? null : focusStack.topRunningNonDelayedActivityLocked(notTop);
boolean movedToFront = false;
if (curTop != null && (curTop.task != intentActivity.task ||
- curTop.task != lastStack.topTask())) {
+ curTop.task != focusStack.topTask())) {
r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
if (sourceRecord == null || (sourceStack.topActivity() != null &&
sourceStack.topActivity().task == sourceRecord.task)) {
- // We really do want to push this one into the
- // user's face, right now.
+ // We really do want to push this one into the user's face, right now.
if (launchTaskBehind && sourceRecord != null) {
intentActivity.setTaskToAffiliateWith(sourceRecord.task);
}
movedHome = true;
- targetStack.moveTaskToFrontLocked(intentActivity.task, r, options,
- "bringingFoundTaskToFront");
+ targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation,
+ options, "bringingFoundTaskToFront");
+ movedToFront = true;
if ((launchFlags &
(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
@@ -1878,15 +1956,20 @@ public final class ActivityStackSupervisor implements DisplayListener {
intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
}
options = null;
- movedToFront = true;
}
}
+ if (!movedToFront) {
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack
+ + " from " + intentActivity);
+ targetStack.moveToFront("intentActivityFound");
+ }
+
// If the caller has requested that the target task be
// reset, then do so.
if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r);
}
- if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
+ if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
// We don't need to start a new activity, and
// the client said not to do anything if that
// is the case, so this is it! And for paranoia, make
@@ -1904,16 +1987,15 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
return ActivityManager.START_RETURN_INTENT_TO_CALLER;
}
- if ((launchFlags &
- (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK))
- == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) {
+ if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
+ == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
// The caller has requested to completely replace any
// existing task with its new activity. Well that should
// not be too hard...
reuseTask = intentActivity.task;
reuseTask.performClearTaskLocked();
reuseTask.setIntent(r);
- } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
+ } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| launchSingleInstance || launchSingleTask) {
// In this situation we want to remove all activities
// from the task up to the one being started. In most
@@ -1933,15 +2015,22 @@ public final class ActivityStackSupervisor implements DisplayListener {
r, top.task);
top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
} else {
- // A special case: we need to
- // start the activity because it is not currently
- // running, and the caller has asked to clear the
- // current task to have this activity at the top.
+ // A special case: we need to start the activity because it is not
+ // currently running, and the caller has asked to clear the current
+ // task to have this activity at the top.
addingToTask = true;
- // Now pretend like this activity is being started
- // by the top of its task, so it is put in the
- // right place.
+ // Now pretend like this activity is being started by the top of its
+ // task, so it is put in the right place.
sourceRecord = intentActivity;
+ TaskRecord task = sourceRecord.task;
+ if (task != null && task.stack == null) {
+ // Target stack got cleared when we all activities were removed
+ // above. Go ahead and reset it.
+ targetStack = computeStackFocus(sourceRecord, false /* newTask */);
+ targetStack.addTask(
+ task, !launchTaskBehind /* toTop */, false /* moving */);
+ }
+
}
} else if (r.realActivity.equals(intentActivity.task.realActivity)) {
// In this case the top activity on the task is the
@@ -2014,7 +2103,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
// If the activity being launched is the same as the one currently
// at the top, then we need to check if it should only be launched
// once.
- ActivityStack topStack = getFocusedStack();
+ ActivityStack topStack = mFocusedStack;
ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop);
if (top != null && r.resultTo == null) {
if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
@@ -2044,7 +2133,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
} else {
- if (r.resultTo != null) {
+ if (r.resultTo != null && r.resultTo.task.stack != null) {
r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho,
r.requestCode, Activity.RESULT_CANCELED, null);
}
@@ -2061,30 +2150,29 @@ public final class ActivityStackSupervisor implements DisplayListener {
// Should this be considered a new task?
if (r.resultTo == null && inTask == null && !addingToTask
&& (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
- if (isLockTaskModeViolation(reuseTask)) {
- Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
- return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
- }
newTask = true;
- targetStack = adjustStackFocus(r, newTask);
- if (!launchTaskBehind) {
- targetStack.moveToFront("startingNewTask");
- }
+ targetStack = computeStackFocus(r, newTask);
+ targetStack.moveToFront("startingNewTask");
+
if (reuseTask == null) {
r.setTask(targetStack.createTaskRecord(getNextTaskId(),
newTaskInfo != null ? newTaskInfo : r.info,
newTaskIntent != null ? newTaskIntent : intent,
voiceSession, voiceInteractor, !launchTaskBehind /* toTop */),
taskToAffiliate);
- if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
- r.task);
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS,
+ "Starting new activity " + r + " in new task " + r.task);
} else {
r.setTask(reuseTask, taskToAffiliate);
}
+ if (isLockTaskModeViolation(r.task)) {
+ Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
+ return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+ }
if (!movedHome) {
if ((launchFlags &
- (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME))
- == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) {
+ (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
+ == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
// Caller wants to appear on home activity, so before starting
// their own activity we will bring home to the front.
r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
@@ -2100,7 +2188,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
targetStack.moveToFront("sourceStackToFront");
final TaskRecord topTask = targetStack.topTask();
if (topTask != sourceTask) {
- targetStack.moveTaskToFrontLocked(sourceTask, r, options, "sourceTaskToFront");
+ targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options,
+ "sourceTaskToFront");
}
if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
// In this case, we are adding the activity to an existing
@@ -2143,7 +2232,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
// to keep the new one in the same task as the one that is starting
// it.
r.setTask(sourceTask, null);
- if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r
+ " in existing task " + r.task + " from source " + sourceRecord);
} else if (inTask != null) {
@@ -2154,7 +2243,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
targetStack = inTask.stack;
- targetStack.moveTaskToFrontLocked(inTask, r, options, "inTaskToFront");
+ targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, "inTaskToFront");
// Check whether we should actually launch the new activity in to the task,
// or just reuse the current activity on top.
@@ -2182,20 +2271,20 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
r.setTask(inTask, null);
- if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r
+ " in explicit task " + r.task);
} else {
// This not being started from an existing activity, and not part
// of a new task... just put it in the top task, though these days
// this case should never happen.
- targetStack = adjustStackFocus(r, newTask);
+ targetStack = computeStackFocus(r, newTask);
targetStack.moveToFront("addingToTopTask");
ActivityRecord prev = targetStack.topActivity();
r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(),
r.info, intent, null, null, true), null);
mWindowManager.moveTaskToTop(r.task.taskId);
- if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r
+ " in new guessed " + r.task);
}
@@ -2235,6 +2324,10 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
+ void setLaunchSource(int uid) {
+ mLaunchingActivity.setWorkSource(new WorkSource(uid));
+ }
+
void acquireLaunchWakelock() {
if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) {
throw new IllegalStateException("Calling must be system uid");
@@ -2267,7 +2360,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
// Checked.
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
Configuration config) {
- if (localLOGV) Slog.v(TAG, "Activity idle: " + token);
+ if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token);
ArrayList<ActivityRecord> stops = null;
ArrayList<ActivityRecord> finishes = null;
@@ -2277,7 +2370,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
boolean booting = false;
boolean activityRemoved = false;
- ActivityRecord r = ActivityRecord.forToken(token);
+ ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r != null) {
if (DEBUG_IDLE) Slog.d(TAG, "activityIdleInternalLocked: Callers=" +
Debug.getCallers(4));
@@ -2325,13 +2418,13 @@ public final class ActivityStackSupervisor implements DisplayListener {
// Atomically retrieve all of the other things to do.
stops = processStoppingActivitiesLocked(true);
NS = stops != null ? stops.size() : 0;
- if ((NF=mFinishingActivities.size()) > 0) {
- finishes = new ArrayList<ActivityRecord>(mFinishingActivities);
+ if ((NF = mFinishingActivities.size()) > 0) {
+ finishes = new ArrayList<>(mFinishingActivities);
mFinishingActivities.clear();
}
if (mStartingUsers.size() > 0) {
- startingUsers = new ArrayList<UserStartedState>(mStartingUsers);
+ startingUsers = new ArrayList<>(mStartingUsers);
mStartingUsers.clear();
}
@@ -2340,10 +2433,12 @@ public final class ActivityStackSupervisor implements DisplayListener {
for (int i = 0; i < NS; i++) {
r = stops.get(i);
final ActivityStack stack = r.task.stack;
- if (r.finishing) {
- stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
- } else {
- stack.stopActivityLocked(r);
+ if (stack != null) {
+ if (r.finishing) {
+ stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
+ } else {
+ stack.stopActivityLocked(r);
+ }
}
}
@@ -2351,7 +2446,10 @@ public final class ActivityStackSupervisor implements DisplayListener {
// waiting for the next one to start.
for (int i = 0; i < NF; i++) {
r = finishes.get(i);
- activityRemoved |= r.task.stack.destroyActivityLocked(r, true, "finish-idle");
+ final ActivityStack stack = r.task.stack;
+ if (stack != null) {
+ activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");
+ }
}
if (!booting) {
@@ -2465,13 +2563,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target,
Bundle targetOptions) {
if (targetStack == null) {
- targetStack = getFocusedStack();
+ targetStack = mFocusedStack;
}
// Do targetStack first.
boolean result = false;
if (isFrontStack(targetStack)) {
result = targetStack.resumeTopActivityLocked(target, targetOptions);
}
+
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
@@ -2488,13 +2587,13 @@ public final class ActivityStackSupervisor implements DisplayListener {
return result;
}
- void finishTopRunningActivityLocked(ProcessRecord app) {
+ void finishTopRunningActivityLocked(ProcessRecord app, String reason) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
final int numStacks = stacks.size();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
- stack.finishTopRunningActivityLocked(app);
+ stack.finishTopRunningActivityLocked(app, reason);
}
}
}
@@ -2519,9 +2618,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
// we'll just indicate that this task returns to the home task.
task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
}
- task.stack.moveTaskToFrontLocked(task, null, options, reason);
- if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack="
- + task.stack);
+ if (task.stack == null) {
+ Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task="
+ + task + " to front. Stack is null");
+ return;
+ }
+ task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options, reason);
+ if (DEBUG_STACK) Slog.d(TAG_STACK,
+ "findTaskToMoveToFront: moved to front of stack=" + task.stack);
}
ActivityStack getStack(int stackId) {
@@ -2565,7 +2669,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
return null;
}
- ActivityContainer createActivityContainer(ActivityRecord parentActivity,
+ ActivityContainer createVirtualActivityContainer(ActivityRecord parentActivity,
IActivityContainerCallback callback) {
ActivityContainer activityContainer =
new VirtualActivityContainer(parentActivity, callback);
@@ -2596,16 +2700,85 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
- private int createStackOnDisplay(int stackId, int displayId) {
+ void resizeStackLocked(int stackId, Rect bounds) {
+ final ActivityStack stack = getStack(stackId);
+ if (stack == null) {
+ Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
+ return;
+ }
+
+ final ActivityRecord r = stack.topRunningActivityLocked(null);
+ if (r != null && !r.task.mResizeable) {
+ Slog.w(TAG, "resizeStack: top task " + r.task + " not resizeable.");
+ return;
+ }
+
+ final Configuration overrideConfig = mWindowManager.resizeStack(stackId, bounds);
+ if (stack.updateOverrideConfiguration(overrideConfig)) {
+ if (r != null) {
+ final boolean updated = stack.ensureActivityConfigurationLocked(r, 0);
+ // And we need to make sure at this point that all other activities
+ // are made visible with the correct configuration.
+ ensureActivitiesVisibleLocked(r, 0);
+ if (!updated) {
+ resumeTopActivitiesLocked(stack, null, null);
+ }
+ }
+ }
+ }
+
+ /** Makes sure the input task is in a stack with the specified bounds by either resizing the
+ * current task stack if it only has one entry, moving the task to a stack that matches the
+ * bounds, or creating a new stack with the required bounds. Also, makes the task resizeable.*/
+ void resizeTaskLocked(TaskRecord task, Rect bounds) {
+ task.mResizeable = true;
+ final ActivityStack currentStack = task.stack;
+ if (currentStack.isHomeStack()) {
+ // Can't move task off the home stack. Sorry!
+ return;
+ }
+
+ final int matchingStackId = mWindowManager.getStackIdWithBounds(bounds);
+ if (matchingStackId != -1) {
+ // There is already a stack with the right bounds!
+ if (currentStack != null && currentStack.mStackId == matchingStackId) {
+ // Nothing to do here. Already in the right stack...
+ return;
+ }
+ // Move task to stack with matching bounds.
+ moveTaskToStackLocked(task.taskId, matchingStackId, true);
+ return;
+ }
+
+ if (currentStack != null && currentStack.numTasks() == 1) {
+ // Just resize the current stack since this is the task in it.
+ resizeStackLocked(currentStack.mStackId, bounds);
+ return;
+ }
+
+ // Create new stack and move the task to it.
+ final int displayId = (currentStack != null && currentStack.mDisplayId != -1)
+ ? currentStack.mDisplayId : Display.DEFAULT_DISPLAY;
+ ActivityStack newStack = createStackOnDisplay(getNextStackId(), displayId);
+
+ if (newStack == null) {
+ Slog.e(TAG, "resizeTaskLocked: Can't create stack for task=" + task);
+ return;
+ }
+ moveTaskToStackLocked(task.taskId, newStack.mStackId, true);
+ resizeStackLocked(newStack.mStackId, bounds);
+ }
+
+ ActivityStack createStackOnDisplay(int stackId, int displayId) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay == null) {
- return -1;
+ return null;
}
ActivityContainer activityContainer = new ActivityContainer(stackId);
mActivityContainers.put(stackId, activityContainer);
activityContainer.attachToDisplayLocked(activityDisplay);
- return stackId;
+ return activityContainer.mStack;
}
int getNextStackId() {
@@ -2631,7 +2804,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack tmpStack = homeDisplayStacks.get(stackNdx);
- if (!tmpStack.isHomeStack()) {
+ if (!tmpStack.isHomeStack() && tmpStack.mFullscreen) {
stack = tmpStack;
break;
}
@@ -2642,29 +2815,29 @@ public final class ActivityStackSupervisor implements DisplayListener {
// We couldn't find a stack to restore the task to. Possible if are restoring recents
// before an application stack is created...Go ahead and create one on the default
// display.
- stack = getStack(createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY));
+ stack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY);
// Restore home stack to top.
moveHomeStack(true, "restoreRecentTask");
- if (DEBUG_RECENTS)
- Slog.v(TAG, "Created stack=" + stack + " for recents restoration.");
+ if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
+ "Created stack=" + stack + " for recents restoration.");
}
if (stack == null) {
// What does this mean??? Not sure how we would get here...
- if (DEBUG_RECENTS)
- Slog.v(TAG, "Unable to find/create stack to restore recent task=" + task);
+ if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
+ "Unable to find/create stack to restore recent task=" + task);
return false;
}
stack.addTask(task, false, false);
- if (DEBUG_RECENTS)
- Slog.v(TAG, "Added restored task=" + task + " to stack=" + stack);
+ if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
+ "Added restored task=" + task + " to stack=" + stack);
final ArrayList<ActivityRecord> activities = task.mActivities;
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
mWindowManager.addAppToken(0, r.appToken, task.taskId, stack.mStackId,
r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
+ (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0,
r.userId, r.info.configChanges, task.voiceSession != null,
r.mLaunchTaskBehind);
}
@@ -2674,6 +2847,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
void moveTaskToStackLocked(int taskId, int stackId, boolean toTop) {
final TaskRecord task = anyTaskForIdLocked(taskId);
if (task == null) {
+ Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
return;
}
final ActivityStack stack = getStack(stackId);
@@ -2681,25 +2855,27 @@ public final class ActivityStackSupervisor implements DisplayListener {
Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId);
return;
}
- task.stack.removeTask(task, "moveTaskToStack");
+ mWindowManager.moveTaskToStack(taskId, stackId, toTop);
+ if (task.stack != null) {
+ task.stack.removeTask(task, "moveTaskToStack", false /* notMoving */);
+ }
stack.addTask(task, toTop, true);
- mWindowManager.addTask(taskId, stackId, toTop);
resumeTopActivitiesLocked();
}
ActivityRecord findTaskLocked(ActivityRecord r) {
- if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + r);
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = stacks.get(stackNdx);
if (!r.isApplicationActivity() && !stack.isHomeStack()) {
- if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: (home activity) " + stack);
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping stack: (home activity) " + stack);
continue;
}
if (!stack.mActivityContainer.isEligibleForNewTasks()) {
- if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: (new task not allowed) " +
- stack);
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS,
+ "Skipping stack: (new task not allowed) " + stack);
continue;
}
final ActivityRecord ar = stack.findTaskLocked(r);
@@ -2708,7 +2884,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
}
- if (DEBUG_TASKS) Slog.d(TAG, "No task found");
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "No task found");
return null;
}
@@ -2817,7 +2993,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
if (mStoppingActivities.size() > 0) {
// Still need to tell some activities to stop; can't sleep yet.
- if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to stop "
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep still need to stop "
+ mStoppingActivities.size() + " activities");
scheduleIdleLocked();
dontSleep = true;
@@ -2825,7 +3001,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
if (mGoingToSleepActivities.size() > 0) {
// Still need to tell some activities to sleep; can't sleep yet.
- if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to sleep "
+ if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep still need to sleep "
+ mGoingToSleepActivities.size() + " activities");
dontSleep = true;
}
@@ -2933,7 +3109,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
r.mLaunchTaskBehind = false;
final TaskRecord task = r.task;
task.setLastThumbnail(task.stack.screenshotActivities(r));
- mService.addRecentTaskLocked(task);
+ mRecentTasks.addLocked(task);
mService.notifyTaskStackChangedLocked();
mWindowManager.setAppVisibility(r.appToken, false);
}
@@ -2976,16 +3152,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
// First, if we find an activity that is in the process of being destroyed,
// then we just aren't going to do anything for now; we want things to settle
// down before we try to prune more activities.
- if (r.finishing || r.state == ActivityState.DESTROYING
- || r.state == ActivityState.DESTROYED) {
+ if (r.finishing || r.state == DESTROYING || r.state == DESTROYED) {
if (DEBUG_RELEASE) Slog.d(TAG, "Abort release; already destroying: " + r);
return;
}
// Don't consider any activies that are currently not in a state where they
// can be destroyed.
- if (r.visible || !r.stopped || !r.haveState
- || r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING
- || r.state == ActivityState.PAUSED || r.state == ActivityState.STOPPING) {
+ if (r.visible || !r.stopped || !r.haveState || r.state == RESUMED || r.state == PAUSING
+ || r.state == PAUSED || r.state == STOPPING) {
if (DEBUG_RELEASE) Slog.d(TAG, "Not releasing in-use activity: " + r);
continue;
}
@@ -3024,7 +3198,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
boolean switchUserLocked(int userId, UserStartedState uss) {
- mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId());
+ mUserStackInFront.put(mCurrentUser, mFocusedStack.getStackId());
final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
mCurrentUser = userId;
@@ -3068,40 +3242,43 @@ public final class ActivityStackSupervisor implements DisplayListener {
mStartingBackgroundUsers.add(uss);
}
- final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) {
- int N = mStoppingActivities.size();
- if (N <= 0) return null;
+ /** Checks whether the userid is a profile of the current user. */
+ boolean isCurrentProfileLocked(int userId) {
+ if (userId == mCurrentUser) return true;
+ for (int i = 0; i < mService.mCurrentProfileIds.length; i++) {
+ if (mService.mCurrentProfileIds[i] == userId) return true;
+ }
+ return false;
+ }
+ final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) {
ArrayList<ActivityRecord> stops = null;
final boolean nowVisible = allResumedActivitiesVisible();
- for (int i=0; i<N; i++) {
- ActivityRecord s = mStoppingActivities.get(i);
- if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible="
- + nowVisible + " waitingVisible=" + s.waitingVisible
- + " finishing=" + s.finishing);
- if (s.waitingVisible && nowVisible) {
+ for (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord s = mStoppingActivities.get(activityNdx);
+ final boolean waitingVisible = mWaitingVisibleActivities.contains(s);
+ if (DEBUG_ALL) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible
+ + " waitingVisible=" + waitingVisible + " finishing=" + s.finishing);
+ if (waitingVisible && nowVisible) {
mWaitingVisibleActivities.remove(s);
- s.waitingVisible = false;
if (s.finishing) {
// If this activity is finishing, it is sitting on top of
// everyone else but we now know it is no longer needed...
// so get rid of it. Otherwise, we need to go through the
// normal flow and hide it once we determine that it is
// hidden by the activities in front of it.
- if (localLOGV) Slog.v(TAG, "Before stopping, can hide: " + s);
+ if (DEBUG_ALL) Slog.v(TAG, "Before stopping, can hide: " + s);
mWindowManager.setAppVisibility(s.appToken, false);
}
}
- if ((!s.waitingVisible || mService.isSleepingOrShuttingDown()) && remove) {
- if (localLOGV) Slog.v(TAG, "Ready to stop: " + s);
+ if ((!waitingVisible || mService.isSleepingOrShuttingDown()) && remove) {
+ if (DEBUG_ALL) Slog.v(TAG, "Ready to stop: " + s);
if (stops == null) {
- stops = new ArrayList<ActivityRecord>();
+ stops = new ArrayList<>();
}
stops.add(s);
- mStoppingActivities.remove(i);
- N--;
- i--;
+ mStoppingActivities.remove(activityNdx);
}
}
@@ -3109,39 +3286,34 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
void validateTopActivitiesLocked() {
- // FIXME
-/* for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = stacks.get(stackNdx);
- final ActivityRecord r = stack.topRunningActivityLocked(null);
- final ActivityState state = r == null ? ActivityState.DESTROYED : r.state;
- if (isFrontStack(stack)) {
- if (r == null) {
- Slog.e(TAG, "validateTop...: null top activity, stack=" + stack);
- } else {
- final ActivityRecord pausing = stack.mPausingActivity;
- if (pausing != null && pausing == r) {
- Slog.e(TAG, "validateTop...: top stack has pausing activity r=" + r +
- " state=" + state);
- }
- if (state != ActivityState.INITIALIZING && state != ActivityState.RESUMED) {
- Slog.e(TAG, "validateTop...: activity in front not resumed r=" + r +
- " state=" + state);
+ for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+ for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = stacks.get(stackNdx);
+ final ActivityRecord r = stack.topRunningActivityLocked(null);
+ final ActivityState state = r == null ? DESTROYED : r.state;
+ if (isFrontStack(stack)) {
+ if (r == null) Slog.e(TAG,
+ "validateTop...: null top activity, stack=" + stack);
+ else {
+ final ActivityRecord pausing = stack.mPausingActivity;
+ if (pausing != null && pausing == r) Slog.e(TAG,
+ "validateTop...: top stack has pausing activity r=" + r
+ + " state=" + state);
+ if (state != INITIALIZING && state != RESUMED) Slog.e(TAG,
+ "validateTop...: activity in front not resumed r=" + r
+ + " state=" + state);
}
- }
- } else {
- final ActivityRecord resumed = stack.mResumedActivity;
- if (resumed != null && resumed == r) {
- Slog.e(TAG, "validateTop...: back stack has resumed activity r=" + r +
- " state=" + state);
- }
- if (r != null && (state == ActivityState.INITIALIZING
- || state == ActivityState.RESUMED)) {
- Slog.e(TAG, "validateTop...: activity in back resumed r=" + r +
- " state=" + state);
+ } else {
+ final ActivityRecord resumed = stack.mResumedActivity;
+ if (resumed != null && resumed == r) Slog.e(TAG,
+ "validateTop...: back stack has resumed activity r=" + r
+ + " state=" + state);
+ if (r != null && (state == INITIALIZING || state == RESUMED)) Slog.e(TAG,
+ "validateTop...: activity in back resumed r=" + r + " state=" + state);
}
}
}
-*/
}
public void dump(PrintWriter pw, String prefix) {
@@ -3151,10 +3323,11 @@ public final class ActivityStackSupervisor implements DisplayListener {
pw.print(prefix); pw.println("mCurTaskId=" + mCurTaskId);
pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
pw.print(prefix); pw.println("mActivityContainers=" + mActivityContainers);
+ pw.print(prefix); pw.println("mLockTaskModeTasks" + mLockTaskModeTasks);
}
ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
- return getFocusedStack().getDumpActivitiesLocked(name);
+ return mFocusedStack.getDumpActivitiesLocked(name);
}
static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage,
@@ -3367,7 +3540,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0));
}
- public void handleDisplayAddedLocked(int displayId) {
+ private void handleDisplayAdded(int displayId) {
boolean newDisplay;
synchronized (mService) {
newDisplay = mActivityDisplays.get(displayId) == null;
@@ -3385,7 +3558,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
- public void handleDisplayRemovedLocked(int displayId) {
+ private void handleDisplayRemoved(int displayId) {
synchronized (mService) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay != null) {
@@ -3399,7 +3572,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
mWindowManager.onDisplayRemoved(displayId);
}
- public void handleDisplayChangedLocked(int displayId) {
+ private void handleDisplayChanged(int displayId) {
synchronized (mService) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay != null) {
@@ -3409,7 +3582,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
mWindowManager.onDisplayChanged(displayId);
}
- StackInfo getStackInfo(ActivityStack stack) {
+ private StackInfo getStackInfoLocked(ActivityStack stack) {
StackInfo info = new StackInfo();
mWindowManager.getStackBounds(stack.mStackId, info.bounds);
info.displayId = Display.DEFAULT_DISPLAY;
@@ -3435,7 +3608,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
StackInfo getStackInfoLocked(int stackId) {
ActivityStack stack = getStack(stackId);
if (stack != null) {
- return getStackInfo(stack);
+ return getStackInfoLocked(stack);
}
return null;
}
@@ -3445,60 +3618,143 @@ public final class ActivityStackSupervisor implements DisplayListener {
for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int ndx = stacks.size() - 1; ndx >= 0; --ndx) {
- list.add(getStackInfo(stacks.get(ndx)));
+ list.add(getStackInfoLocked(stacks.get(ndx)));
}
}
return list;
}
+ TaskRecord getLockedTaskLocked() {
+ final int top = mLockTaskModeTasks.size() - 1;
+ if (top >= 0) {
+ return mLockTaskModeTasks.get(top);
+ }
+ return null;
+ }
+
+ boolean isLockedTask(TaskRecord task) {
+ return mLockTaskModeTasks.contains(task);
+ }
+
+ boolean isLastLockedTask(TaskRecord task) {
+ return mLockTaskModeTasks.size() == 1 && mLockTaskModeTasks.contains(task);
+ }
+
+ void removeLockedTaskLocked(final TaskRecord task) {
+ if (mLockTaskModeTasks.remove(task) && mLockTaskModeTasks.isEmpty()) {
+ // Last one.
+ final Message lockTaskMsg = Message.obtain();
+ lockTaskMsg.arg1 = task.userId;
+ lockTaskMsg.what = LOCK_TASK_END_MSG;
+ mHandler.sendMessage(lockTaskMsg);
+ }
+ }
+
void showLockTaskToast() {
- mLockTaskNotify.showToast(mLockTaskIsLocked);
+ mLockTaskNotify.showToast(mLockTaskModeState);
}
- void setLockTaskModeLocked(TaskRecord task, boolean isLocked, String reason) {
+ void showLockTaskEscapeMessageLocked(TaskRecord task) {
+ if (mLockTaskModeTasks.contains(task)) {
+ mHandler.sendEmptyMessage(SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG);
+ }
+ }
+
+ void setLockTaskModeLocked(TaskRecord task, int lockTaskModeState, String reason) {
if (task == null) {
// Take out of lock task mode if necessary
- if (mLockTaskModeTask != null) {
- final Message lockTaskMsg = Message.obtain();
- lockTaskMsg.arg1 = mLockTaskModeTask.userId;
- lockTaskMsg.what = LOCK_TASK_END_MSG;
- mLockTaskModeTask = null;
- mHandler.sendMessage(lockTaskMsg);
+ final TaskRecord lockedTask = getLockedTaskLocked();
+ if (lockedTask != null) {
+ removeLockedTaskLocked(lockedTask);
+ if (!mLockTaskModeTasks.isEmpty()) {
+ // There are locked tasks remaining, can only finish this task, not unlock it.
+ lockedTask.performClearTaskLocked();
+ resumeTopActivitiesLocked();
+ return;
+ }
}
return;
}
+
+ // Should have already been checked, but do it again.
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
+ return;
+ }
if (isLockTaskModeViolation(task)) {
- Slog.e(TAG, "setLockTaskMode: Attempt to start a second Lock Task Mode task.");
+ Slog.e(TAG, "setLockTaskMode: Attempt to start an unauthorized lock task.");
return;
}
- mLockTaskModeTask = task;
+
+ if (mLockTaskModeTasks.isEmpty()) {
+ // First locktask.
+ final Message lockTaskMsg = Message.obtain();
+ lockTaskMsg.obj = task.intent.getComponent().getPackageName();
+ lockTaskMsg.arg1 = task.userId;
+ lockTaskMsg.what = LOCK_TASK_START_MSG;
+ lockTaskMsg.arg2 = lockTaskModeState;
+ mHandler.sendMessage(lockTaskMsg);
+ }
+ // Add it or move it to the top.
+ mLockTaskModeTasks.remove(task);
+ mLockTaskModeTasks.add(task);
+
+ if (task.mLockTaskUid == -1) {
+ task.mLockTaskUid = task.mCallingUid;
+ }
findTaskToMoveToFrontLocked(task, 0, null, reason);
resumeTopActivitiesLocked();
-
- final Message lockTaskMsg = Message.obtain();
- lockTaskMsg.obj = mLockTaskModeTask.intent.getComponent().getPackageName();
- lockTaskMsg.arg1 = mLockTaskModeTask.userId;
- lockTaskMsg.what = LOCK_TASK_START_MSG;
- lockTaskMsg.arg2 = !isLocked ? 1 : 0;
- mHandler.sendMessage(lockTaskMsg);
}
boolean isLockTaskModeViolation(TaskRecord task) {
- return mLockTaskModeTask != null && mLockTaskModeTask != task;
+ if (getLockedTaskLocked() == task) {
+ return false;
+ }
+ final int lockTaskAuth = task.mLockTaskAuth;
+ switch (lockTaskAuth) {
+ case LOCK_TASK_AUTH_DONT_LOCK:
+ return !mLockTaskModeTasks.isEmpty();
+ case LOCK_TASK_AUTH_LAUNCHABLE:
+ case LOCK_TASK_AUTH_WHITELISTED:
+ return false;
+ case LOCK_TASK_AUTH_PINNABLE:
+ // Pinnable tasks can't be launched on top of locktask tasks.
+ return !mLockTaskModeTasks.isEmpty();
+ default:
+ Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth);
+ return true;
+ }
}
- void endLockTaskModeIfTaskEnding(TaskRecord task) {
- if (mLockTaskModeTask != null && mLockTaskModeTask == task) {
- final Message lockTaskMsg = Message.obtain();
- lockTaskMsg.arg1 = mLockTaskModeTask.userId;
- lockTaskMsg.what = LOCK_TASK_END_MSG;
- mLockTaskModeTask = null;
- mHandler.sendMessage(lockTaskMsg);
+ void onLockTaskPackagesUpdatedLocked() {
+ boolean didSomething = false;
+ for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx >= 0; --taskNdx) {
+ final TaskRecord lockedTask = mLockTaskModeTasks.get(taskNdx);
+ if (lockedTask.mLockTaskMode != LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED) {
+ continue;
+ }
+ final boolean wasLaunchable = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE;
+ lockedTask.setLockTaskAuth();
+ if (wasLaunchable && lockedTask.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE) {
+ // Lost whitelisting authorization. End it now.
+ removeLockedTaskLocked(lockedTask);
+ lockedTask.performClearTaskLocked();
+ didSomething = true;
+ }
+ }
+ for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+ for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = stacks.get(stackNdx);
+ stack.onLockTaskPackagesUpdatedLocked();
+ }
+ }
+ if (didSomething) {
+ resumeTopActivitiesLocked();
}
}
- boolean isInLockTaskMode() {
- return mLockTaskModeTask != null;
+ int getLockTaskModeState() {
+ return mLockTaskModeState;
}
private final class ActivityStackSupervisorHandler extends Handler {
@@ -3565,13 +3821,13 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
} break;
case HANDLE_DISPLAY_ADDED: {
- handleDisplayAddedLocked(msg.arg1);
+ handleDisplayAdded(msg.arg1);
} break;
case HANDLE_DISPLAY_CHANGED: {
- handleDisplayChangedLocked(msg.arg1);
+ handleDisplayChanged(msg.arg1);
} break;
case HANDLE_DISPLAY_REMOVED: {
- handleDisplayRemovedLocked(msg.arg1);
+ handleDisplayRemoved(msg.arg1);
} break;
case CONTAINER_CALLBACK_VISIBILITY: {
final ActivityContainer container = (ActivityContainer) msg.obj;
@@ -3590,13 +3846,17 @@ public final class ActivityStackSupervisor implements DisplayListener {
mLockTaskNotify = new LockTaskNotify(mService.mContext);
}
mLockTaskNotify.show(true);
- mLockTaskIsLocked = msg.arg2 == 0;
+ mLockTaskModeState = msg.arg2;
if (getStatusBarService() != null) {
- int flags =
- StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK;
- if (!mLockTaskIsLocked) {
- flags ^= StatusBarManager.DISABLE_HOME
- | StatusBarManager.DISABLE_RECENT;
+ int flags = 0;
+ if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+ flags = StatusBarManager.DISABLE_MASK
+ & (~StatusBarManager.DISABLE_BACK);
+ } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+ flags = StatusBarManager.DISABLE_MASK
+ & (~StatusBarManager.DISABLE_BACK)
+ & (~StatusBarManager.DISABLE_HOME)
+ & (~StatusBarManager.DISABLE_RECENT);
}
getStatusBarService().disable(flags, mToken,
mService.mContext.getPackageName());
@@ -3630,7 +3890,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
boolean shouldLockKeyguard = Settings.Secure.getInt(
mService.mContext.getContentResolver(),
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED) != 0;
- if (!mLockTaskIsLocked && shouldLockKeyguard) {
+ if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) {
mWindowManager.lockNow(null);
mWindowManager.dismissKeyguard();
new LockPatternUtils(mService.mContext)
@@ -3641,7 +3901,15 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
} catch (RemoteException ex) {
throw new RuntimeException(ex);
+ } finally {
+ mLockTaskModeState = LOCK_TASK_MODE_NONE;
+ }
+ } break;
+ case SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG: {
+ if (mLockTaskNotify == null) {
+ mLockTaskNotify = new LockTaskNotify(mService.mContext);
}
+ mLockTaskNotify.showToast(LOCK_TASK_MODE_PINNED);
} break;
case CONTAINER_CALLBACK_TASK_LIST_EMPTY: {
final ActivityContainer container = (ActivityContainer) msg.obj;
@@ -3653,18 +3921,9 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
} break;
- case CONTAINER_TASK_LIST_EMPTY_TIMEOUT: {
- synchronized (mService) {
- Slog.w(TAG, "Timeout waiting for all activities in task to finish. " +
- msg.obj);
- final ActivityContainer container = (ActivityContainer) msg.obj;
- container.mStack.finishAllActivitiesLocked(true);
- container.onTaskListEmptyLocked();
- }
- } break;
case LAUNCH_TASK_BEHIND_COMPLETE: {
synchronized (mService) {
- ActivityRecord r = ActivityRecord.forToken((IBinder) msg.obj);
+ ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj);
if (r != null) {
handleLaunchTaskBehindCompleteLocked(r);
}
@@ -3696,14 +3955,14 @@ public final class ActivityStackSupervisor implements DisplayListener {
ActivityContainer(int stackId) {
synchronized (mService) {
mStackId = stackId;
- mStack = new ActivityStack(this);
+ mStack = new ActivityStack(this, mRecentTasks);
mIdString = "ActivtyContainer{" + mStackId + "}";
- if (DEBUG_STACK) Slog.d(TAG, "Creating " + this);
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "Creating " + this);
}
}
void attachToDisplayLocked(ActivityDisplay activityDisplay) {
- if (DEBUG_STACK) Slog.d(TAG, "attachToDisplayLocked: " + this
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "attachToDisplayLocked: " + this
+ " to display=" + activityDisplay);
mActivityDisplay = activityDisplay;
mStack.mDisplayId = activityDisplay.mDisplayId;
@@ -3735,6 +3994,13 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
@Override
+ public int getStackId() {
+ synchronized (mService) {
+ return mStackId;
+ }
+ }
+
+ @Override
public boolean injectEvent(InputEvent event) {
final long origId = Binder.clearCallingIdentity();
try {
@@ -3759,10 +4025,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
mContainerState = CONTAINER_STATE_FINISHING;
- final Message msg =
- mHandler.obtainMessage(CONTAINER_TASK_LIST_EMPTY_TIMEOUT, this);
- mHandler.sendMessageDelayed(msg, 2000);
-
long origId = Binder.clearCallingIdentity();
try {
mStack.finishAllActivitiesLocked(false);
@@ -3774,7 +4036,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
protected void detachLocked() {
- if (DEBUG_STACK) Slog.d(TAG, "detachLocked: " + this + " from display="
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "detachLocked: " + this + " from display="
+ mActivityDisplay + " Callers=" + Debug.getCallers(2));
if (mActivityDisplay != null) {
mActivityDisplay.detachActivitiesLocked(mStack);
@@ -3788,43 +4050,45 @@ public final class ActivityStackSupervisor implements DisplayListener {
@Override
public final int startActivity(Intent intent) {
mService.enforceNotIsolatedCaller("ActivityContainer.startActivity");
- int userId = mService.handleIncomingUser(Binder.getCallingPid(),
+ final int userId = mService.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), mCurrentUser, false,
ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
+
// TODO: Switch to user app stacks here.
- intent.addFlags(FORCE_NEW_TASK_FLAGS);
String mimeType = intent.getType();
- if (mimeType == null && intent.getData() != null
- && "content".equals(intent.getData().getScheme())) {
- mimeType = mService.getProviderMimeType(intent.getData(), userId);
+ final Uri data = intent.getData();
+ if (mimeType == null && data != null && "content".equals(data.getScheme())) {
+ mimeType = mService.getProviderMimeType(data, userId);
}
- return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, 0,
- 0, null, null, null, null, userId, this, null);
+ checkEmbeddedAllowedInner(userId, intent, mimeType);
+
+ intent.addFlags(FORCE_NEW_TASK_FLAGS);
+ return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null,
+ 0, 0, null, null, null, null, userId, this, null);
}
@Override
- public final int startActivityIntentSender(IIntentSender intentSender) {
+ public final int startActivityIntentSender(IIntentSender intentSender)
+ throws TransactionTooLargeException {
mService.enforceNotIsolatedCaller("ActivityContainer.startActivityIntentSender");
if (!(intentSender instanceof PendingIntentRecord)) {
throw new IllegalArgumentException("Bad PendingIntent object");
}
- return ((PendingIntentRecord)intentSender).sendInner(0, null, null, null, null, null,
- null, 0, FORCE_NEW_TASK_FLAGS, FORCE_NEW_TASK_FLAGS, null, this);
- }
-
- private void checkEmbeddedAllowedInner(Intent intent, String resolvedType) {
- int userId = mService.handleIncomingUser(Binder.getCallingPid(),
+ final int userId = mService.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), mCurrentUser, false,
ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
- if (resolvedType == null) {
- resolvedType = intent.getType();
- if (resolvedType == null && intent.getData() != null
- && "content".equals(intent.getData().getScheme())) {
- resolvedType = mService.getProviderMimeType(intent.getData(), userId);
- }
- }
+
+ final PendingIntentRecord pendingIntent = (PendingIntentRecord) intentSender;
+ checkEmbeddedAllowedInner(userId, pendingIntent.key.requestIntent,
+ pendingIntent.key.requestResolvedType);
+
+ return pendingIntent.sendInner(0, null, null, null, null, null, null, 0,
+ FORCE_NEW_TASK_FLAGS, FORCE_NEW_TASK_FLAGS, null, this);
+ }
+
+ private void checkEmbeddedAllowedInner(int userId, Intent intent, String resolvedType) {
ActivityInfo aInfo = resolveActivity(intent, resolvedType, 0, null, userId);
if (aInfo != null && (aInfo.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) {
throw new SecurityException(
@@ -3832,23 +4096,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
- /** Throw a SecurityException if allowEmbedded is not true */
- @Override
- public final void checkEmbeddedAllowed(Intent intent) {
- checkEmbeddedAllowedInner(intent, null);
- }
-
- /** Throw a SecurityException if allowEmbedded is not true */
- @Override
- public final void checkEmbeddedAllowedIntentSender(IIntentSender intentSender) {
- if (!(intentSender instanceof PendingIntentRecord)) {
- throw new IllegalArgumentException("Bad PendingIntent object");
- }
- PendingIntentRecord pendingIntent = (PendingIntentRecord) intentSender;
- checkEmbeddedAllowedInner(pendingIntent.key.requestIntent,
- pendingIntent.key.requestResolvedType);
- }
-
@Override
public IBinder asBinder() {
return this;
@@ -3897,6 +4144,9 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
void onTaskListEmptyLocked() {
+ detachLocked();
+ deleteActivityContainer(this);
+ mHandler.obtainMessage(CONTAINER_CALLBACK_TASK_LIST_EMPTY, this).sendToTarget();
}
@Override
@@ -3962,8 +4212,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
setSurfaceIfReadyLocked();
- if (DEBUG_STACK) Slog.d(TAG, "setSurface: " + this + " to display="
- + virtualActivityDisplay);
+ if (DEBUG_STACK) Slog.d(TAG_STACK,
+ "setSurface: " + this + " to display=" + virtualActivityDisplay);
}
@Override
@@ -3985,15 +4235,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
return false;
}
- void onTaskListEmptyLocked() {
- mHandler.removeMessages(CONTAINER_TASK_LIST_EMPTY_TIMEOUT, this);
- detachLocked();
- deleteActivityContainer(this);
- mHandler.obtainMessage(CONTAINER_CALLBACK_TASK_LIST_EMPTY, this).sendToTarget();
- }
-
private void setSurfaceIfReadyLocked() {
- if (DEBUG_STACK) Slog.v(TAG, "setSurfaceIfReadyLocked: mDrawn=" + mDrawn +
+ if (DEBUG_STACK) Slog.v(TAG_STACK, "setSurfaceIfReadyLocked: mDrawn=" + mDrawn +
" mContainerState=" + mContainerState + " mSurface=" + mSurface);
if (mDrawn && mSurface != null && mContainerState == CONTAINER_STATE_NO_SURFACE) {
((VirtualActivityDisplay) mActivityDisplay).setSurface(mSurface);
@@ -4036,13 +4279,13 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
void attachActivities(ActivityStack stack) {
- if (DEBUG_STACK) Slog.v(TAG, "attachActivities: attaching " + stack + " to displayId="
- + mDisplayId);
+ if (DEBUG_STACK) Slog.v(TAG_STACK,
+ "attachActivities: attaching " + stack + " to displayId=" + mDisplayId);
mStacks.add(stack);
}
void detachActivitiesLocked(ActivityStack stack) {
- if (DEBUG_STACK) Slog.v(TAG, "detachActivitiesLocked: detaching " + stack
+ if (DEBUG_STACK) Slog.v(TAG_STACK, "detachActivitiesLocked: detaching " + stack
+ " from displayId=" + mDisplayId);
mStacks.remove(stack);
}
diff --git a/services/core/java/com/android/server/am/AppBindRecord.java b/services/core/java/com/android/server/am/AppBindRecord.java
index 65273c9..df833ad 100644
--- a/services/core/java/com/android/server/am/AppBindRecord.java
+++ b/services/core/java/com/android/server/am/AppBindRecord.java
@@ -19,7 +19,6 @@ package com.android.server.am;
import android.util.ArraySet;
import java.io.PrintWriter;
-import java.util.Iterator;
/**
* An association between a service and one of its client applications.
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c0928c7..58665d7 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -16,20 +16,24 @@
package com.android.server.am;
+import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.WifiActivityEnergyInfo;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PowerManagerInternal;
import android.os.Process;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -38,15 +42,16 @@ import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BatteryStatsHelper;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.PowerProfile;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@@ -60,15 +65,51 @@ public final class BatteryStatsService extends IBatteryStats.Stub
static final String TAG = "BatteryStatsService";
static IBatteryStats sService;
-
final BatteryStatsImpl mStats;
+ final BatteryStatsHandler mHandler;
Context mContext;
- private boolean mBluetoothPendingStats;
- private BluetoothHeadset mBluetoothHeadset;
PowerManagerInternal mPowerManagerInternal;
+ class BatteryStatsHandler extends Handler implements BatteryStatsImpl.ExternalStatsSync {
+ public static final int MSG_SYNC_EXTERNAL_STATS = 1;
+ public static final int MSG_WRITE_TO_DISK = 2;
+
+ public BatteryStatsHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SYNC_EXTERNAL_STATS:
+ updateExternalStats((String)msg.obj);
+ break;
+
+ case MSG_WRITE_TO_DISK:
+ updateExternalStats("write");
+ synchronized (mStats) {
+ mStats.writeAsyncLocked();
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void scheduleSync(String reason) {
+ if (!hasMessages(MSG_SYNC_EXTERNAL_STATS)) {
+ Message msg = Message.obtain(this, MSG_SYNC_EXTERNAL_STATS, reason);
+ sendMessage(msg);
+ }
+ }
+ }
+
BatteryStatsService(File systemDir, Handler handler) {
- mStats = new BatteryStatsImpl(systemDir, handler);
+ // Our handler here will be accessing the disk, use a different thread than
+ // what the ActivityManagerService gave us (no I/O on that one!).
+ mHandler = new BatteryStatsHandler(FgThread.getHandler().getLooper());
+
+ // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
+ mStats = new BatteryStatsImpl(systemDir, handler, mHandler);
}
public void publish(Context context) {
@@ -78,6 +119,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout)
* 1000L);
+ mStats.setPowerProfile(new PowerProfile(context));
}
/**
@@ -87,12 +129,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
public void initPowerManagement() {
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mPowerManagerInternal.registerLowPowerModeObserver(this);
- mStats.noteLowPowerMode(mPowerManagerInternal.getLowPowerModeEnabled());
+ mStats.notePowerSaveMode(mPowerManagerInternal.getLowPowerModeEnabled());
(new WakeupReasonThread()).start();
}
public void shutdown() {
Slog.w("BatteryStats", "Writing battery stats before shutdown...");
+
+ updateExternalStats("shutdown");
synchronized (mStats) {
mStats.shutdownLocked();
}
@@ -110,7 +154,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
@Override
public void onLowPowerModeChanged(boolean enabled) {
synchronized (mStats) {
- mStats.noteLowPowerMode(enabled);
+ mStats.notePowerSaveMode(enabled);
}
}
@@ -123,6 +167,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
return mStats;
}
+ /**
+ * Schedules a write to disk to occur. This will cause the BatteryStatsImpl
+ * object to update with the latest info, then write to disk.
+ */
+ public void scheduleWriteToDisk() {
+ mHandler.sendEmptyMessage(BatteryStatsHandler.MSG_WRITE_TO_DISK);
+ }
+
// These are for direct use by the activity manager...
void addIsolatedUid(int isolatedUid, int appUid) {
@@ -175,7 +227,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- mStats.writeToParcel(out, 0);
+ updateExternalStats("get-stats");
+ synchronized (mStats) {
+ mStats.writeToParcel(out, 0);
+ }
byte[] data = out.marshall();
out.recycle();
return data;
@@ -187,7 +242,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
Parcel out = Parcel.obtain();
- mStats.writeToParcel(out, 0);
+ updateExternalStats("get-stats");
+ synchronized (mStats) {
+ mStats.writeToParcel(out, 0);
+ }
byte[] data = out.marshall();
out.recycle();
try {
@@ -198,6 +256,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ public boolean isCharging() {
+ synchronized (mStats) {
+ return mStats.isCharging();
+ }
+ }
+
public long computeBatteryTimeRemaining() {
synchronized (mStats) {
long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
@@ -247,6 +311,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ public void noteAlarmStart(String name, int uid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteAlarmStartLocked(name, uid);
+ }
+ }
+
+ public void noteAlarmFinish(String name, int uid) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteAlarmFinishLocked(name, uid);
+ }
+ }
+
public void noteStartWakelock(int uid, int pid, String name, String historyName, int type,
boolean unimportantForLogging) {
enforceCallingPermission();
@@ -481,6 +559,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ @Override
+ public void noteWifiRadioPowerState(int powerState, long tsNanos) {
+ enforceCallingPermission();
+
+ // There was a change in WiFi power state.
+ // Collect data now for the past activity.
+ mHandler.scheduleSync("wifi-data");
+ synchronized (mStats) {
+ mStats.noteWifiRadioPowerState(powerState, tsNanos);
+ }
+ }
+
public void noteWifiRunning(WorkSource ws) {
enforceCallingPermission();
synchronized (mStats) {
@@ -523,56 +613,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
- public void noteBluetoothOn() {
- enforceCallingPermission();
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.HEADSET);
- }
- synchronized (mStats) {
- if (mBluetoothHeadset != null) {
- mStats.noteBluetoothOnLocked();
- mStats.setBtHeadset(mBluetoothHeadset);
- } else {
- mBluetoothPendingStats = true;
- }
- }
- }
-
- private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
- new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mBluetoothHeadset = (BluetoothHeadset) proxy;
- synchronized (mStats) {
- if (mBluetoothPendingStats) {
- mStats.noteBluetoothOnLocked();
- mStats.setBtHeadset(mBluetoothHeadset);
- mBluetoothPendingStats = false;
- }
- }
- }
-
- public void onServiceDisconnected(int profile) {
- mBluetoothHeadset = null;
- }
- };
-
- public void noteBluetoothOff() {
- enforceCallingPermission();
- synchronized (mStats) {
- mBluetoothPendingStats = false;
- mStats.noteBluetoothOffLocked();
- }
- }
-
- public void noteBluetoothState(int bluetoothState) {
- enforceCallingPermission();
- synchronized (mStats) {
- mStats.noteBluetoothStateLocked(bluetoothState);
- }
- }
-
public void noteFullWifiLockAcquired(int uid) {
enforceCallingPermission();
synchronized (mStats) {
@@ -664,6 +704,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ @Override
public void noteWifiMulticastDisabledFromSource(WorkSource ws) {
enforceCallingPermission();
synchronized (mStats) {
@@ -672,10 +713,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
@Override
- public void noteNetworkInterfaceType(String iface, int type) {
+ public void noteNetworkInterfaceType(String iface, int networkType) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteNetworkInterfaceTypeLocked(iface, type);
+ mStats.noteNetworkInterfaceTypeLocked(iface, networkType);
}
}
@@ -687,6 +728,28 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ @Override
+ public void noteDeviceIdleMode(boolean enabled, boolean fromActive, boolean fromMotion) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteDeviceIdleModeLocked(enabled, fromActive, fromMotion);
+ }
+ }
+
+ public void notePackageInstalled(String pkgName, int versionCode) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.notePackageInstalledLocked(pkgName, versionCode);
+ }
+ }
+
+ public void notePackageUninstalled(String pkgName) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.notePackageUninstalledLocked(pkgName);
+ }
+ }
+
public boolean isOnBattery() {
return mStats.isOnBattery();
}
@@ -694,7 +757,22 @@ public final class BatteryStatsService extends IBatteryStats.Stub
public void setBatteryState(int status, int health, int plugType, int level,
int temp, int volt) {
enforceCallingPermission();
- mStats.setBatteryState(status, health, plugType, level, temp, volt);
+ synchronized (mStats) {
+ final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
+ if (mStats.isOnBattery() == onBattery) {
+ // The battery state has not changed, so we don't need to sync external
+ // stats immediately.
+ mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt);
+ return;
+ }
+ }
+
+ // Sync external stats first as the battery has changed states. If we don't sync
+ // immediately here, we may not collect the relevant data later.
+ updateExternalStats("battery-state");
+ synchronized (mStats) {
+ mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt);
+ }
}
public long getAwakeTimeBattery() {
@@ -751,15 +829,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub
private void dumpHelp(PrintWriter pw) {
pw.println("Battery stats (batterystats) dump options:");
- pw.println(" [--checkin] [--history] [--history-start] [--unplugged] [--charged] [-c]");
- pw.println(" [--reset] [--write] [-h] [<package.name>]");
+ pw.println(" [--checkin] [--history] [--history-start] [--charged] [-c]");
+ pw.println(" [--daily] [--reset] [--write] [--new-daily] [--read-daily] [-h] [<package.name>]");
pw.println(" --checkin: format output for a checkin report.");
pw.println(" --history: show only history data.");
pw.println(" --history-start <num>: show only history data starting at given time offset.");
- pw.println(" --unplugged: only output data since last unplugged.");
pw.println(" --charged: only output data since last charged.");
+ pw.println(" --daily: only output full daily data.");
pw.println(" --reset: reset the stats, clearing all current data.");
pw.println(" --write: force write current collected stats to disk.");
+ pw.println(" --new-daily: immediately create and write new daily stats record.");
+ pw.println(" --read-daily: read-load last written daily stats.");
pw.println(" <package.name>: optional name of package to filter output by.");
pw.println(" -h: print this help text.");
pw.println("Battery stats (batterystats) commands:");
@@ -767,7 +847,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
pw.println(" Enable or disable a running option. Option state is not saved across boots.");
pw.println(" Options are:");
pw.println(" full-history: include additional detailed events in battery history:");
- pw.println(" wake_lock_in and proc events");
+ pw.println(" wake_lock_in, alarms and proc events");
pw.println(" no-auto-reset: don't automatically reset stats when unplugged");
}
@@ -794,6 +874,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
return i;
}
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -832,22 +913,36 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if ("-c".equals(arg)) {
useCheckinFormat = true;
flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
- } else if ("--unplugged".equals(arg)) {
- flags |= BatteryStats.DUMP_UNPLUGGED_ONLY;
} else if ("--charged".equals(arg)) {
flags |= BatteryStats.DUMP_CHARGED_ONLY;
+ } else if ("--daily".equals(arg)) {
+ flags |= BatteryStats.DUMP_DAILY_ONLY;
} else if ("--reset".equals(arg)) {
synchronized (mStats) {
mStats.resetAllStatsCmdLocked();
pw.println("Battery stats reset.");
noOutput = true;
}
+ updateExternalStats("dump");
} else if ("--write".equals(arg)) {
+ updateExternalStats("dump");
synchronized (mStats) {
mStats.writeSyncLocked();
pw.println("Battery stats written.");
noOutput = true;
}
+ } else if ("--new-daily".equals(arg)) {
+ synchronized (mStats) {
+ mStats.recordDailyStatsLocked();
+ pw.println("New daily stats written.");
+ noOutput = true;
+ }
+ } else if ("--read-daily".equals(arg)) {
+ synchronized (mStats) {
+ mStats.readDailyStatsLocked();
+ pw.println("Last daily stats read.");
+ noOutput = true;
+ }
} else if ("--enable".equals(arg) || "enable".equals(arg)) {
i = doEnableOrDisable(pw, i, args, true);
if (i < 0) {
@@ -887,19 +982,28 @@ public final class BatteryStatsService extends IBatteryStats.Stub
if (noOutput) {
return;
}
- if (BatteryStatsHelper.checkWifiOnly(mContext)) {
- flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (BatteryStatsHelper.checkWifiOnly(mContext)) {
+ flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
+ }
+ // Fetch data from external sources and update the BatteryStatsImpl object with them.
+ updateExternalStats("dump");
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
+
if (reqUid >= 0) {
// By default, if the caller is only interested in a specific package, then
// we only dump the aggregated data since charged.
- if ((flags&(BatteryStats.DUMP_HISTORY_ONLY|BatteryStats.DUMP_UNPLUGGED_ONLY
- |BatteryStats.DUMP_CHARGED_ONLY)) == 0) {
+ if ((flags&(BatteryStats.DUMP_HISTORY_ONLY|BatteryStats.DUMP_CHARGED_ONLY)) == 0) {
flags |= BatteryStats.DUMP_CHARGED_ONLY;
// Also if they are doing -c, we don't want history.
flags &= ~BatteryStats.DUMP_INCLUDE_HISTORY;
}
}
+
if (useCheckinFormat) {
List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
if (isRealCheckin) {
@@ -914,7 +1018,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
in.unmarshall(raw, 0, raw.length);
in.setDataPosition(0);
BatteryStatsImpl checkinStats = new BatteryStatsImpl(
- null, mStats.mHandler);
+ null, mStats.mHandler, null);
checkinStats.readSummaryFromParcel(in);
in.recycle();
checkinStats.dumpCheckinLocked(mContext, pw, apps, flags,
@@ -944,4 +1048,113 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
}
+
+ // Objects for extracting data from external sources.
+ private final Object mExternalStatsLock = new Object();
+
+ @GuardedBy("mExternalStatsLock")
+ private IWifiManager mWifiManager;
+
+ // WiFi keeps an accumulated total of stats, unlike Bluetooth.
+ // Keep the last WiFi stats so we can compute a delta.
+ @GuardedBy("mExternalStatsLock")
+ private WifiActivityEnergyInfo mLastInfo = new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
+
+ @GuardedBy("mExternalStatsLock")
+ private WifiActivityEnergyInfo pullWifiEnergyInfoLocked() {
+ if (mWifiManager == null) {
+ mWifiManager = IWifiManager.Stub.asInterface(
+ ServiceManager.getService(Context.WIFI_SERVICE));
+ if (mWifiManager == null) {
+ return null;
+ }
+ }
+
+ try {
+ // We read the data even if we are not on battery. This is so that we keep the
+ // correct delta from when we should start reading (aka when we are on battery).
+ WifiActivityEnergyInfo info = mWifiManager.reportActivityInfo();
+ if (info != null && info.isValid()) {
+ // We will modify the last info object to be the delta, and store the new
+ // WifiActivityEnergyInfo object as our last one.
+ final WifiActivityEnergyInfo result = mLastInfo;
+ result.mTimestamp = info.getTimeStamp();
+ result.mStackState = info.getStackState();
+ result.mControllerTxTimeMs =
+ info.mControllerTxTimeMs - mLastInfo.mControllerTxTimeMs;
+ result.mControllerRxTimeMs =
+ info.mControllerRxTimeMs - mLastInfo.mControllerRxTimeMs;
+ result.mControllerEnergyUsed =
+ info.mControllerEnergyUsed - mLastInfo.mControllerEnergyUsed;
+
+ // WiFi calculates the idle time as a difference from the on time and the various
+ // Rx + Tx times. There seems to be some missing time there because this sometimes
+ // becomes negative. Just cap it at 0 and move on.
+ result.mControllerIdleTimeMs =
+ Math.max(0, info.mControllerIdleTimeMs - mLastInfo.mControllerIdleTimeMs);
+
+ if (result.mControllerTxTimeMs < 0 ||
+ result.mControllerRxTimeMs < 0) {
+ // The stats were reset by the WiFi system (which is why our delta is negative).
+ // Returns the unaltered stats.
+ result.mControllerEnergyUsed = info.mControllerEnergyUsed;
+ result.mControllerRxTimeMs = info.mControllerRxTimeMs;
+ result.mControllerTxTimeMs = info.mControllerTxTimeMs;
+ result.mControllerIdleTimeMs = info.mControllerIdleTimeMs;
+
+ Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + result);
+ }
+ mLastInfo = info;
+ return result;
+ }
+ } catch (RemoteException e) {
+ // Nothing to report, WiFi is dead.
+ }
+ return null;
+ }
+
+ @GuardedBy("mExternalStatsLock")
+ private BluetoothActivityEnergyInfo pullBluetoothEnergyInfoLocked() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ BluetoothActivityEnergyInfo info = adapter.getControllerActivityEnergyInfo(
+ BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED);
+ if (info != null && info.isValid()) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Fetches data from external sources (WiFi controller, bluetooth chipset) and updates
+ * batterystats with that information.
+ *
+ * We first grab a lock specific to this method, then once all the data has been collected,
+ * we grab the mStats lock and update the data.
+ */
+ void updateExternalStats(String reason) {
+ synchronized (mExternalStatsLock) {
+ if (mContext == null) {
+ // We haven't started yet (which means the BatteryStatsImpl object has
+ // no power profile. Don't consume data we can't compute yet.
+ return;
+ }
+
+ final WifiActivityEnergyInfo wifiEnergyInfo = pullWifiEnergyInfoLocked();
+ final BluetoothActivityEnergyInfo bluetoothEnergyInfo = pullBluetoothEnergyInfoLocked();
+ synchronized (mStats) {
+ if (mStats.mRecordAllHistory) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ mStats.addHistoryEventLocked(elapsedRealtime, uptime,
+ BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, reason, 0);
+ }
+ mStats.updateKernelWakelocksLocked();
+ mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime());
+ mStats.updateWifiStateLocked(wifiEnergyInfo);
+ mStats.updateBluetoothStateLocked(bluetoothEnergyInfo);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 9b7d0b2..a91a7ca 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -18,7 +18,9 @@ package com.android.server.am;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Date;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -27,7 +29,6 @@ import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
@@ -40,9 +41,10 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.EventLog;
-import android.util.Log;
import android.util.Slog;
+import static com.android.server.am.ActivityManagerDebugConfig.*;
+
/**
* BROADCASTS
*
@@ -50,11 +52,9 @@ import android.util.Slog;
* foreground priority, and one for normal (background-priority) broadcasts.
*/
public final class BroadcastQueue {
- static final String TAG = "BroadcastQueue";
- static final String TAG_MU = ActivityManagerService.TAG_MU;
- static final boolean DEBUG_BROADCAST = ActivityManagerService.DEBUG_BROADCAST;
- static final boolean DEBUG_BROADCAST_LIGHT = ActivityManagerService.DEBUG_BROADCAST_LIGHT;
- static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU;
+ private static final String TAG = "BroadcastQueue";
+ private static final String TAG_MU = TAG + POSTFIX_MU;
+ private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
static final int MAX_BROADCAST_SUMMARY_HISTORY
@@ -97,14 +97,27 @@ public final class BroadcastQueue {
final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<BroadcastRecord>();
/**
- * Historical data of past broadcasts, for debugging.
+ * Historical data of past broadcasts, for debugging. This is a ring buffer
+ * whose last element is at mHistoryNext.
*/
final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+ int mHistoryNext = 0;
/**
- * Summary of historical data of past broadcasts, for debugging.
+ * Summary of historical data of past broadcasts, for debugging. This is a
+ * ring buffer whose last element is at mSummaryHistoryNext.
*/
final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
+ int mSummaryHistoryNext = 0;
+
+ /**
+ * Various milestone timestamps of entries in the mBroadcastSummaryHistory ring
+ * buffer, also tracked via the mSummaryHistoryNext index. These are all in wall
+ * clock time, not elapsed.
+ */
+ final long[] mSummaryHistoryEnqueueTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+ final long[] mSummaryHistoryDispatchTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
+ final long[] mSummaryHistoryFinishTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
/**
* Set when we current have a BROADCAST_INTENT_MSG in flight.
@@ -145,7 +158,7 @@ public final class BroadcastQueue {
switch (msg.what) {
case BROADCAST_INTENT_MSG: {
if (DEBUG_BROADCAST) Slog.v(
- TAG, "Received BROADCAST_INTENT_MSG");
+ TAG_BROADCAST, "Received BROADCAST_INTENT_MSG");
processNextBroadcast(true);
} break;
case BROADCAST_TIMEOUT_MSG: {
@@ -187,16 +200,18 @@ public final class BroadcastQueue {
public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
mParallelBroadcasts.add(r);
+ r.enqueueClockTime = System.currentTimeMillis();
}
public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
mOrderedBroadcasts.add(r);
+ r.enqueueClockTime = System.currentTimeMillis();
}
public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) {
for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {
if (r.intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"***** DROPPING PARALLEL ["
+ mQueueName + "]: " + r.intent);
mParallelBroadcasts.set(i, r);
@@ -209,7 +224,7 @@ public final class BroadcastQueue {
public final boolean replaceOrderedBroadcastLocked(BroadcastRecord r) {
for (int i=mOrderedBroadcasts.size()-1; i>0; i--) {
if (r.intent.filterEquals(mOrderedBroadcasts.get(i).intent)) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"***** DROPPING ORDERED ["
+ mQueueName + "]: " + r.intent);
mOrderedBroadcasts.set(i, r);
@@ -221,7 +236,7 @@ public final class BroadcastQueue {
private final void processCurBroadcastLocked(BroadcastRecord r,
ProcessRecord app) throws RemoteException {
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + " for app " + app);
if (app.thread == null) {
throw new RemoteException();
@@ -238,7 +253,7 @@ public final class BroadcastQueue {
boolean started = false;
try {
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG,
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
"Delivering to component " + r.curComponent
+ ": " + r);
mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
@@ -246,12 +261,12 @@ public final class BroadcastQueue {
mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo),
r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
app.repProcState);
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + " DELIVERED for app " + app);
started = true;
} finally {
if (!started) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Process cur broadcast " + r + ": NOT STARTED!");
r.receiver = null;
r.curApp = null;
@@ -305,23 +320,22 @@ public final class BroadcastQueue {
r.resultExtras, r.resultAbort, false);
reschedule = true;
}
-
- r = mPendingBroadcast;
- if (r != null && r.curApp == app) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"[" + mQueueName + "] skip & discard pending app " + r);
+ r = mPendingBroadcast;
+ }
+
+ if (r != null) {
logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
- reschedule = true;
- }
- if (reschedule) {
scheduleBroadcastsLocked();
}
}
public void scheduleBroadcastsLocked() {
- if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts ["
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
+ mQueueName + "]: current="
+ mBroadcastsScheduled);
@@ -391,8 +405,7 @@ public final class BroadcastQueue {
// on. If there are background services currently starting, then we will go into a
// special state where we hold off on continuing this broadcast until they are done.
if (mService.mServices.hasBackgroundServices(r.userId)) {
- Slog.i(ActivityManagerService.TAG, "Delay finish: "
- + r.curComponent.flattenToShortString());
+ Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
r.state = BroadcastRecord.WAITING_SERVICES;
return false;
}
@@ -412,7 +425,7 @@ public final class BroadcastQueue {
if (mOrderedBroadcasts.size() > 0) {
BroadcastRecord br = mOrderedBroadcasts.get(0);
if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
- Slog.i(ActivityManagerService.TAG, "Resuming delayed broadcast");
+ Slog.i(TAG, "Resuming delayed broadcast");
br.curComponent = null;
br.state = BroadcastRecord.IDLE;
processNextBroadcast(false);
@@ -475,7 +488,7 @@ public final class BroadcastQueue {
int mode = mService.mAppOpsService.noteOperation(r.appOp,
filter.receiverList.uid, filter.packageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"App op " + r.appOp + " not allowed for broadcast to uid "
+ filter.receiverList.uid + " pkg " + filter.packageName);
skip = true;
@@ -513,14 +526,11 @@ public final class BroadcastQueue {
}
}
try {
- if (DEBUG_BROADCAST_LIGHT) {
- int seq = r.intent.getIntExtra("seq", -1);
- Slog.i(TAG, "Delivering to " + filter
- + " (seq=" + seq + "): " + r);
- }
+ if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
+ "Delivering to " + filter + " : " + r);
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
- new Intent(r.intent), r.resultCode, r.resultData,
- r.resultExtras, r.ordered, r.initialSticky, r.userId);
+ new Intent(r.intent), r.resultCode, r.resultData,
+ r.resultExtras, r.ordered, r.initialSticky, r.userId);
if (ordered) {
r.state = BroadcastRecord.CALL_DONE_RECEIVE;
}
@@ -542,7 +552,7 @@ public final class BroadcastQueue {
synchronized(mService) {
BroadcastRecord r;
- if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast ["
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
+ mQueueName + "]: "
+ mParallelBroadcasts.size() + " broadcasts, "
+ mOrderedBroadcasts.size() + " ordered broadcasts");
@@ -559,17 +569,17 @@ public final class BroadcastQueue {
r.dispatchTime = SystemClock.uptimeMillis();
r.dispatchClockTime = System.currentTimeMillis();
final int N = r.receivers.size();
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast ["
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
+ mQueueName + "] " + r);
for (int i=0; i<N; i++) {
Object target = r.receivers.get(i);
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Delivering non-ordered on [" + mQueueName + "] to registered "
+ target + ": " + r);
deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
}
addBroadcastToHistoryLocked(r);
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast ["
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
+ mQueueName + "] " + r);
}
@@ -579,11 +589,9 @@ public final class BroadcastQueue {
// broadcast, then do nothing at this point. Just in case, we
// check that the process we're waiting for still exists.
if (mPendingBroadcast != null) {
- if (DEBUG_BROADCAST_LIGHT) {
- Slog.v(TAG, "processNextBroadcast ["
- + mQueueName + "]: waiting for "
- + mPendingBroadcast.curApp);
- }
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+ "processNextBroadcast [" + mQueueName + "]: waiting for "
+ + mPendingBroadcast.curApp);
boolean isDead;
synchronized (mService.mPidsSelfLocked) {
@@ -649,7 +657,7 @@ public final class BroadcastQueue {
}
if (r.state != BroadcastRecord.IDLE) {
- if (DEBUG_BROADCAST) Slog.d(TAG,
+ if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
"processNextBroadcast("
+ mQueueName + ") called when not idle (state="
+ r.state + ")");
@@ -662,12 +670,9 @@ public final class BroadcastQueue {
// result if requested...
if (r.resultTo != null) {
try {
- if (DEBUG_BROADCAST) {
- int seq = r.intent.getIntExtra("seq", -1);
- Slog.i(TAG, "Finishing broadcast ["
- + mQueueName + "] " + r.intent.getAction()
- + " seq=" + seq + " app=" + r.callerApp);
- }
+ if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
+ "Finishing broadcast [" + mQueueName + "] "
+ + r.intent.getAction() + " app=" + r.callerApp);
performReceiveLocked(r.callerApp, r.resultTo,
new Intent(r.intent), r.resultCode,
r.resultData, r.resultExtras, false, false, r.userId);
@@ -682,11 +687,11 @@ public final class BroadcastQueue {
}
}
- if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG");
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
cancelBroadcastTimeoutLocked();
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast "
- + r);
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
+ "Finished with ordered broadcast " + r);
// ... and on to the next...
addBroadcastToHistoryLocked(r);
@@ -706,12 +711,12 @@ public final class BroadcastQueue {
if (recIdx == 0) {
r.dispatchTime = r.receiverTime;
r.dispatchClockTime = System.currentTimeMillis();
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast ["
+ if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
+ mQueueName + "] " + r);
}
if (! mPendingBroadcastTimeoutMessage) {
long timeoutTime = r.receiverTime + mTimeoutPeriod;
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Submitting BROADCAST_TIMEOUT_MSG ["
+ mQueueName + "] for " + r + " at " + timeoutTime);
setBroadcastTimeoutLocked(timeoutTime);
@@ -722,7 +727,7 @@ public final class BroadcastQueue {
// Simple case: this is a registered receiver who gets
// a direct call.
BroadcastFilter filter = (BroadcastFilter)nextReceiver;
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Delivering ordered ["
+ mQueueName + "] to registered "
+ filter + ": " + r);
@@ -730,7 +735,7 @@ public final class BroadcastQueue {
if (r.receiver == null || !r.ordered) {
// The receiver has already finished, so schedule to
// process the next one.
- if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing ["
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
+ mQueueName + "]: ordered="
+ r.ordered + " receiver=" + r.receiver);
r.state = BroadcastRecord.IDLE;
@@ -775,7 +780,8 @@ public final class BroadcastQueue {
try {
perm = AppGlobals.getPackageManager().
checkPermission(r.requiredPermission,
- info.activityInfo.applicationInfo.packageName);
+ info.activityInfo.applicationInfo.packageName,
+ UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
} catch (RemoteException e) {
perm = PackageManager.PERMISSION_DENIED;
}
@@ -793,7 +799,7 @@ public final class BroadcastQueue {
int mode = mService.mAppOpsService.noteOperation(r.appOp,
info.activityInfo.applicationInfo.uid, info.activityInfo.packageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"App op " + r.appOp + " not allowed for broadcast to uid "
+ info.activityInfo.applicationInfo.uid + " pkg "
+ info.activityInfo.packageName);
@@ -842,19 +848,18 @@ public final class BroadcastQueue {
+ info.activityInfo.packageName, e);
}
if (!isAvailable) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Skipping delivery to " + info.activityInfo.packageName
- + " / " + info.activityInfo.applicationInfo.uid
- + " : package no longer available");
- }
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+ "Skipping delivery to " + info.activityInfo.packageName + " / "
+ + info.activityInfo.applicationInfo.uid
+ + " : package no longer available");
skip = true;
}
}
if (skip) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "Skipping delivery of ordered ["
- + mQueueName + "] " + r + " for whatever reason");
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
+ "Skipping delivery of ordered [" + mQueueName + "] "
+ + r + " for whatever reason");
r.receiver = null;
r.curFilter = null;
r.state = BroadcastRecord.IDLE;
@@ -922,7 +927,7 @@ public final class BroadcastQueue {
}
// Not running -- get it started, to be executed when the app comes up.
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Need to start app ["
+ mQueueName + "] " + targetProcess + " for broadcast " + r);
if ((r.curApp=mService.startProcessLocked(targetProcess,
@@ -997,7 +1002,7 @@ public final class BroadcastQueue {
// broadcast timeout message after each receiver finishes. Instead, we set up
// an initial timeout then kick it down the road a little further as needed
// when it expires.
- if (DEBUG_BROADCAST) Slog.v(TAG,
+ if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Premature timeout ["
+ mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
+ timeoutTime);
@@ -1012,7 +1017,7 @@ public final class BroadcastQueue {
// for started services to finish as well before going on. So if we have actually
// waited long enough time timeout the broadcast, let's give up on the whole thing
// and just move on to the next.
- Slog.i(ActivityManagerService.TAG, "Waited long enough for: " + (br.curComponent != null
+ Slog.i(TAG, "Waited long enough for: " + (br.curComponent != null
? br.curComponent.flattenToShortString() : "(null)"));
br.curComponent = null;
br.state = BroadcastRecord.IDLE;
@@ -1070,18 +1075,28 @@ public final class BroadcastQueue {
}
}
+ private final int ringAdvance(int x, final int increment, final int ringSize) {
+ x += increment;
+ if (x < 0) return (ringSize - 1);
+ else if (x >= ringSize) return 0;
+ else return x;
+ }
+
private final void addBroadcastToHistoryLocked(BroadcastRecord r) {
if (r.callingUid < 0) {
// This was from a registerReceiver() call; ignore it.
return;
}
- System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1,
- MAX_BROADCAST_HISTORY-1);
r.finishTime = SystemClock.uptimeMillis();
- mBroadcastHistory[0] = r;
- System.arraycopy(mBroadcastSummaryHistory, 0, mBroadcastSummaryHistory, 1,
- MAX_BROADCAST_SUMMARY_HISTORY-1);
- mBroadcastSummaryHistory[0] = r.intent;
+
+ mBroadcastHistory[mHistoryNext] = r;
+ mHistoryNext = ringAdvance(mHistoryNext, 1, MAX_BROADCAST_HISTORY);
+
+ mBroadcastSummaryHistory[mSummaryHistoryNext] = r.intent;
+ mSummaryHistoryEnqueueTime[mSummaryHistoryNext] = r.enqueueClockTime;
+ mSummaryHistoryDispatchTime[mSummaryHistoryNext] = r.dispatchClockTime;
+ mSummaryHistoryFinishTime[mSummaryHistoryNext] = System.currentTimeMillis();
+ mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY);
}
final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
@@ -1168,11 +1183,20 @@ public final class BroadcastQueue {
int i;
boolean printed = false;
- for (i=0; i<MAX_BROADCAST_HISTORY; i++) {
- BroadcastRecord r = mBroadcastHistory[i];
+
+ i = -1;
+ int lastIndex = mHistoryNext;
+ int ringIndex = lastIndex;
+ do {
+ // increasing index = more recent entry, and we want to print the most
+ // recent first and work backwards, so we roll through the ring backwards.
+ ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
+ BroadcastRecord r = mBroadcastHistory[ringIndex];
if (r == null) {
- break;
+ continue;
}
+
+ i++; // genuine record of some sort even if we're filtering it out
if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) {
continue;
}
@@ -1200,17 +1224,33 @@ public final class BroadcastQueue {
pw.print(" extras: "); pw.println(bundle.toString());
}
}
- }
+ } while (ringIndex != lastIndex);
if (dumpPackage == null) {
+ lastIndex = ringIndex = mSummaryHistoryNext;
if (dumpAll) {
- i = 0;
printed = false;
+ i = -1;
+ } else {
+ // roll over the 'i' full dumps that have already been issued
+ for (int j = i;
+ j > 0 && ringIndex != lastIndex;) {
+ ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+ BroadcastRecord r = mBroadcastHistory[ringIndex];
+ if (r == null) {
+ continue;
+ }
+ j--;
+ }
}
- for (; i<MAX_BROADCAST_SUMMARY_HISTORY; i++) {
- Intent intent = mBroadcastSummaryHistory[i];
+ // done skipping; dump the remainder of the ring. 'i' is still the ordinal within
+ // the overall broadcast history.
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ do {
+ ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+ Intent intent = mBroadcastSummaryHistory[ringIndex];
if (intent == null) {
- break;
+ continue;
}
if (!printed) {
if (needSep) {
@@ -1224,13 +1264,17 @@ public final class BroadcastQueue {
pw.println(" ...");
break;
}
+ i++;
pw.print(" #"); pw.print(i); pw.print(": ");
pw.println(intent.toShortString(false, true, true, false));
+ pw.print(" enq="); pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
+ pw.print(" disp="); pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
+ pw.print(" fin="); pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
Bundle bundle = intent.getExtras();
if (bundle != null) {
pw.print(" extras: "); pw.println(bundle.toString());
}
- }
+ } while (ringIndex != lastIndex);
}
return needSep;
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index b2cfd7a..9a4d7a0 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -52,6 +52,7 @@ final class BroadcastRecord extends Binder {
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 enqueueClockTime; // the clock time the broadcast was enqueued
long dispatchTime; // when dispatch started on this set of receivers
long dispatchClockTime; // the clock time the dispatch started
long receiverTime; // when current receiver started for timeouts.
@@ -102,7 +103,9 @@ final class BroadcastRecord extends Binder {
pw.print(prefix); pw.print("requiredPermission="); pw.print(requiredPermission);
pw.print(" appOp="); pw.println(appOp);
}
- pw.print(prefix); pw.print("dispatchClockTime=");
+ pw.print(prefix); pw.print("enqueueClockTime=");
+ pw.print(new Date(enqueueClockTime));
+ pw.print(" dispatchClockTime=");
pw.println(new Date(dispatchClockTime));
pw.print(prefix); pw.print("dispatchTime=");
TimeUtils.formatDuration(dispatchTime, now, pw);
diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
index ec500c2..0fe9231 100644
--- a/services/core/java/com/android/server/am/CompatModePackages.java
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static com.android.server.am.ActivityManagerDebugConfig.*;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -43,8 +45,8 @@ import android.util.Slog;
import android.util.Xml;
public final class CompatModePackages {
- private final String TAG = ActivityManagerService.TAG;
- private final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "CompatModePackages" : TAG_AM;
+ private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
private final ActivityManagerService mService;
private final AtomicFile mFile;
@@ -332,7 +334,7 @@ public final class CompatModePackages {
}
try {
if (app.thread != null) {
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
+ app.processName + " new compat " + ci);
app.thread.updatePackageCompatibilityInfo(packageName, ci);
}
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index d1682b8..9dd07a9 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -21,9 +21,6 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.util.Log;
-
import java.util.HashMap;
import java.util.Map;
diff --git a/services/core/java/com/android/server/am/DumpHeapProvider.java b/services/core/java/com/android/server/am/DumpHeapProvider.java
new file mode 100644
index 0000000..a8b639e
--- /dev/null
+++ b/services/core/java/com/android/server/am/DumpHeapProvider.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 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.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+public class DumpHeapProvider extends ContentProvider {
+ static final Object sLock = new Object();
+ static File sHeapDumpJavaFile;
+
+ static public File getJavaFile() {
+ synchronized (sLock) {
+ return sHeapDumpJavaFile;
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ synchronized (sLock) {
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ File heapdumpDir = new File(systemDir, "heapdump");
+ heapdumpDir.mkdir();
+ sHeapDumpJavaFile = new File(heapdumpDir, "javaheap.bin");
+ }
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return "application/octet-stream";
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ synchronized (sLock) {
+ String path = uri.getEncodedPath();
+ final String tag = Uri.decode(path);
+ if (tag.equals("/java")) {
+ return ParcelFileDescriptor.open(sHeapDumpJavaFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ } else {
+ throw new FileNotFoundException("Invalid path for " + uri);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index c376744..9a645df 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -95,3 +95,11 @@ option java_package com.android.server.am
# Home Stack brought to front or rear
30044 am_home_stack_moved (User|1|5),(To Front|1|5),(Top Stack Id|1|5),(Focused Stack Id|1|5),(Reason|3)
+
+# Running pre boot receiver
+30045 am_pre_boot (User|1|5),(Package|3)
+
+# Report collection of global memory state
+30046 am_meminfo (CachedKb|2|2),(FreeKb|2|2),(ZramKb|2|2),(KernelKb|2|2),(NativeKb|2|2)
+# Report collection of memory used by a process
+30047 am_pss (Pid|1|5),(UID|1|5),(Process Name|3),(PssKb|2|2),(UssKb|2|2)
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
index b3777ed..afde322 100644
--- a/services/core/java/com/android/server/am/LockTaskNotify.java
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.app.ActivityManager;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
@@ -44,15 +45,20 @@ public class LockTaskNotify {
mHandler = new H();
}
- public void showToast(boolean isLocked) {
- mHandler.obtainMessage(H.SHOW_TOAST, isLocked ? 1 : 0, 0 /* Not used */).sendToTarget();
+ public void showToast(int lockTaskModeState) {
+ mHandler.obtainMessage(H.SHOW_TOAST, lockTaskModeState, 0 /* Not used */).sendToTarget();
}
- public void handleShowToast(boolean isLocked) {
- String text = mContext.getString(isLocked
- ? R.string.lock_to_app_toast_locked : R.string.lock_to_app_toast);
- if (!isLocked && mAccessibilityManager.isEnabled()) {
- text = mContext.getString(R.string.lock_to_app_toast_accessible);
+ public void handleShowToast(int lockTaskModeState) {
+ String text = null;
+ if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) {
+ text = mContext.getString(R.string.lock_to_app_toast_locked);
+ } else if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_PINNED) {
+ text = mContext.getString(mAccessibilityManager.isEnabled()
+ ? R.string.lock_to_app_toast_accessible : R.string.lock_to_app_toast);
+ }
+ if (text == null) {
+ return;
}
if (mLastToast != null) {
mLastToast.cancel();
@@ -83,7 +89,7 @@ public class LockTaskNotify {
public void handleMessage(Message msg) {
switch(msg.what) {
case SHOW_TOAST:
- handleShowToast(msg.arg1 != 0);
+ handleShowToast(msg.arg1);
break;
}
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index ffaa03d..531de46 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
import android.app.ActivityManager;
import android.app.IActivityContainer;
import android.content.IIntentSender;
@@ -26,13 +29,18 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.TransactionTooLargeException;
import android.os.UserHandle;
import android.util.Slog;
+import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
final class PendingIntentRecord extends IIntentSender.Stub {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "PendingIntentRecord" : TAG_AM;
+
final ActivityManagerService owner;
final Key key;
final int uid;
@@ -43,7 +51,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
String stringName;
String lastTagPrefix;
String lastTag;
-
+
final static class Key {
final int type;
final String packageName;
@@ -159,7 +167,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
public int hashCode() {
return hashCode;
}
-
+
public String toString() {
return "Key{" + typeName() + " pkg=" + packageName
+ " intent="
@@ -167,7 +175,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
? requestIntent.toShortString(false, true, false, false) : "<null>")
+ " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}";
}
-
+
String typeName() {
switch (type) {
case ActivityManager.INTENT_SENDER_ACTIVITY:
@@ -182,7 +190,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
return Integer.toString(type);
}
}
-
+
PendingIntentRecord(ActivityManagerService _owner, Key _k, int _u) {
owner = _owner;
key = _k;
@@ -190,39 +198,53 @@ final class PendingIntentRecord extends IIntentSender.Stub {
ref = new WeakReference<PendingIntentRecord>(this);
}
- public int send(int code, Intent intent, String resolvedType,
- IIntentReceiver finishedReceiver, String requiredPermission) {
+ public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver,
+ String requiredPermission) throws TransactionTooLargeException {
return sendInner(code, intent, resolvedType, finishedReceiver,
requiredPermission, null, null, 0, 0, 0, null, null);
}
-
- int sendInner(int code, Intent intent, String resolvedType,
- IIntentReceiver finishedReceiver, String requiredPermission,
- IBinder resultTo, String resultWho, int requestCode,
- int flagsMask, int flagsValues, Bundle options, IActivityContainer container) {
+
+ int sendInner(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver,
+ String requiredPermission, IBinder resultTo, String resultWho, int requestCode,
+ int flagsMask, int flagsValues, Bundle options, IActivityContainer container)
+ throws TransactionTooLargeException {
synchronized(owner) {
+ final ActivityContainer activityContainer = (ActivityContainer)container;
+ if (activityContainer != null && activityContainer.mParentActivity != null &&
+ activityContainer.mParentActivity.state
+ != ActivityStack.ActivityState.RESUMED) {
+ // Cannot start a child activity if the parent is not resumed.
+ return ActivityManager.START_CANCELED;
+ }
if (!canceled) {
sent = true;
if ((key.flags&PendingIntent.FLAG_ONE_SHOT) != 0) {
owner.cancelIntentSenderLocked(this, true);
canceled = true;
}
+
Intent finalIntent = key.requestIntent != null
? new Intent(key.requestIntent) : new Intent();
- if (intent != null) {
- int changes = finalIntent.fillIn(intent, key.flags);
- if ((changes&Intent.FILL_IN_DATA) == 0) {
+
+ final boolean immutable = (key.flags & PendingIntent.FLAG_IMMUTABLE) != 0;
+ if (!immutable) {
+ if (intent != null) {
+ int changes = finalIntent.fillIn(intent, key.flags);
+ if ((changes & Intent.FILL_IN_DATA) == 0) {
+ resolvedType = key.requestResolvedType;
+ }
+ } else {
resolvedType = key.requestResolvedType;
}
+ flagsMask &= ~Intent.IMMUTABLE_FLAGS;
+ flagsValues &= flagsMask;
+ finalIntent.setFlags((finalIntent.getFlags() & ~flagsMask) | flagsValues);
} else {
resolvedType = key.requestResolvedType;
}
- flagsMask &= ~Intent.IMMUTABLE_FLAGS;
- flagsValues &= flagsMask;
- finalIntent.setFlags((finalIntent.getFlags()&~flagsMask) | flagsValues);
-
+
final long origId = Binder.clearCallingIdentity();
-
+
boolean sendFinish = finishedReceiver != null;
int userId = key.userId;
if (userId == UserHandle.USER_CURRENT) {
@@ -257,13 +279,14 @@ final class PendingIntentRecord extends IIntentSender.Stub {
options, userId, container, null);
}
} catch (RuntimeException e) {
- Slog.w(ActivityManagerService.TAG,
- "Unable to send startActivity intent", e);
+ Slog.w(TAG, "Unable to send startActivity intent", e);
}
break;
case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
- key.activity.task.stack.sendActivityResultLocked(-1, key.activity,
- key.who, key.requestCode, code, finalIntent);
+ if (key.activity.task.stack != null) {
+ key.activity.task.stack.sendActivityResultLocked(-1, key.activity,
+ key.who, key.requestCode, code, finalIntent);
+ }
break;
case ActivityManager.INTENT_SENDER_BROADCAST:
try {
@@ -277,21 +300,18 @@ final class PendingIntentRecord extends IIntentSender.Stub {
sendFinish = false;
}
} catch (RuntimeException e) {
- Slog.w(ActivityManagerService.TAG,
- "Unable to send startActivity intent", e);
+ Slog.w(TAG, "Unable to send startActivity intent", e);
}
break;
case ActivityManager.INTENT_SENDER_SERVICE:
try {
- owner.startServiceInPackage(uid,
- finalIntent, resolvedType, userId);
+ owner.startServiceInPackage(uid, finalIntent, resolvedType, userId);
} catch (RuntimeException e) {
- Slog.w(ActivityManagerService.TAG,
- "Unable to send startService intent", e);
+ Slog.w(TAG, "Unable to send startService intent", e);
}
break;
}
-
+
if (sendFinish) {
try {
finishedReceiver.performReceive(new Intent(finalIntent), 0,
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c380160..cdfcd0c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
@@ -38,6 +41,8 @@ import android.view.Display;
* Activity manager code dealing with processes.
*/
final class ProcessList {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
+
// The minimum time we allow between crashes, for us to consider this
// application to be bad and stop and its services and reject broadcasts.
static final int MIN_CRASH_INTERVAL = 60*1000;
@@ -363,6 +368,12 @@ final class ProcessList {
case ActivityManager.PROCESS_STATE_TOP:
procState = "T ";
break;
+ case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
+ procState = "FS";
+ break;
+ case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+ procState = "TS";
+ break;
case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
procState = "IF";
break;
@@ -470,6 +481,8 @@ final class ProcessList {
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP
+ PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP
@@ -487,6 +500,8 @@ final class ProcessList {
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -504,6 +519,8 @@ final class ProcessList {
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -521,6 +538,8 @@ final class ProcessList {
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -538,6 +557,8 @@ final class ProcessList {
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -633,8 +654,7 @@ final class ProcessList {
LocalSocketAddress.Namespace.RESERVED));
sLmkdOutputStream = sLmkdSocket.getOutputStream();
} catch (IOException ex) {
- Slog.w(ActivityManagerService.TAG,
- "lowmemorykiller daemon socket open failed");
+ Slog.w(TAG, "lowmemorykiller daemon socket open failed");
sLmkdSocket = null;
return false;
}
@@ -659,8 +679,7 @@ final class ProcessList {
sLmkdOutputStream.write(buf.array(), 0, buf.position());
return;
} catch (IOException ex) {
- Slog.w(ActivityManagerService.TAG,
- "Error writing to lowmemorykiller socket");
+ Slog.w(TAG, "Error writing to lowmemorykiller socket");
try {
sLmkdSocket.close();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a6c616a..29e14f8 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,7 +16,12 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.EventLog;
import android.util.Slog;
import com.android.internal.app.ProcessStats;
@@ -48,6 +53,8 @@ import java.util.ArrayList;
* is currently running.
*/
final class ProcessRecord {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessRecord" : TAG_AM;
+
private final BatteryStatsImpl mBatteryStats; // where to collect runtime statistics
final ApplicationInfo info; // all about the first app in the process
final boolean isolated; // true if this is a special isolated process
@@ -83,10 +90,10 @@ final class ProcessRecord {
int curSchedGroup; // Currently desired scheduling class
int setSchedGroup; // Last set to background scheduling class
int trimMemoryLevel; // Last selected memory trimming level
- int curProcState = -1; // Currently computed process state: ActivityManager.PROCESS_STATE_*
- int repProcState = -1; // Last reported process state
- int setProcState = -1; // Last set process state in process tracker
- int pssProcState = -1; // The proc state we are currently requesting pss for
+ int curProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state
+ int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state
+ int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker
+ int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for
boolean serviceb; // Process currently is on the service B list
boolean serviceHighRam; // We are forcing to service B list due to its RAM use
boolean setIsForeground; // Running foreground UI when last set?
@@ -248,8 +255,9 @@ final class ProcessRecord {
pw.println();
pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
pw.print(" lruSeq="); pw.print(lruSeq);
- pw.print(" lastPss="); pw.print(lastPss);
- pw.print(" lastCachedPss="); pw.println(lastCachedPss);
+ pw.print(" lastPss="); DebugUtils.printSizeValue(pw, lastPss*1024);
+ pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, lastCachedPss*1024);
+ pw.println();
pw.print(prefix); pw.print("cached="); pw.print(cached);
pw.print(" empty="); pw.println(empty);
if (serviceb) {
@@ -418,7 +426,7 @@ final class ProcessRecord {
tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList);
origBase.makeInactive();
}
- baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
+ baseProcessTracker = tracker.getProcessStateLocked(info.packageName, uid,
info.versionCode, processName);
baseProcessTracker.makeActive();
for (int i=0; i<pkgList.size(); i++) {
@@ -426,7 +434,7 @@ final class ProcessRecord {
if (holder.state != null && holder.state != origBase) {
holder.state.makeInactive();
}
- holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
+ holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), uid,
info.versionCode, processName);
if (holder.state != baseProcessTracker) {
holder.state.makeActive();
@@ -470,7 +478,7 @@ final class ProcessRecord {
}
return false;
}
-
+
public void stopFreezingAllLocked() {
int i = activities.size();
while (i > 0) {
@@ -478,7 +486,7 @@ final class ProcessRecord {
activities.get(i).stopFreezingScreenLocked(true);
}
}
-
+
public void unlinkDeathRecipient() {
if (deathRecipient != null && thread != null) {
thread.asBinder().unlinkToDeath(deathRecipient, 0);
@@ -522,8 +530,7 @@ final class ProcessRecord {
void kill(String reason, boolean noisy) {
if (!killedByAm) {
if (noisy) {
- Slog.i(ActivityManagerService.TAG, "Killing " + toShortString() + " (adj " + setAdj
- + "): " + reason);
+ Slog.i(TAG, "Killing " + toShortString() + " (adj " + setAdj + "): " + reason);
}
EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
Process.killProcessQuiet(pid);
@@ -617,7 +624,7 @@ final class ProcessRecord {
versionCode);
if (baseProcessTracker != null) {
holder.state = tracker.getProcessStateLocked(
- pkg, info.uid, versionCode, processName);
+ pkg, uid, versionCode, processName);
pkgList.put(pkg, holder);
if (holder.state != baseProcessTracker) {
holder.state.makeActive();
@@ -664,7 +671,7 @@ final class ProcessRecord {
}
pkgList.clear();
ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
- info.packageName, info.uid, info.versionCode, processName);
+ info.packageName, uid, info.versionCode, processName);
ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
info.versionCode);
holder.state = ps;
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 55aec65..9634dff 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -445,14 +445,14 @@ public final class ProcessStatsService extends IProcessStats.Stub {
mAm.mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.PACKAGE_USAGE_STATS, null);
Parcel current = Parcel.obtain();
+ synchronized (mAm) {
+ long now = SystemClock.uptimeMillis();
+ mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+ mProcessStats.mTimePeriodEndUptime = now;
+ mProcessStats.writeToParcel(current, now, 0);
+ }
mWriteLock.lock();
try {
- synchronized (mAm) {
- long now = SystemClock.uptimeMillis();
- mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
- mProcessStats.mTimePeriodEndUptime = now;
- mProcessStats.writeToParcel(current, now, 0);
- }
if (historic != null) {
ArrayList<String> files = getCommittedFiles(0, false, true);
if (files != null) {
@@ -476,18 +476,18 @@ public final class ProcessStatsService extends IProcessStats.Stub {
public ParcelFileDescriptor getStatsOverTime(long minTime) {
mAm.mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.PACKAGE_USAGE_STATS, null);
+ Parcel current = Parcel.obtain();
+ long curTime;
+ synchronized (mAm) {
+ long now = SystemClock.uptimeMillis();
+ mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+ mProcessStats.mTimePeriodEndUptime = now;
+ mProcessStats.writeToParcel(current, now, 0);
+ curTime = mProcessStats.mTimePeriodEndRealtime
+ - mProcessStats.mTimePeriodStartRealtime;
+ }
mWriteLock.lock();
try {
- Parcel current = Parcel.obtain();
- long curTime;
- synchronized (mAm) {
- long now = SystemClock.uptimeMillis();
- mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
- mProcessStats.mTimePeriodEndUptime = now;
- mProcessStats.writeToParcel(current, now, 0);
- curTime = mProcessStats.mTimePeriodEndRealtime
- - mProcessStats.mTimePeriodStartRealtime;
- }
if (curTime < minTime) {
// Need to add in older stats to reach desired time.
ArrayList<String> files = getCommittedFiles(0, false, true);
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
new file mode 100644
index 0000000..3a20ded
--- /dev/null
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) 2014 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 static com.android.server.am.ActivityManagerDebugConfig.*;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+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.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+
+/**
+ * Class for managing the recent tasks list.
+ */
+class RecentTasks extends ArrayList<TaskRecord> {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
+ private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
+ private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
+
+ // Maximum number recent bitmaps to keep in memory.
+ private static final int MAX_RECENT_BITMAPS = 3;
+
+ // Activity manager service.
+ private final ActivityManagerService mService;
+
+ // Mainly to avoid object recreation on multiple calls.
+ private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
+ private final HashMap<ComponentName, ActivityInfo> tmpAvailActCache = new HashMap<>();
+ private final HashMap<String, ApplicationInfo> tmpAvailAppCache = new HashMap<>();
+ private final ActivityInfo tmpActivityInfo = new ActivityInfo();
+ private final ApplicationInfo tmpAppInfo = new ApplicationInfo();
+
+ RecentTasks(ActivityManagerService service) {
+ mService = service;
+ }
+
+ TaskRecord taskForIdLocked(int id) {
+ final int recentsCount = size();
+ for (int i = 0; i < recentsCount; i++) {
+ TaskRecord tr = get(i);
+ if (tr.taskId == id) {
+ return tr;
+ }
+ }
+ return null;
+ }
+
+ /** Remove recent tasks for a user. */
+ void removeTasksForUserLocked(int userId) {
+ if(userId <= 0) {
+ Slog.i(TAG, "Can't remove recent task on user " + userId);
+ return;
+ }
+
+ for (int i = size() - 1; i >= 0; --i) {
+ TaskRecord tr = get(i);
+ if (tr.userId == userId) {
+ if(DEBUG_TASKS) Slog.i(TAG_TASKS,
+ "remove RecentTask " + tr + " when finishing user" + userId);
+ remove(i);
+ tr.removedFromRecents();
+ }
+ }
+
+ // Remove tasks from persistent storage.
+ mService.notifyTaskPersisterLocked(null, true);
+ }
+
+ /**
+ * Update the recent tasks lists: make sure tasks should still be here (their
+ * applications / activities still exist), update their availability, fix-up ordering
+ * of affiliations.
+ */
+ void cleanupLocked(int userId) {
+ int recentsCount = size();
+ if (recentsCount == 0) {
+ // Happens when called from the packagemanager broadcast before boot,
+ // or just any empty list.
+ return;
+ }
+
+ final IPackageManager pm = AppGlobals.getPackageManager();
+ final int[] users = (userId == UserHandle.USER_ALL)
+ ? mService.getUsersLocked() : new int[] { userId };
+ for (int userIdx = 0; userIdx < users.length; userIdx++) {
+ final int user = users[userIdx];
+ recentsCount = size() - 1;
+ for (int i = recentsCount; i >= 0; i--) {
+ TaskRecord task = get(i);
+ if (task.userId != user) {
+ // Only look at tasks for the user ID of interest.
+ continue;
+ }
+ if (task.autoRemoveRecents && task.getTopActivity() == null) {
+ // This situation is broken, and we should just get rid of it now.
+ remove(i);
+ task.removedFromRecents();
+ Slog.w(TAG, "Removing auto-remove without activity: " + task);
+ continue;
+ }
+ // Check whether this activity is currently available.
+ if (task.realActivity != null) {
+ ActivityInfo ai = tmpAvailActCache.get(task.realActivity);
+ if (ai == null) {
+ try {
+ ai = pm.getActivityInfo(task.realActivity,
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS, user);
+ } catch (RemoteException e) {
+ // Will never happen.
+ continue;
+ }
+ if (ai == null) {
+ ai = tmpActivityInfo;
+ }
+ tmpAvailActCache.put(task.realActivity, ai);
+ }
+ if (ai == tmpActivityInfo) {
+ // This could be either because the activity no longer exists, or the
+ // app is temporarily gone. For the former we want to remove the recents
+ // entry; for the latter we want to mark it as unavailable.
+ ApplicationInfo app = tmpAvailAppCache.get(task.realActivity.getPackageName());
+ if (app == null) {
+ try {
+ app = pm.getApplicationInfo(task.realActivity.getPackageName(),
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS, user);
+ } catch (RemoteException e) {
+ // Will never happen.
+ continue;
+ }
+ if (app == null) {
+ app = tmpAppInfo;
+ }
+ tmpAvailAppCache.put(task.realActivity.getPackageName(), app);
+ }
+ if (app == tmpAppInfo || (app.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
+ // Doesn't exist any more! Good-bye.
+ remove(i);
+ task.removedFromRecents();
+ Slog.w(TAG, "Removing no longer valid recent: " + task);
+ continue;
+ } else {
+ // Otherwise just not available for now.
+ if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
+ "Making recent unavailable: " + task);
+ task.isAvailable = false;
+ }
+ } else {
+ if (!ai.enabled || !ai.applicationInfo.enabled
+ || (ai.applicationInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
+ if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
+ "Making recent unavailable: " + task
+ + " (enabled=" + ai.enabled + "/" + ai.applicationInfo.enabled
+ + " flags=" + Integer.toHexString(ai.applicationInfo.flags)
+ + ")");
+ task.isAvailable = false;
+ } else {
+ if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS,
+ "Making recent available: " + task);
+ task.isAvailable = true;
+ }
+ }
+ }
+ }
+ }
+
+ // Verify the affiliate chain for each task.
+ int i = 0;
+ recentsCount = size();
+ while (i < recentsCount) {
+ i = processNextAffiliateChainLocked(i);
+ }
+ // recent tasks are now in sorted, affiliated order.
+ }
+
+ private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) {
+ int recentsCount = size();
+ TaskRecord top = task;
+ int topIndex = taskIndex;
+ while (top.mNextAffiliate != null && topIndex > 0) {
+ top = top.mNextAffiliate;
+ topIndex--;
+ }
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at "
+ + topIndex + " from intial " + taskIndex);
+ // Find the end of the chain, doing a sanity check along the way.
+ boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
+ int endIndex = topIndex;
+ TaskRecord prev = top;
+ while (endIndex < recentsCount) {
+ TaskRecord cur = get(endIndex);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @"
+ + endIndex + " " + cur);
+ if (cur == top) {
+ // Verify start of the chain.
+ if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": first task has next affiliate: " + prev);
+ sane = false;
+ break;
+ }
+ } else {
+ // Verify middle of the chain's next points back to the one before.
+ if (cur.mNextAffiliate != prev
+ || cur.mNextAffiliateTaskId != prev.taskId) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": middle task " + cur + " @" + endIndex
+ + " has bad next affiliate "
+ + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId
+ + ", expected " + prev);
+ sane = false;
+ break;
+ }
+ }
+ if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) {
+ // Chain ends here.
+ if (cur.mPrevAffiliate != null) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": last task " + cur + " has previous affiliate "
+ + cur.mPrevAffiliate);
+ sane = false;
+ }
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex);
+ break;
+ } else {
+ // Verify middle of the chain's prev points to a valid item.
+ if (cur.mPrevAffiliate == null) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": task " + cur + " has previous affiliate "
+ + cur.mPrevAffiliate + " but should be id "
+ + cur.mPrevAffiliate);
+ sane = false;
+ break;
+ }
+ }
+ if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": task " + cur + " has affiliated id "
+ + cur.mAffiliatedTaskId + " but should be "
+ + task.mAffiliatedTaskId);
+ sane = false;
+ break;
+ }
+ prev = cur;
+ endIndex++;
+ if (endIndex >= recentsCount) {
+ Slog.wtf(TAG, "Bad chain ran off index " + endIndex
+ + ": last task " + prev);
+ sane = false;
+ break;
+ }
+ }
+ if (sane) {
+ if (endIndex < taskIndex) {
+ Slog.wtf(TAG, "Bad chain @" + endIndex
+ + ": did not extend to task " + task + " @" + taskIndex);
+ sane = false;
+ }
+ }
+ if (sane) {
+ // All looks good, we can just move all of the affiliated tasks
+ // to the top.
+ for (int i=topIndex; i<=endIndex; i++) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task
+ + " from " + i + " to " + (i-topIndex));
+ TaskRecord cur = remove(i);
+ add(i - topIndex, cur);
+ }
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex
+ + " to " + endIndex);
+ return true;
+ }
+
+ // Whoops, couldn't do it.
+ return false;
+ }
+
+ final void addLocked(TaskRecord task) {
+ final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
+ || task.mNextAffiliateTaskId != INVALID_TASK_ID
+ || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
+
+ int recentsCount = size();
+ // Quick case: never add voice sessions.
+ if (task.voiceSession != null) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "addRecent: not adding voice interaction " + task);
+ return;
+ }
+ // Another quick case: check if the top-most recent task is the same.
+ if (!isAffiliated && recentsCount > 0 && get(0) == task) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task);
+ return;
+ }
+ // Another quick case: check if this is part of a set of affiliated
+ // tasks that are at the top.
+ if (isAffiliated && recentsCount > 0 && task.inRecents
+ && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + get(0)
+ + " at top when adding " + task);
+ return;
+ }
+
+ boolean needAffiliationFix = false;
+
+ // Slightly less quick case: the task is already in recents, so all we need
+ // to do is move it.
+ if (task.inRecents) {
+ int taskIndex = indexOf(task);
+ if (taskIndex >= 0) {
+ if (!isAffiliated) {
+ // Simple case: this is not an affiliated task, so we just move it to the front.
+ remove(taskIndex);
+ add(0, task);
+ mService.notifyTaskPersisterLocked(task, false);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
+ + " from " + taskIndex);
+ return;
+ } else {
+ // More complicated: need to keep all affiliated tasks together.
+ if (moveAffiliatedTasksToFront(task, taskIndex)) {
+ // All went well.
+ return;
+ }
+
+ // Uh oh... something bad in the affiliation chain, try to rebuild
+ // everything and then go through our general path of adding a new task.
+ needAffiliationFix = true;
+ }
+ } else {
+ Slog.wtf(TAG, "Task with inRecent not in recents: " + task);
+ needAffiliationFix = true;
+ }
+ }
+
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task);
+ trimForTaskLocked(task, true);
+
+ recentsCount = size();
+ final int maxRecents = ActivityManager.getMaxRecentTasksStatic();
+ while (recentsCount >= maxRecents) {
+ final TaskRecord tr = remove(recentsCount - 1);
+ tr.removedFromRecents();
+ recentsCount--;
+ }
+ task.inRecents = true;
+ if (!isAffiliated || needAffiliationFix) {
+ // If this is a simple non-affiliated task, or we had some failure trying to
+ // handle it as part of an affilated task, then just place it at the top.
+ add(0, task);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task);
+ } else if (isAffiliated) {
+ // If this is a new affiliated task, then move all of the affiliated tasks
+ // to the front and insert this new one.
+ TaskRecord other = task.mNextAffiliate;
+ if (other == null) {
+ other = task.mPrevAffiliate;
+ }
+ if (other != null) {
+ int otherIndex = indexOf(other);
+ if (otherIndex >= 0) {
+ // Insert new task at appropriate location.
+ int taskIndex;
+ if (other == task.mNextAffiliate) {
+ // We found the index of our next affiliation, which is who is
+ // before us in the list, so add after that point.
+ taskIndex = otherIndex+1;
+ } else {
+ // We found the index of our previous affiliation, which is who is
+ // after us in the list, so add at their position.
+ taskIndex = otherIndex;
+ }
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "addRecent: new affiliated task added at " + taskIndex + ": " + task);
+ add(taskIndex, task);
+
+ // Now move everything to the front.
+ if (moveAffiliatedTasksToFront(task, taskIndex)) {
+ // All went well.
+ return;
+ }
+
+ // Uh oh... something bad in the affiliation chain, try to rebuild
+ // everything and then go through our general path of adding a new task.
+ needAffiliationFix = true;
+ } else {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "addRecent: couldn't find other affiliation " + other);
+ needAffiliationFix = true;
+ }
+ } else {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+ "addRecent: adding affiliated task without next/prev:" + task);
+ needAffiliationFix = true;
+ }
+ }
+
+ if (needAffiliationFix) {
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations");
+ cleanupLocked(task.userId);
+ }
+ }
+
+ /**
+ * If needed, remove oldest existing entries in recents that are for the same kind
+ * of task as the given one.
+ */
+ int trimForTaskLocked(TaskRecord task, boolean doTrim) {
+ int recentsCount = size();
+ final Intent intent = task.intent;
+ final boolean document = intent != null && intent.isDocument();
+
+ int maxRecents = task.maxRecents - 1;
+ for (int i = 0; i < recentsCount; i++) {
+ final TaskRecord tr = get(i);
+ if (task != tr) {
+ if (task.userId != tr.userId) {
+ continue;
+ }
+ if (i > MAX_RECENT_BITMAPS) {
+ tr.freeLastThumbnail();
+ }
+ final Intent trIntent = tr.intent;
+ if ((task.affinity == null || !task.affinity.equals(tr.affinity)) &&
+ (intent == null || !intent.filterEquals(trIntent))) {
+ continue;
+ }
+ final boolean trIsDocument = trIntent != null && trIntent.isDocument();
+ if (document && trIsDocument) {
+ // These are the same document activity (not necessarily the same doc).
+ if (maxRecents > 0) {
+ --maxRecents;
+ continue;
+ }
+ // Hit the maximum number of documents for this task. Fall through
+ // and remove this document from recents.
+ } else if (document || trIsDocument) {
+ // Only one of these is a document. Not the droid we're looking for.
+ continue;
+ }
+ }
+
+ if (!doTrim) {
+ // If the caller is not actually asking for a trim, just tell them we reached
+ // a point where the trim would happen.
+ return i;
+ }
+
+ // Either task and tr are the same or, their affinities match or their intents match
+ // and neither of them is a document, or they are documents using the same activity
+ // and their maxRecents has been reached.
+ tr.disposeThumbnail();
+ remove(i);
+ if (task != tr) {
+ tr.removedFromRecents();
+ }
+ i--;
+ recentsCount--;
+ if (task.intent == null) {
+ // If the new recent task we are adding is not fully
+ // specified, then replace it with the existing recent task.
+ task = tr;
+ }
+ mService.notifyTaskPersisterLocked(tr, false);
+ }
+
+ return -1;
+ }
+
+ // Sort by taskId
+ private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() {
+ @Override
+ public int compare(TaskRecord lhs, TaskRecord rhs) {
+ return rhs.taskId - lhs.taskId;
+ }
+ };
+
+ // Extract the affiliates of the chain containing recent at index start.
+ private int processNextAffiliateChainLocked(int start) {
+ final TaskRecord startTask = get(start);
+ final int affiliateId = startTask.mAffiliatedTaskId;
+
+ // Quick identification of isolated tasks. I.e. those not launched behind.
+ if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null &&
+ startTask.mNextAffiliate == null) {
+ // There is still a slim chance that there are other tasks that point to this task
+ // and that the chain is so messed up that this task no longer points to them but
+ // the gain of this optimization outweighs the risk.
+ startTask.inRecents = true;
+ return start + 1;
+ }
+
+ // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents.
+ mTmpRecents.clear();
+ for (int i = size() - 1; i >= start; --i) {
+ final TaskRecord task = get(i);
+ if (task.mAffiliatedTaskId == affiliateId) {
+ remove(i);
+ mTmpRecents.add(task);
+ }
+ }
+
+ // Sort them all by taskId. That is the order they were create in and that order will
+ // always be correct.
+ Collections.sort(mTmpRecents, sTaskRecordComparator);
+
+ // Go through and fix up the linked list.
+ // The first one is the end of the chain and has no next.
+ final TaskRecord first = mTmpRecents.get(0);
+ first.inRecents = true;
+ if (first.mNextAffiliate != null) {
+ Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate);
+ first.setNextAffiliate(null);
+ mService.notifyTaskPersisterLocked(first, false);
+ }
+ // Everything in the middle is doubly linked from next to prev.
+ final int tmpSize = mTmpRecents.size();
+ for (int i = 0; i < tmpSize - 1; ++i) {
+ final TaskRecord next = mTmpRecents.get(i);
+ final TaskRecord prev = mTmpRecents.get(i + 1);
+ if (next.mPrevAffiliate != prev) {
+ Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate +
+ " setting prev=" + prev);
+ next.setPrevAffiliate(prev);
+ mService.notifyTaskPersisterLocked(next, false);
+ }
+ if (prev.mNextAffiliate != next) {
+ Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate +
+ " setting next=" + next);
+ prev.setNextAffiliate(next);
+ mService.notifyTaskPersisterLocked(prev, false);
+ }
+ prev.inRecents = true;
+ }
+ // The last one is the beginning of the list and has no prev.
+ final TaskRecord last = mTmpRecents.get(tmpSize - 1);
+ if (last.mPrevAffiliate != null) {
+ Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate);
+ last.setPrevAffiliate(null);
+ mService.notifyTaskPersisterLocked(last, false);
+ }
+
+ // Insert the group back into mRecentTasks at start.
+ addAll(start, mTmpRecents);
+ mTmpRecents.clear();
+
+ // Let the caller know where we left off.
+ return start + tmpSize;
+ }
+
+}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 1cb1bb8..f7d241e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -47,10 +47,15 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
/**
* A running application service.
*/
final class ServiceRecord extends Binder {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ServiceRecord" : TAG_AM;
+
// Maximum number of delivery attempts before giving up.
static final int MAX_DELIVERY_COUNT = 3;
@@ -458,7 +463,7 @@ final class ServiceRecord extends Binder {
appInfo.packageName, null));
PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0,
runningIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- localForegroundNoti.color = ams.mContext.getResources().getColor(
+ localForegroundNoti.color = ams.mContext.getColor(
com.android.internal
.R.color.system_notification_accent_color);
localForegroundNoti.setLatestEventInfo(ctx,
@@ -487,8 +492,7 @@ final class ServiceRecord extends Binder {
appUid, appPid, null, localForegroundId, localForegroundNoti,
outId, userId);
} catch (RuntimeException e) {
- Slog.w(ActivityManagerService.TAG,
- "Error showing notification for service", e);
+ Slog.w(TAG, "Error showing notification for service", e);
// If it gave us a garbage notification, it doesn't
// get to be foreground.
ams.setServiceForeground(name, ServiceRecord.this,
@@ -517,8 +521,7 @@ final class ServiceRecord extends Binder {
inm.cancelNotificationWithTag(localPackageName, null,
localForegroundId, userId);
} catch (RuntimeException e) {
- Slog.w(ActivityManagerService.TAG,
- "Error canceling notification for service", e);
+ Slog.w(TAG, "Error canceling notification for service", e);
} catch (RemoteException e) {
}
}
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 24c723f..318cd45 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -99,6 +99,7 @@ public class TaskPersister {
private final ActivityManagerService mService;
private final ActivityStackSupervisor mStackSupervisor;
+ private final RecentTasks mRecentTasks;
/** Value determines write delay mode as follows:
* < 0 We are Flushing. No delays between writes until the image queue is drained and all
@@ -140,7 +141,8 @@ public class TaskPersister {
// tasks.
private long mExpiredTasksCleanupTime = Long.MAX_VALUE;
- TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
+ TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
+ RecentTasks recentTasks) {
sTasksDir = new File(systemDir, TASKS_DIRNAME);
if (!sTasksDir.exists()) {
if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
@@ -161,12 +163,14 @@ public class TaskPersister {
mStackSupervisor = stackSupervisor;
mService = stackSupervisor.mService;
-
+ mRecentTasks = recentTasks;
mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
}
void startPersisting() {
- mLazyTaskWriterThread.start();
+ if (!mLazyTaskWriterThread.isAlive()) {
+ mLazyTaskWriterThread.start();
+ }
}
private void removeThumbnails(TaskRecord task) {
@@ -725,10 +729,9 @@ public class TaskPersister {
// {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just
// adding to the back of the list.
int spaceLeft =
- ActivityManager.getMaxRecentTasksStatic()
- - mService.mRecentTasks.size();
+ ActivityManager.getMaxRecentTasksStatic() - mRecentTasks.size();
if (spaceLeft >= tasks.size()) {
- mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks);
+ mRecentTasks.addAll(mRecentTasks.size(), tasks);
for (int k = tasks.size() - 1; k >= 0; k--) {
// Persist new tasks.
wakeup(tasks.get(k), false);
@@ -941,10 +944,9 @@ public class TaskPersister {
if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
persistentTaskIds.clear();
synchronized (mService) {
- final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
- if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks);
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final TaskRecord task = tasks.get(taskNdx);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + mRecentTasks);
+ for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) {
+ final TaskRecord task = mRecentTasks.get(taskNdx);
if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
" persistable=" + task.isPersistable);
if ((task.isPersistable || task.inRecents)
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 60f8a48..f3b4516 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,7 +16,13 @@
package com.android.server.am;
-import static com.android.server.am.ActivityManagerService.TAG;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+import static com.android.server.am.ActivityManagerDebugConfig.*;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
@@ -54,6 +60,10 @@ import java.io.PrintWriter;
import java.util.ArrayList;
final class TaskRecord {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM;
+ private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
+ private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
+
static final String ATTR_TASKID = "task_id";
private static final String TAG_INTENT = "intent";
private static final String TAG_AFFINITYINTENT = "affinity_intent";
@@ -79,6 +89,7 @@ final class TaskRecord {
private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
private static final String ATTR_CALLING_UID = "calling_uid";
private static final String ATTR_CALLING_PACKAGE = "calling_package";
+ private static final String ATTR_RESIZEABLE = "resizeable";
private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
@@ -109,10 +120,26 @@ final class TaskRecord {
String stringName; // caching of toString() result.
int userId; // user for which this task was created
- int creatorUid; // The app uid that originally created the task
int numFullscreen; // Number of fullscreen activities.
+ boolean mResizeable; // Activities in the task resizeable. Based on the resizable setting of
+ // the root activity.
+ int mLockTaskMode; // Which tasklock mode to launch this task in. One of
+ // ActivityManager.LOCK_TASK_LAUNCH_MODE_*
+ /** Can't be put in lockTask mode. */
+ final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
+ /** Can enter lockTask with user approval if not already in lockTask. */
+ final static int LOCK_TASK_AUTH_PINNABLE = 1;
+ /** Starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing lockTask task. */
+ final static int LOCK_TASK_AUTH_LAUNCHABLE = 2;
+ /** Enters LOCK_TASK_MODE_LOCKED via startLockTask(), enters LOCK_TASK_MODE_PINNED from
+ * Overview. Can start over existing lockTask task. */
+ final static int LOCK_TASK_AUTH_WHITELISTED = 3;
+ int mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE;
+
+ int mLockTaskUid = -1; // The uid of the application that called startLockTask().
+
// This represents the last resolved activity values for this task
// NOTE: This value needs to be persisted with each task
TaskDescription lastTaskDescription = new TaskDescription();
@@ -176,7 +203,9 @@ final class TaskRecord {
voiceSession = _voiceSession;
voiceInteractor = _voiceInteractor;
isAvailable = true;
- mActivities = new ArrayList<ActivityRecord>();
+ mActivities = new ArrayList<>();
+ mCallingUid = info.applicationInfo.uid;
+ mCallingPackage = info.packageName;
setIntent(_intent, info);
}
@@ -191,13 +220,13 @@ final class TaskRecord {
voiceSession = null;
voiceInteractor = null;
isAvailable = true;
- mActivities = new ArrayList<ActivityRecord>();
+ mActivities = new ArrayList<>();
+ mCallingUid = info.applicationInfo.uid;
+ mCallingPackage = info.packageName;
setIntent(_intent, info);
taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
isPersistable = true;
- mCallingUid = info.applicationInfo.uid;
- mCallingPackage = info.packageName;
// Clamp to [1, max].
maxRecents = Math.min(Math.max(info.maxRecents, 1),
ActivityManager.getMaxAppRecentsLimitStatic());
@@ -206,19 +235,17 @@ final class TaskRecord {
mTaskToReturnTo = HOME_ACTIVITY_TYPE;
userId = UserHandle.getUserId(info.applicationInfo.uid);
lastTaskDescription = _taskDescription;
- mCallingUid = info.applicationInfo.uid;
- mCallingPackage = info.packageName;
}
- TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent,
- String _affinity, String _rootAffinity, ComponentName _realActivity,
- ComponentName _origActivity, boolean _rootWasReset, boolean _autoRemoveRecents,
- boolean _askedCompatMode, int _taskType, int _userId, int _effectiveUid,
- String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime,
- long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity,
- TaskDescription _lastTaskDescription, int taskAffiliation,
- int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid,
- String callingPackage) {
+ private TaskRecord(ActivityManagerService service, int _taskId, Intent _intent,
+ Intent _affinityIntent, String _affinity, String _rootAffinity,
+ ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
+ boolean _autoRemoveRecents, boolean _askedCompatMode, int _taskType, int _userId,
+ int _effectiveUid, String _lastDescription, ArrayList<ActivityRecord> activities,
+ long _firstActiveTime, long _lastActiveTime, long lastTimeMoved,
+ boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
+ int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor,
+ int callingUid, String callingPackage, boolean resizeable) {
mService = service;
mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
TaskPersister.IMAGE_EXTENSION;
@@ -227,7 +254,7 @@ final class TaskRecord {
intent = _intent;
affinityIntent = _affinityIntent;
affinity = _affinity;
- rootAffinity = _affinity;
+ rootAffinity = _rootAffinity;
voiceSession = null;
voiceInteractor = null;
realActivity = _realActivity;
@@ -253,6 +280,7 @@ final class TaskRecord {
mNextAffiliateTaskId = nextTaskId;
mCallingUid = callingUid;
mCallingPackage = callingPackage;
+ mResizeable = resizeable;
}
void touchActiveTime() {
@@ -268,9 +296,9 @@ final class TaskRecord {
/** Sets the original intent, and the calling uid and package. */
void setIntent(ActivityRecord r) {
- setIntent(r.intent, r.info);
mCallingUid = r.launchedFromUid;
mCallingPackage = r.launchedFromPackage;
+ setIntent(r.intent, r.info);
}
/** Sets the original intent, _without_ updating the calling uid or package. */
@@ -303,8 +331,7 @@ final class TaskRecord {
_intent.setSourceBounds(null);
}
}
- if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
- "Setting Intent of " + this + " to " + _intent);
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Setting Intent of " + this + " to " + _intent);
intent = _intent;
realActivity = _intent != null ? _intent.getComponent() : null;
origActivity = null;
@@ -316,7 +343,7 @@ final class TaskRecord {
targetIntent.setComponent(targetComponent);
targetIntent.setSelector(null);
targetIntent.setSourceBounds(null);
- if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS,
"Setting Intent of " + this + " to target " + targetIntent);
intent = targetIntent;
realActivity = targetComponent;
@@ -339,8 +366,8 @@ final class TaskRecord {
if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
// If the activity itself has requested auto-remove, then just always do it.
autoRemoveRecents = true;
- } else if ((intentFlags & (Intent.FLAG_ACTIVITY_NEW_DOCUMENT
- | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)) == Intent.FLAG_ACTIVITY_NEW_DOCUMENT) {
+ } else if ((intentFlags & (FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS))
+ == FLAG_ACTIVITY_NEW_DOCUMENT) {
// If the caller has not asked for the document to be retained, then we may
// want to turn on auto-remove, depending on whether the target has set its
// own document launch mode.
@@ -352,13 +379,14 @@ final class TaskRecord {
} else {
autoRemoveRecents = false;
}
+ mResizeable = info.resizeable;
+ mLockTaskMode = info.lockTaskLaunchMode;
+ setLockTaskAuth();
}
void setTaskToReturnTo(int taskToReturnTo) {
- if (IGNORE_RETURN_TO_RECENTS && taskToReturnTo == RECENTS_ACTIVITY_TYPE) {
- taskToReturnTo = HOME_ACTIVITY_TYPE;
- }
- mTaskToReturnTo = taskToReturnTo;
+ mTaskToReturnTo = (IGNORE_RETURN_TO_RECENTS && taskToReturnTo == RECENTS_ACTIVITY_TYPE)
+ ? HOME_ACTIVITY_TYPE : taskToReturnTo;
}
int getTaskToReturnTo() {
@@ -492,10 +520,12 @@ final class TaskRecord {
}
ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = mActivities.get(activityNdx);
- if (!r.finishing && r != notTop && stack.okToShowLocked(r)) {
- return r;
+ if (stack != null) {
+ for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = mActivities.get(activityNdx);
+ if (!r.finishing && r != notTop && stack.okToShowLocked(r)) {
+ return r;
+ }
}
}
return null;
@@ -610,8 +640,8 @@ final class TaskRecord {
mActivities.remove(activityNdx);
--activityNdx;
--numActivities;
- } else if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear",
- false)) {
+ } else if (stack.finishActivityLocked(
+ r, Activity.RESULT_CANCELED, null, "clear-task-index", false)) {
--activityNdx;
--numActivities;
}
@@ -658,8 +688,8 @@ final class TaskRecord {
if (opts != null) {
ret.updateOptionsLocked(opts);
}
- if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear",
- false)) {
+ if (stack != null && stack.finishActivityLocked(
+ r, Activity.RESULT_CANCELED, null, "clear-task-stack", false)) {
--activityNdx;
--numActivities;
}
@@ -671,8 +701,10 @@ final class TaskRecord {
if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE
&& (launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) {
if (!ret.finishing) {
- stack.finishActivityLocked(ret, Activity.RESULT_CANCELED, null,
- "clear", false);
+ if (stack != null) {
+ stack.finishActivityLocked(
+ ret, Activity.RESULT_CANCELED, null, "clear-task-top", false);
+ }
return null;
}
}
@@ -702,6 +734,53 @@ final class TaskRecord {
performClearTaskAtIndexLocked(0);
}
+ private boolean isPrivileged() {
+ final ProcessRecord proc = mService.mProcessNames.get(mCallingPackage, mCallingUid);
+ if (proc != null) {
+ return (proc.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
+ }
+ return false;
+ }
+
+ void setLockTaskAuth() {
+ switch (mLockTaskMode) {
+ case LOCK_TASK_LAUNCH_MODE_DEFAULT:
+ mLockTaskAuth = isLockTaskWhitelistedLocked() ?
+ LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
+ break;
+
+ case LOCK_TASK_LAUNCH_MODE_NEVER:
+ mLockTaskAuth = isPrivileged() ?
+ LOCK_TASK_AUTH_DONT_LOCK : LOCK_TASK_AUTH_PINNABLE;
+ break;
+
+ case LOCK_TASK_LAUNCH_MODE_ALWAYS:
+ mLockTaskAuth = isPrivileged() ?
+ LOCK_TASK_AUTH_LAUNCHABLE: LOCK_TASK_AUTH_PINNABLE;
+ break;
+
+ case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED:
+ mLockTaskAuth = isLockTaskWhitelistedLocked() ?
+ LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
+ break;
+ }
+ }
+
+ boolean isLockTaskWhitelistedLocked() {
+ if (mCallingPackage == null) {
+ return false;
+ }
+ String[] packages = mService.mLockTaskPackages.get(userId);
+ if (packages == null) {
+ return false;
+ }
+ for (int i = packages.length - 1; i >= 0; --i) {
+ if (mCallingPackage.equals(packages[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
boolean isHomeTask() {
return taskType == HOME_ACTIVITY_TYPE;
}
@@ -807,7 +886,7 @@ final class TaskRecord {
}
void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
- if (ActivityManagerService.DEBUG_RECENTS) Slog.i(TAG, "Saving task=" + this);
+ if (DEBUG_RECENTS) Slog.i(TAG_RECENTS, "Saving task=" + this);
out.attribute(null, ATTR_TASKID, String.valueOf(taskId));
if (realActivity != null) {
@@ -850,6 +929,7 @@ final class TaskRecord {
out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId));
out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid));
out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage);
+ out.attribute(null, ATTR_RESIZEABLE, String.valueOf(mResizeable));
if (affinityIntent != null) {
out.startTag(null, TAG_AFFINITYINTENT);
@@ -866,7 +946,8 @@ final class TaskRecord {
for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
- ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) &&
+ ((r.intent.getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT
+ | FLAG_ACTIVITY_RETAIN_IN_RECENTS) == FLAG_ACTIVITY_NEW_DOCUMENT) &&
activityNdx > 0) {
// Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET).
break;
@@ -911,6 +992,7 @@ final class TaskRecord {
int nextTaskId = INVALID_TASK_ID;
int callingUid = -1;
String callingPackage = "";
+ boolean resizeable = false;
for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
final String attrName = in.getAttributeName(attrNdx);
@@ -964,6 +1046,8 @@ final class TaskRecord {
callingUid = Integer.valueOf(attrValue);
} else if (ATTR_CALLING_PACKAGE.equals(attrName)) {
callingPackage = attrValue;
+ } else if (ATTR_RESIZEABLE.equals(attrName)) {
+ resizeable = Boolean.valueOf(attrValue);
} else {
Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
}
@@ -993,13 +1077,11 @@ final class TaskRecord {
}
}
}
-
if (!hasRootAffinity) {
rootAffinity = affinity;
} else if ("@".equals(rootAffinity)) {
rootAffinity = null;
}
-
if (effectiveUid <= 0) {
Intent checkIntent = intent != null ? intent : affinityIntent;
effectiveUid = 0;
@@ -1025,13 +1107,13 @@ final class TaskRecord {
autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
- callingUid, callingPackage);
+ callingUid, callingPackage, resizeable);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
activities.get(activityNdx).task = task;
}
- if (ActivityManagerService.DEBUG_RECENTS) Slog.d(TAG, "Restored task=" + task);
+ if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Restored task=" + task);
return task;
}
@@ -1121,6 +1203,7 @@ final class TaskRecord {
pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription);
}
pw.print(prefix); pw.print("hasBeenVisible="); pw.print(hasBeenVisible);
+ pw.print(" mResizeable="); pw.print(mResizeable);
pw.print(" firstActiveTime="); pw.print(lastActiveTime);
pw.print(" lastActiveTime="); pw.print(lastActiveTime);
pw.print(" (inactive for ");
diff --git a/services/core/java/com/android/server/am/UriPermission.java b/services/core/java/com/android/server/am/UriPermission.java
index 650a837..6e371c1 100644
--- a/services/core/java/com/android/server/am/UriPermission.java
+++ b/services/core/java/com/android/server/am/UriPermission.java
@@ -19,7 +19,6 @@ package com.android.server.am;
import android.content.Intent;
import android.os.UserHandle;
import android.util.ArraySet;
-import android.util.Log;
import android.util.Slog;
import com.android.server.am.ActivityManagerService.GrantUri;
diff --git a/services/core/java/com/android/server/am/UriPermissionOwner.java b/services/core/java/com/android/server/am/UriPermissionOwner.java
index ae83940..28344df 100644
--- a/services/core/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/core/java/com/android/server/am/UriPermissionOwner.java
@@ -17,7 +17,6 @@
package com.android.server.am;
import android.content.Intent;
-import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.util.ArraySet;
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 36263ec..5a66f4a 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -19,6 +19,8 @@ package com.android.server.am;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -26,6 +28,7 @@ import android.view.WindowManager;
import android.widget.TextView;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
/**
* Dialog to show when a user switch it about to happen. The intent is to snapshot the screen
@@ -37,8 +40,14 @@ final class UserSwitchingDialog extends AlertDialog
implements ViewTreeObserver.OnWindowShownListener {
private static final String TAG = "ActivityManagerUserSwitchingDialog";
+ // Time to wait for the onWindowShown() callback before continuing the user switch
+ private static final int WINDOW_SHOWN_TIMEOUT_MS = 3000;
+
private final ActivityManagerService mService;
private final int mUserId;
+ private static final int MSG_START_USER = 1;
+ @GuardedBy("this")
+ private boolean mStartedUser;
public UserSwitchingDialog(ActivityManagerService service, Context context,
int userId, String userName, boolean aboveSystem) {
@@ -73,15 +82,40 @@ final class UserSwitchingDialog extends AlertDialog
if (decorView != null) {
decorView.getViewTreeObserver().addOnWindowShownListener(this);
}
+ // Add a timeout as a safeguard, in case a race in screen on/off causes the window
+ // callback to never come.
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_USER),
+ WINDOW_SHOWN_TIMEOUT_MS);
}
@Override
public void onWindowShown() {
// Slog.v(TAG, "onWindowShown called");
- mService.startUserInForeground(mUserId, this);
- final View decorView = getWindow().getDecorView();
- if (decorView != null) {
- decorView.getViewTreeObserver().removeOnWindowShownListener(this);
+ startUser();
+ }
+
+ void startUser() {
+ synchronized (this) {
+ if (!mStartedUser) {
+ mService.startUserInForeground(mUserId, this);
+ mStartedUser = true;
+ final View decorView = getWindow().getDecorView();
+ if (decorView != null) {
+ decorView.getViewTreeObserver().removeOnWindowShownListener(this);
+ }
+ mHandler.removeMessages(MSG_START_USER);
+ }
}
}
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_USER:
+ startUser();
+ break;
+ }
+ }
+ };
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
new file mode 100644
index 0000000..6b56279
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -0,0 +1,5960 @@
+/*
+ * 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.audio;
+
+import static android.Manifest.permission.REMOTE_AUDIO_PLAYBACK;
+import static android.media.AudioManager.RINGER_MODE_NORMAL;
+import static android.media.AudioManager.RINGER_MODE_SILENT;
+import static android.media.AudioManager.RINGER_MODE_VIBRATE;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
+import android.app.KeyguardManager;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+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.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.database.ContentObserver;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPlaybackClient;
+import android.hardware.hdmi.HdmiTvClient;
+import android.hardware.usb.UsbManager;
+import android.media.AudioAttributes;
+import android.media.AudioDevicePort;
+import android.media.AudioSystem;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioManagerInternal;
+import android.media.AudioPort;
+import android.media.AudioRoutesInfo;
+import android.media.IAudioFocusDispatcher;
+import android.media.IAudioRoutesObserver;
+import android.media.IAudioService;
+import android.media.IRemoteControlDisplay;
+import android.media.IRingtonePlayer;
+import android.media.IVolumeController;
+import android.media.MediaPlayer;
+import android.media.SoundPool;
+import android.media.VolumePolicy;
+import android.media.MediaPlayer.OnCompletionListener;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.audiopolicy.AudioPolicy;
+import android.media.audiopolicy.AudioPolicyConfig;
+import android.media.audiopolicy.IAudioPolicyCallback;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.view.KeyEvent;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.util.XmlUtils;
+import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * The implementation of the volume manager service.
+ * <p>
+ * This implementation focuses on delivering a responsive UI. Most methods are
+ * asynchronous to external calls. For example, the task of setting a volume
+ * will update our internal state, but in a separate thread will set the system
+ * volume and later persist to the database. Similarly, setting the ringer mode
+ * will update the state and broadcast a change and in a separate thread later
+ * persist the ringer mode.
+ *
+ * @hide
+ */
+public class AudioService extends IAudioService.Stub {
+
+ private static final String TAG = "AudioService";
+
+ /** Debug audio mode */
+ protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG);
+
+ /** Debug audio policy feature */
+ protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG);
+
+ /** Debug volumes */
+ protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG);
+
+ /** debug calls to devices APIs */
+ protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG);
+
+ /** How long to delay before persisting a change in volume/ringer mode. */
+ private static final int PERSIST_DELAY = 500;
+
+ /** How long to delay after a volume down event before unmuting a stream */
+ private static final int UNMUTE_STREAM_DELAY = 350;
+
+ /**
+ * Only used in the result from {@link #checkForRingerModeChange(int, int, int)}
+ */
+ private static final int FLAG_ADJUST_VOLUME = 1;
+
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private final AppOpsManager mAppOps;
+
+ // the platform type affects volume and silent mode behavior
+ private final int mPlatformType;
+
+ private boolean isPlatformVoice() {
+ return mPlatformType == AudioSystem.PLATFORM_VOICE;
+ }
+
+ private boolean isPlatformTelevision() {
+ return mPlatformType == AudioSystem.PLATFORM_TELEVISION;
+ }
+
+ /** The controller for the volume UI. */
+ private final VolumeController mVolumeController = new VolumeController();
+ private final ControllerService mControllerService = new ControllerService();
+
+ // sendMsg() flags
+ /** If the msg is already queued, replace it with this one. */
+ private static final int SENDMSG_REPLACE = 0;
+ /** If the msg is already queued, ignore this one and leave the old. */
+ private static final int SENDMSG_NOOP = 1;
+ /** If the msg is already queued, queue this one and leave the old. */
+ private static final int SENDMSG_QUEUE = 2;
+
+ // AudioHandler messages
+ private static final int MSG_SET_DEVICE_VOLUME = 0;
+ private static final int MSG_PERSIST_VOLUME = 1;
+ private static final int MSG_PERSIST_RINGER_MODE = 3;
+ private static final int MSG_MEDIA_SERVER_DIED = 4;
+ private static final int MSG_PLAY_SOUND_EFFECT = 5;
+ private static final int MSG_BTA2DP_DOCK_TIMEOUT = 6;
+ private static final int MSG_LOAD_SOUND_EFFECTS = 7;
+ private static final int MSG_SET_FORCE_USE = 8;
+ private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
+ private static final int MSG_SET_ALL_VOLUMES = 10;
+ private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 11;
+ private static final int MSG_REPORT_NEW_ROUTES = 12;
+ private static final int MSG_SET_FORCE_BT_A2DP_USE = 13;
+ private static final int MSG_CHECK_MUSIC_ACTIVE = 14;
+ private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17;
+ private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18;
+ private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19;
+ private static final int MSG_UNLOAD_SOUND_EFFECTS = 20;
+ private static final int MSG_SYSTEM_READY = 21;
+ private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22;
+ private static final int MSG_PERSIST_MICROPHONE_MUTE = 23;
+ private static final int MSG_UNMUTE_STREAM = 24;
+ // start of messages handled under wakelock
+ // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
+ // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
+ private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100;
+ private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101;
+ private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102;
+ // end of messages handled under wakelock
+
+ private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
+ // Timeout for connection to bluetooth headset service
+ private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
+
+ /** @see AudioSystemThread */
+ private AudioSystemThread mAudioSystemThread;
+ /** @see AudioHandler */
+ private AudioHandler mAudioHandler;
+ /** @see VolumeStreamState */
+ private VolumeStreamState[] mStreamStates;
+ private SettingsObserver mSettingsObserver;
+
+ private int mMode = AudioSystem.MODE_NORMAL;
+ // protects mRingerMode
+ private final Object mSettingsLock = new Object();
+
+ private SoundPool mSoundPool;
+ private final Object mSoundEffectsLock = new Object();
+ private static final int NUM_SOUNDPOOL_CHANNELS = 4;
+
+ /* Sound effect file names */
+ private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
+ private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>();
+
+ /* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to
+ * file index in SOUND_EFFECT_FILES[] (first column) and indicating if effect
+ * uses soundpool (second column) */
+ private final int[][] SOUND_EFFECT_FILES_MAP = new int[AudioManager.NUM_SOUND_EFFECTS][2];
+
+ /** Maximum volume index values for audio streams */
+ private static int[] MAX_STREAM_VOLUME = new int[] {
+ 5, // STREAM_VOICE_CALL
+ 7, // STREAM_SYSTEM
+ 7, // STREAM_RING
+ 15, // STREAM_MUSIC
+ 7, // STREAM_ALARM
+ 7, // STREAM_NOTIFICATION
+ 15, // STREAM_BLUETOOTH_SCO
+ 7, // STREAM_SYSTEM_ENFORCED
+ 15, // STREAM_DTMF
+ 15 // STREAM_TTS
+ };
+
+ /** Minimum volume index values for audio streams */
+ private static int[] MIN_STREAM_VOLUME = new int[] {
+ 1, // STREAM_VOICE_CALL
+ 0, // STREAM_SYSTEM
+ 0, // STREAM_RING
+ 0, // STREAM_MUSIC
+ 0, // STREAM_ALARM
+ 0, // STREAM_NOTIFICATION
+ 1, // STREAM_BLUETOOTH_SCO
+ 0, // STREAM_SYSTEM_ENFORCED
+ 0, // STREAM_DTMF
+ 0 // STREAM_TTS
+ };
+
+ /* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings
+ * of another stream: This avoids multiplying the volume settings for hidden
+ * stream types that follow other stream behavior for volume settings
+ * NOTE: do not create loops in aliases!
+ * Some streams alias to different streams according to device category (phone or tablet) or
+ * use case (in call vs off call...). See updateStreamVolumeAlias() for more details.
+ * mStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device
+ * (phone), STREAM_VOLUME_ALIAS_TELEVISION for a television or set-top box and
+ * STREAM_VOLUME_ALIAS_DEFAULT for other devices (e.g. tablets).*/
+ private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
+ AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
+ AudioSystem.STREAM_RING, // STREAM_SYSTEM
+ AudioSystem.STREAM_RING, // STREAM_RING
+ AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
+ AudioSystem.STREAM_ALARM, // STREAM_ALARM
+ AudioSystem.STREAM_RING, // STREAM_NOTIFICATION
+ AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
+ AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
+ AudioSystem.STREAM_RING, // STREAM_DTMF
+ AudioSystem.STREAM_MUSIC // STREAM_TTS
+ };
+ private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
+ AudioSystem.STREAM_MUSIC, // STREAM_VOICE_CALL
+ AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM
+ AudioSystem.STREAM_MUSIC, // STREAM_RING
+ AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
+ AudioSystem.STREAM_MUSIC, // STREAM_ALARM
+ AudioSystem.STREAM_MUSIC, // STREAM_NOTIFICATION
+ AudioSystem.STREAM_MUSIC, // STREAM_BLUETOOTH_SCO
+ AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM_ENFORCED
+ AudioSystem.STREAM_MUSIC, // STREAM_DTMF
+ AudioSystem.STREAM_MUSIC // STREAM_TTS
+ };
+ private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
+ AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
+ AudioSystem.STREAM_RING, // STREAM_SYSTEM
+ AudioSystem.STREAM_RING, // STREAM_RING
+ AudioSystem.STREAM_MUSIC, // STREAM_MUSIC
+ AudioSystem.STREAM_ALARM, // STREAM_ALARM
+ AudioSystem.STREAM_RING, // STREAM_NOTIFICATION
+ AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
+ AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
+ AudioSystem.STREAM_RING, // STREAM_DTMF
+ AudioSystem.STREAM_MUSIC // STREAM_TTS
+ };
+ private int[] mStreamVolumeAlias;
+
+ /**
+ * Map AudioSystem.STREAM_* constants to app ops. This should be used
+ * after mapping through mStreamVolumeAlias.
+ */
+ private static final int[] STREAM_VOLUME_OPS = new int[] {
+ AppOpsManager.OP_AUDIO_VOICE_VOLUME, // STREAM_VOICE_CALL
+ AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_SYSTEM
+ AppOpsManager.OP_AUDIO_RING_VOLUME, // STREAM_RING
+ AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_MUSIC
+ AppOpsManager.OP_AUDIO_ALARM_VOLUME, // STREAM_ALARM
+ AppOpsManager.OP_AUDIO_NOTIFICATION_VOLUME, // STREAM_NOTIFICATION
+ AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME, // STREAM_BLUETOOTH_SCO
+ AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_SYSTEM_ENFORCED
+ AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_DTMF
+ AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_TTS
+ };
+
+ private final boolean mUseFixedVolume;
+
+ private final AudioSystem.ErrorCallback mAudioSystemCallback = new AudioSystem.ErrorCallback() {
+ public void onError(int error) {
+ switch (error) {
+ case AudioSystem.AUDIO_STATUS_SERVER_DIED:
+ sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED,
+ SENDMSG_NOOP, 0, 0, null, 0);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ /**
+ * Current ringer mode from one of {@link AudioManager#RINGER_MODE_NORMAL},
+ * {@link AudioManager#RINGER_MODE_SILENT}, or
+ * {@link AudioManager#RINGER_MODE_VIBRATE}.
+ */
+ // protected by mSettingsLock
+ private int mRingerMode; // internal ringer mode, affects muting of underlying streams
+ private int mRingerModeExternal = -1; // reported ringer mode to outside clients (AudioManager)
+
+ /** @see System#MODE_RINGER_STREAMS_AFFECTED */
+ private int mRingerModeAffectedStreams = 0;
+
+ // Streams currently muted by ringer mode
+ private int mRingerModeMutedStreams;
+
+ /** Streams that can be muted. Do not resolve to aliases when checking.
+ * @see System#MUTE_STREAMS_AFFECTED */
+ private int mMuteAffectedStreams;
+
+ /**
+ * NOTE: setVibrateSetting(), getVibrateSetting(), shouldVibrate() are deprecated.
+ * mVibrateSetting is just maintained during deprecation period but vibration policy is
+ * now only controlled by mHasVibrator and mRingerMode
+ */
+ private int mVibrateSetting;
+
+ // Is there a vibrator
+ private final boolean mHasVibrator;
+
+ // Broadcast receiver for device connections intent broadcasts
+ private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
+
+ // Devices currently connected
+ // Use makeDeviceListKey() to make a unique key for this list.
+ private class DeviceListSpec {
+ int mDeviceType;
+ String mDeviceName;
+ String mDeviceAddress;
+
+ public DeviceListSpec(int deviceType, String deviceName, String deviceAddress) {
+ mDeviceType = deviceType;
+ mDeviceName = deviceName;
+ mDeviceAddress = deviceAddress;
+ }
+
+ public String toString() {
+ return "[type:0x" + Integer.toHexString(mDeviceType) + " name:" + mDeviceName
+ + " address:" + mDeviceAddress + "]";
+ }
+ }
+
+ // Generate a unique key for the mConnectedDevices List by composing the device "type"
+ // and the "address" associated with a specific instance of that device type
+ private String makeDeviceListKey(int device, String deviceAddress) {
+ return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
+ }
+
+ private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>();
+
+ // Forced device usage for communications
+ private int mForcedUseForComm;
+
+ // List of binder death handlers for setMode() client processes.
+ // The last process to have called setMode() is at the top of the list.
+ private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
+
+ // List of clients having issued a SCO start request
+ private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>();
+
+ // BluetoothHeadset API to control SCO connection
+ private BluetoothHeadset mBluetoothHeadset;
+
+ // Bluetooth headset device
+ private BluetoothDevice mBluetoothHeadsetDevice;
+
+ // Indicate if SCO audio connection is currently active and if the initiator is
+ // audio service (internal) or bluetooth headset (external)
+ private int mScoAudioState;
+ // SCO audio state is not active
+ private static final int SCO_STATE_INACTIVE = 0;
+ // SCO audio activation request waiting for headset service to connect
+ private static final int SCO_STATE_ACTIVATE_REQ = 1;
+ // SCO audio state is active or starting due to a request from AudioManager API
+ private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
+ // SCO audio deactivation request waiting for headset service to connect
+ private static final int SCO_STATE_DEACTIVATE_REQ = 5;
+
+ // SCO audio state is active due to an action in BT handsfree (either voice recognition or
+ // in call audio)
+ private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
+ // Deactivation request for all SCO connections (initiated by audio mode change)
+ // waiting for headset service to connect
+ private static final int SCO_STATE_DEACTIVATE_EXT_REQ = 4;
+
+ // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
+ // originated from an app targeting an API version before JB MR2 and raw audio after that.
+ private int mScoAudioMode;
+ // SCO audio mode is undefined
+ private static final int SCO_MODE_UNDEFINED = -1;
+ // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
+ private static final int SCO_MODE_VIRTUAL_CALL = 0;
+ // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
+ private static final int SCO_MODE_RAW = 1;
+ // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
+ private static final int SCO_MODE_VR = 2;
+
+ private static final int SCO_MODE_MAX = 2;
+
+ // Current connection state indicated by bluetooth headset
+ private int mScoConnectionState;
+
+ // true if boot sequence has been completed
+ private boolean mSystemReady;
+ // listener for SoundPool sample load completion indication
+ private SoundPoolCallback mSoundPoolCallBack;
+ // thread for SoundPool listener
+ private SoundPoolListenerThread mSoundPoolListenerThread;
+ // message looper for SoundPool listener
+ private Looper mSoundPoolLooper = null;
+ // volume applied to sound played with playSoundEffect()
+ private static int sSoundEffectVolumeDb;
+ // previous volume adjustment direction received by checkForRingerModeChange()
+ private int mPrevVolDirection = AudioManager.ADJUST_SAME;
+ // Keyguard manager proxy
+ private KeyguardManager mKeyguardManager;
+ // mVolumeControlStream is set by VolumePanel to temporarily force the stream type which volume
+ // is controlled by Vol keys.
+ private int mVolumeControlStream = -1;
+ private final Object mForceControlStreamLock = new Object();
+ // VolumePanel is currently the only client of forceVolumeControlStream() and runs in system
+ // server process so in theory it is not necessary to monitor the client death.
+ // However it is good to be ready for future evolutions.
+ private ForceControlStreamClient mForceControlStreamClient = null;
+ // Used to play ringtones outside system_server
+ private volatile IRingtonePlayer mRingtonePlayer;
+
+ private int mDeviceOrientation = Configuration.ORIENTATION_UNDEFINED;
+ private int mDeviceRotation = Surface.ROTATION_0;
+
+ // Request to override default use of A2DP for media.
+ private boolean mBluetoothA2dpEnabled;
+ private final Object mBluetoothA2dpEnabledLock = new Object();
+
+ // Monitoring of audio routes. Protected by mCurAudioRoutes.
+ final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
+ final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers
+ = new RemoteCallbackList<IAudioRoutesObserver>();
+
+ // Devices for which the volume is fixed and VolumePanel slider should be disabled
+ int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
+ AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
+ AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET |
+ AudioSystem.DEVICE_OUT_HDMI_ARC |
+ AudioSystem.DEVICE_OUT_SPDIF |
+ AudioSystem.DEVICE_OUT_AUX_LINE;
+ int mFullVolumeDevices = 0;
+
+ // TODO merge orientation and rotation
+ private final boolean mMonitorOrientation;
+ private final boolean mMonitorRotation;
+
+ private boolean mDockAudioMediaEnabled = true;
+
+ private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+ // Used when safe volume warning message display is requested by setStreamVolume(). In this
+ // case, the new requested volume, stream type and device are stored in mPendingVolumeCommand
+ // and used later when/if disableSafeMediaVolume() is called.
+ private StreamVolumeCommand mPendingVolumeCommand;
+
+ private PowerManager.WakeLock mAudioEventWakeLock;
+
+ private final MediaFocusControl mMediaFocusControl;
+
+ // Reference to BluetoothA2dp to query for AbsoluteVolume.
+ private BluetoothA2dp mA2dp;
+ // lock always taken synchronized on mConnectedDevices
+ private final Object mA2dpAvrcpLock = new Object();
+ // If absolute volume is supported in AVRCP device
+ private boolean mAvrcpAbsVolSupported = false;
+
+ private AudioOrientationEventListener mOrientationListener;
+
+ private static Long mLastDeviceConnectMsgTime = new Long(0);
+
+ private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
+ private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
+ private long mLoweredFromNormalToVibrateTime;
+
+ // Intent "extra" data keys.
+ public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
+ public static final String CONNECT_INTENT_KEY_STATE = "state";
+ public static final String CONNECT_INTENT_KEY_ADDRESS = "address";
+ public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
+ public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
+ public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
+ public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
+
+ // Defines the format for the connection "address" for ALSA devices
+ public static String makeAlsaAddressString(int card, int device) {
+ return "card=" + card + ";device=" + device + ";";
+ }
+
+ private final String DEVICE_NAME_A2DP = "a2dp-device";
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Construction
+ ///////////////////////////////////////////////////////////////////////////
+
+ /** @hide */
+ public AudioService(Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+
+ mPlatformType = AudioSystem.getPlatformType(context);
+
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
+
+ Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ mHasVibrator = vibrator == null ? false : vibrator.hasVibrator();
+
+ // Initialize volume
+ int maxVolume = SystemProperties.getInt("ro.config.vc_call_vol_steps",
+ MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL]);
+ if (maxVolume != MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL]) {
+ MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = maxVolume;
+ AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = (maxVolume * 3) / 4;
+ }
+ maxVolume = SystemProperties.getInt("ro.config.media_vol_steps",
+ MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]);
+ if (maxVolume != MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]) {
+ MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = maxVolume;
+ AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = (maxVolume * 3) / 4;
+ }
+
+ sSoundEffectVolumeDb = context.getResources().getInteger(
+ com.android.internal.R.integer.config_soundEffectVolumeDb);
+
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+
+ createAudioSystemThread();
+
+ AudioSystem.setErrorCallback(mAudioSystemCallback);
+
+ boolean cameraSoundForced = readCameraSoundForced();
+ mCameraSoundForced = new Boolean(cameraSoundForced);
+ sendMsg(mAudioHandler,
+ MSG_SET_FORCE_USE,
+ SENDMSG_QUEUE,
+ AudioSystem.FOR_SYSTEM,
+ cameraSoundForced ?
+ AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
+ null,
+ 0);
+
+ mSafeMediaVolumeState = new Integer(Settings.Global.getInt(mContentResolver,
+ Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+ SAFE_MEDIA_VOLUME_NOT_CONFIGURED));
+ // The default safe volume index read here will be replaced by the actual value when
+ // the mcc is read by onConfigureSafeVolume()
+ mSafeMediaVolumeIndex = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+
+ mUseFixedVolume = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_useFixedVolume);
+
+ // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
+ // array initialized by updateStreamVolumeAlias()
+ updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
+ readPersistedSettings();
+ mSettingsObserver = new SettingsObserver();
+ createStreamStates();
+
+ mMediaFocusControl = new MediaFocusControl(mAudioHandler.getLooper(),
+ mContext, mVolumeController, this);
+
+ readAndSetLowRamDevice();
+
+ // Call setRingerModeInt() to apply correct mute
+ // state on streams affected by ringer mode.
+ mRingerModeMutedStreams = 0;
+ setRingerModeInt(getRingerModeInternal(), false);
+
+ // Register for device connection intent broadcasts.
+ IntentFilter intentFilter =
+ new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+ intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+
+ intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ // TODO merge orientation and rotation
+ mMonitorOrientation = SystemProperties.getBoolean("ro.audio.monitorOrientation", false);
+ if (mMonitorOrientation) {
+ Log.v(TAG, "monitoring device orientation");
+ // initialize orientation in AudioSystem
+ setOrientationForAudioSystem();
+ }
+ mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
+ if (mMonitorRotation) {
+ mDeviceRotation = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
+ .getDefaultDisplay().getRotation();
+ Log.v(TAG, "monitoring device rotation, initial=" + mDeviceRotation);
+
+ mOrientationListener = new AudioOrientationEventListener(mContext);
+ mOrientationListener.enable();
+
+ // initialize rotation in AudioSystem
+ setRotationForAudioSystem();
+ }
+
+ context.registerReceiver(mReceiver, intentFilter);
+
+ LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal());
+ }
+
+ public void systemReady() {
+ sendMsg(mAudioHandler, MSG_SYSTEM_READY, SENDMSG_QUEUE,
+ 0, 0, null, 0);
+ }
+
+ public void onSystemReady() {
+ mSystemReady = true;
+ sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE,
+ 0, 0, null, 0);
+
+ mKeyguardManager =
+ (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR;
+ resetBluetoothSco();
+ getBluetoothHeadset();
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ sendStickyBroadcastToAll(newIntent);
+
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+ BluetoothProfile.A2DP);
+ }
+
+ mHdmiManager =
+ (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ mHdmiTvClient = mHdmiManager.getTvClient();
+ if (mHdmiTvClient != null) {
+ mFixedVolumeDevices &= ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER;
+ }
+ mHdmiPlaybackClient = mHdmiManager.getPlaybackClient();
+ mHdmiCecSink = false;
+ }
+ }
+
+ sendMsg(mAudioHandler,
+ MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ TAG,
+ SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
+
+ StreamOverride.init(mContext);
+ mControllerService.init();
+ }
+
+ private void createAudioSystemThread() {
+ mAudioSystemThread = new AudioSystemThread();
+ mAudioSystemThread.start();
+ waitForAudioHandlerCreation();
+ }
+
+ /** Waits for the volume handler to be created by the other thread. */
+ private void waitForAudioHandlerCreation() {
+ synchronized(this) {
+ while (mAudioHandler == null) {
+ try {
+ // Wait for mAudioHandler to be set by the other thread
+ wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting on volume handler.");
+ }
+ }
+ }
+ }
+
+ private void checkAllAliasStreamVolumes() {
+ synchronized (VolumeStreamState.class) {
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+ if (streamType != mStreamVolumeAlias[streamType]) {
+ mStreamStates[streamType].
+ setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]],
+ TAG);
+ }
+ // apply stream volume
+ if (!mStreamStates[streamType].mIsMuted) {
+ mStreamStates[streamType].applyAllVolumes();
+ }
+ }
+ }
+ }
+
+ private void checkAllFixedVolumeDevices()
+ {
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+ mStreamStates[streamType].checkFixedVolumeDevices();
+ }
+ }
+
+ private void checkAllFixedVolumeDevices(int streamType) {
+ mStreamStates[streamType].checkFixedVolumeDevices();
+ }
+
+ private void checkMuteAffectedStreams() {
+ // any stream with a min level > 0 is not muteable by definition
+ for (int i = 0; i < mStreamStates.length; i++) {
+ final VolumeStreamState vss = mStreamStates[i];
+ if (vss.mIndexMin > 0) {
+ mMuteAffectedStreams &= ~(1 << vss.mStreamType);
+ }
+ }
+ }
+
+ private void createStreamStates() {
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
+
+ for (int i = 0; i < numStreamTypes; i++) {
+ streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS[mStreamVolumeAlias[i]], i);
+ }
+
+ checkAllFixedVolumeDevices();
+ checkAllAliasStreamVolumes();
+ checkMuteAffectedStreams();
+ }
+
+ private void dumpStreamStates(PrintWriter pw) {
+ pw.println("\nStream volumes (device: index)");
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int i = 0; i < numStreamTypes; i++) {
+ pw.println("- " + AudioSystem.STREAM_NAMES[i] + ":");
+ mStreamStates[i].dump(pw);
+ pw.println("");
+ }
+ pw.print("\n- mute affected streams = 0x");
+ pw.println(Integer.toHexString(mMuteAffectedStreams));
+ }
+
+ private void updateStreamVolumeAlias(boolean updateVolumes, String caller) {
+ int dtmfStreamAlias;
+
+ switch (mPlatformType) {
+ case AudioSystem.PLATFORM_VOICE:
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE;
+ dtmfStreamAlias = AudioSystem.STREAM_RING;
+ break;
+ case AudioSystem.PLATFORM_TELEVISION:
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
+ dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
+ break;
+ default:
+ mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
+ dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
+ }
+
+ if (isPlatformTelevision()) {
+ mRingerModeAffectedStreams = 0;
+ } else {
+ if (isInCommunication()) {
+ dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL;
+ mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
+ } else {
+ mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
+ }
+ }
+
+ mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
+ if (updateVolumes) {
+ mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias],
+ caller);
+ // apply stream mute states according to new value of mRingerModeAffectedStreams
+ setRingerModeInt(getRingerModeInternal(), false);
+ sendMsg(mAudioHandler,
+ MSG_SET_ALL_VOLUMES,
+ SENDMSG_QUEUE,
+ 0,
+ 0,
+ mStreamStates[AudioSystem.STREAM_DTMF], 0);
+ }
+ }
+
+ private void readDockAudioSettings(ContentResolver cr)
+ {
+ mDockAudioMediaEnabled = Settings.Global.getInt(
+ cr, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 0) == 1;
+
+ sendMsg(mAudioHandler,
+ MSG_SET_FORCE_USE,
+ SENDMSG_QUEUE,
+ AudioSystem.FOR_DOCK,
+ mDockAudioMediaEnabled ?
+ AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE,
+ null,
+ 0);
+ }
+
+ private void readPersistedSettings() {
+ final ContentResolver cr = mContentResolver;
+
+ int ringerModeFromSettings =
+ Settings.Global.getInt(
+ cr, Settings.Global.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL);
+ int ringerMode = ringerModeFromSettings;
+ // sanity check in case the settings are restored from a device with incompatible
+ // ringer modes
+ if (!isValidRingerMode(ringerMode)) {
+ ringerMode = AudioManager.RINGER_MODE_NORMAL;
+ }
+ if ((ringerMode == AudioManager.RINGER_MODE_VIBRATE) && !mHasVibrator) {
+ ringerMode = AudioManager.RINGER_MODE_SILENT;
+ }
+ if (ringerMode != ringerModeFromSettings) {
+ Settings.Global.putInt(cr, Settings.Global.MODE_RINGER, ringerMode);
+ }
+ if (mUseFixedVolume || isPlatformTelevision()) {
+ ringerMode = AudioManager.RINGER_MODE_NORMAL;
+ }
+ synchronized(mSettingsLock) {
+ mRingerMode = ringerMode;
+ if (mRingerModeExternal == -1) {
+ mRingerModeExternal = mRingerMode;
+ }
+
+ // System.VIBRATE_ON is not used any more but defaults for mVibrateSetting
+ // are still needed while setVibrateSetting() and getVibrateSetting() are being
+ // deprecated.
+ mVibrateSetting = AudioSystem.getValueForVibrateSetting(0,
+ AudioManager.VIBRATE_TYPE_NOTIFICATION,
+ mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT
+ : AudioManager.VIBRATE_SETTING_OFF);
+ mVibrateSetting = AudioSystem.getValueForVibrateSetting(mVibrateSetting,
+ AudioManager.VIBRATE_TYPE_RINGER,
+ mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT
+ : AudioManager.VIBRATE_SETTING_OFF);
+
+ updateRingerModeAffectedStreams();
+ readDockAudioSettings(cr);
+ }
+
+ mMuteAffectedStreams = System.getIntForUser(cr,
+ System.MUTE_STREAMS_AFFECTED, AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED,
+ UserHandle.USER_CURRENT);
+
+ boolean masterMute = System.getIntForUser(cr, System.VOLUME_MASTER_MUTE,
+ 0, UserHandle.USER_CURRENT) == 1;
+ if (mUseFixedVolume) {
+ masterMute = false;
+ AudioSystem.setMasterVolume(1.0f);
+ }
+ AudioSystem.setMasterMute(masterMute);
+ broadcastMasterMuteStatus(masterMute);
+
+ boolean microphoneMute =
+ System.getIntForUser(cr, System.MICROPHONE_MUTE, 0, UserHandle.USER_CURRENT) == 1;
+ AudioSystem.muteMicrophone(microphoneMute);
+
+ // Each stream will read its own persisted settings
+
+ // Broadcast the sticky intents
+ broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, mRingerModeExternal);
+ broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, mRingerMode);
+
+ // Broadcast vibrate settings
+ broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER);
+ broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION);
+
+ // Load settings for the volume controller
+ mVolumeController.loadSettings(cr);
+ }
+
+ private int rescaleIndex(int index, int srcStream, int dstStream) {
+ return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex();
+ }
+
+ private class AudioOrientationEventListener
+ extends OrientationEventListener {
+ public AudioOrientationEventListener(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ //Even though we're responding to phone orientation events,
+ //use display rotation so audio stays in sync with video/dialogs
+ int newRotation = ((WindowManager) mContext.getSystemService(
+ Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
+ if (newRotation != mDeviceRotation) {
+ mDeviceRotation = newRotation;
+ setRotationForAudioSystem();
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // IPC methods
+ ///////////////////////////////////////////////////////////////////////////
+ /** @see AudioManager#adjustVolume(int, int) */
+ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
+ String callingPackage, String caller) {
+ adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
+ caller, Binder.getCallingUid());
+ }
+
+ private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
+ String callingPackage, String caller, int uid) {
+ if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
+ + ", flags=" + flags + ", caller=" + caller);
+ int streamType;
+ boolean isMute = isMuteAdjust(direction);
+ if (mVolumeControlStream != -1) {
+ streamType = mVolumeControlStream;
+ } else {
+ streamType = getActiveStreamType(suggestedStreamType);
+ }
+ ensureValidStreamType(streamType);
+ final int resolvedStream = mStreamVolumeAlias[streamType];
+
+ // Play sounds on STREAM_RING only.
+ if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
+ resolvedStream != AudioSystem.STREAM_RING) {
+ flags &= ~AudioManager.FLAG_PLAY_SOUND;
+ }
+
+ // For notifications/ring, show the ui before making any adjustments
+ // Don't suppress mute/unmute requests
+ if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) {
+ direction = 0;
+ flags &= ~AudioManager.FLAG_PLAY_SOUND;
+ flags &= ~AudioManager.FLAG_VIBRATE;
+ if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
+ }
+
+ adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
+ }
+
+ /** @see AudioManager#adjustStreamVolume(int, int, int) */
+ public void adjustStreamVolume(int streamType, int direction, int flags,
+ String callingPackage) {
+ adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
+ Binder.getCallingUid());
+ }
+
+ private void adjustStreamVolume(int streamType, int direction, int flags,
+ String callingPackage, String caller, int uid) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction
+ + ", flags=" + flags + ", caller=" + caller);
+
+ ensureValidDirection(direction);
+ ensureValidStreamType(streamType);
+
+ boolean isMuteAdjust = isMuteAdjust(direction);
+
+ if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {
+ return;
+ }
+
+ // use stream type alias here so that streams with same alias have the same behavior,
+ // including with regard to silent mode control (e.g the use of STREAM_RING below and in
+ // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
+ int streamTypeAlias = mStreamVolumeAlias[streamType];
+
+ VolumeStreamState streamState = mStreamStates[streamTypeAlias];
+
+ final int device = getDeviceForStream(streamTypeAlias);
+
+ int aliasIndex = streamState.getIndex(device);
+ boolean adjustVolume = true;
+ int step;
+
+ // skip a2dp absolute volume control request when the device
+ // is not an a2dp device
+ if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 &&
+ (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
+ return;
+ }
+
+ if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
+ != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+
+ // reset any pending volume command
+ synchronized (mSafeMediaVolumeState) {
+ mPendingVolumeCommand = null;
+ }
+
+ flags &= ~AudioManager.FLAG_FIXED_VOLUME;
+ if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
+ ((device & mFixedVolumeDevices) != 0)) {
+ flags |= AudioManager.FLAG_FIXED_VOLUME;
+
+ // Always toggle between max safe volume and 0 for fixed volume devices where safe
+ // volume is enforced, and max and 0 for the others.
+ // This is simulated by stepping by the full allowed volume range
+ if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
+ (device & mSafeMediaVolumeDevices) != 0) {
+ step = mSafeMediaVolumeIndex;
+ } else {
+ step = streamState.getMaxIndex();
+ }
+ if (aliasIndex != 0) {
+ aliasIndex = step;
+ }
+ } else {
+ // convert one UI step (+/-1) into a number of internal units on the stream alias
+ step = rescaleIndex(10, streamType, streamTypeAlias);
+ }
+
+ // If either the client forces allowing ringer modes for this adjustment,
+ // or the stream type is one that is affected by ringer modes
+ if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
+ (streamTypeAlias == getUiSoundsStreamType())) {
+ int ringerMode = getRingerModeInternal();
+ // do not vibrate if already in vibrate mode
+ if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
+ flags &= ~AudioManager.FLAG_VIBRATE;
+ }
+ // Check if the ringer mode handles this adjustment. If it does we don't
+ // need to adjust the volume further.
+ final int result = checkForRingerModeChange(aliasIndex, direction, step, streamState.mIsMuted);
+ adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
+ // If suppressing a volume adjustment in silent mode, display the UI hint
+ if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
+ flags |= AudioManager.FLAG_SHOW_SILENT_HINT;
+ }
+ // If suppressing a volume down adjustment in vibrate mode, display the UI hint
+ if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
+ flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
+ }
+ }
+
+ int oldIndex = mStreamStates[streamType].getIndex(device);
+
+ if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
+ mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
+
+ // Check if volume update should be send to AVRCP
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+ (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
+ (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+ synchronized (mA2dpAvrcpLock) {
+ if (mA2dp != null && mAvrcpAbsVolSupported) {
+ mA2dp.adjustAvrcpAbsoluteVolume(direction);
+ }
+ }
+ }
+
+ if (isMuteAdjust) {
+ boolean state;
+ if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {
+ state = !streamState.mIsMuted;
+ } else {
+ state = direction == AudioManager.ADJUST_MUTE;
+ }
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
+ setSystemAudioMute(state);
+ }
+ for (int stream = 0; stream < mStreamStates.length; stream++) {
+ if (streamTypeAlias == mStreamVolumeAlias[stream]) {
+ mStreamStates[stream].mute(state);
+ }
+ }
+ } else if ((direction == AudioManager.ADJUST_RAISE) &&
+ !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
+ Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
+ mVolumeController.postDisplaySafeVolumeWarning(flags);
+ } else if (streamState.adjustIndex(direction * step, device, caller)
+ || streamState.mIsMuted) {
+ // Post message to set system volume (it in turn will post a
+ // message to persist).
+ if (streamState.mIsMuted) {
+ // Unmute the stream if it was previously muted
+ if (direction == AudioManager.ADJUST_RAISE) {
+ // unmute immediately for volume up
+ streamState.mute(false);
+ } else if (direction == AudioManager.ADJUST_LOWER) {
+ if (mPlatformType == AudioSystem.PLATFORM_TELEVISION) {
+ sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
+ streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
+ }
+ }
+ }
+ sendMsg(mAudioHandler,
+ MSG_SET_DEVICE_VOLUME,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ streamState,
+ 0);
+ }
+
+ // Check if volume update should be sent to Hdmi system audio.
+ int newIndex = mStreamStates[streamType].getIndex(device);
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
+ setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
+ }
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ // mHdmiCecSink true => mHdmiPlaybackClient != null
+ if (mHdmiCecSink &&
+ streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+ oldIndex != newIndex) {
+ synchronized (mHdmiPlaybackClient) {
+ int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :
+ KeyEvent.KEYCODE_VOLUME_UP;
+ mHdmiPlaybackClient.sendKeyEvent(keyCode, true);
+ mHdmiPlaybackClient.sendKeyEvent(keyCode, false);
+ }
+ }
+ }
+ }
+ }
+ int index = mStreamStates[streamType].getIndex(device);
+ sendVolumeUpdate(streamType, oldIndex, index, flags);
+ }
+
+ // Called after a delay when volume down is pressed while muted
+ private void onUnmuteStream(int stream, int flags) {
+ VolumeStreamState streamState = mStreamStates[stream];
+ streamState.mute(false);
+
+ final int device = getDeviceForStream(stream);
+ final int index = mStreamStates[stream].getIndex(device);
+ sendVolumeUpdate(stream, index, index, flags);
+ }
+
+ private void setSystemAudioVolume(int oldVolume, int newVolume, int maxVolume, int flags) {
+ if (mHdmiManager == null
+ || mHdmiTvClient == null
+ || oldVolume == newVolume
+ || (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) != 0) return;
+
+ // Sets the audio volume of AVR when we are in system audio mode. The new volume info
+ // is tranformed to HDMI-CEC commands and passed through CEC bus.
+ synchronized (mHdmiManager) {
+ if (!mHdmiSystemAudioSupported) return;
+ synchronized (mHdmiTvClient) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mHdmiTvClient.setSystemAudioVolume(oldVolume, newVolume, maxVolume);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ }
+
+ // StreamVolumeCommand contains the information needed to defer the process of
+ // setStreamVolume() in case the user has to acknowledge the safe volume warning message.
+ class StreamVolumeCommand {
+ public final int mStreamType;
+ public final int mIndex;
+ public final int mFlags;
+ public final int mDevice;
+
+ StreamVolumeCommand(int streamType, int index, int flags, int device) {
+ mStreamType = streamType;
+ mIndex = index;
+ mFlags = flags;
+ mDevice = device;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=")
+ .append(mIndex).append(",flags=").append(mFlags).append(",device=")
+ .append(mDevice).append('}').toString();
+ }
+ };
+
+ private void onSetStreamVolume(int streamType, int index, int flags, int device,
+ String caller) {
+ setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false, caller);
+ // setting volume on ui sounds stream type also controls silent mode
+ if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
+ (mStreamVolumeAlias[streamType] == getUiSoundsStreamType())) {
+ int newRingerMode;
+ if (index == 0) {
+ newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE
+ : mVolumePolicy.volumeDownToEnterSilent ? AudioManager.RINGER_MODE_SILENT
+ : AudioManager.RINGER_MODE_NORMAL;
+ } else {
+ newRingerMode = AudioManager.RINGER_MODE_NORMAL;
+ }
+ setRingerMode(newRingerMode, TAG + ".onSetStreamVolume", false /*external*/);
+ }
+ }
+
+ /** @see AudioManager#setStreamVolume(int, int, int) */
+ public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
+ setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
+ Binder.getCallingUid());
+ }
+
+ private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
+ String caller, int uid) {
+ if (mUseFixedVolume) {
+ return;
+ }
+
+ ensureValidStreamType(streamType);
+ int streamTypeAlias = mStreamVolumeAlias[streamType];
+ VolumeStreamState streamState = mStreamStates[streamTypeAlias];
+
+ final int device = getDeviceForStream(streamType);
+ int oldIndex;
+
+ // skip a2dp absolute volume control request when the device
+ // is not an a2dp device
+ if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 &&
+ (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
+ return;
+ }
+
+ if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
+ != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+
+ synchronized (mSafeMediaVolumeState) {
+ // reset any pending volume command
+ mPendingVolumeCommand = null;
+
+ oldIndex = streamState.getIndex(device);
+
+ index = rescaleIndex(index * 10, streamType, streamTypeAlias);
+
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
+ (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
+ (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+ synchronized (mA2dpAvrcpLock) {
+ if (mA2dp != null && mAvrcpAbsVolSupported) {
+ mA2dp.setAvrcpAbsoluteVolume(index / 10);
+ }
+ }
+ }
+
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
+ setSystemAudioVolume(oldIndex, index, getStreamMaxVolume(streamType), flags);
+ }
+
+ flags &= ~AudioManager.FLAG_FIXED_VOLUME;
+ if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
+ ((device & mFixedVolumeDevices) != 0)) {
+ flags |= AudioManager.FLAG_FIXED_VOLUME;
+
+ // volume is either 0 or max allowed for fixed volume devices
+ if (index != 0) {
+ if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
+ (device & mSafeMediaVolumeDevices) != 0) {
+ index = mSafeMediaVolumeIndex;
+ } else {
+ index = streamState.getMaxIndex();
+ }
+ }
+ }
+
+ if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {
+ mVolumeController.postDisplaySafeVolumeWarning(flags);
+ mPendingVolumeCommand = new StreamVolumeCommand(
+ streamType, index, flags, device);
+ } else {
+ onSetStreamVolume(streamType, index, flags, device, caller);
+ index = mStreamStates[streamType].getIndex(device);
+ }
+ }
+ sendVolumeUpdate(streamType, oldIndex, index, flags);
+ }
+
+ /** @see AudioManager#forceVolumeControlStream(int) */
+ public void forceVolumeControlStream(int streamType, IBinder cb) {
+ synchronized(mForceControlStreamLock) {
+ mVolumeControlStream = streamType;
+ if (mVolumeControlStream == -1) {
+ if (mForceControlStreamClient != null) {
+ mForceControlStreamClient.release();
+ mForceControlStreamClient = null;
+ }
+ } else {
+ mForceControlStreamClient = new ForceControlStreamClient(cb);
+ }
+ }
+ }
+
+ private class ForceControlStreamClient implements IBinder.DeathRecipient {
+ private IBinder mCb; // To be notified of client's death
+
+ ForceControlStreamClient(IBinder cb) {
+ if (cb != null) {
+ try {
+ cb.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ // Client has died!
+ Log.w(TAG, "ForceControlStreamClient() could not link to "+cb+" binder death");
+ cb = null;
+ }
+ }
+ mCb = cb;
+ }
+
+ public void binderDied() {
+ synchronized(mForceControlStreamLock) {
+ Log.w(TAG, "SCO client died");
+ if (mForceControlStreamClient != this) {
+ Log.w(TAG, "unregistered control stream client died");
+ } else {
+ mForceControlStreamClient = null;
+ mVolumeControlStream = -1;
+ }
+ }
+ }
+
+ public void release() {
+ if (mCb != null) {
+ mCb.unlinkToDeath(this, 0);
+ mCb = null;
+ }
+ }
+ }
+
+ private void sendBroadcastToAll(Intent intent) {
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void sendStickyBroadcastToAll(Intent intent) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ // UI update and Broadcast Intent
+ private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
+ if (!isPlatformVoice() && (streamType == AudioSystem.STREAM_RING)) {
+ streamType = AudioSystem.STREAM_NOTIFICATION;
+ } else {
+ streamType = mStreamVolumeAlias[streamType];
+ }
+
+ if (streamType == AudioSystem.STREAM_MUSIC) {
+ flags = updateFlagsForSystemAudio(flags);
+ }
+ mVolumeController.postVolumeChanged(streamType, flags);
+ }
+
+ // If Hdmi-CEC system audio mode is on, we show volume bar only when TV
+ // receives volume notification from Audio Receiver.
+ private int updateFlagsForSystemAudio(int flags) {
+ if (mHdmiTvClient != null) {
+ synchronized (mHdmiTvClient) {
+ if (mHdmiSystemAudioSupported &&
+ ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) {
+ flags &= ~AudioManager.FLAG_SHOW_UI;
+ }
+ }
+ }
+ return flags;
+ }
+
+ // UI update and Broadcast Intent
+ private void sendMasterMuteUpdate(boolean muted, int flags) {
+ mVolumeController.postMasterMuteChanged(updateFlagsForSystemAudio(flags));
+ broadcastMasterMuteStatus(muted);
+ }
+
+ private void broadcastMasterMuteStatus(boolean muted) {
+ Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
+ intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ sendStickyBroadcastToAll(intent);
+ }
+
+ /**
+ * Sets the stream state's index, and posts a message to set system volume.
+ * This will not call out to the UI. Assumes a valid stream type.
+ *
+ * @param streamType Type of the stream
+ * @param index Desired volume index of the stream
+ * @param device the device whose volume must be changed
+ * @param force If true, set the volume even if the desired volume is same
+ * as the current volume.
+ */
+ private void setStreamVolumeInt(int streamType,
+ int index,
+ int device,
+ boolean force,
+ String caller) {
+ VolumeStreamState streamState = mStreamStates[streamType];
+
+ if (streamState.setIndex(index, device, caller) || force) {
+ // Post message to set system volume (it in turn will post a message
+ // to persist).
+ sendMsg(mAudioHandler,
+ MSG_SET_DEVICE_VOLUME,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ streamState,
+ 0);
+ }
+ }
+
+ private void setSystemAudioMute(boolean state) {
+ if (mHdmiManager == null || mHdmiTvClient == null) return;
+ synchronized (mHdmiManager) {
+ if (!mHdmiSystemAudioSupported) return;
+ synchronized (mHdmiTvClient) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mHdmiTvClient.setSystemAudioMute(state);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ }
+
+ /** get stream mute state. */
+ public boolean isStreamMute(int streamType) {
+ if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ streamType = getActiveStreamType(streamType);
+ }
+ synchronized (VolumeStreamState.class) {
+ return mStreamStates[streamType].mIsMuted;
+ }
+ }
+
+ private class RmtSbmxFullVolDeathHandler implements IBinder.DeathRecipient {
+ private IBinder mICallback; // To be notified of client's death
+
+ RmtSbmxFullVolDeathHandler(IBinder cb) {
+ mICallback = cb;
+ try {
+ cb.linkToDeath(this, 0/*flags*/);
+ } catch (RemoteException e) {
+ Log.e(TAG, "can't link to death", e);
+ }
+ }
+
+ boolean isHandlerFor(IBinder cb) {
+ return mICallback.equals(cb);
+ }
+
+ void forget() {
+ try {
+ mICallback.unlinkToDeath(this, 0/*flags*/);
+ } catch (NoSuchElementException e) {
+ Log.e(TAG, "error unlinking to death", e);
+ }
+ }
+
+ public void binderDied() {
+ Log.w(TAG, "Recorder with remote submix at full volume died " + mICallback);
+ forceRemoteSubmixFullVolume(false, mICallback);
+ }
+ }
+
+ /**
+ * call must be synchronized on mRmtSbmxFullVolDeathHandlers
+ * @return true if there is a registered death handler, false otherwise */
+ private boolean discardRmtSbmxFullVolDeathHandlerFor(IBinder cb) {
+ Iterator<RmtSbmxFullVolDeathHandler> it = mRmtSbmxFullVolDeathHandlers.iterator();
+ while (it.hasNext()) {
+ final RmtSbmxFullVolDeathHandler handler = it.next();
+ if (handler.isHandlerFor(cb)) {
+ handler.forget();
+ mRmtSbmxFullVolDeathHandlers.remove(handler);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** call synchronized on mRmtSbmxFullVolDeathHandlers */
+ private boolean hasRmtSbmxFullVolDeathHandlerFor(IBinder cb) {
+ Iterator<RmtSbmxFullVolDeathHandler> it = mRmtSbmxFullVolDeathHandlers.iterator();
+ while (it.hasNext()) {
+ if (it.next().isHandlerFor(cb)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int mRmtSbmxFullVolRefCount = 0;
+ private ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers =
+ new ArrayList<RmtSbmxFullVolDeathHandler>();
+
+ public void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb) {
+ if (cb == null) {
+ return;
+ }
+ if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.CAPTURE_AUDIO_OUTPUT))) {
+ Log.w(TAG, "Trying to call forceRemoteSubmixFullVolume() without CAPTURE_AUDIO_OUTPUT");
+ return;
+ }
+ synchronized(mRmtSbmxFullVolDeathHandlers) {
+ boolean applyRequired = false;
+ if (startForcing) {
+ if (!hasRmtSbmxFullVolDeathHandlerFor(cb)) {
+ mRmtSbmxFullVolDeathHandlers.add(new RmtSbmxFullVolDeathHandler(cb));
+ if (mRmtSbmxFullVolRefCount == 0) {
+ mFullVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+ mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+ applyRequired = true;
+ }
+ mRmtSbmxFullVolRefCount++;
+ }
+ } else {
+ if (discardRmtSbmxFullVolDeathHandlerFor(cb) && (mRmtSbmxFullVolRefCount > 0)) {
+ mRmtSbmxFullVolRefCount--;
+ if (mRmtSbmxFullVolRefCount == 0) {
+ mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+ mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+ applyRequired = true;
+ }
+ }
+ }
+ if (applyRequired) {
+ // Assumes only STREAM_MUSIC going through DEVICE_OUT_REMOTE_SUBMIX
+ checkAllFixedVolumeDevices(AudioSystem.STREAM_MUSIC);
+ mStreamStates[AudioSystem.STREAM_MUSIC].applyAllVolumes();
+ }
+ }
+ }
+
+ private void setMasterMuteInternal(boolean mute, int flags, String callingPackage, int uid) {
+ if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
+ != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+ if (mute != AudioSystem.getMasterMute()) {
+ setSystemAudioMute(mute);
+ AudioSystem.setMasterMute(mute);
+ // Post a persist master volume msg
+ sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, mute ? 1
+ : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY);
+ sendMasterMuteUpdate(mute, flags);
+
+ Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
+ intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, mute);
+ sendBroadcastToAll(intent);
+ }
+ }
+
+ /** get master mute state. */
+ public boolean isMasterMute() {
+ return AudioSystem.getMasterMute();
+ }
+
+ public void setMasterMute(boolean mute, int flags, String callingPackage) {
+ setMasterMuteInternal(mute, flags, callingPackage, Binder.getCallingUid());
+ }
+
+ /** @see AudioManager#getStreamVolume(int) */
+ public int getStreamVolume(int streamType) {
+ ensureValidStreamType(streamType);
+ int device = getDeviceForStream(streamType);
+ synchronized (VolumeStreamState.class) {
+ int index = mStreamStates[streamType].getIndex(device);
+
+ // by convention getStreamVolume() returns 0 when a stream is muted.
+ if (mStreamStates[streamType].mIsMuted) {
+ index = 0;
+ }
+ if (index != 0 && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&
+ (device & mFixedVolumeDevices) != 0) {
+ index = mStreamStates[streamType].getMaxIndex();
+ }
+ return (index + 5) / 10;
+ }
+ }
+
+ /** @see AudioManager#getStreamMaxVolume(int) */
+ public int getStreamMaxVolume(int streamType) {
+ ensureValidStreamType(streamType);
+ return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
+ }
+
+ /** @see AudioManager#getStreamMinVolume(int) */
+ public int getStreamMinVolume(int streamType) {
+ ensureValidStreamType(streamType);
+ return (mStreamStates[streamType].getMinIndex() + 5) / 10;
+ }
+
+ /** Get last audible volume before stream was muted. */
+ public int getLastAudibleStreamVolume(int streamType) {
+ ensureValidStreamType(streamType);
+ int device = getDeviceForStream(streamType);
+ return (mStreamStates[streamType].getIndex(device) + 5) / 10;
+ }
+
+ /** @see AudioManager#getUiSoundsStreamType() */
+ public int getUiSoundsStreamType() {
+ return mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM];
+ }
+
+ /** @see AudioManager#setMicrophoneMute(boolean) */
+ public void setMicrophoneMute(boolean on, String callingPackage) {
+ if (mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+ if (!checkAudioSettingsPermission("setMicrophoneMute()")) {
+ return;
+ }
+
+ AudioSystem.muteMicrophone(on);
+ // Post a persist microphone msg.
+ sendMsg(mAudioHandler, MSG_PERSIST_MICROPHONE_MUTE, SENDMSG_REPLACE, on ? 1
+ : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY);
+ }
+
+ @Override
+ public int getRingerModeExternal() {
+ synchronized(mSettingsLock) {
+ return mRingerModeExternal;
+ }
+ }
+
+ @Override
+ public int getRingerModeInternal() {
+ synchronized(mSettingsLock) {
+ return mRingerMode;
+ }
+ }
+
+ private void ensureValidRingerMode(int ringerMode) {
+ if (!isValidRingerMode(ringerMode)) {
+ throw new IllegalArgumentException("Bad ringer mode " + ringerMode);
+ }
+ }
+
+ /** @see AudioManager#isValidRingerMode(int) */
+ public boolean isValidRingerMode(int ringerMode) {
+ return ringerMode >= 0 && ringerMode <= AudioManager.RINGER_MODE_MAX;
+ }
+
+ public void setRingerModeExternal(int ringerMode, String caller) {
+ setRingerMode(ringerMode, caller, true /*external*/);
+ }
+
+ public void setRingerModeInternal(int ringerMode, String caller) {
+ enforceVolumeController("setRingerModeInternal");
+ setRingerMode(ringerMode, caller, false /*external*/);
+ }
+
+ private void setRingerMode(int ringerMode, String caller, boolean external) {
+ if (mUseFixedVolume || isPlatformTelevision()) {
+ return;
+ }
+ if (caller == null || caller.length() == 0) {
+ throw new IllegalArgumentException("Bad caller: " + caller);
+ }
+ ensureValidRingerMode(ringerMode);
+ if ((ringerMode == AudioManager.RINGER_MODE_VIBRATE) && !mHasVibrator) {
+ ringerMode = AudioManager.RINGER_MODE_SILENT;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSettingsLock) {
+ final int ringerModeInternal = getRingerModeInternal();
+ final int ringerModeExternal = getRingerModeExternal();
+ if (external) {
+ setRingerModeExt(ringerMode);
+ if (mRingerModeDelegate != null) {
+ ringerMode = mRingerModeDelegate.onSetRingerModeExternal(ringerModeExternal,
+ ringerMode, caller, ringerModeInternal, mVolumePolicy);
+ }
+ if (ringerMode != ringerModeInternal) {
+ setRingerModeInt(ringerMode, true /*persist*/);
+ }
+ } else /*internal*/ {
+ if (ringerMode != ringerModeInternal) {
+ setRingerModeInt(ringerMode, true /*persist*/);
+ }
+ if (mRingerModeDelegate != null) {
+ ringerMode = mRingerModeDelegate.onSetRingerModeInternal(ringerModeInternal,
+ ringerMode, caller, ringerModeExternal, mVolumePolicy);
+ }
+ setRingerModeExt(ringerMode);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void setRingerModeExt(int ringerMode) {
+ synchronized(mSettingsLock) {
+ if (ringerMode == mRingerModeExternal) return;
+ mRingerModeExternal = ringerMode;
+ }
+ // Send sticky broadcast
+ broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, ringerMode);
+ }
+
+ private void setRingerModeInt(int ringerMode, boolean persist) {
+ final boolean change;
+ synchronized(mSettingsLock) {
+ change = mRingerMode != ringerMode;
+ mRingerMode = ringerMode;
+ }
+
+ // Mute stream if not previously muted by ringer mode and ringer mode
+ // is not RINGER_MODE_NORMAL and stream is affected by ringer mode.
+ // Unmute stream if previously muted by ringer mode and ringer mode
+ // is RINGER_MODE_NORMAL or stream is not affected by ringer mode.
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
+ || ringerMode == AudioManager.RINGER_MODE_SILENT;
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ final boolean isMuted = isStreamMutedByRingerMode(streamType);
+ final boolean shouldMute = ringerModeMute && isStreamAffectedByRingerMode(streamType);
+ if (isMuted == shouldMute) continue;
+ if (!shouldMute) {
+ // unmute
+ // ring and notifications volume should never be 0 when not silenced
+ // on voice capable devices or devices that support vibration
+ if ((isPlatformVoice() || mHasVibrator) &&
+ mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
+ synchronized (VolumeStreamState.class) {
+ final VolumeStreamState vss = mStreamStates[streamType];
+ for (int i = 0; i < vss.mIndexMap.size(); i++) {
+ int device = vss.mIndexMap.keyAt(i);
+ int value = vss.mIndexMap.valueAt(i);
+ if (value == 0) {
+ vss.setIndex(10, device, TAG);
+ }
+ }
+ // Persist volume for stream ring when it is changed here
+ final int device = getDeviceForStream(streamType);
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_VOLUME,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ mStreamStates[streamType],
+ PERSIST_DELAY);
+ }
+ }
+ mStreamStates[streamType].mute(false);
+ mRingerModeMutedStreams &= ~(1 << streamType);
+ } else {
+ // mute
+ mStreamStates[streamType].mute(true);
+ mRingerModeMutedStreams |= (1 << streamType);
+ }
+ }
+
+ // Post a persist ringer mode msg
+ if (persist) {
+ sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE,
+ SENDMSG_REPLACE, 0, 0, null, PERSIST_DELAY);
+ }
+ if (change) {
+ // Send sticky broadcast
+ broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, ringerMode);
+ }
+ }
+
+ /** @see AudioManager#shouldVibrate(int) */
+ public boolean shouldVibrate(int vibrateType) {
+ if (!mHasVibrator) return false;
+
+ switch (getVibrateSetting(vibrateType)) {
+
+ case AudioManager.VIBRATE_SETTING_ON:
+ return getRingerModeExternal() != AudioManager.RINGER_MODE_SILENT;
+
+ case AudioManager.VIBRATE_SETTING_ONLY_SILENT:
+ return getRingerModeExternal() == AudioManager.RINGER_MODE_VIBRATE;
+
+ case AudioManager.VIBRATE_SETTING_OFF:
+ // return false, even for incoming calls
+ return false;
+
+ default:
+ return false;
+ }
+ }
+
+ /** @see AudioManager#getVibrateSetting(int) */
+ public int getVibrateSetting(int vibrateType) {
+ if (!mHasVibrator) return AudioManager.VIBRATE_SETTING_OFF;
+ return (mVibrateSetting >> (vibrateType * 2)) & 3;
+ }
+
+ /** @see AudioManager#setVibrateSetting(int, int) */
+ public void setVibrateSetting(int vibrateType, int vibrateSetting) {
+
+ if (!mHasVibrator) return;
+
+ mVibrateSetting = AudioSystem.getValueForVibrateSetting(mVibrateSetting, vibrateType,
+ vibrateSetting);
+
+ // Broadcast change
+ broadcastVibrateSetting(vibrateType);
+
+ }
+
+ private class SetModeDeathHandler implements IBinder.DeathRecipient {
+ private IBinder mCb; // To be notified of client's death
+ private int mPid;
+ private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
+
+ SetModeDeathHandler(IBinder cb, int pid) {
+ mCb = cb;
+ mPid = pid;
+ }
+
+ public void binderDied() {
+ int newModeOwnerPid = 0;
+ synchronized(mSetModeDeathHandlers) {
+ Log.w(TAG, "setMode() client died");
+ int index = mSetModeDeathHandlers.indexOf(this);
+ if (index < 0) {
+ Log.w(TAG, "unregistered setMode() client died");
+ } else {
+ newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG);
+ }
+ }
+ // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
+ // SCO connections not started by the application changing the mode
+ if (newModeOwnerPid != 0) {
+ final long ident = Binder.clearCallingIdentity();
+ disconnectBluetoothSco(newModeOwnerPid);
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public int getPid() {
+ return mPid;
+ }
+
+ public void setMode(int mode) {
+ mMode = mode;
+ }
+
+ public int getMode() {
+ return mMode;
+ }
+
+ public IBinder getBinder() {
+ return mCb;
+ }
+ }
+
+ /** @see AudioManager#setMode(int) */
+ public void setMode(int mode, IBinder cb, String callingPackage) {
+ if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); }
+ if (!checkAudioSettingsPermission("setMode()")) {
+ return;
+ }
+
+ if ( (mode == AudioSystem.MODE_IN_CALL) &&
+ (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_PHONE_STATE)
+ != PackageManager.PERMISSION_GRANTED)) {
+ Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ if (mode < AudioSystem.MODE_CURRENT || mode >= AudioSystem.NUM_MODES) {
+ return;
+ }
+
+ int newModeOwnerPid = 0;
+ synchronized(mSetModeDeathHandlers) {
+ if (mode == AudioSystem.MODE_CURRENT) {
+ mode = mMode;
+ }
+ newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
+ }
+ // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
+ // SCO connections not started by the application changing the mode
+ if (newModeOwnerPid != 0) {
+ disconnectBluetoothSco(newModeOwnerPid);
+ }
+ }
+
+ // must be called synchronized on mSetModeDeathHandlers
+ // setModeInt() returns a valid PID if the audio mode was successfully set to
+ // any mode other than NORMAL.
+ private int setModeInt(int mode, IBinder cb, int pid, String caller) {
+ if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller="
+ + caller + ")"); }
+ int newModeOwnerPid = 0;
+ if (cb == null) {
+ Log.e(TAG, "setModeInt() called with null binder");
+ return newModeOwnerPid;
+ }
+
+ SetModeDeathHandler hdlr = null;
+ Iterator iter = mSetModeDeathHandlers.iterator();
+ while (iter.hasNext()) {
+ SetModeDeathHandler h = (SetModeDeathHandler)iter.next();
+ if (h.getPid() == pid) {
+ hdlr = h;
+ // Remove from client list so that it is re-inserted at top of list
+ iter.remove();
+ hdlr.getBinder().unlinkToDeath(hdlr, 0);
+ break;
+ }
+ }
+ int status = AudioSystem.AUDIO_STATUS_OK;
+ do {
+ if (mode == AudioSystem.MODE_NORMAL) {
+ // get new mode from client at top the list if any
+ if (!mSetModeDeathHandlers.isEmpty()) {
+ hdlr = mSetModeDeathHandlers.get(0);
+ cb = hdlr.getBinder();
+ mode = hdlr.getMode();
+ if (DEBUG_MODE) {
+ Log.w(TAG, " using mode=" + mode + " instead due to death hdlr at pid="
+ + hdlr.mPid);
+ }
+ }
+ } else {
+ if (hdlr == null) {
+ hdlr = new SetModeDeathHandler(cb, pid);
+ }
+ // Register for client death notification
+ try {
+ cb.linkToDeath(hdlr, 0);
+ } catch (RemoteException e) {
+ // Client has died!
+ Log.w(TAG, "setMode() could not link to "+cb+" binder death");
+ }
+
+ // Last client to call setMode() is always at top of client list
+ // as required by SetModeDeathHandler.binderDied()
+ mSetModeDeathHandlers.add(0, hdlr);
+ hdlr.setMode(mode);
+ }
+
+ if (mode != mMode) {
+ status = AudioSystem.setPhoneState(mode);
+ if (status == AudioSystem.AUDIO_STATUS_OK) {
+ if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + mode); }
+ mMode = mode;
+ } else {
+ if (hdlr != null) {
+ mSetModeDeathHandlers.remove(hdlr);
+ cb.unlinkToDeath(hdlr, 0);
+ }
+ // force reading new top of mSetModeDeathHandlers stack
+ if (DEBUG_MODE) { Log.w(TAG, " mode set to MODE_NORMAL after phoneState pb"); }
+ mode = AudioSystem.MODE_NORMAL;
+ }
+ } else {
+ status = AudioSystem.AUDIO_STATUS_OK;
+ }
+ } while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty());
+
+ if (status == AudioSystem.AUDIO_STATUS_OK) {
+ if (mode != AudioSystem.MODE_NORMAL) {
+ if (mSetModeDeathHandlers.isEmpty()) {
+ Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack");
+ } else {
+ newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
+ }
+ }
+ int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
+ int device = getDeviceForStream(streamType);
+ int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
+ setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller);
+
+ updateStreamVolumeAlias(true /*updateVolumes*/, caller);
+ }
+ return newModeOwnerPid;
+ }
+
+ /** @see AudioManager#getMode() */
+ public int getMode() {
+ return mMode;
+ }
+
+ //==========================================================================================
+ // Sound Effects
+ //==========================================================================================
+
+ private static final String TAG_AUDIO_ASSETS = "audio_assets";
+ private static final String ATTR_VERSION = "version";
+ private static final String TAG_GROUP = "group";
+ private static final String ATTR_GROUP_NAME = "name";
+ private static final String TAG_ASSET = "asset";
+ private static final String ATTR_ASSET_ID = "id";
+ private static final String ATTR_ASSET_FILE = "file";
+
+ private static final String ASSET_FILE_VERSION = "1.0";
+ private static final String GROUP_TOUCH_SOUNDS = "touch_sounds";
+
+ private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 5000;
+
+ class LoadSoundEffectReply {
+ public int mStatus = 1;
+ };
+
+ private void loadTouchSoundAssetDefaults() {
+ SOUND_EFFECT_FILES.add("Effect_Tick.ogg");
+ for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) {
+ SOUND_EFFECT_FILES_MAP[i][0] = 0;
+ SOUND_EFFECT_FILES_MAP[i][1] = -1;
+ }
+ }
+
+ private void loadTouchSoundAssets() {
+ XmlResourceParser parser = null;
+
+ // only load assets once.
+ if (!SOUND_EFFECT_FILES.isEmpty()) {
+ return;
+ }
+
+ loadTouchSoundAssetDefaults();
+
+ try {
+ parser = mContext.getResources().getXml(com.android.internal.R.xml.audio_assets);
+
+ XmlUtils.beginDocument(parser, TAG_AUDIO_ASSETS);
+ String version = parser.getAttributeValue(null, ATTR_VERSION);
+ boolean inTouchSoundsGroup = false;
+
+ if (ASSET_FILE_VERSION.equals(version)) {
+ while (true) {
+ XmlUtils.nextElement(parser);
+ String element = parser.getName();
+ if (element == null) {
+ break;
+ }
+ if (element.equals(TAG_GROUP)) {
+ String name = parser.getAttributeValue(null, ATTR_GROUP_NAME);
+ if (GROUP_TOUCH_SOUNDS.equals(name)) {
+ inTouchSoundsGroup = true;
+ break;
+ }
+ }
+ }
+ while (inTouchSoundsGroup) {
+ XmlUtils.nextElement(parser);
+ String element = parser.getName();
+ if (element == null) {
+ break;
+ }
+ if (element.equals(TAG_ASSET)) {
+ String id = parser.getAttributeValue(null, ATTR_ASSET_ID);
+ String file = parser.getAttributeValue(null, ATTR_ASSET_FILE);
+ int fx;
+
+ try {
+ Field field = AudioManager.class.getField(id);
+ fx = field.getInt(null);
+ } catch (Exception e) {
+ Log.w(TAG, "Invalid touch sound ID: "+id);
+ continue;
+ }
+
+ int i = SOUND_EFFECT_FILES.indexOf(file);
+ if (i == -1) {
+ i = SOUND_EFFECT_FILES.size();
+ SOUND_EFFECT_FILES.add(file);
+ }
+ SOUND_EFFECT_FILES_MAP[fx][0] = i;
+ } else {
+ break;
+ }
+ }
+ }
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "audio assets file not found", e);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "XML parser exception reading touch sound assets", e);
+ } catch (IOException e) {
+ Log.w(TAG, "I/O exception reading touch sound assets", e);
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ /** @see AudioManager#playSoundEffect(int) */
+ public void playSoundEffect(int effectType) {
+ playSoundEffectVolume(effectType, -1.0f);
+ }
+
+ /** @see AudioManager#playSoundEffect(int, float) */
+ public void playSoundEffectVolume(int effectType, float volume) {
+ if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) {
+ Log.w(TAG, "AudioService effectType value " + effectType + " out of range");
+ return;
+ }
+
+ sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE,
+ effectType, (int) (volume * 1000), null, 0);
+ }
+
+ /**
+ * Loads samples into the soundpool.
+ * This method must be called at first when sound effects are enabled
+ */
+ public boolean loadSoundEffects() {
+ int attempts = 3;
+ LoadSoundEffectReply reply = new LoadSoundEffectReply();
+
+ synchronized (reply) {
+ sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);
+ while ((reply.mStatus == 1) && (attempts-- > 0)) {
+ try {
+ reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");
+ }
+ }
+ }
+ return (reply.mStatus == 0);
+ }
+
+ /**
+ * Unloads samples from the sound pool.
+ * This method can be called to free some memory when
+ * sound effects are disabled.
+ */
+ public void unloadSoundEffects() {
+ sendMsg(mAudioHandler, MSG_UNLOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0);
+ }
+
+ class SoundPoolListenerThread extends Thread {
+ public SoundPoolListenerThread() {
+ super("SoundPoolListenerThread");
+ }
+
+ @Override
+ public void run() {
+
+ Looper.prepare();
+ mSoundPoolLooper = Looper.myLooper();
+
+ synchronized (mSoundEffectsLock) {
+ if (mSoundPool != null) {
+ mSoundPoolCallBack = new SoundPoolCallback();
+ mSoundPool.setOnLoadCompleteListener(mSoundPoolCallBack);
+ }
+ mSoundEffectsLock.notify();
+ }
+ Looper.loop();
+ }
+ }
+
+ private final class SoundPoolCallback implements
+ android.media.SoundPool.OnLoadCompleteListener {
+
+ int mStatus = 1; // 1 means neither error nor last sample loaded yet
+ List<Integer> mSamples = new ArrayList<Integer>();
+
+ public int status() {
+ return mStatus;
+ }
+
+ public void setSamples(int[] samples) {
+ for (int i = 0; i < samples.length; i++) {
+ // do not wait ack for samples rejected upfront by SoundPool
+ if (samples[i] > 0) {
+ mSamples.add(samples[i]);
+ }
+ }
+ }
+
+ public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
+ synchronized (mSoundEffectsLock) {
+ int i = mSamples.indexOf(sampleId);
+ if (i >= 0) {
+ mSamples.remove(i);
+ }
+ if ((status != 0) || mSamples. isEmpty()) {
+ mStatus = status;
+ mSoundEffectsLock.notify();
+ }
+ }
+ }
+ }
+
+ /** @see AudioManager#reloadAudioSettings() */
+ public void reloadAudioSettings() {
+ readAudioSettings(false /*userSwitch*/);
+ }
+
+ private void readAudioSettings(boolean userSwitch) {
+ // restore ringer mode, ringer mode affected streams, mute affected streams and vibrate settings
+ readPersistedSettings();
+
+ // restore volume settings
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+ VolumeStreamState streamState = mStreamStates[streamType];
+
+ if (userSwitch && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) {
+ continue;
+ }
+
+ streamState.readSettings();
+ synchronized (VolumeStreamState.class) {
+ // unmute stream that was muted but is not affect by mute anymore
+ if (streamState.mIsMuted && ((!isStreamAffectedByMute(streamType) &&
+ !isStreamMutedByRingerMode(streamType)) || mUseFixedVolume)) {
+ streamState.mIsMuted = false;
+ }
+ }
+ }
+
+ // apply new ringer mode before checking volume for alias streams so that streams
+ // muted by ringer mode have the correct volume
+ setRingerModeInt(getRingerModeInternal(), false);
+
+ checkAllFixedVolumeDevices();
+ checkAllAliasStreamVolumes();
+ checkMuteAffectedStreams();
+
+ synchronized (mSafeMediaVolumeState) {
+ mMusicActiveMs = MathUtils.constrain(Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0, UserHandle.USER_CURRENT),
+ 0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX);
+ if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) {
+ enforceSafeMediaVolume(TAG);
+ }
+ }
+ }
+
+ /** @see AudioManager#setSpeakerphoneOn(boolean) */
+ public void setSpeakerphoneOn(boolean on){
+ if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
+ return;
+ }
+
+ if (on) {
+ if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+ sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, null, 0);
+ }
+ mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
+ } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ }
+
+ sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0);
+ }
+
+ /** @see AudioManager#isSpeakerphoneOn() */
+ public boolean isSpeakerphoneOn() {
+ return (mForcedUseForComm == AudioSystem.FORCE_SPEAKER);
+ }
+
+ /** @see AudioManager#setBluetoothScoOn(boolean) */
+ public void setBluetoothScoOn(boolean on){
+ if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
+ return;
+ }
+
+ if (on) {
+ mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+ } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+ mForcedUseForComm = AudioSystem.FORCE_NONE;
+ }
+
+ sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0);
+ sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_RECORD, mForcedUseForComm, null, 0);
+ }
+
+ /** @see AudioManager#isBluetoothScoOn() */
+ public boolean isBluetoothScoOn() {
+ return (mForcedUseForComm == AudioSystem.FORCE_BT_SCO);
+ }
+
+ /** @see AudioManager#setBluetoothA2dpOn(boolean) */
+ public void setBluetoothA2dpOn(boolean on) {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ mBluetoothA2dpEnabled = on;
+ sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
+ AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+ null, 0);
+ }
+ }
+
+ /** @see AudioManager#isBluetoothA2dpOn() */
+ public boolean isBluetoothA2dpOn() {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ return mBluetoothA2dpEnabled;
+ }
+ }
+
+ /** @see AudioManager#startBluetoothSco() */
+ public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+ int scoAudioMode =
+ (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
+ SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED;
+ startBluetoothScoInt(cb, scoAudioMode);
+ }
+
+ /** @see AudioManager#startBluetoothScoVirtualCall() */
+ public void startBluetoothScoVirtualCall(IBinder cb) {
+ startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL);
+ }
+
+ void startBluetoothScoInt(IBinder cb, int scoAudioMode){
+ if (!checkAudioSettingsPermission("startBluetoothSco()") ||
+ !mSystemReady) {
+ return;
+ }
+ ScoClient client = getScoClient(cb, true);
+ // The calling identity must be cleared before calling ScoClient.incCount().
+ // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+ // and this must be done on behalf of system server to make sure permissions are granted.
+ // The caller identity must be cleared after getScoClient() because it is needed if a new
+ // client is created.
+ final long ident = Binder.clearCallingIdentity();
+ client.incCount(scoAudioMode);
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ /** @see AudioManager#stopBluetoothSco() */
+ public void stopBluetoothSco(IBinder cb){
+ if (!checkAudioSettingsPermission("stopBluetoothSco()") ||
+ !mSystemReady) {
+ return;
+ }
+ ScoClient client = getScoClient(cb, false);
+ // The calling identity must be cleared before calling ScoClient.decCount().
+ // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+ // and this must be done on behalf of system server to make sure permissions are granted.
+ final long ident = Binder.clearCallingIdentity();
+ if (client != null) {
+ client.decCount();
+ }
+ Binder.restoreCallingIdentity(ident);
+ }
+
+
+ private class ScoClient implements IBinder.DeathRecipient {
+ private IBinder mCb; // To be notified of client's death
+ private int mCreatorPid;
+ private int mStartcount; // number of SCO connections started by this client
+
+ ScoClient(IBinder cb) {
+ mCb = cb;
+ mCreatorPid = Binder.getCallingPid();
+ mStartcount = 0;
+ }
+
+ public void binderDied() {
+ synchronized(mScoClients) {
+ Log.w(TAG, "SCO client died");
+ int index = mScoClients.indexOf(this);
+ if (index < 0) {
+ Log.w(TAG, "unregistered SCO client died");
+ } else {
+ clearCount(true);
+ mScoClients.remove(this);
+ }
+ }
+ }
+
+ public void incCount(int scoAudioMode) {
+ synchronized(mScoClients) {
+ requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
+ if (mStartcount == 0) {
+ try {
+ mCb.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ // client has already died!
+ Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death");
+ }
+ }
+ mStartcount++;
+ }
+ }
+
+ public void decCount() {
+ synchronized(mScoClients) {
+ if (mStartcount == 0) {
+ Log.w(TAG, "ScoClient.decCount() already 0");
+ } else {
+ mStartcount--;
+ if (mStartcount == 0) {
+ try {
+ mCb.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "decCount() going to 0 but not registered to binder");
+ }
+ }
+ requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+ }
+ }
+ }
+
+ public void clearCount(boolean stopSco) {
+ synchronized(mScoClients) {
+ if (mStartcount != 0) {
+ try {
+ mCb.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder");
+ }
+ }
+ mStartcount = 0;
+ if (stopSco) {
+ requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+ }
+ }
+ }
+
+ public int getCount() {
+ return mStartcount;
+ }
+
+ public IBinder getBinder() {
+ return mCb;
+ }
+
+ public int getPid() {
+ return mCreatorPid;
+ }
+
+ public int totalCount() {
+ synchronized(mScoClients) {
+ int count = 0;
+ int size = mScoClients.size();
+ for (int i = 0; i < size; i++) {
+ count += mScoClients.get(i).getCount();
+ }
+ return count;
+ }
+ }
+
+ private void requestScoState(int state, int scoAudioMode) {
+ checkScoAudioState();
+ if (totalCount() == 0) {
+ if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ // Make sure that the state transitions to CONNECTING even if we cannot initiate
+ // the connection.
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+ // Accept SCO audio activation only in NORMAL audio mode or if the mode is
+ // currently controlled by the same client process.
+ synchronized(mSetModeDeathHandlers) {
+ if ((mSetModeDeathHandlers.isEmpty() ||
+ mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) &&
+ (mScoAudioState == SCO_STATE_INACTIVE ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
+ if (mScoAudioState == SCO_STATE_INACTIVE) {
+ mScoAudioMode = scoAudioMode;
+ if (scoAudioMode == SCO_MODE_UNDEFINED) {
+ if (mBluetoothHeadsetDevice != null) {
+ mScoAudioMode = new Integer(Settings.Global.getInt(
+ mContentResolver,
+ "bluetooth_sco_channel_"+
+ mBluetoothHeadsetDevice.getAddress(),
+ SCO_MODE_VIRTUAL_CALL));
+ if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
+ mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+ }
+ } else {
+ mScoAudioMode = SCO_MODE_RAW;
+ }
+ }
+ if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
+ boolean status = false;
+ if (mScoAudioMode == SCO_MODE_RAW) {
+ status = mBluetoothHeadset.connectAudio();
+ } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
+ status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ } else if (mScoAudioMode == SCO_MODE_VR) {
+ status = mBluetoothHeadset.startVoiceRecognition(
+ mBluetoothHeadsetDevice);
+ }
+
+ if (status) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ } else {
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ } else if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ }
+ } else {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+ }
+ } else {
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ }
+ } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED &&
+ (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
+ mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
+ if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
+ if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
+ boolean status = false;
+ if (mScoAudioMode == SCO_MODE_RAW) {
+ status = mBluetoothHeadset.disconnectAudio();
+ } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
+ status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ } else if (mScoAudioMode == SCO_MODE_VR) {
+ status = mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice);
+ }
+
+ if (!status) {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ } else if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+ }
+ } else {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ }
+ }
+ }
+ }
+
+ private void checkScoAudioState() {
+ if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
+ mScoAudioState == SCO_STATE_INACTIVE &&
+ mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ }
+
+ private ScoClient getScoClient(IBinder cb, boolean create) {
+ synchronized(mScoClients) {
+ ScoClient client = null;
+ int size = mScoClients.size();
+ for (int i = 0; i < size; i++) {
+ client = mScoClients.get(i);
+ if (client.getBinder() == cb)
+ return client;
+ }
+ if (create) {
+ client = new ScoClient(cb);
+ mScoClients.add(client);
+ }
+ return client;
+ }
+ }
+
+ public void clearAllScoClients(int exceptPid, boolean stopSco) {
+ synchronized(mScoClients) {
+ ScoClient savedClient = null;
+ int size = mScoClients.size();
+ for (int i = 0; i < size; i++) {
+ ScoClient cl = mScoClients.get(i);
+ if (cl.getPid() != exceptPid) {
+ cl.clearCount(stopSco);
+ } else {
+ savedClient = cl;
+ }
+ }
+ mScoClients.clear();
+ if (savedClient != null) {
+ mScoClients.add(savedClient);
+ }
+ }
+ }
+
+ private boolean getBluetoothHeadset() {
+ boolean result = false;
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+ BluetoothProfile.HEADSET);
+ }
+ // If we could not get a bluetooth headset proxy, send a failure message
+ // without delay to reset the SCO audio state and clear SCO clients.
+ // If we could get a proxy, send a delayed failure message that will reset our state
+ // in case we don't receive onServiceConnected().
+ sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
+ SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0);
+ return result;
+ }
+
+ private void disconnectBluetoothSco(int exceptPid) {
+ synchronized(mScoClients) {
+ checkScoAudioState();
+ if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
+ if (mBluetoothHeadsetDevice != null) {
+ if (mBluetoothHeadset != null) {
+ if (!mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice)) {
+ sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
+ SENDMSG_REPLACE, 0, 0, null, 0);
+ }
+ } else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL &&
+ getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_EXT_REQ;
+ }
+ }
+ } else {
+ clearAllScoClients(exceptPid, true);
+ }
+ }
+ }
+
+ private void resetBluetoothSco() {
+ synchronized(mScoClients) {
+ clearAllScoClients(0, false);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ }
+
+ private void broadcastScoConnectionState(int state) {
+ sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE,
+ SENDMSG_QUEUE, state, 0, null, 0);
+ }
+
+ private void onBroadcastScoConnectionState(int state) {
+ if (state != mScoConnectionState) {
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
+ mScoConnectionState);
+ sendStickyBroadcastToAll(newIntent);
+ mScoConnectionState = state;
+ }
+ }
+
+ private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+ new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ BluetoothDevice btDevice;
+ List<BluetoothDevice> deviceList;
+ switch(profile) {
+ case BluetoothProfile.A2DP:
+ synchronized (mConnectedDevices) {
+ synchronized (mA2dpAvrcpLock) {
+ mA2dp = (BluetoothA2dp) proxy;
+ deviceList = mA2dp.getConnectedDevices();
+ if (deviceList.size() > 0) {
+ btDevice = deviceList.get(0);
+ int state = mA2dp.getConnectionState(btDevice);
+ int delay = checkSendBecomingNoisyIntent(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
+ queueMsgUnderWakeLock(mAudioHandler,
+ MSG_SET_A2DP_SINK_CONNECTION_STATE,
+ state,
+ 0,
+ btDevice,
+ delay);
+ }
+ }
+ }
+ break;
+
+ case BluetoothProfile.A2DP_SINK:
+ deviceList = proxy.getConnectedDevices();
+ if (deviceList.size() > 0) {
+ btDevice = deviceList.get(0);
+ synchronized (mConnectedDevices) {
+ int state = proxy.getConnectionState(btDevice);
+ queueMsgUnderWakeLock(mAudioHandler,
+ MSG_SET_A2DP_SRC_CONNECTION_STATE,
+ state,
+ 0,
+ btDevice,
+ 0 /* delay */);
+ }
+ }
+ break;
+
+ case BluetoothProfile.HEADSET:
+ synchronized (mScoClients) {
+ // Discard timeout message
+ mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ deviceList = mBluetoothHeadset.getConnectedDevices();
+ if (deviceList.size() > 0) {
+ mBluetoothHeadsetDevice = deviceList.get(0);
+ } else {
+ mBluetoothHeadsetDevice = null;
+ }
+ // Refresh SCO audio state
+ checkScoAudioState();
+ // Continue pending action if any
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
+ boolean status = false;
+ if (mBluetoothHeadsetDevice != null) {
+ switch (mScoAudioState) {
+ case SCO_STATE_ACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ if (mScoAudioMode == SCO_MODE_RAW) {
+ status = mBluetoothHeadset.connectAudio();
+ } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
+ status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ } else if (mScoAudioMode == SCO_MODE_VR) {
+ status = mBluetoothHeadset.startVoiceRecognition(
+ mBluetoothHeadsetDevice);
+ }
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ if (mScoAudioMode == SCO_MODE_RAW) {
+ status = mBluetoothHeadset.disconnectAudio();
+ } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
+ status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
+ mBluetoothHeadsetDevice);
+ } else if (mScoAudioMode == SCO_MODE_VR) {
+ status = mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice);
+ }
+ break;
+ case SCO_STATE_DEACTIVATE_EXT_REQ:
+ status = mBluetoothHeadset.stopVoiceRecognition(
+ mBluetoothHeadsetDevice);
+ }
+ }
+ if (!status) {
+ sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
+ SENDMSG_REPLACE, 0, 0, null, 0);
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ public void onServiceDisconnected(int profile) {
+ ArraySet<String> toRemove = null;
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ synchronized (mConnectedDevices) {
+ synchronized (mA2dpAvrcpLock) {
+ // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
+ if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ toRemove = toRemove != null ? toRemove : new ArraySet<String>();
+ toRemove.add(deviceSpec.mDeviceAddress);
+ }
+ }
+ if (toRemove != null) {
+ for (int i = 0; i < toRemove.size(); i++) {
+ makeA2dpDeviceUnavailableNow(toRemove.valueAt(i));
+ }
+ }
+ }
+ }
+ break;
+
+ case BluetoothProfile.A2DP_SINK:
+ synchronized (mConnectedDevices) {
+ // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
+ for(int i = 0; i < mConnectedDevices.size(); i++) {
+ DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
+ if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
+ toRemove = toRemove != null ? toRemove : new ArraySet<String>();
+ toRemove.add(deviceSpec.mDeviceAddress);
+ }
+ }
+ if (toRemove != null) {
+ for (int i = 0; i < toRemove.size(); i++) {
+ makeA2dpSrcUnavailable(toRemove.valueAt(i));
+ }
+ }
+ }
+ break;
+
+ case BluetoothProfile.HEADSET:
+ synchronized (mScoClients) {
+ mBluetoothHeadset = null;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ };
+
+ private void onCheckMusicActive(String caller) {
+ synchronized (mSafeMediaVolumeState) {
+ if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
+ int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
+
+ if ((device & mSafeMediaVolumeDevices) != 0) {
+ sendMsg(mAudioHandler,
+ MSG_CHECK_MUSIC_ACTIVE,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ caller,
+ MUSIC_ACTIVE_POLL_PERIOD_MS);
+ int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
+ if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
+ (index > mSafeMediaVolumeIndex)) {
+ // Approximate cumulative active music time
+ mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
+ if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
+ setSafeMediaVolumeEnabled(true, caller);
+ mMusicActiveMs = 0;
+ }
+ saveMusicActiveMs();
+ }
+ }
+ }
+ }
+ }
+
+ private void saveMusicActiveMs() {
+ mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
+ }
+
+ private void onConfigureSafeVolume(boolean force, String caller) {
+ synchronized (mSafeMediaVolumeState) {
+ int mcc = mContext.getResources().getConfiguration().mcc;
+ if ((mMcc != mcc) || ((mMcc == 0) && force)) {
+ mSafeMediaVolumeIndex = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+ boolean safeMediaVolumeEnabled =
+ SystemProperties.getBoolean("audio.safemedia.force", false)
+ || mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_safe_media_volume_enabled);
+
+ // The persisted state is either "disabled" or "active": this is the state applied
+ // next time we boot and cannot be "inactive"
+ int persistedState;
+ if (safeMediaVolumeEnabled) {
+ persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
+ // The state can already be "inactive" here if the user has forced it before
+ // the 30 seconds timeout for forced configuration. In this case we don't reset
+ // it to "active".
+ if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
+ if (mMusicActiveMs == 0) {
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
+ enforceSafeMediaVolume(caller);
+ } else {
+ // We have existing playback time recorded, already confirmed.
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+ }
+ }
+ } else {
+ persistedState = SAFE_MEDIA_VOLUME_DISABLED;
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
+ }
+ mMcc = mcc;
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_SAFE_VOLUME_STATE,
+ SENDMSG_QUEUE,
+ persistedState,
+ 0,
+ null,
+ 0);
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Internal methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Checks if the adjustment should change ringer mode instead of just
+ * adjusting volume. If so, this will set the proper ringer mode and volume
+ * indices on the stream states.
+ */
+ private int checkForRingerModeChange(int oldIndex, int direction, int step, boolean isMuted) {
+ final boolean isTv = mPlatformType == AudioSystem.PLATFORM_TELEVISION;
+ int result = FLAG_ADJUST_VOLUME;
+ int ringerMode = getRingerModeInternal();
+
+ switch (ringerMode) {
+ case RINGER_MODE_NORMAL:
+ if (direction == AudioManager.ADJUST_LOWER) {
+ if (mHasVibrator) {
+ // "step" is the delta in internal index units corresponding to a
+ // change of 1 in UI index units.
+ // Because of rounding when rescaling from one stream index range to its alias
+ // index range, we cannot simply test oldIndex == step:
+ // (step <= oldIndex < 2 * step) is equivalent to: (old UI index == 1)
+ if (step <= oldIndex && oldIndex < 2 * step) {
+ ringerMode = RINGER_MODE_VIBRATE;
+ mLoweredFromNormalToVibrateTime = SystemClock.uptimeMillis();
+ }
+ } else {
+ // (oldIndex < step) is equivalent to (old UI index == 0)
+ if ((oldIndex < step)
+ && mVolumePolicy.volumeDownToEnterSilent
+ && mPrevVolDirection != AudioManager.ADJUST_LOWER) {
+ ringerMode = RINGER_MODE_SILENT;
+ }
+ }
+ } else if (isTv && (direction == AudioManager.ADJUST_TOGGLE_MUTE
+ || direction == AudioManager.ADJUST_MUTE)) {
+ if (mHasVibrator) {
+ ringerMode = RINGER_MODE_VIBRATE;
+ } else {
+ ringerMode = RINGER_MODE_SILENT;
+ }
+ // Setting the ringer mode will toggle mute
+ result &= ~FLAG_ADJUST_VOLUME;
+ }
+ break;
+ case RINGER_MODE_VIBRATE:
+ if (!mHasVibrator) {
+ Log.e(TAG, "checkForRingerModeChange() current ringer mode is vibrate" +
+ "but no vibrator is present");
+ break;
+ }
+ if ((direction == AudioManager.ADJUST_LOWER)) {
+ // This is the case we were muted with the volume turned up
+ if (isTv && oldIndex >= 2 * step && isMuted) {
+ ringerMode = RINGER_MODE_NORMAL;
+ } else if (mPrevVolDirection != AudioManager.ADJUST_LOWER) {
+ if (mVolumePolicy.volumeDownToEnterSilent) {
+ final long diff = SystemClock.uptimeMillis()
+ - mLoweredFromNormalToVibrateTime;
+ if (diff > mVolumePolicy.vibrateToSilentDebounce) {
+ ringerMode = RINGER_MODE_SILENT;
+ }
+ } else {
+ result |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
+ }
+ }
+ } else if (direction == AudioManager.ADJUST_RAISE
+ || direction == AudioManager.ADJUST_TOGGLE_MUTE
+ || direction == AudioManager.ADJUST_UNMUTE) {
+ ringerMode = RINGER_MODE_NORMAL;
+ }
+ result &= ~FLAG_ADJUST_VOLUME;
+ break;
+ case RINGER_MODE_SILENT:
+ if (isTv && direction == AudioManager.ADJUST_LOWER && oldIndex >= 2 * step && isMuted) {
+ // This is the case we were muted with the volume turned up
+ ringerMode = RINGER_MODE_NORMAL;
+ } else if (direction == AudioManager.ADJUST_RAISE
+ || direction == AudioManager.ADJUST_TOGGLE_MUTE
+ || direction == AudioManager.ADJUST_UNMUTE) {
+ if (!mVolumePolicy.volumeUpToExitSilent) {
+ result |= AudioManager.FLAG_SHOW_SILENT_HINT;
+ } else {
+ if (mHasVibrator && direction == AudioManager.ADJUST_RAISE) {
+ ringerMode = RINGER_MODE_VIBRATE;
+ } else {
+ // If we don't have a vibrator or they were toggling mute
+ // go straight back to normal.
+ ringerMode = RINGER_MODE_NORMAL;
+ }
+ }
+ }
+ result &= ~FLAG_ADJUST_VOLUME;
+ break;
+ default:
+ Log.e(TAG, "checkForRingerModeChange() wrong ringer mode: "+ringerMode);
+ break;
+ }
+
+ setRingerMode(ringerMode, TAG + ".checkForRingerModeChange", false /*external*/);
+
+ mPrevVolDirection = direction;
+
+ return result;
+ }
+
+ @Override
+ public boolean isStreamAffectedByRingerMode(int streamType) {
+ return (mRingerModeAffectedStreams & (1 << streamType)) != 0;
+ }
+
+ private boolean isStreamMutedByRingerMode(int streamType) {
+ return (mRingerModeMutedStreams & (1 << streamType)) != 0;
+ }
+
+ boolean updateRingerModeAffectedStreams() {
+ int ringerModeAffectedStreams;
+ // make sure settings for ringer mode are consistent with device type: non voice capable
+ // devices (tablets) include media stream in silent mode whereas phones don't.
+ ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver,
+ Settings.System.MODE_RINGER_STREAMS_AFFECTED,
+ ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)|
+ (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)),
+ UserHandle.USER_CURRENT);
+
+ // ringtone, notification and system streams are always affected by ringer mode
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_RING)|
+ (1 << AudioSystem.STREAM_NOTIFICATION)|
+ (1 << AudioSystem.STREAM_SYSTEM);
+
+ switch (mPlatformType) {
+ case AudioSystem.PLATFORM_TELEVISION:
+ ringerModeAffectedStreams = 0;
+ break;
+ default:
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC);
+ break;
+ }
+
+ synchronized (mCameraSoundForced) {
+ if (mCameraSoundForced) {
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ } else {
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ }
+ }
+ if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) {
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
+ } else {
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
+ }
+
+ if (ringerModeAffectedStreams != mRingerModeAffectedStreams) {
+ Settings.System.putIntForUser(mContentResolver,
+ Settings.System.MODE_RINGER_STREAMS_AFFECTED,
+ ringerModeAffectedStreams,
+ UserHandle.USER_CURRENT);
+ mRingerModeAffectedStreams = ringerModeAffectedStreams;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isStreamAffectedByMute(int streamType) {
+ return (mMuteAffectedStreams & (1 << streamType)) != 0;
+ }
+
+ private void ensureValidDirection(int direction) {
+ switch (direction) {
+ case AudioManager.ADJUST_LOWER:
+ case AudioManager.ADJUST_RAISE:
+ case AudioManager.ADJUST_SAME:
+ case AudioManager.ADJUST_MUTE:
+ case AudioManager.ADJUST_UNMUTE:
+ case AudioManager.ADJUST_TOGGLE_MUTE:
+ break;
+ default:
+ throw new IllegalArgumentException("Bad direction " + direction);
+ }
+ }
+
+ private void ensureValidStreamType(int streamType) {
+ if (streamType < 0 || streamType >= mStreamStates.length) {
+ throw new IllegalArgumentException("Bad stream type " + streamType);
+ }
+ }
+
+ private boolean isMuteAdjust(int adjust) {
+ return adjust == AudioManager.ADJUST_MUTE || adjust == AudioManager.ADJUST_UNMUTE
+ || adjust == AudioManager.ADJUST_TOGGLE_MUTE;
+ }
+
+ private boolean isInCommunication() {
+ boolean IsInCall = false;
+
+ TelecomManager telecomManager =
+ (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+
+ final long ident = Binder.clearCallingIdentity();
+ IsInCall = telecomManager.isInCall();
+ Binder.restoreCallingIdentity(ident);
+
+ return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION);
+ }
+
+ /**
+ * For code clarity for getActiveStreamType(int)
+ * @param delay_ms max time since last STREAM_MUSIC activity to consider
+ * @return true if STREAM_MUSIC is active in streams handled by AudioFlinger now or
+ * in the last "delay_ms" ms.
+ */
+ private boolean isAfMusicActiveRecently(int delay_ms) {
+ return AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, delay_ms)
+ || AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, delay_ms);
+ }
+
+ private int getActiveStreamType(int suggestedStreamType) {
+ switch (mPlatformType) {
+ case AudioSystem.PLATFORM_VOICE:
+ if (isInCommunication()) {
+ if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
+ == AudioSystem.FORCE_BT_SCO) {
+ // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
+ return AudioSystem.STREAM_BLUETOOTH_SCO;
+ } else {
+ // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
+ return AudioSystem.STREAM_VOICE_CALL;
+ }
+ } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
+ return AudioSystem.STREAM_MUSIC;
+ } else {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default");
+ return AudioSystem.STREAM_RING;
+ }
+ } else if (isAfMusicActiveRecently(0)) {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
+ return AudioSystem.STREAM_MUSIC;
+ }
+ break;
+ case AudioSystem.PLATFORM_TELEVISION:
+ if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ // TV always defaults to STREAM_MUSIC
+ return AudioSystem.STREAM_MUSIC;
+ }
+ break;
+ default:
+ if (isInCommunication()) {
+ if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
+ == AudioSystem.FORCE_BT_SCO) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO");
+ return AudioSystem.STREAM_BLUETOOTH_SCO;
+ } else {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL");
+ return AudioSystem.STREAM_VOICE_CALL;
+ }
+ } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION,
+ StreamOverride.sDelayMs) ||
+ AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
+ StreamOverride.sDelayMs)) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
+ return AudioSystem.STREAM_NOTIFICATION;
+ } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
+ return AudioSystem.STREAM_MUSIC;
+ } else {
+ if (DEBUG_VOL) Log.v(TAG,
+ "getActiveStreamType: using STREAM_NOTIFICATION as default");
+ return AudioSystem.STREAM_NOTIFICATION;
+ }
+ }
+ break;
+ }
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type "
+ + suggestedStreamType);
+ return suggestedStreamType;
+ }
+
+ private void broadcastRingerMode(String action, int ringerMode) {
+ // Send sticky broadcast
+ Intent broadcast = new Intent(action);
+ broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, ringerMode);
+ broadcast.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ sendStickyBroadcastToAll(broadcast);
+ }
+
+ private void broadcastVibrateSetting(int vibrateType) {
+ // Send broadcast
+ if (ActivityManagerNative.isSystemReady()) {
+ Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION);
+ broadcast.putExtra(AudioManager.EXTRA_VIBRATE_TYPE, vibrateType);
+ broadcast.putExtra(AudioManager.EXTRA_VIBRATE_SETTING, getVibrateSetting(vibrateType));
+ sendBroadcastToAll(broadcast);
+ }
+ }
+
+ // Message helper methods
+ /**
+ * Queue a message on the given handler's message queue, after acquiring the service wake lock.
+ * Note that the wake lock needs to be released after the message has been handled.
+ */
+ private void queueMsgUnderWakeLock(Handler handler, int msg,
+ int arg1, int arg2, Object obj, int delay) {
+ final long ident = Binder.clearCallingIdentity();
+ // Always acquire the wake lock as AudioService because it is released by the
+ // message handler.
+ mAudioEventWakeLock.acquire();
+ Binder.restoreCallingIdentity(ident);
+ sendMsg(handler, msg, SENDMSG_QUEUE, arg1, arg2, obj, delay);
+ }
+
+ private static void sendMsg(Handler handler, int msg,
+ int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
+
+ if (existingMsgPolicy == SENDMSG_REPLACE) {
+ handler.removeMessages(msg);
+ } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
+ return;
+ }
+ synchronized (mLastDeviceConnectMsgTime) {
+ long time = SystemClock.uptimeMillis() + delay;
+ handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
+ if (msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
+ msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
+ msg == MSG_SET_A2DP_SINK_CONNECTION_STATE) {
+ mLastDeviceConnectMsgTime = time;
+ }
+ }
+ }
+
+ boolean checkAudioSettingsPermission(String method) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ String msg = "Audio Settings Permission Denial: " + method + " from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid();
+ Log.w(TAG, msg);
+ return false;
+ }
+
+ private int getDeviceForStream(int stream) {
+ int device = getDevicesForStream(stream);
+ if ((device & (device - 1)) != 0) {
+ // Multiple device selection is either:
+ // - speaker + one other device: give priority to speaker in this case.
+ // - one A2DP device + another device: happens with duplicated output. In this case
+ // retain the device on the A2DP output as the other must not correspond to an active
+ // selection if not the speaker.
+ // - HDMI-CEC system audio mode only output: give priority to available item in order.
+ if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {
+ device = AudioSystem.DEVICE_OUT_SPEAKER;
+ } else if ((device & AudioSystem.DEVICE_OUT_HDMI_ARC) != 0) {
+ device = AudioSystem.DEVICE_OUT_HDMI_ARC;
+ } else if ((device & AudioSystem.DEVICE_OUT_SPDIF) != 0) {
+ device = AudioSystem.DEVICE_OUT_SPDIF;
+ } else if ((device & AudioSystem.DEVICE_OUT_AUX_LINE) != 0) {
+ device = AudioSystem.DEVICE_OUT_AUX_LINE;
+ } else {
+ device &= AudioSystem.DEVICE_OUT_ALL_A2DP;
+ }
+ }
+ return device;
+ }
+
+ private int getDevicesForStream(int stream) {
+ return getDevicesForStream(stream, true /*checkOthers*/);
+ }
+
+ private int getDevicesForStream(int stream, boolean checkOthers) {
+ ensureValidStreamType(stream);
+ synchronized (VolumeStreamState.class) {
+ return mStreamStates[stream].observeDevicesForStream_syncVSS(checkOthers);
+ }
+ }
+
+ private void observeDevicesForStreams(int skipStream) {
+ synchronized (VolumeStreamState.class) {
+ for (int stream = 0; stream < mStreamStates.length; stream++) {
+ if (stream != skipStream) {
+ mStreamStates[stream].observeDevicesForStream_syncVSS(false /*checkOthers*/);
+ }
+ }
+ }
+ }
+
+ /*
+ * A class just for packaging up a set of connection parameters.
+ */
+ private class WiredDeviceConnectionState {
+ public final int mType;
+ public final int mState;
+ public final String mAddress;
+ public final String mName;
+ public final String mCaller;
+
+ public WiredDeviceConnectionState(int type, int state, String address, String name,
+ String caller) {
+ mType = type;
+ mState = state;
+ mAddress = address;
+ mName = name;
+ mCaller = caller;
+ }
+ }
+
+ public void setWiredDeviceConnectionState(int type, int state, String address, String name,
+ String caller) {
+ synchronized (mConnectedDevices) {
+ if (DEBUG_DEVICES) {
+ Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
+ + address + ")");
+ }
+ int delay = checkSendBecomingNoisyIntent(type, state);
+ queueMsgUnderWakeLock(mAudioHandler,
+ MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
+ 0,
+ 0,
+ new WiredDeviceConnectionState(type, state, address, name, caller),
+ delay);
+ }
+ }
+
+ public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state, int profile)
+ {
+ int delay;
+ if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
+ throw new IllegalArgumentException("invalid profile " + profile);
+ }
+ synchronized (mConnectedDevices) {
+ if (profile == BluetoothProfile.A2DP) {
+ delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0);
+ } else {
+ delay = 0;
+ }
+ queueMsgUnderWakeLock(mAudioHandler,
+ (profile == BluetoothProfile.A2DP ?
+ MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE),
+ state,
+ 0,
+ device,
+ delay);
+ }
+ return delay;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Inner classes
+ ///////////////////////////////////////////////////////////////////////////
+
+ // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
+ // 1 mScoclient OR mSafeMediaVolumeState
+ // 2 mSetModeDeathHandlers
+ // 3 mSettingsLock
+ // 4 VolumeStreamState.class
+ // 5 mCameraSoundForced
+ public class VolumeStreamState {
+ private final int mStreamType;
+ private final int mIndexMin;
+ private final int mIndexMax;
+
+ private boolean mIsMuted;
+ private String mVolumeIndexSettingName;
+ private int mObservedDevices;
+
+ private final SparseIntArray mIndexMap = new SparseIntArray(8);
+ private final Intent mVolumeChanged;
+ private final Intent mStreamDevicesChanged;
+
+ private VolumeStreamState(String settingName, int streamType) {
+
+ mVolumeIndexSettingName = settingName;
+
+ mStreamType = streamType;
+ mIndexMin = MIN_STREAM_VOLUME[streamType] * 10;
+ mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
+ AudioSystem.initStreamVolume(streamType, mIndexMin / 10, mIndexMax / 10);
+
+ readSettings();
+ mVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION);
+ mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
+ mStreamDevicesChanged = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
+ mStreamDevicesChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
+ }
+
+ public int observeDevicesForStream_syncVSS(boolean checkOthers) {
+ final int devices = AudioSystem.getDevicesForStream(mStreamType);
+ if (devices == mObservedDevices) {
+ return devices;
+ }
+ final int prevDevices = mObservedDevices;
+ mObservedDevices = devices;
+ if (checkOthers) {
+ // one stream's devices have changed, check the others
+ observeDevicesForStreams(mStreamType);
+ }
+ // log base stream changes to the event log
+ if (mStreamVolumeAlias[mStreamType] == mStreamType) {
+ EventLogTags.writeStreamDevicesChanged(mStreamType, prevDevices, devices);
+ }
+ sendBroadcastToAll(mStreamDevicesChanged
+ .putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, prevDevices)
+ .putExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, devices));
+ return devices;
+ }
+
+ public String getSettingNameForDevice(int device) {
+ String name = mVolumeIndexSettingName;
+ String suffix = AudioSystem.getOutputDeviceName(device);
+ if (suffix.isEmpty()) {
+ return name;
+ }
+ return name + "_" + suffix;
+ }
+
+ public void readSettings() {
+ synchronized (VolumeStreamState.class) {
+ // force maximum volume on all streams if fixed volume property is set
+ if (mUseFixedVolume) {
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+ return;
+ }
+ // do not read system stream volume from settings: this stream is always aliased
+ // to another stream type and its volume is never persisted. Values in settings can
+ // only be stale values
+ if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
+ (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
+ int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
+ synchronized (mCameraSoundForced) {
+ if (mCameraSoundForced) {
+ index = mIndexMax;
+ }
+ }
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+ return;
+ }
+
+ int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
+
+ for (int i = 0; remainingDevices != 0; i++) {
+ int device = (1 << i);
+ if ((device & remainingDevices) == 0) {
+ continue;
+ }
+ remainingDevices &= ~device;
+
+ // retrieve current volume for device
+ String name = getSettingNameForDevice(device);
+ // if no volume stored for current stream and device, use default volume if default
+ // device, continue otherwise
+ int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ?
+ AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType] : -1;
+ int index = Settings.System.getIntForUser(
+ mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+ if (index == -1) {
+ continue;
+ }
+
+ mIndexMap.put(device, getValidIndex(10 * index));
+ }
+ }
+ }
+
+ // must be called while synchronized VolumeStreamState.class
+ public void applyDeviceVolume_syncVSS(int device) {
+ int index;
+ if (mIsMuted) {
+ index = 0;
+ } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported)
+ || ((device & mFullVolumeDevices) != 0)) {
+ index = (mIndexMax + 5)/10;
+ } else {
+ index = (getIndex(device) + 5)/10;
+ }
+ AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+ }
+
+ public void applyAllVolumes() {
+ synchronized (VolumeStreamState.class) {
+ // apply default volume first: by convention this will reset all
+ // devices volumes in audio policy manager to the supplied value
+ int index;
+ if (mIsMuted) {
+ index = 0;
+ } else {
+ index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
+ }
+ AudioSystem.setStreamVolumeIndex(mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
+ // then apply device specific volumes
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ int device = mIndexMap.keyAt(i);
+ if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
+ if (mIsMuted) {
+ index = 0;
+ } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
+ mAvrcpAbsVolSupported)
+ || ((device & mFullVolumeDevices) != 0))
+ {
+ index = (mIndexMax + 5)/10;
+ } else {
+ index = (mIndexMap.valueAt(i) + 5)/10;
+ }
+ AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+ }
+ }
+ }
+ }
+
+ public boolean adjustIndex(int deltaIndex, int device, String caller) {
+ return setIndex(getIndex(device) + deltaIndex, device, caller);
+ }
+
+ public boolean setIndex(int index, int device, String caller) {
+ boolean changed = false;
+ int oldIndex;
+ synchronized (VolumeStreamState.class) {
+ oldIndex = getIndex(device);
+ index = getValidIndex(index);
+ synchronized (mCameraSoundForced) {
+ if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
+ index = mIndexMax;
+ }
+ }
+ mIndexMap.put(device, index);
+
+ changed = oldIndex != index;
+ if (changed) {
+ // Apply change to all streams using this one as alias
+ // if changing volume of current device, also change volume of current
+ // device on aliased stream
+ boolean currentDevice = (device == getDeviceForStream(mStreamType));
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ if (streamType != mStreamType &&
+ mStreamVolumeAlias[streamType] == mStreamType) {
+ int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+ mStreamStates[streamType].setIndex(scaledIndex, device, caller);
+ if (currentDevice) {
+ mStreamStates[streamType].setIndex(scaledIndex,
+ getDeviceForStream(streamType), caller);
+ }
+ }
+ }
+ }
+ }
+ if (changed) {
+ oldIndex = (oldIndex + 5) / 10;
+ index = (index + 5) / 10;
+ // log base stream changes to the event log
+ if (mStreamVolumeAlias[mStreamType] == mStreamType) {
+ if (caller == null) {
+ Log.w(TAG, "No caller for volume_changed event", new Throwable());
+ }
+ EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10,
+ caller);
+ }
+ // fire changed intents for all streams
+ mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
+ mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
+ sendBroadcastToAll(mVolumeChanged);
+ }
+ return changed;
+ }
+
+ public int getIndex(int device) {
+ synchronized (VolumeStreamState.class) {
+ int index = mIndexMap.get(device, -1);
+ if (index == -1) {
+ // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+ index = mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
+ }
+ return index;
+ }
+ }
+
+ public int getMaxIndex() {
+ return mIndexMax;
+ }
+
+ public int getMinIndex() {
+ return mIndexMin;
+ }
+
+ public void setAllIndexes(VolumeStreamState srcStream, String caller) {
+ synchronized (VolumeStreamState.class) {
+ int srcStreamType = srcStream.getStreamType();
+ // apply default device volume from source stream to all devices first in case
+ // some devices are present in this stream state but not in source stream state
+ int index = srcStream.getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+ index = rescaleIndex(index, srcStreamType, mStreamType);
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ mIndexMap.put(mIndexMap.keyAt(i), index);
+ }
+ // Now apply actual volume for devices in source stream state
+ SparseIntArray srcMap = srcStream.mIndexMap;
+ for (int i = 0; i < srcMap.size(); i++) {
+ int device = srcMap.keyAt(i);
+ index = srcMap.valueAt(i);
+ index = rescaleIndex(index, srcStreamType, mStreamType);
+
+ setIndex(index, device, caller);
+ }
+ }
+ }
+
+ public void setAllIndexesToMax() {
+ synchronized (VolumeStreamState.class) {
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ mIndexMap.put(mIndexMap.keyAt(i), mIndexMax);
+ }
+ }
+ }
+
+ public void mute(boolean state) {
+ boolean changed = false;
+ synchronized (VolumeStreamState.class) {
+ if (state != mIsMuted) {
+ changed = true;
+ mIsMuted = state;
+
+ // Set the new mute volume. This propagates the values to
+ // the audio system, otherwise the volume won't be changed
+ // at the lower level.
+ sendMsg(mAudioHandler,
+ MSG_SET_ALL_VOLUMES,
+ SENDMSG_QUEUE,
+ 0,
+ 0,
+ this, 0);
+ }
+ }
+ if (changed) {
+ // Stream mute changed, fire the intent.
+ Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+ intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
+ intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state);
+ sendBroadcastToAll(intent);
+ }
+ }
+
+ public int getStreamType() {
+ return mStreamType;
+ }
+
+ public void checkFixedVolumeDevices() {
+ synchronized (VolumeStreamState.class) {
+ // ignore settings for fixed volume devices: volume should always be at max or 0
+ if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ int device = mIndexMap.keyAt(i);
+ int index = mIndexMap.valueAt(i);
+ if (((device & mFullVolumeDevices) != 0)
+ || (((device & mFixedVolumeDevices) != 0) && index != 0)) {
+ mIndexMap.put(device, mIndexMax);
+ }
+ applyDeviceVolume_syncVSS(device);
+ }
+ }
+ }
+ }
+
+ private int getValidIndex(int index) {
+ if (index < mIndexMin) {
+ return mIndexMin;
+ } else if (mUseFixedVolume || index > mIndexMax) {
+ return mIndexMax;
+ }
+
+ return index;
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.print(" Muted: ");
+ pw.println(mIsMuted);
+ pw.print(" Min: ");
+ pw.println((mIndexMin + 5) / 10);
+ pw.print(" Max: ");
+ pw.println((mIndexMax + 5) / 10);
+ pw.print(" Current: ");
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ final int device = mIndexMap.keyAt(i);
+ pw.print(Integer.toHexString(device));
+ final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+ : AudioSystem.getOutputDeviceName(device);
+ if (!deviceName.isEmpty()) {
+ pw.print(" (");
+ pw.print(deviceName);
+ pw.print(")");
+ }
+ pw.print(": ");
+ final int index = (mIndexMap.valueAt(i) + 5) / 10;
+ pw.print(index);
+ }
+ pw.println();
+ pw.print(" Devices: ");
+ final int devices = getDevicesForStream(mStreamType);
+ int device, i = 0, n = 0;
+ // iterate all devices from 1 to DEVICE_OUT_DEFAULT exclusive
+ // (the default device is not returned by getDevicesForStream)
+ while ((device = 1 << i) != AudioSystem.DEVICE_OUT_DEFAULT) {
+ if ((devices & device) != 0) {
+ if (n++ > 0) {
+ pw.print(", ");
+ }
+ pw.print(AudioSystem.getOutputDeviceName(device));
+ }
+ i++;
+ }
+ }
+ }
+
+ /** Thread that handles native AudioSystem control. */
+ private class AudioSystemThread extends Thread {
+ AudioSystemThread() {
+ super("AudioService");
+ }
+
+ @Override
+ public void run() {
+ // Set this thread up so the handler will work on it
+ Looper.prepare();
+
+ synchronized(AudioService.this) {
+ mAudioHandler = new AudioHandler();
+
+ // Notify that the handler has been created
+ AudioService.this.notify();
+ }
+
+ // Listen for volume change requests that are set by VolumePanel
+ Looper.loop();
+ }
+ }
+
+ /** Handles internal volume messages in separate volume thread. */
+ private class AudioHandler extends Handler {
+
+ private void setDeviceVolume(VolumeStreamState streamState, int device) {
+
+ synchronized (VolumeStreamState.class) {
+ // Apply volume
+ streamState.applyDeviceVolume_syncVSS(device);
+
+ // Apply change to all streams using this one as alias
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ if (streamType != streamState.mStreamType &&
+ mStreamVolumeAlias[streamType] == streamState.mStreamType) {
+ // Make sure volume is also maxed out on A2DP device for aliased stream
+ // that may have a different device selected
+ int streamDevice = getDeviceForStream(streamType);
+ if ((device != streamDevice) && mAvrcpAbsVolSupported &&
+ ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
+ mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
+ }
+ mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
+ }
+ }
+ }
+ // Post a persist volume msg
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_VOLUME,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ streamState,
+ PERSIST_DELAY);
+
+ }
+
+ private void setAllVolumes(VolumeStreamState streamState) {
+
+ // Apply volume
+ streamState.applyAllVolumes();
+
+ // Apply change to all streams using this one as alias
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ if (streamType != streamState.mStreamType &&
+ mStreamVolumeAlias[streamType] == streamState.mStreamType) {
+ mStreamStates[streamType].applyAllVolumes();
+ }
+ }
+ }
+
+ private void persistVolume(VolumeStreamState streamState, int device) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ if (isPlatformTelevision() && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
+ return;
+ }
+ System.putIntForUser(mContentResolver,
+ streamState.getSettingNameForDevice(device),
+ (streamState.getIndex(device) + 5)/ 10,
+ UserHandle.USER_CURRENT);
+ }
+
+ private void persistRingerMode(int ringerMode) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ Settings.Global.putInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode);
+ }
+
+ private boolean onLoadSoundEffects() {
+ int status;
+
+ synchronized (mSoundEffectsLock) {
+ if (!mSystemReady) {
+ Log.w(TAG, "onLoadSoundEffects() called before boot complete");
+ return false;
+ }
+
+ if (mSoundPool != null) {
+ return true;
+ }
+
+ loadTouchSoundAssets();
+
+ mSoundPool = new SoundPool.Builder()
+ .setMaxStreams(NUM_SOUNDPOOL_CHANNELS)
+ .setAudioAttributes(new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build())
+ .build();
+ mSoundPoolCallBack = null;
+ mSoundPoolListenerThread = new SoundPoolListenerThread();
+ mSoundPoolListenerThread.start();
+ int attempts = 3;
+ while ((mSoundPoolCallBack == null) && (attempts-- > 0)) {
+ try {
+ // Wait for mSoundPoolCallBack to be set by the other thread
+ mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while waiting sound pool listener thread.");
+ }
+ }
+
+ if (mSoundPoolCallBack == null) {
+ Log.w(TAG, "onLoadSoundEffects() SoundPool listener or thread creation error");
+ if (mSoundPoolLooper != null) {
+ mSoundPoolLooper.quit();
+ mSoundPoolLooper = null;
+ }
+ mSoundPoolListenerThread = null;
+ mSoundPool.release();
+ mSoundPool = null;
+ return false;
+ }
+ /*
+ * poolId table: The value -1 in this table indicates that corresponding
+ * file (same index in SOUND_EFFECT_FILES[] has not been loaded.
+ * Once loaded, the value in poolId is the sample ID and the same
+ * sample can be reused for another effect using the same file.
+ */
+ int[] poolId = new int[SOUND_EFFECT_FILES.size()];
+ for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
+ poolId[fileIdx] = -1;
+ }
+ /*
+ * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded.
+ * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0:
+ * this indicates we have a valid sample loaded for this effect.
+ */
+
+ int numSamples = 0;
+ for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
+ // Do not load sample if this effect uses the MediaPlayer
+ if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {
+ continue;
+ }
+ if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {
+ String filePath = Environment.getRootDirectory()
+ + SOUND_EFFECTS_PATH
+ + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effect][0]);
+ int sampleId = mSoundPool.load(filePath, 0);
+ if (sampleId <= 0) {
+ Log.w(TAG, "Soundpool could not load file: "+filePath);
+ } else {
+ SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;
+ poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;
+ numSamples++;
+ }
+ } else {
+ SOUND_EFFECT_FILES_MAP[effect][1] =
+ poolId[SOUND_EFFECT_FILES_MAP[effect][0]];
+ }
+ }
+ // wait for all samples to be loaded
+ if (numSamples > 0) {
+ mSoundPoolCallBack.setSamples(poolId);
+
+ attempts = 3;
+ status = 1;
+ while ((status == 1) && (attempts-- > 0)) {
+ try {
+ mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);
+ status = mSoundPoolCallBack.status();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while waiting sound pool callback.");
+ }
+ }
+ } else {
+ status = -1;
+ }
+
+ if (mSoundPoolLooper != null) {
+ mSoundPoolLooper.quit();
+ mSoundPoolLooper = null;
+ }
+ mSoundPoolListenerThread = null;
+ if (status != 0) {
+ Log.w(TAG,
+ "onLoadSoundEffects(), Error "+status+ " while loading samples");
+ for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
+ if (SOUND_EFFECT_FILES_MAP[effect][1] > 0) {
+ SOUND_EFFECT_FILES_MAP[effect][1] = -1;
+ }
+ }
+
+ mSoundPool.release();
+ mSoundPool = null;
+ }
+ }
+ return (status == 0);
+ }
+
+ /**
+ * Unloads samples from the sound pool.
+ * This method can be called to free some memory when
+ * sound effects are disabled.
+ */
+ private void onUnloadSoundEffects() {
+ synchronized (mSoundEffectsLock) {
+ if (mSoundPool == null) {
+ return;
+ }
+
+ int[] poolId = new int[SOUND_EFFECT_FILES.size()];
+ for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) {
+ poolId[fileIdx] = 0;
+ }
+
+ for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {
+ if (SOUND_EFFECT_FILES_MAP[effect][1] <= 0) {
+ continue;
+ }
+ if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == 0) {
+ mSoundPool.unload(SOUND_EFFECT_FILES_MAP[effect][1]);
+ SOUND_EFFECT_FILES_MAP[effect][1] = -1;
+ poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = -1;
+ }
+ }
+ mSoundPool.release();
+ mSoundPool = null;
+ }
+ }
+
+ private void onPlaySoundEffect(int effectType, int volume) {
+ synchronized (mSoundEffectsLock) {
+
+ onLoadSoundEffects();
+
+ if (mSoundPool == null) {
+ return;
+ }
+ float volFloat;
+ // use default if volume is not specified by caller
+ if (volume < 0) {
+ volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);
+ } else {
+ volFloat = volume / 1000.0f;
+ }
+
+ if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {
+ mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],
+ volFloat, volFloat, 0, 0, 1.0f);
+ } else {
+ MediaPlayer mediaPlayer = new MediaPlayer();
+ try {
+ String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH +
+ SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);
+ mediaPlayer.setDataSource(filePath);
+ mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);
+ mediaPlayer.prepare();
+ mediaPlayer.setVolume(volFloat);
+ mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+ public void onCompletion(MediaPlayer mp) {
+ cleanupPlayer(mp);
+ }
+ });
+ mediaPlayer.setOnErrorListener(new OnErrorListener() {
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ cleanupPlayer(mp);
+ return true;
+ }
+ });
+ mediaPlayer.start();
+ } catch (IOException ex) {
+ Log.w(TAG, "MediaPlayer IOException: "+ex);
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);
+ } catch (IllegalStateException ex) {
+ Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
+ }
+ }
+ }
+ }
+
+ private void cleanupPlayer(MediaPlayer mp) {
+ if (mp != null) {
+ try {
+ mp.stop();
+ mp.release();
+ } catch (IllegalStateException ex) {
+ Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);
+ }
+ }
+ }
+
+ private void setForceUse(int usage, int config) {
+ synchronized (mConnectedDevices) {
+ setForceUseInt_SyncDevices(usage, config);
+ }
+ }
+
+ private void onPersistSafeVolumeState(int state) {
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+ state);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+
+ case MSG_SET_DEVICE_VOLUME:
+ setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);
+ break;
+
+ case MSG_SET_ALL_VOLUMES:
+ setAllVolumes((VolumeStreamState) msg.obj);
+ break;
+
+ case MSG_PERSIST_VOLUME:
+ persistVolume((VolumeStreamState) msg.obj, msg.arg1);
+ break;
+
+ case MSG_PERSIST_MASTER_VOLUME_MUTE:
+ if (mUseFixedVolume) {
+ return;
+ }
+ Settings.System.putIntForUser(mContentResolver,
+ Settings.System.VOLUME_MASTER_MUTE,
+ msg.arg1,
+ msg.arg2);
+ break;
+
+ case MSG_PERSIST_RINGER_MODE:
+ // note that the value persisted is the current ringer mode, not the
+ // value of ringer mode as of the time the request was made to persist
+ persistRingerMode(getRingerModeInternal());
+ break;
+
+ case MSG_MEDIA_SERVER_DIED:
+ if (!mSystemReady ||
+ (AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK)) {
+ Log.e(TAG, "Media server died.");
+ sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SENDMSG_NOOP, 0, 0,
+ null, 500);
+ break;
+ }
+ Log.e(TAG, "Media server started.");
+
+ // indicate to audio HAL that we start the reconfiguration phase after a media
+ // server crash
+ // Note that we only execute this when the media server
+ // process restarts after a crash, not the first time it is started.
+ AudioSystem.setParameters("restarting=true");
+
+ readAndSetLowRamDevice();
+
+ // Restore device connection states
+ synchronized (mConnectedDevices) {
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ DeviceListSpec spec = mConnectedDevices.valueAt(i);
+ AudioSystem.setDeviceConnectionState(
+ spec.mDeviceType,
+ AudioSystem.DEVICE_STATE_AVAILABLE,
+ spec.mDeviceAddress,
+ spec.mDeviceName);
+ }
+ }
+ // Restore call state
+ AudioSystem.setPhoneState(mMode);
+
+ // Restore forced usage for communcations and record
+ AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm);
+ AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
+ AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, mCameraSoundForced ?
+ AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE);
+
+ // Restore stream volumes
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ VolumeStreamState streamState = mStreamStates[streamType];
+ AudioSystem.initStreamVolume(streamType, 0, (streamState.mIndexMax + 5) / 10);
+
+ streamState.applyAllVolumes();
+ }
+
+ // Restore ringer mode
+ setRingerModeInt(getRingerModeInternal(), false);
+
+ // Reset device orientation (if monitored for this device)
+ if (mMonitorOrientation) {
+ setOrientationForAudioSystem();
+ }
+ if (mMonitorRotation) {
+ setRotationForAudioSystem();
+ }
+
+ synchronized (mBluetoothA2dpEnabledLock) {
+ AudioSystem.setForceUse(AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ?
+ AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP);
+ }
+
+ synchronized (mSettingsLock) {
+ AudioSystem.setForceUse(AudioSystem.FOR_DOCK,
+ mDockAudioMediaEnabled ?
+ AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE);
+ }
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ if (mHdmiTvClient != null) {
+ setHdmiSystemAudioSupported(mHdmiSystemAudioSupported);
+ }
+ }
+ }
+
+ synchronized (mAudioPolicies) {
+ for(AudioPolicyProxy policy : mAudioPolicies.values()) {
+ policy.connectMixes();
+ }
+ }
+
+ // indicate the end of reconfiguration phase to audio HAL
+ AudioSystem.setParameters("restarting=false");
+ break;
+
+ case MSG_UNLOAD_SOUND_EFFECTS:
+ onUnloadSoundEffects();
+ break;
+
+ case MSG_LOAD_SOUND_EFFECTS:
+ //FIXME: onLoadSoundEffects() should be executed in a separate thread as it
+ // can take several dozens of milliseconds to complete
+ boolean loaded = onLoadSoundEffects();
+ if (msg.obj != null) {
+ LoadSoundEffectReply reply = (LoadSoundEffectReply)msg.obj;
+ synchronized (reply) {
+ reply.mStatus = loaded ? 0 : -1;
+ reply.notify();
+ }
+ }
+ break;
+
+ case MSG_PLAY_SOUND_EFFECT:
+ onPlaySoundEffect(msg.arg1, msg.arg2);
+ break;
+
+ case MSG_BTA2DP_DOCK_TIMEOUT:
+ // msg.obj == address of BTA2DP device
+ synchronized (mConnectedDevices) {
+ makeA2dpDeviceUnavailableNow( (String) msg.obj );
+ }
+ break;
+
+ case MSG_SET_FORCE_USE:
+ case MSG_SET_FORCE_BT_A2DP_USE:
+ setForceUse(msg.arg1, msg.arg2);
+ break;
+
+ case MSG_BT_HEADSET_CNCT_FAILED:
+ resetBluetoothSco();
+ break;
+
+ case MSG_SET_WIRED_DEVICE_CONNECTION_STATE:
+ { WiredDeviceConnectionState connectState =
+ (WiredDeviceConnectionState)msg.obj;
+ onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
+ connectState.mAddress, connectState.mName, connectState.mCaller);
+ mAudioEventWakeLock.release();
+ }
+ break;
+
+ case MSG_SET_A2DP_SRC_CONNECTION_STATE:
+ onSetA2dpSourceConnectionState((BluetoothDevice)msg.obj, msg.arg1);
+ mAudioEventWakeLock.release();
+ break;
+
+ case MSG_SET_A2DP_SINK_CONNECTION_STATE:
+ onSetA2dpSinkConnectionState((BluetoothDevice)msg.obj, msg.arg1);
+ mAudioEventWakeLock.release();
+ break;
+
+ case MSG_REPORT_NEW_ROUTES: {
+ int N = mRoutesObservers.beginBroadcast();
+ if (N > 0) {
+ AudioRoutesInfo routes;
+ synchronized (mCurAudioRoutes) {
+ routes = new AudioRoutesInfo(mCurAudioRoutes);
+ }
+ while (N > 0) {
+ N--;
+ IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N);
+ try {
+ obs.dispatchAudioRoutesChanged(routes);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ mRoutesObservers.finishBroadcast();
+ observeDevicesForStreams(-1);
+ break;
+ }
+
+ case MSG_CHECK_MUSIC_ACTIVE:
+ onCheckMusicActive((String) msg.obj);
+ break;
+
+ case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
+ onSendBecomingNoisyIntent();
+ break;
+
+ case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
+ case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
+ onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED),
+ (String) msg.obj);
+ break;
+ case MSG_PERSIST_SAFE_VOLUME_STATE:
+ onPersistSafeVolumeState(msg.arg1);
+ break;
+
+ case MSG_BROADCAST_BT_CONNECTION_STATE:
+ onBroadcastScoConnectionState(msg.arg1);
+ break;
+
+ case MSG_SYSTEM_READY:
+ onSystemReady();
+ break;
+
+ case MSG_PERSIST_MUSIC_ACTIVE_MS:
+ final int musicActiveMs = msg.arg1;
+ Settings.Secure.putIntForUser(mContentResolver,
+ Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
+ UserHandle.USER_CURRENT);
+ break;
+ case MSG_PERSIST_MICROPHONE_MUTE:
+ Settings.System.putIntForUser(mContentResolver,
+ Settings.System.MICROPHONE_MUTE,
+ msg.arg1,
+ msg.arg2);
+ break;
+ case MSG_UNMUTE_STREAM:
+ onUnmuteStream(msg.arg1, msg.arg2);
+ break;
+ }
+ }
+ }
+
+ private class SettingsObserver extends ContentObserver {
+
+ SettingsObserver() {
+ super(new Handler());
+ mContentResolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this);
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ // FIXME This synchronized is not necessary if mSettingsLock only protects mRingerMode.
+ // However there appear to be some missing locks around mRingerModeMutedStreams
+ // and mRingerModeAffectedStreams, so will leave this synchronized for now.
+ // mRingerModeMutedStreams and mMuteAffectedStreams are safe (only accessed once).
+ synchronized (mSettingsLock) {
+ if (updateRingerModeAffectedStreams()) {
+ /*
+ * Ensure all stream types that should be affected by ringer mode
+ * are in the proper state.
+ */
+ setRingerModeInt(getRingerModeInternal(), false);
+ }
+ readDockAudioSettings(mContentResolver);
+ }
+ }
+ }
+
+ // must be called synchronized on mConnectedDevices
+ private void makeA2dpDeviceAvailable(String address) {
+ // enable A2DP before notifying A2DP connection to avoid unecessary processing in
+ // audio policy manager
+ VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
+ sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, streamState, 0);
+ setBluetoothA2dpOnInt(true);
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, DEVICE_NAME_A2DP);
+ // Reset A2DP suspend state each time a new sink is connected
+ AudioSystem.setParameters("A2dpSuspended=false");
+ mConnectedDevices.put(
+ makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+ new DeviceListSpec(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, DEVICE_NAME_A2DP,
+ address));
+ }
+
+ private void onSendBecomingNoisyIntent() {
+ sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
+ }
+
+ // must be called synchronized on mConnectedDevices
+ private void makeA2dpDeviceUnavailableNow(String address) {
+ synchronized (mA2dpAvrcpLock) {
+ mAvrcpAbsVolSupported = false;
+ }
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, DEVICE_NAME_A2DP);
+ mConnectedDevices.remove(
+ makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+ synchronized (mCurAudioRoutes) {
+ // Remove A2DP routes as well
+ if (mCurAudioRoutes.bluetoothName != null) {
+ mCurAudioRoutes.bluetoothName = null;
+ sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
+ SENDMSG_NOOP, 0, 0, null, 0);
+ }
+ }
+ }
+
+ // must be called synchronized on mConnectedDevices
+ private void makeA2dpDeviceUnavailableLater(String address) {
+ // prevent any activity on the A2DP audio output to avoid unwanted
+ // reconnection of the sink.
+ AudioSystem.setParameters("A2dpSuspended=true");
+ // the device will be made unavailable later, so consider it disconnected right away
+ mConnectedDevices.remove(
+ makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+ // send the delayed message to make the device unavailable later
+ Message msg = mAudioHandler.obtainMessage(MSG_BTA2DP_DOCK_TIMEOUT, address);
+ mAudioHandler.sendMessageDelayed(msg, BTA2DP_DOCK_TIMEOUT_MILLIS);
+
+ }
+
+ // must be called synchronized on mConnectedDevices
+ private void makeA2dpSrcAvailable(String address) {
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, DEVICE_NAME_A2DP);
+ mConnectedDevices.put(
+ makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+ new DeviceListSpec(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, DEVICE_NAME_A2DP,
+ address));
+ }
+
+ // must be called synchronized on mConnectedDevices
+ private void makeA2dpSrcUnavailable(String address) {
+ AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, address, DEVICE_NAME_A2DP);
+ mConnectedDevices.remove(
+ makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
+ }
+
+ // must be called synchronized on mConnectedDevices
+ private void cancelA2dpDeviceTimeout() {
+ mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT);
+ }
+
+ // must be called synchronized on mConnectedDevices
+ private boolean hasScheduledA2dpDockTimeout() {
+ return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT);
+ }
+
+ private void onSetA2dpSinkConnectionState(BluetoothDevice btDevice, int state)
+ {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "onSetA2dpSinkConnectionState btDevice="+btDevice+"state="+state);
+ }
+ if (btDevice == null) {
+ return;
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+
+ synchronized (mConnectedDevices) {
+ String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ btDevice.getAddress());
+ DeviceListSpec deviceSpec = mConnectedDevices.get(key);
+ boolean isConnected = deviceSpec != null;
+
+ if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+ if (btDevice.isBluetoothDock()) {
+ if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ // introduction of a delay for transient disconnections of docks when
+ // power is rapidly turned off/on, this message will be canceled if
+ // we reconnect the dock under a preset delay
+ makeA2dpDeviceUnavailableLater(address);
+ // the next time isConnected is evaluated, it will be false for the dock
+ }
+ } else {
+ makeA2dpDeviceUnavailableNow(address);
+ }
+ synchronized (mCurAudioRoutes) {
+ if (mCurAudioRoutes.bluetoothName != null) {
+ mCurAudioRoutes.bluetoothName = null;
+ sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
+ SENDMSG_NOOP, 0, 0, null, 0);
+ }
+ }
+ } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+ if (btDevice.isBluetoothDock()) {
+ // this could be a reconnection after a transient disconnection
+ cancelA2dpDeviceTimeout();
+ mDockAddress = address;
+ } else {
+ // this could be a connection of another A2DP device before the timeout of
+ // a dock: cancel the dock timeout, and make the dock unavailable now
+ if(hasScheduledA2dpDockTimeout()) {
+ cancelA2dpDeviceTimeout();
+ makeA2dpDeviceUnavailableNow(mDockAddress);
+ }
+ }
+ makeA2dpDeviceAvailable(address);
+ synchronized (mCurAudioRoutes) {
+ String name = btDevice.getAliasName();
+ if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
+ mCurAudioRoutes.bluetoothName = name;
+ sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
+ SENDMSG_NOOP, 0, 0, null, 0);
+ }
+ }
+ }
+ }
+ }
+
+ private void onSetA2dpSourceConnectionState(BluetoothDevice btDevice, int state)
+ {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "onSetA2dpSourceConnectionState btDevice="+btDevice+" state="+state);
+ }
+ if (btDevice == null) {
+ return;
+ }
+ String address = btDevice.getAddress();
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+
+ synchronized (mConnectedDevices) {
+ String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+ DeviceListSpec deviceSpec = mConnectedDevices.get(key);
+ boolean isConnected = deviceSpec != null;
+
+ if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+ makeA2dpSrcUnavailable(address);
+ } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+ makeA2dpSrcAvailable(address);
+ }
+ }
+ }
+
+ public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
+ // address is not used for now, but may be used when multiple a2dp devices are supported
+ synchronized (mA2dpAvrcpLock) {
+ mAvrcpAbsVolSupported = support;
+ sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
+ mStreamStates[AudioSystem.STREAM_MUSIC], 0);
+ sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
+ mStreamStates[AudioSystem.STREAM_RING], 0);
+ }
+ }
+
+ private boolean handleDeviceConnection(boolean connect, int device, String address,
+ String deviceName) {
+ if (DEBUG_DEVICES) {
+ Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device)
+ + " address:" + address + " name:" + deviceName + ")");
+ }
+ synchronized (mConnectedDevices) {
+ String deviceKey = makeDeviceListKey(device, address);
+ if (DEBUG_DEVICES) {
+ Slog.i(TAG, "deviceKey:" + deviceKey);
+ }
+ DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey);
+ boolean isConnected = deviceSpec != null;
+ if (DEBUG_DEVICES) {
+ Slog.i(TAG, "deviceSpec:" + deviceSpec + " is(already)Connected:" + isConnected);
+ }
+ if (connect && !isConnected) {
+ AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE,
+ address, deviceName);
+ mConnectedDevices.put(deviceKey, new DeviceListSpec(device, deviceName, address));
+ return true;
+ } else if (!connect && isConnected) {
+ AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE,
+ address, deviceName);
+ mConnectedDevices.remove(deviceKey);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
+ // sent if none of these devices is connected.
+ // Access synchronized on mConnectedDevices
+ int mBecomingNoisyIntentDevices =
+ AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
+ AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI |
+ AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
+ AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE;
+
+ // must be called before removing the device from mConnectedDevices
+ // Called synchronized on mConnectedDevices
+ private int checkSendBecomingNoisyIntent(int device, int state) {
+ int delay = 0;
+ if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) {
+ int devices = 0;
+ for (int i = 0; i < mConnectedDevices.size(); i++) {
+ int dev = mConnectedDevices.valueAt(i).mDeviceType;
+ if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
+ && ((dev & mBecomingNoisyIntentDevices) != 0)) {
+ devices |= dev;
+ }
+ }
+ if (devices == device) {
+ sendMsg(mAudioHandler,
+ MSG_BROADCAST_AUDIO_BECOMING_NOISY,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ null,
+ 0);
+ delay = 1000;
+ }
+ }
+
+ if (mAudioHandler.hasMessages(MSG_SET_A2DP_SRC_CONNECTION_STATE) ||
+ mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE) ||
+ mAudioHandler.hasMessages(MSG_SET_WIRED_DEVICE_CONNECTION_STATE)) {
+ synchronized (mLastDeviceConnectMsgTime) {
+ long time = SystemClock.uptimeMillis();
+ if (mLastDeviceConnectMsgTime > time) {
+ delay = (int)(mLastDeviceConnectMsgTime - time) + 30;
+ }
+ }
+ }
+ return delay;
+ }
+
+ private void sendDeviceConnectionIntent(int device, int state, String address,
+ String deviceName) {
+ if (DEBUG_DEVICES) {
+ Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
+ " state:0x" + Integer.toHexString(state) + " address:" + address +
+ " name:" + deviceName + ");");
+ }
+ Intent intent = new Intent();
+
+ intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
+ intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
+ intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
+
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+ int connType = 0;
+
+ if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
+ connType = AudioRoutesInfo.MAIN_HEADSET;
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone", 1);
+ } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
+ device == AudioSystem.DEVICE_OUT_LINE) {
+ /*do apps care about line-out vs headphones?*/
+ connType = AudioRoutesInfo.MAIN_HEADPHONES;
+ intent.setAction(Intent.ACTION_HEADSET_PLUG);
+ intent.putExtra("microphone", 0);
+ } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
+ device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
+ connType = AudioRoutesInfo.MAIN_HDMI;
+ configureHdmiPlugIntent(intent, state);
+ } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE) {
+ connType = AudioRoutesInfo.MAIN_USB;
+ }
+
+ synchronized (mCurAudioRoutes) {
+ if (connType != 0) {
+ int newConn = mCurAudioRoutes.mainType;
+ if (state != 0) {
+ newConn |= connType;
+ } else {
+ newConn &= ~connType;
+ }
+ if (newConn != mCurAudioRoutes.mainType) {
+ mCurAudioRoutes.mainType = newConn;
+ sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
+ SENDMSG_NOOP, 0, 0, null, 0);
+ }
+ }
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void onSetWiredDeviceConnectionState(int device, int state, String address,
+ String deviceName, String caller) {
+ if (DEBUG_DEVICES) {
+ Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
+ + " state:" + Integer.toHexString(state)
+ + " address:" + address
+ + " deviceName:" + deviceName
+ + " caller: " + caller + ");");
+ }
+
+ synchronized (mConnectedDevices) {
+ if ((state == 0) && ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
+ (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
+ (device == AudioSystem.DEVICE_OUT_LINE))) {
+ setBluetoothA2dpOnInt(true);
+ }
+ boolean isUsb = ((device & ~AudioSystem.DEVICE_OUT_ALL_USB) == 0) ||
+ (((device & AudioSystem.DEVICE_BIT_IN) != 0) &&
+ ((device & ~AudioSystem.DEVICE_IN_ALL_USB) == 0));
+ handleDeviceConnection(state == 1, device, address, deviceName);
+ if (state != 0) {
+ if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
+ (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) ||
+ (device == AudioSystem.DEVICE_OUT_LINE)) {
+ setBluetoothA2dpOnInt(false);
+ }
+ if ((device & mSafeMediaVolumeDevices) != 0) {
+ sendMsg(mAudioHandler,
+ MSG_CHECK_MUSIC_ACTIVE,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ caller,
+ MUSIC_ACTIVE_POLL_PERIOD_MS);
+ }
+ // Television devices without CEC service apply software volume on HDMI output
+ if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
+ mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+ checkAllFixedVolumeDevices();
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ if (mHdmiPlaybackClient != null) {
+ mHdmiCecSink = false;
+ mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+ }
+ }
+ }
+ }
+ } else {
+ if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ mHdmiCecSink = false;
+ }
+ }
+ }
+ }
+ if (!isUsb && device != AudioSystem.DEVICE_IN_WIRED_HEADSET) {
+ sendDeviceConnectionIntent(device, state, address, deviceName);
+ }
+ }
+ }
+
+ private void configureHdmiPlugIntent(Intent intent, int state) {
+ intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
+ intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
+ if (state == 1) {
+ ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
+ int[] portGeneration = new int[1];
+ int status = AudioSystem.listAudioPorts(ports, portGeneration);
+ if (status == AudioManager.SUCCESS) {
+ for (AudioPort port : ports) {
+ if (port instanceof AudioDevicePort) {
+ final AudioDevicePort devicePort = (AudioDevicePort) port;
+ if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI ||
+ devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) {
+ // format the list of supported encodings
+ int[] formats = devicePort.formats();
+ if (formats.length > 0) {
+ ArrayList<Integer> encodingList = new ArrayList(1);
+ for (int format : formats) {
+ // a format in the list can be 0, skip it
+ if (format != AudioFormat.ENCODING_INVALID) {
+ encodingList.add(format);
+ }
+ }
+ int[] encodingArray = new int[encodingList.size()];
+ for (int i = 0 ; i < encodingArray.length ; i++) {
+ encodingArray[i] = encodingList.get(i);
+ }
+ intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
+ }
+ // find the maximum supported number of channels
+ int maxChannels = 0;
+ for (int mask : devicePort.channelMasks()) {
+ int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
+ if (channelCount > maxChannels) {
+ maxChannels = channelCount;
+ }
+ }
+ intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* cache of the address of the last dock the device was connected to */
+ private String mDockAddress;
+
+ /**
+ * Receiver for misc intent broadcasts the Phone app cares about.
+ */
+ private class AudioServiceBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ int outDevice;
+ int inDevice;
+ int state;
+
+ if (action.equals(Intent.ACTION_DOCK_EVENT)) {
+ int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ int config;
+ switch (dockState) {
+ case Intent.EXTRA_DOCK_STATE_DESK:
+ config = AudioSystem.FORCE_BT_DESK_DOCK;
+ break;
+ case Intent.EXTRA_DOCK_STATE_CAR:
+ config = AudioSystem.FORCE_BT_CAR_DOCK;
+ break;
+ case Intent.EXTRA_DOCK_STATE_LE_DESK:
+ config = AudioSystem.FORCE_ANALOG_DOCK;
+ break;
+ case Intent.EXTRA_DOCK_STATE_HE_DESK:
+ config = AudioSystem.FORCE_DIGITAL_DOCK;
+ break;
+ case Intent.EXTRA_DOCK_STATE_UNDOCKED:
+ default:
+ config = AudioSystem.FORCE_NONE;
+ }
+ // Low end docks have a menu to enable or disable audio
+ // (see mDockAudioMediaEnabled)
+ if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
+ ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) &&
+ (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) {
+ AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
+ }
+ mDockState = dockState;
+ } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+ state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+ BluetoothProfile.STATE_DISCONNECTED);
+ outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
+ inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+ String address = null;
+
+ BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (btDevice == null) {
+ return;
+ }
+
+ address = btDevice.getAddress();
+ BluetoothClass btClass = btDevice.getBluetoothClass();
+ if (btClass != null) {
+ switch (btClass.getDeviceClass()) {
+ case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+ case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+ outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+ break;
+ case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+ outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+ break;
+ }
+ }
+
+ if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+ address = "";
+ }
+
+ boolean connected = (state == BluetoothProfile.STATE_CONNECTED);
+ boolean success =
+ handleDeviceConnection(connected, outDevice, address, "Bluetooth Headset") &&
+ handleDeviceConnection(connected, inDevice, address, "Bluetooth Headset");
+ if (success) {
+ synchronized (mScoClients) {
+ if (connected) {
+ mBluetoothHeadsetDevice = btDevice;
+ } else {
+ mBluetoothHeadsetDevice = null;
+ resetBluetoothSco();
+ }
+ }
+ }
+ } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+ boolean broadcast = false;
+ int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
+ synchronized (mScoClients) {
+ int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ // broadcast intent if the connection was initated by AudioService
+ if (!mScoClients.isEmpty() &&
+ (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
+ mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
+ broadcast = true;
+ }
+ switch (btState) {
+ case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ break;
+ case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+ mScoAudioState = SCO_STATE_INACTIVE;
+ clearAllScoClients(0, false);
+ break;
+ case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ default:
+ // do not broadcast CONNECTING or invalid state
+ broadcast = false;
+ break;
+ }
+ }
+ if (broadcast) {
+ broadcastScoConnectionState(scoAudioState);
+ //FIXME: this is to maintain compatibility with deprecated intent
+ // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+ Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+ newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
+ sendStickyBroadcastToAll(newIntent);
+ }
+ } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ if (mMonitorRotation) {
+ mOrientationListener.onOrientationChanged(0); //argument is ignored anyway
+ mOrientationListener.enable();
+ }
+ AudioSystem.setParameters("screen_state=on");
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ if (mMonitorRotation) {
+ //reduce wakeups (save current) by only listening when display is on
+ mOrientationListener.disable();
+ }
+ AudioSystem.setParameters("screen_state=off");
+ } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ handleConfigurationChanged(context);
+ } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
+ // attempt to stop music playback for background user
+ sendMsg(mAudioHandler,
+ MSG_BROADCAST_AUDIO_BECOMING_NOISY,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ null,
+ 0);
+ // the current audio focus owner is no longer valid
+ mMediaFocusControl.discardAudioFocusOwner();
+
+ // load volume settings for new user
+ readAudioSettings(true /*userSwitch*/);
+ // preserve STREAM_MUSIC volume from one user to the next.
+ sendMsg(mAudioHandler,
+ MSG_SET_ALL_VOLUMES,
+ SENDMSG_QUEUE,
+ 0,
+ 0,
+ mStreamStates[AudioSystem.STREAM_MUSIC], 0);
+ }
+ }
+ } // end class AudioServiceBroadcastReceiver
+
+ //==========================================================================================
+ // RemoteControlDisplay / RemoteControlClient / Remote info
+ //==========================================================================================
+ public boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
+ ComponentName listenerComp) {
+ return mMediaFocusControl.registerRemoteController(rcd, w, h, listenerComp);
+ }
+
+ public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
+ return mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h);
+ }
+
+ public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ mMediaFocusControl.unregisterRemoteControlDisplay(rcd);
+ }
+
+ public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
+ mMediaFocusControl.remoteControlDisplayUsesBitmapSize(rcd, w, h);
+ }
+
+ public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
+ boolean wantsSync) {
+ mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync);
+ }
+
+ @Override
+ public void setRemoteStreamVolume(int index) {
+ enforceVolumeController("set the remote stream volume");
+ mMediaFocusControl.setRemoteStreamVolume(index);
+ }
+
+ //==========================================================================================
+ // Audio Focus
+ //==========================================================================================
+ public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
+ IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
+ IAudioPolicyCallback pcb) {
+ // permission checks
+ if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
+ if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
+ if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_PHONE_STATE)) {
+ Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception());
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+ } else {
+ // only a registered audio policy can be used to lock focus
+ synchronized (mAudioPolicies) {
+ if (!mAudioPolicies.containsKey(pcb.asBinder())) {
+ Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus");
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+ }
+ }
+ }
+
+ return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
+ clientId, callingPackageName, flags);
+ }
+
+ public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa) {
+ return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa);
+ }
+
+ public void unregisterAudioFocusClient(String clientId) {
+ mMediaFocusControl.unregisterAudioFocusClient(clientId);
+ }
+
+ public int getCurrentAudioFocus() {
+ return mMediaFocusControl.getCurrentAudioFocus();
+ }
+
+ private boolean readCameraSoundForced() {
+ return SystemProperties.getBoolean("audio.camerasound.force", false) ||
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_camera_sound_forced);
+ }
+
+ //==========================================================================================
+ // Device orientation
+ //==========================================================================================
+ /**
+ * Handles device configuration changes that may map to a change in the orientation
+ * or orientation.
+ * Monitoring orientation and rotation is optional, and is defined by the definition and value
+ * of the "ro.audio.monitorOrientation" and "ro.audio.monitorRotation" system properties.
+ */
+ private void handleConfigurationChanged(Context context) {
+ try {
+ // reading new orientation "safely" (i.e. under try catch) in case anything
+ // goes wrong when obtaining resources and configuration
+ Configuration config = context.getResources().getConfiguration();
+ // TODO merge rotation and orientation
+ if (mMonitorOrientation) {
+ int newOrientation = config.orientation;
+ if (newOrientation != mDeviceOrientation) {
+ mDeviceOrientation = newOrientation;
+ setOrientationForAudioSystem();
+ }
+ }
+ sendMsg(mAudioHandler,
+ MSG_CONFIGURE_SAFE_MEDIA_VOLUME,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ TAG,
+ 0);
+
+ boolean cameraSoundForced = readCameraSoundForced();
+ synchronized (mSettingsLock) {
+ boolean cameraSoundForcedChanged = false;
+ synchronized (mCameraSoundForced) {
+ if (cameraSoundForced != mCameraSoundForced) {
+ mCameraSoundForced = cameraSoundForced;
+ cameraSoundForcedChanged = true;
+ }
+ }
+ if (cameraSoundForcedChanged) {
+ if (!isPlatformTelevision()) {
+ VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
+ if (cameraSoundForced) {
+ s.setAllIndexesToMax();
+ mRingerModeAffectedStreams &=
+ ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ } else {
+ s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], TAG);
+ mRingerModeAffectedStreams |=
+ (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ }
+ // take new state into account for streams muted by ringer mode
+ setRingerModeInt(getRingerModeInternal(), false);
+ }
+
+ sendMsg(mAudioHandler,
+ MSG_SET_FORCE_USE,
+ SENDMSG_QUEUE,
+ AudioSystem.FOR_SYSTEM,
+ cameraSoundForced ?
+ AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
+ null,
+ 0);
+
+ sendMsg(mAudioHandler,
+ MSG_SET_ALL_VOLUMES,
+ SENDMSG_QUEUE,
+ 0,
+ 0,
+ mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0);
+ }
+ }
+ mVolumeController.setLayoutDirection(config.getLayoutDirection());
+ } catch (Exception e) {
+ Log.e(TAG, "Error handling configuration change: ", e);
+ }
+ }
+
+ private void setOrientationForAudioSystem() {
+ switch (mDeviceOrientation) {
+ case Configuration.ORIENTATION_LANDSCAPE:
+ //Log.i(TAG, "orientation is landscape");
+ AudioSystem.setParameters("orientation=landscape");
+ break;
+ case Configuration.ORIENTATION_PORTRAIT:
+ //Log.i(TAG, "orientation is portrait");
+ AudioSystem.setParameters("orientation=portrait");
+ break;
+ case Configuration.ORIENTATION_SQUARE:
+ //Log.i(TAG, "orientation is square");
+ AudioSystem.setParameters("orientation=square");
+ break;
+ case Configuration.ORIENTATION_UNDEFINED:
+ //Log.i(TAG, "orientation is undefined");
+ AudioSystem.setParameters("orientation=undefined");
+ break;
+ default:
+ Log.e(TAG, "Unknown orientation");
+ }
+ }
+
+ private void setRotationForAudioSystem() {
+ switch (mDeviceRotation) {
+ case Surface.ROTATION_0:
+ AudioSystem.setParameters("rotation=0");
+ break;
+ case Surface.ROTATION_90:
+ AudioSystem.setParameters("rotation=90");
+ break;
+ case Surface.ROTATION_180:
+ AudioSystem.setParameters("rotation=180");
+ break;
+ case Surface.ROTATION_270:
+ AudioSystem.setParameters("rotation=270");
+ break;
+ default:
+ Log.e(TAG, "Unknown device rotation");
+ }
+ }
+
+
+ // Handles request to override default use of A2DP for media.
+ // Must be called synchronized on mConnectedDevices
+ public void setBluetoothA2dpOnInt(boolean on) {
+ synchronized (mBluetoothA2dpEnabledLock) {
+ mBluetoothA2dpEnabled = on;
+ mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE);
+ setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA,
+ mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP);
+ }
+ }
+
+ // Must be called synchronized on mConnectedDevices
+ private void setForceUseInt_SyncDevices(int usage, int config) {
+ switch (usage) {
+ case AudioSystem.FOR_MEDIA:
+ if (config == AudioSystem.FORCE_NO_BT_A2DP) {
+ mBecomingNoisyIntentDevices &= ~AudioSystem.DEVICE_OUT_ALL_A2DP;
+ } else { // config == AudioSystem.FORCE_NONE
+ mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ALL_A2DP;
+ }
+ break;
+ case AudioSystem.FOR_DOCK:
+ if (config == AudioSystem.FORCE_ANALOG_DOCK) {
+ mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
+ } else { // config == AudioSystem.FORCE_NONE
+ mBecomingNoisyIntentDevices &= ~AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
+ }
+ break;
+ default:
+ // usage doesn't affect the broadcast of ACTION_AUDIO_BECOMING_NOISY
+ }
+ AudioSystem.setForceUse(usage, config);
+ }
+
+ @Override
+ public void setRingtonePlayer(IRingtonePlayer player) {
+ mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null);
+ mRingtonePlayer = player;
+ }
+
+ @Override
+ public IRingtonePlayer getRingtonePlayer() {
+ return mRingtonePlayer;
+ }
+
+ @Override
+ public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
+ synchronized (mCurAudioRoutes) {
+ AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
+ mRoutesObservers.register(observer);
+ return routes;
+ }
+ }
+
+
+ //==========================================================================================
+ // Safe media volume management.
+ // MUSIC stream volume level is limited when headphones are connected according to safety
+ // regulation. When the user attempts to raise the volume above the limit, a warning is
+ // displayed and the user has to acknowlegde before the volume is actually changed.
+ // The volume index corresponding to the limit is stored in config_safe_media_volume_index
+ // property. Platforms with a different limit must set this property accordingly in their
+ // overlay.
+ //==========================================================================================
+
+ // mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
+ // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
+ // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
+ // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
+ // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
+ // (when user opts out).
+ private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
+ private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
+ private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed
+ private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3; // unconfirmed
+ private Integer mSafeMediaVolumeState;
+
+ private int mMcc = 0;
+ // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
+ private int mSafeMediaVolumeIndex;
+ // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
+ private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
+ AudioSystem.DEVICE_OUT_WIRED_HEADPHONE;
+ // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
+ // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
+ // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
+ private int mMusicActiveMs;
+ private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
+ private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
+ private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed
+
+ private void setSafeMediaVolumeEnabled(boolean on, String caller) {
+ synchronized (mSafeMediaVolumeState) {
+ if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) &&
+ (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) {
+ if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
+ enforceSafeMediaVolume(caller);
+ } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+ mMusicActiveMs = 1; // nonzero = confirmed
+ saveMusicActiveMs();
+ sendMsg(mAudioHandler,
+ MSG_CHECK_MUSIC_ACTIVE,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ caller,
+ MUSIC_ACTIVE_POLL_PERIOD_MS);
+ }
+ }
+ }
+ }
+
+ private void enforceSafeMediaVolume(String caller) {
+ VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
+ int devices = mSafeMediaVolumeDevices;
+ int i = 0;
+
+ while (devices != 0) {
+ int device = 1 << i++;
+ if ((device & devices) == 0) {
+ continue;
+ }
+ int index = streamState.getIndex(device);
+ if (index > mSafeMediaVolumeIndex) {
+ streamState.setIndex(mSafeMediaVolumeIndex, device, caller);
+ sendMsg(mAudioHandler,
+ MSG_SET_DEVICE_VOLUME,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ streamState,
+ 0);
+ }
+ devices &= ~device;
+ }
+ }
+
+ private boolean checkSafeMediaVolume(int streamType, int index, int device) {
+ synchronized (mSafeMediaVolumeState) {
+ if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) &&
+ (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&
+ ((device & mSafeMediaVolumeDevices) != 0) &&
+ (index > mSafeMediaVolumeIndex)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ @Override
+ public void disableSafeMediaVolume(String callingPackage) {
+ enforceVolumeController("disable the safe media volume");
+ synchronized (mSafeMediaVolumeState) {
+ setSafeMediaVolumeEnabled(false, callingPackage);
+ if (mPendingVolumeCommand != null) {
+ onSetStreamVolume(mPendingVolumeCommand.mStreamType,
+ mPendingVolumeCommand.mIndex,
+ mPendingVolumeCommand.mFlags,
+ mPendingVolumeCommand.mDevice,
+ callingPackage);
+ mPendingVolumeCommand = null;
+ }
+ }
+ }
+
+ //==========================================================================================
+ // Hdmi Cec system audio mode.
+ // If Hdmi Cec's system audio mode is on, audio service should send the volume change
+ // to HdmiControlService so that the audio receiver can handle it.
+ //==========================================================================================
+
+ private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback {
+ public void onComplete(int status) {
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN);
+ // Television devices without CEC service apply software volume on HDMI output
+ if (isPlatformTelevision() && !mHdmiCecSink) {
+ mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
+ }
+ checkAllFixedVolumeDevices();
+ }
+ }
+ }
+ };
+
+ // If HDMI-CEC system audio is supported
+ private boolean mHdmiSystemAudioSupported = false;
+ // Set only when device is tv.
+ private HdmiTvClient mHdmiTvClient;
+ // true if the device has system feature PackageManager.FEATURE_LEANBACK.
+ // cached HdmiControlManager interface
+ private HdmiControlManager mHdmiManager;
+ // Set only when device is a set-top box.
+ private HdmiPlaybackClient mHdmiPlaybackClient;
+ // true if we are a set-top box, an HDMI sink is connected and it supports CEC.
+ private boolean mHdmiCecSink;
+
+ private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback();
+
+ @Override
+ public int setHdmiSystemAudioSupported(boolean on) {
+ int device = AudioSystem.DEVICE_NONE;
+ if (mHdmiManager != null) {
+ synchronized (mHdmiManager) {
+ if (mHdmiTvClient == null) {
+ Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode.");
+ return device;
+ }
+
+ synchronized (mHdmiTvClient) {
+ if (mHdmiSystemAudioSupported != on) {
+ mHdmiSystemAudioSupported = on;
+ AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
+ on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
+ AudioSystem.FORCE_NONE);
+ }
+ device = getDevicesForStream(AudioSystem.STREAM_MUSIC);
+ }
+ }
+ }
+ return device;
+ }
+
+ @Override
+ public boolean isHdmiSystemAudioSupported() {
+ return mHdmiSystemAudioSupported;
+ }
+
+ //==========================================================================================
+ // Accessibility: taking touch exploration into account for selecting the default
+ // stream override timeout when adjusting volume
+ //==========================================================================================
+ private static class StreamOverride
+ implements AccessibilityManager.TouchExplorationStateChangeListener {
+
+ // AudioService.getActiveStreamType() will return:
+ // - STREAM_NOTIFICATION on tablets during this period after a notification stopped
+ // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt
+ // stopped
+ private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 5000;
+ private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000;
+
+ static int sDelayMs;
+
+ static void init(Context ctxt) {
+ AccessibilityManager accessibilityManager =
+ (AccessibilityManager) ctxt.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ updateDefaultStreamOverrideDelay(
+ accessibilityManager.isTouchExplorationEnabled());
+ accessibilityManager.addTouchExplorationStateChangeListener(
+ new StreamOverride());
+ }
+
+ @Override
+ public void onTouchExplorationStateChanged(boolean enabled) {
+ updateDefaultStreamOverrideDelay(enabled);
+ }
+
+ private static void updateDefaultStreamOverrideDelay(boolean touchExploreEnabled) {
+ if (touchExploreEnabled) {
+ sDelayMs = TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS;
+ } else {
+ sDelayMs = DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS;
+ }
+ if (DEBUG_VOL) Log.d(TAG, "Touch exploration enabled=" + touchExploreEnabled
+ + " stream override delay is now " + sDelayMs + " ms");
+ }
+ }
+
+ //==========================================================================================
+ // Camera shutter sound policy.
+ // config_camera_sound_forced configuration option in config.xml defines if the camera shutter
+ // sound is forced (sound even if the device is in silent mode) or not. This option is false by
+ // default and can be overridden by country specific overlay in values-mccXXX/config.xml.
+ //==========================================================================================
+
+ // cached value of com.android.internal.R.bool.config_camera_sound_forced
+ private Boolean mCameraSoundForced;
+
+ // called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound
+ public boolean isCameraSoundForced() {
+ synchronized (mCameraSoundForced) {
+ return mCameraSoundForced;
+ }
+ }
+
+ private static final String[] RINGER_MODE_NAMES = new String[] {
+ "SILENT",
+ "VIBRATE",
+ "NORMAL"
+ };
+
+ private void dumpRingerMode(PrintWriter pw) {
+ pw.println("\nRinger mode: ");
+ pw.println("- mode (internal) = " + RINGER_MODE_NAMES[mRingerMode]);
+ pw.println("- mode (external) = " + RINGER_MODE_NAMES[mRingerModeExternal]);
+ pw.print("- ringer mode affected streams = 0x");
+ pw.println(Integer.toHexString(mRingerModeAffectedStreams));
+ pw.print("- ringer mode muted streams = 0x");
+ pw.println(Integer.toHexString(mRingerModeMutedStreams));
+ pw.print("- delegate = "); pw.println(mRingerModeDelegate);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+ mMediaFocusControl.dump(pw);
+ dumpStreamStates(pw);
+ dumpRingerMode(pw);
+ pw.println("\nAudio routes:");
+ pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mainType));
+ pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.bluetoothName);
+
+ pw.println("\nOther state:");
+ pw.print(" mVolumeController="); pw.println(mVolumeController);
+ pw.print(" mSafeMediaVolumeState=");
+ pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
+ pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
+ pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
+ pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
+ pw.print(" mMcc="); pw.println(mMcc);
+ pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced);
+ pw.print(" mHasVibrator="); pw.println(mHasVibrator);
+ pw.print(" mControllerService="); pw.println(mControllerService);
+ pw.print(" mVolumePolicy="); pw.println(mVolumePolicy);
+
+ dumpAudioPolicies(pw);
+ }
+
+ private static String safeMediaVolumeStateToString(Integer state) {
+ switch(state) {
+ case SAFE_MEDIA_VOLUME_NOT_CONFIGURED: return "SAFE_MEDIA_VOLUME_NOT_CONFIGURED";
+ case SAFE_MEDIA_VOLUME_DISABLED: return "SAFE_MEDIA_VOLUME_DISABLED";
+ case SAFE_MEDIA_VOLUME_INACTIVE: return "SAFE_MEDIA_VOLUME_INACTIVE";
+ case SAFE_MEDIA_VOLUME_ACTIVE: return "SAFE_MEDIA_VOLUME_ACTIVE";
+ }
+ return null;
+ }
+
+ // Inform AudioFlinger of our device's low RAM attribute
+ private static void readAndSetLowRamDevice()
+ {
+ int status = AudioSystem.setLowRamDevice(ActivityManager.isLowRamDeviceStatic());
+ if (status != 0) {
+ Log.w(TAG, "AudioFlinger informed of device's low RAM attribute; status " + status);
+ }
+ }
+
+ private void enforceVolumeController(String action) {
+ if (mControllerService.mUid != 0 && Binder.getCallingUid() == mControllerService.mUid) {
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ "Only SystemUI can " + action);
+ }
+
+ @Override
+ public void setVolumeController(final IVolumeController controller) {
+ enforceVolumeController("set the volume controller");
+
+ // return early if things are not actually changing
+ if (mVolumeController.isSameBinder(controller)) {
+ return;
+ }
+
+ // dismiss the old volume controller
+ mVolumeController.postDismiss();
+ if (controller != null) {
+ // we are about to register a new controller, listen for its death
+ try {
+ controller.asBinder().linkToDeath(new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ if (mVolumeController.isSameBinder(controller)) {
+ Log.w(TAG, "Current remote volume controller died, unregistering");
+ setVolumeController(null);
+ }
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ // noop
+ }
+ }
+ mVolumeController.setController(controller);
+ if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController);
+ }
+
+ @Override
+ public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) {
+ enforceVolumeController("notify about volume controller visibility");
+
+ // return early if the controller is not current
+ if (!mVolumeController.isSameBinder(controller)) {
+ return;
+ }
+
+ mVolumeController.setVisible(visible);
+ if (DEBUG_VOL) Log.d(TAG, "Volume controller visible: " + visible);
+ }
+
+ @Override
+ public void setVolumePolicy(VolumePolicy policy) {
+ enforceVolumeController("set volume policy");
+ if (policy != null && !policy.equals(mVolumePolicy)) {
+ mVolumePolicy = policy;
+ if (DEBUG_VOL) Log.d(TAG, "Volume policy changed: " + mVolumePolicy);
+ }
+ }
+
+ public static class VolumeController {
+ private static final String TAG = "VolumeController";
+
+ private IVolumeController mController;
+ private boolean mVisible;
+ private long mNextLongPress;
+ private int mLongPressTimeout;
+
+ public void setController(IVolumeController controller) {
+ mController = controller;
+ mVisible = false;
+ }
+
+ public void loadSettings(ContentResolver cr) {
+ mLongPressTimeout = Settings.Secure.getIntForUser(cr,
+ Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT);
+ }
+
+ public boolean suppressAdjustment(int resolvedStream, int flags, boolean isMute) {
+ if (isMute) {
+ return false;
+ }
+ boolean suppress = false;
+ if (resolvedStream == AudioSystem.STREAM_RING && mController != null) {
+ final long now = SystemClock.uptimeMillis();
+ if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
+ // ui will become visible
+ if (mNextLongPress < now) {
+ mNextLongPress = now + mLongPressTimeout;
+ }
+ suppress = true;
+ } else if (mNextLongPress > 0) { // in a long-press
+ if (now > mNextLongPress) {
+ // long press triggered, no more suppression
+ mNextLongPress = 0;
+ } else {
+ // keep suppressing until the long press triggers
+ suppress = true;
+ }
+ }
+ }
+ return suppress;
+ }
+
+ public void setVisible(boolean visible) {
+ mVisible = visible;
+ }
+
+ public boolean isSameBinder(IVolumeController controller) {
+ return Objects.equals(asBinder(), binder(controller));
+ }
+
+ public IBinder asBinder() {
+ return binder(mController);
+ }
+
+ private static IBinder binder(IVolumeController controller) {
+ return controller == null ? null : controller.asBinder();
+ }
+
+ @Override
+ public String toString() {
+ return "VolumeController(" + asBinder() + ",mVisible=" + mVisible + ")";
+ }
+
+ public void postDisplaySafeVolumeWarning(int flags) {
+ if (mController == null)
+ return;
+ try {
+ mController.displaySafeVolumeWarning(flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling displaySafeVolumeWarning", e);
+ }
+ }
+
+ public void postVolumeChanged(int streamType, int flags) {
+ if (mController == null)
+ return;
+ try {
+ mController.volumeChanged(streamType, flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling volumeChanged", e);
+ }
+ }
+
+ public void postMasterMuteChanged(int flags) {
+ if (mController == null)
+ return;
+ try {
+ mController.masterMuteChanged(flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling masterMuteChanged", e);
+ }
+ }
+
+ public void setLayoutDirection(int layoutDirection) {
+ if (mController == null)
+ return;
+ try {
+ mController.setLayoutDirection(layoutDirection);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling setLayoutDirection", e);
+ }
+ }
+
+ public void postDismiss() {
+ if (mController == null)
+ return;
+ try {
+ mController.dismiss();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling dismiss", e);
+ }
+ }
+ }
+
+ /**
+ * Interface for system components to get some extra functionality through
+ * LocalServices.
+ */
+ final class AudioServiceInternal extends AudioManagerInternal {
+ @Override
+ public void setRingerModeDelegate(RingerModeDelegate delegate) {
+ mRingerModeDelegate = delegate;
+ if (mRingerModeDelegate != null) {
+ setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate");
+ }
+ }
+
+ @Override
+ public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags,
+ String callingPackage, int uid) {
+ // direction and stream type swap here because the public
+ // adjustSuggested has a different order than the other methods.
+ adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage,
+ callingPackage, uid);
+ }
+
+ @Override
+ public void adjustStreamVolumeForUid(int streamType, int direction, int flags,
+ String callingPackage, int uid) {
+ adjustStreamVolume(streamType, direction, flags, callingPackage,
+ callingPackage, uid);
+ }
+
+ @Override
+ public void setStreamVolumeForUid(int streamType, int direction, int flags,
+ String callingPackage, int uid) {
+ setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid);
+ }
+
+ @Override
+ public int getRingerModeInternal() {
+ return AudioService.this.getRingerModeInternal();
+ }
+
+ @Override
+ public void setRingerModeInternal(int ringerMode, String caller) {
+ AudioService.this.setRingerModeInternal(ringerMode, caller);
+ }
+
+ @Override
+ public int getVolumeControllerUid() {
+ return mControllerService.mUid;
+ }
+ }
+
+ //==========================================================================================
+ // Audio policy management
+ //==========================================================================================
+ public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb,
+ boolean hasFocusListener) {
+ if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder()
+ + " with config:" + policyConfig);
+ String regId = null;
+ // error handling
+ boolean hasPermissionForPolicy =
+ (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+ if (!hasPermissionForPolicy) {
+ Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
+ + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
+ return null;
+ }
+
+ synchronized (mAudioPolicies) {
+ try {
+ if (mAudioPolicies.containsKey(pcb.asBinder())) {
+ Slog.e(TAG, "Cannot re-register policy");
+ return null;
+ }
+ AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener);
+ pcb.asBinder().linkToDeath(app, 0/*flags*/);
+ regId = app.getRegistrationId();
+ mAudioPolicies.put(pcb.asBinder(), app);
+ } catch (RemoteException e) {
+ // audio policy owner has already died!
+ Slog.w(TAG, "Audio policy registration failed, could not link to " + pcb +
+ " binder death", e);
+ return null;
+ }
+ }
+ return regId;
+ }
+
+ public void unregisterAudioPolicyAsync(IAudioPolicyCallback pcb) {
+ if (DEBUG_AP) Log.d(TAG, "unregisterAudioPolicyAsync for " + pcb.asBinder());
+ synchronized (mAudioPolicies) {
+ AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder());
+ if (app == null) {
+ Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
+ + Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
+ return;
+ } else {
+ pcb.asBinder().unlinkToDeath(app, 0/*flags*/);
+ }
+ app.release();
+ }
+ // TODO implement clearing mix attribute matching info in native audio policy
+ }
+
+ public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) {
+ if (DEBUG_AP) Log.d(TAG, "setFocusPropertiesForPolicy() duck behavior=" + duckingBehavior
+ + " policy " + pcb.asBinder());
+ // error handling
+ boolean hasPermissionForPolicy =
+ (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+ if (!hasPermissionForPolicy) {
+ Slog.w(TAG, "Cannot change audio policy ducking handling for pid " +
+ + Binder.getCallingPid() + " / uid "
+ + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
+ return AudioManager.ERROR;
+ }
+
+ synchronized (mAudioPolicies) {
+ if (!mAudioPolicies.containsKey(pcb.asBinder())) {
+ Slog.e(TAG, "Cannot change audio policy focus properties, unregistered policy");
+ return AudioManager.ERROR;
+ }
+ final AudioPolicyProxy app = mAudioPolicies.get(pcb.asBinder());
+ if (duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
+ // is there already one policy managing ducking?
+ for(AudioPolicyProxy policy : mAudioPolicies.values()) {
+ if (policy.mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
+ Slog.e(TAG, "Cannot change audio policy ducking behavior, already handled");
+ return AudioManager.ERROR;
+ }
+ }
+ }
+ app.mFocusDuckBehavior = duckingBehavior;
+ mMediaFocusControl.setDuckingInExtPolicyAvailable(
+ duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY);
+ }
+ return AudioManager.SUCCESS;
+ }
+
+ private void dumpAudioPolicies(PrintWriter pw) {
+ pw.println("\nAudio policies:");
+ synchronized (mAudioPolicies) {
+ for(AudioPolicyProxy policy : mAudioPolicies.values()) {
+ pw.println(policy.toLogFriendlyString());
+ }
+ }
+ }
+
+ //======================
+ // Audio policy proxy
+ //======================
+ /**
+ * This internal class inherits from AudioPolicyConfig, each instance contains all the
+ * mixes of an AudioPolicy and their configurations.
+ */
+ public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
+ private static final String TAG = "AudioPolicyProxy";
+ AudioPolicyConfig mConfig;
+ IAudioPolicyCallback mPolicyToken;
+ boolean mHasFocusListener;
+ /**
+ * Audio focus ducking behavior for an audio policy.
+ * This variable reflects the value that was successfully set in
+ * {@link AudioService#setFocusPropertiesForPolicy(int, IAudioPolicyCallback)}. This
+ * implies that a value of FOCUS_POLICY_DUCKING_IN_POLICY means the corresponding policy
+ * is handling ducking for audio focus.
+ */
+ int mFocusDuckBehavior = AudioPolicy.FOCUS_POLICY_DUCKING_DEFAULT;
+
+ AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token,
+ boolean hasFocusListener) {
+ super(config);
+ setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
+ mPolicyToken = token;
+ mHasFocusListener = hasFocusListener;
+ if (mHasFocusListener) {
+ mMediaFocusControl.addFocusFollower(mPolicyToken);
+ }
+ connectMixes();
+ }
+
+ public void binderDied() {
+ synchronized (mAudioPolicies) {
+ Log.i(TAG, "audio policy " + mPolicyToken + " died");
+ release();
+ mAudioPolicies.remove(mPolicyToken.asBinder());
+ }
+ }
+
+ String getRegistrationId() {
+ return getRegistration();
+ }
+
+ void release() {
+ if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) {
+ mMediaFocusControl.setDuckingInExtPolicyAvailable(false);
+ }
+ if (mHasFocusListener) {
+ mMediaFocusControl.removeFocusFollower(mPolicyToken);
+ }
+ AudioSystem.registerPolicyMixes(mMixes, false);
+ }
+
+ void connectMixes() {
+ AudioSystem.registerPolicyMixes(mMixes, true);
+ }
+ };
+
+ private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies =
+ new HashMap<IBinder, AudioPolicyProxy>();
+ private int mAudioPolicyCounter = 0; // always accessed synchronized on mAudioPolicies
+
+ private class ControllerService extends ContentObserver {
+ private int mUid;
+ private ComponentName mComponent;
+
+ public ControllerService() {
+ super(null);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{mUid=%d,mComponent=%s}", mUid, mComponent);
+ }
+
+ public void init() {
+ onChange(true);
+ mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mUid = 0;
+ mComponent = null;
+ final String setting = Settings.Secure.getString(mContentResolver,
+ Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
+ if (setting == null) return;
+ try {
+ mComponent = ComponentName.unflattenFromString(setting);
+ if (mComponent == null) return;
+ mUid = mContext.getPackageManager()
+ .getApplicationInfo(mComponent.getPackageName(), 0).uid;
+ } catch (Exception e) {
+ Log.w(TAG, "Error loading controller service", e);
+ }
+ if (DEBUG_VOL) Log.d(TAG, "Reloaded controller service: " + this);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
new file mode 100644
index 0000000..49be879
--- /dev/null
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -0,0 +1,333 @@
+/*
+ * 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.audio;
+
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.media.AudioFocusInfo;
+import android.media.AudioManager;
+import android.media.IAudioFocusDispatcher;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler;
+
+import java.io.PrintWriter;
+
+/**
+ * @hide
+ * Class to handle all the information about a user of audio focus. The lifecycle of each
+ * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus
+ * stack to its release.
+ */
+public class FocusRequester {
+
+ // on purpose not using this classe's name, as it will only be used from MediaFocusControl
+ private static final String TAG = "MediaFocusControl";
+ private static final boolean DEBUG = false;
+
+ private AudioFocusDeathHandler mDeathHandler;
+ private final IAudioFocusDispatcher mFocusDispatcher; // may be null
+ private final IBinder mSourceRef;
+ private final String mClientId;
+ private final String mPackageName;
+ private final int mCallingUid;
+ private final MediaFocusControl mFocusController; // never null
+ /**
+ * the audio focus gain request that caused the addition of this object in the focus stack.
+ */
+ private final int mFocusGainRequest;
+ /**
+ * the flags associated with the gain request that qualify the type of grant (e.g. accepting
+ * delay vs grant must be immediate)
+ */
+ private final int mGrantFlags;
+ /**
+ * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if
+ * it never lost focus.
+ */
+ private int mFocusLossReceived;
+ /**
+ * the audio attributes associated with the focus request
+ */
+ private final AudioAttributes mAttributes;
+
+ /**
+ * Class constructor
+ * @param aa
+ * @param focusRequest
+ * @param grantFlags
+ * @param afl
+ * @param source
+ * @param id
+ * @param hdlr
+ * @param pn
+ * @param uid
+ * @param ctlr cannot be null
+ */
+ FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags,
+ IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr,
+ String pn, int uid, @NonNull MediaFocusControl ctlr) {
+ mAttributes = aa;
+ mFocusDispatcher = afl;
+ mSourceRef = source;
+ mClientId = id;
+ mDeathHandler = hdlr;
+ mPackageName = pn;
+ mCallingUid = uid;
+ mFocusGainRequest = focusRequest;
+ mGrantFlags = grantFlags;
+ mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
+ mFocusController = ctlr;
+ }
+
+
+ boolean hasSameClient(String otherClient) {
+ try {
+ return mClientId.compareTo(otherClient) == 0;
+ } catch (NullPointerException e) {
+ return false;
+ }
+ }
+
+ boolean isLockedFocusOwner() {
+ return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0);
+ }
+
+ boolean hasSameBinder(IBinder ib) {
+ return (mSourceRef != null) && mSourceRef.equals(ib);
+ }
+
+ boolean hasSamePackage(String pack) {
+ try {
+ return mPackageName.compareTo(pack) == 0;
+ } catch (NullPointerException e) {
+ return false;
+ }
+ }
+
+ boolean hasSameUid(int uid) {
+ return mCallingUid == uid;
+ }
+
+ String getClientId() {
+ return mClientId;
+ }
+
+ int getGainRequest() {
+ return mFocusGainRequest;
+ }
+
+ int getGrantFlags() {
+ return mGrantFlags;
+ }
+
+ AudioAttributes getAudioAttributes() {
+ return mAttributes;
+ }
+
+
+ private static String focusChangeToString(int focus) {
+ switch(focus) {
+ case AudioManager.AUDIOFOCUS_NONE:
+ return "none";
+ case AudioManager.AUDIOFOCUS_GAIN:
+ return "GAIN";
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+ return "GAIN_TRANSIENT";
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+ return "GAIN_TRANSIENT_MAY_DUCK";
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
+ return "GAIN_TRANSIENT_EXCLUSIVE";
+ case AudioManager.AUDIOFOCUS_LOSS:
+ return "LOSS";
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ return "LOSS_TRANSIENT";
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ return "LOSS_TRANSIENT_CAN_DUCK";
+ default:
+ return "[invalid focus change" + focus + "]";
+ }
+ }
+
+ private String focusGainToString() {
+ return focusChangeToString(mFocusGainRequest);
+ }
+
+ private String focusLossToString() {
+ return focusChangeToString(mFocusLossReceived);
+ }
+
+ private static String flagsToString(int flags) {
+ String msg = new String();
+ if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) {
+ msg += "DELAY_OK";
+ }
+ if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0) {
+ if (!msg.isEmpty()) { msg += "|"; }
+ msg += "LOCK";
+ }
+ if ((flags & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) {
+ if (!msg.isEmpty()) { msg += "|"; }
+ msg += "PAUSES_ON_DUCKABLE_LOSS";
+ }
+ return msg;
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println(" source:" + mSourceRef
+ + " -- pack: " + mPackageName
+ + " -- client: " + mClientId
+ + " -- gain: " + focusGainToString()
+ + " -- flags: " + flagsToString(mGrantFlags)
+ + " -- loss: " + focusLossToString()
+ + " -- uid: " + mCallingUid
+ + " -- attr: " + mAttributes);
+ }
+
+
+ void release() {
+ try {
+ if (mSourceRef != null && mDeathHandler != null) {
+ mSourceRef.unlinkToDeath(mDeathHandler, 0);
+ mDeathHandler = null;
+ }
+ } catch (java.util.NoSuchElementException e) {
+ Log.e(TAG, "FocusRequester.release() hit ", e);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ release();
+ super.finalize();
+ }
+
+ /**
+ * For a given audio focus gain request, return the audio focus loss type that will result
+ * from it, taking into account any previous focus loss.
+ * @param gainRequest
+ * @return the audio focus loss type that matches the gain request
+ */
+ private int focusLossForGainRequest(int gainRequest) {
+ switch(gainRequest) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ switch(mFocusLossReceived) {
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ case AudioManager.AUDIOFOCUS_LOSS:
+ case AudioManager.AUDIOFOCUS_NONE:
+ return AudioManager.AUDIOFOCUS_LOSS;
+ }
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+ switch(mFocusLossReceived) {
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ case AudioManager.AUDIOFOCUS_NONE:
+ return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+ case AudioManager.AUDIOFOCUS_LOSS:
+ return AudioManager.AUDIOFOCUS_LOSS;
+ }
+ case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+ switch(mFocusLossReceived) {
+ case AudioManager.AUDIOFOCUS_NONE:
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+ case AudioManager.AUDIOFOCUS_LOSS:
+ return AudioManager.AUDIOFOCUS_LOSS;
+ }
+ default:
+ Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest);
+ return AudioManager.AUDIOFOCUS_NONE;
+ }
+ }
+
+ /**
+ * Called synchronized on MediaFocusControl.mAudioFocusLock
+ */
+ void handleExternalFocusGain(int focusGain) {
+ int focusLoss = focusLossForGainRequest(focusGain);
+ handleFocusLoss(focusLoss);
+ }
+
+ /**
+ * Called synchronized on MediaFocusControl.mAudioFocusLock
+ */
+ void handleFocusGain(int focusGain) {
+ try {
+ mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE;
+ mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(),
+ AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ if (mFocusDispatcher != null) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to "
+ + mClientId);
+ }
+ mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId);
+ }
+ } catch (android.os.RemoteException e) {
+ Log.e(TAG, "Failure to signal gain of audio focus due to: ", e);
+ }
+ }
+
+ /**
+ * Called synchronized on MediaFocusControl.mAudioFocusLock
+ */
+ void handleFocusLoss(int focusLoss) {
+ try {
+ if (focusLoss != mFocusLossReceived) {
+ mFocusLossReceived = focusLoss;
+ // before dispatching a focus loss, check if the following conditions are met:
+ // 1/ the framework is not supposed to notify the focus loser on a DUCK loss
+ // 2/ it is a DUCK loss
+ // 3/ the focus loser isn't flagged as pausing in a DUCK loss
+ // if they are, do not notify the focus loser
+ if (!mFocusController.mustNotifyFocusOwnerOnDuck()
+ && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
+ && (mGrantFlags
+ & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) {
+ if (DEBUG) {
+ Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+ + " to " + mClientId + ", to be handled externally");
+ }
+ mFocusController.notifyExtPolicyFocusLoss_syncAf(
+ toAudioFocusInfo(), false /* wasDispatched */);
+ return;
+ }
+ if (mFocusDispatcher != null) {
+ if (DEBUG) {
+ Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to "
+ + mClientId);
+ }
+ mFocusController.notifyExtPolicyFocusLoss_syncAf(
+ toAudioFocusInfo(), true /* wasDispatched */);
+ mFocusDispatcher.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
+ }
+ }
+ } catch (android.os.RemoteException e) {
+ Log.e(TAG, "Failure to signal loss of audio focus due to:", e);
+ }
+ }
+
+ AudioFocusInfo toAudioFocusInfo() {
+ return new AudioFocusInfo(mAttributes, mClientId, mPackageName,
+ mFocusGainRequest, mFocusLossReceived, mGrantFlags);
+ }
+}
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
new file mode 100644
index 0000000..4ccb5ad
--- /dev/null
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -0,0 +1,2228 @@
+/*
+ * 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.audio;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.PendingIntent.OnFinished;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.media.AudioAttributes;
+import android.media.AudioFocusInfo;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.IAudioFocusDispatcher;
+import android.media.IRemoteControlClient;
+import android.media.IRemoteControlDisplay;
+import android.media.IRemoteVolumeObserver;
+import android.media.RemoteControlClient;
+import android.media.audiopolicy.IAudioPolicyCallback;
+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.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.speech.RecognizerIntent;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.Slog;
+import android.view.KeyEvent;
+
+import com.android.server.audio.PlayerRecord.RemotePlaybackState;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Stack;
+import java.text.DateFormat;
+
+/**
+ * @hide
+ *
+ */
+public class MediaFocusControl implements OnFinished {
+
+ private static final String TAG = "MediaFocusControl";
+
+ /** Debug remote control client/display feature */
+ protected static final boolean DEBUG_RC = false;
+ /** Debug volumes */
+ protected static final boolean DEBUG_VOL = false;
+
+ /** Used to alter media button redirection when the phone is ringing. */
+ private boolean mIsRinging = false;
+
+ private final PowerManager.WakeLock mMediaEventWakeLock;
+ private final MediaEventHandler mEventHandler;
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private final AudioService.VolumeController mVolumeController;
+ private final AppOpsManager mAppOps;
+ private final KeyguardManager mKeyguardManager;
+ private final AudioService mAudioService;
+ private final NotificationListenerObserver mNotifListenerObserver;
+
+ protected MediaFocusControl(Looper looper, Context cntxt,
+ AudioService.VolumeController volumeCtrl, AudioService as) {
+ mEventHandler = new MediaEventHandler(looper);
+ mContext = cntxt;
+ mContentResolver = mContext.getContentResolver();
+ mVolumeController = volumeCtrl;
+ mAudioService = as;
+
+ PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
+ int maxMusicLevel = as.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ mMainRemote = new RemotePlaybackState(-1, maxMusicLevel, maxMusicLevel);
+
+ // Register for phone state monitoring
+ TelephonyManager tmgr = (TelephonyManager)
+ mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+ mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mKeyguardManager =
+ (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mNotifListenerObserver = new NotificationListenerObserver();
+
+ mHasRemotePlayback = false;
+ mMainRemoteIsActive = false;
+
+ PlayerRecord.setMediaFocusControl(this);
+
+ postReevaluateRemote();
+ }
+
+ protected void dump(PrintWriter pw) {
+ pw.println("\nMediaFocusControl dump time: "
+ + DateFormat.getTimeInstance().format(new Date()));
+ dumpFocusStack(pw);
+ dumpRCStack(pw);
+ dumpRCCStack(pw);
+ dumpRCDList(pw);
+ }
+
+ //==========================================================================================
+ // Management of RemoteControlDisplay registration permissions
+ //==========================================================================================
+ private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI =
+ Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
+ private class NotificationListenerObserver extends ContentObserver {
+
+ NotificationListenerObserver() {
+ super(mEventHandler);
+ mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) {
+ return;
+ }
+ if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); }
+ postReevaluateRemoteControlDisplays();
+ }
+ }
+
+ private final static int RCD_REG_FAILURE = 0;
+ private final static int RCD_REG_SUCCESS_PERMISSION = 1;
+ private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2;
+
+ /**
+ * Checks a caller's authorization to register an IRemoteControlDisplay.
+ * Authorization is granted if one of the following is true:
+ * <ul>
+ * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li>
+ * <li>the caller's listener is one of the enabled notification listeners</li>
+ * </ul>
+ * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay
+ * registration.
+ */
+ private int checkRcdRegistrationAuthorization(ComponentName listenerComp) {
+ // MEDIA_CONTENT_CONTROL permission check
+ if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MEDIA_CONTENT_CONTROL)) {
+ if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");}
+ return RCD_REG_SUCCESS_PERMISSION;
+ }
+
+ // ENABLED_NOTIFICATION_LISTENERS settings check
+ if (listenerComp != null) {
+ // this call is coming from an app, can't use its identity to read secure settings
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ final int currentUser = ActivityManager.getCurrentUser();
+ final String enabledNotifListeners = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+ currentUser);
+ if (enabledNotifListeners != null) {
+ final String[] components = enabledNotifListeners.split(":");
+ for (int i=0; i<components.length; i++) {
+ final ComponentName component =
+ ComponentName.unflattenFromString(components[i]);
+ if (component != null) {
+ if (listenerComp.equals(component)) {
+ if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component +
+ " is authorized notification listener"); }
+ return RCD_REG_SUCCESS_ENABLED_NOTIF;
+ }
+ }
+ }
+ }
+ if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp +
+ " is not in list of ENABLED_NOTIFICATION_LISTENERS"); }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ return RCD_REG_FAILURE;
+ }
+
+ protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
+ ComponentName listenerComp) {
+ int reg = checkRcdRegistrationAuthorization(listenerComp);
+ if (reg != RCD_REG_FAILURE) {
+ registerRemoteControlDisplay_int(rcd, w, h, listenerComp);
+ return true;
+ } else {
+ Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
+ ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
+ " or be an enabled NotificationListenerService for registerRemoteController");
+ return false;
+ }
+ }
+
+ protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
+ int reg = checkRcdRegistrationAuthorization(null);
+ if (reg != RCD_REG_FAILURE) {
+ registerRemoteControlDisplay_int(rcd, w, h, null);
+ return true;
+ } else {
+ Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
+ ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
+ " to register IRemoteControlDisplay");
+ return false;
+ }
+ }
+
+ private void postReevaluateRemoteControlDisplays() {
+ sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0);
+ }
+
+ private void onReevaluateRemoteControlDisplays() {
+ if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); }
+ // read which components are enabled notification listeners
+ final int currentUser = ActivityManager.getCurrentUser();
+ final String enabledNotifListeners = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+ currentUser);
+ if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); }
+ synchronized(mAudioFocusLock) {
+ synchronized(mPRStack) {
+ // check whether the "enable" status of each RCD with a notification listener
+ // has changed
+ final String[] enabledComponents;
+ if (enabledNotifListeners == null) {
+ enabledComponents = null;
+ } else {
+ enabledComponents = enabledNotifListeners.split(":");
+ }
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di =
+ displayIterator.next();
+ if (di.mClientNotifListComp != null) {
+ boolean wasEnabled = di.mEnabled;
+ di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
+ enabledComponents);
+ if (wasEnabled != di.mEnabled){
+ try {
+ // tell the RCD whether it's enabled
+ di.mRcDisplay.setEnabled(di.mEnabled);
+ // tell the RCCs about the change for this RCD
+ enableRemoteControlDisplayForClient_syncRcStack(
+ di.mRcDisplay, di.mEnabled);
+ // when enabling, refresh the information on the display
+ if (di.mEnabled) {
+ sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
+ di.mArtworkExpectedWidth /*arg1*/,
+ di.mArtworkExpectedHeight/*arg2*/,
+ di.mRcDisplay /*obj*/, 0/*delay*/);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error en/disabling RCD: ", e);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @param comp a non-null ComponentName
+ * @param enabledArray may be null
+ * @return
+ */
+ private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) {
+ if (enabledArray == null || enabledArray.length == 0) {
+ if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); }
+ return false;
+ }
+ final String compString = comp.flattenToString();
+ for (int i=0; i<enabledArray.length; i++) {
+ if (compString.equals(enabledArray[i])) {
+ if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); }
+ return true;
+ }
+ }
+ if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); }
+ return false;
+ }
+
+ //==========================================================================================
+ // Internal event handling
+ //==========================================================================================
+
+ // event handler messages
+ private static final int MSG_RCDISPLAY_CLEAR = 1;
+ private static final int MSG_RCDISPLAY_UPDATE = 2;
+ private static final int MSG_REEVALUATE_REMOTE = 3;
+ private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4;
+ private static final int MSG_RCC_NEW_VOLUME_OBS = 5;
+ private static final int MSG_RCC_NEW_PLAYBACK_STATE = 6;
+ private static final int MSG_RCC_SEEK_REQUEST = 7;
+ private static final int MSG_RCC_UPDATE_METADATA = 8;
+ private static final int MSG_RCDISPLAY_INIT_INFO = 9;
+ private static final int MSG_REEVALUATE_RCD = 10;
+ private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 11;
+
+ // sendMsg() flags
+ /** If the msg is already queued, replace it with this one. */
+ private static final int SENDMSG_REPLACE = 0;
+ /** If the msg is already queued, ignore this one and leave the old. */
+ private static final int SENDMSG_NOOP = 1;
+ /** If the msg is already queued, queue this one and leave the old. */
+ private static final int SENDMSG_QUEUE = 2;
+
+ private static void sendMsg(Handler handler, int msg,
+ int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
+
+ if (existingMsgPolicy == SENDMSG_REPLACE) {
+ handler.removeMessages(msg);
+ } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
+ return;
+ }
+
+ handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
+ }
+
+ private class MediaEventHandler extends Handler {
+ MediaEventHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_RCDISPLAY_CLEAR:
+ onRcDisplayClear();
+ break;
+
+ case MSG_RCDISPLAY_UPDATE:
+ // msg.obj is guaranteed to be non null
+ onRcDisplayUpdate( (PlayerRecord) msg.obj, msg.arg1);
+ break;
+
+ case MSG_REEVALUATE_REMOTE:
+ onReevaluateRemote();
+ break;
+
+ case MSG_RCC_NEW_VOLUME_OBS:
+ onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
+ (IRemoteVolumeObserver)msg.obj /* rvo */);
+ break;
+
+ case MSG_RCDISPLAY_INIT_INFO:
+ // msg.obj is guaranteed to be non null
+ onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
+ msg.arg1/*w*/, msg.arg2/*h*/);
+ break;
+
+ case MSG_REEVALUATE_RCD:
+ onReevaluateRemoteControlDisplays();
+ break;
+
+ case MSG_UNREGISTER_MEDIABUTTONINTENT:
+ unregisterMediaButtonIntent( (PendingIntent) msg.obj );
+ break;
+ }
+ }
+ }
+
+
+ //==========================================================================================
+ // AudioFocus
+ //==========================================================================================
+
+ private final static Object mAudioFocusLock = new Object();
+
+ private final static Object mRingingLock = new Object();
+
+ private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ if (state == TelephonyManager.CALL_STATE_RINGING) {
+ //Log.v(TAG, " CALL_STATE_RINGING");
+ synchronized(mRingingLock) {
+ mIsRinging = true;
+ }
+ } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK)
+ || (state == TelephonyManager.CALL_STATE_IDLE)) {
+ synchronized(mRingingLock) {
+ mIsRinging = false;
+ }
+ }
+ }
+ };
+
+ /**
+ * Discard the current audio focus owner.
+ * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
+ * focus), remove it from the stack, and clear the remote control display.
+ */
+ protected void discardAudioFocusOwner() {
+ synchronized(mAudioFocusLock) {
+ if (!mFocusStack.empty()) {
+ // notify the current focus owner it lost focus after removing it from stack
+ final FocusRequester exFocusOwner = mFocusStack.pop();
+ exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
+ exFocusOwner.release();
+ }
+ }
+ }
+
+ /**
+ * Called synchronized on mAudioFocusLock
+ */
+ private void notifyTopOfAudioFocusStack() {
+ // notify the top of the stack it gained focus
+ if (!mFocusStack.empty()) {
+ if (canReassignAudioFocus()) {
+ mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
+ }
+ }
+ }
+
+ /**
+ * Focus is requested, propagate the associated loss throughout the stack.
+ * @param focusGain the new focus gain that will later be added at the top of the stack
+ */
+ private void propagateFocusLossFromGain_syncAf(int focusGain) {
+ // going through the audio focus stack to signal new focus, traversing order doesn't
+ // matter as all entries respond to the same external focus gain
+ Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
+ while(stackIterator.hasNext()) {
+ stackIterator.next().handleExternalFocusGain(focusGain);
+ }
+ }
+
+ private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
+
+ /**
+ * Helper function:
+ * Display in the log the current entries in the audio focus stack
+ */
+ private void dumpFocusStack(PrintWriter pw) {
+ pw.println("\nAudio Focus stack entries (last is top of stack):");
+ synchronized(mAudioFocusLock) {
+ Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
+ while(stackIterator.hasNext()) {
+ stackIterator.next().dump(pw);
+ }
+ }
+ pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n");
+ }
+
+ /**
+ * Helper function:
+ * Called synchronized on mAudioFocusLock
+ * Remove a focus listener from the focus stack.
+ * @param clientToRemove the focus listener
+ * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
+ * focus, notify the next item in the stack it gained focus.
+ */
+ private void removeFocusStackEntry(String clientToRemove, boolean signal,
+ boolean notifyFocusFollowers) {
+ // is the current top of the focus stack abandoning focus? (because of request, not death)
+ if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
+ {
+ //Log.i(TAG, " removeFocusStackEntry() removing top of stack");
+ FocusRequester fr = mFocusStack.pop();
+ fr.release();
+ if (notifyFocusFollowers) {
+ final AudioFocusInfo afi = fr.toAudioFocusInfo();
+ afi.clearLossReceived();
+ notifyExtPolicyFocusLoss_syncAf(afi, false);
+ }
+ if (signal) {
+ // notify the new top of the stack it gained focus
+ notifyTopOfAudioFocusStack();
+ }
+ } else {
+ // focus is abandoned by a client that's not at the top of the stack,
+ // no need to update focus.
+ // (using an iterator on the stack so we can safely remove an entry after having
+ // evaluated it, traversal order doesn't matter here)
+ Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
+ while(stackIterator.hasNext()) {
+ FocusRequester fr = stackIterator.next();
+ if(fr.hasSameClient(clientToRemove)) {
+ Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
+ + clientToRemove);
+ stackIterator.remove();
+ fr.release();
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function:
+ * Called synchronized on mAudioFocusLock
+ * Remove focus listeners from the focus stack for a particular client when it has died.
+ */
+ private void removeFocusStackEntryForClient(IBinder cb) {
+ // is the owner of the audio focus part of the client to remove?
+ boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
+ mFocusStack.peek().hasSameBinder(cb);
+ // (using an iterator on the stack so we can safely remove an entry after having
+ // evaluated it, traversal order doesn't matter here)
+ Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
+ while(stackIterator.hasNext()) {
+ FocusRequester fr = stackIterator.next();
+ if(fr.hasSameBinder(cb)) {
+ Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb);
+ stackIterator.remove();
+ // the client just died, no need to unlink to its death
+ }
+ }
+ if (isTopOfStackForClientToRemove) {
+ // we removed an entry at the top of the stack:
+ // notify the new top of the stack it gained focus.
+ notifyTopOfAudioFocusStack();
+ }
+ }
+
+ /**
+ * Helper function:
+ * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
+ * The implementation guarantees that a state where focus cannot be immediately reassigned
+ * implies that an "locked" focus owner is at the top of the focus stack.
+ * Modifications to the implementation that break this assumption will cause focus requests to
+ * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.
+ */
+ private boolean canReassignAudioFocus() {
+ // focus requests are rejected during a phone call or when the phone is ringing
+ // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
+ if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isLockedFocusOwner(FocusRequester fr) {
+ return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
+ }
+
+ /**
+ * Helper function
+ * Pre-conditions: focus stack is not empty, there is one or more locked focus owner
+ * at the top of the focus stack
+ * Push the focus requester onto the audio focus stack at the first position immediately
+ * following the locked focus owners.
+ * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
+ * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
+ */
+ private int pushBelowLockedFocusOwners(FocusRequester nfr) {
+ int lastLockedFocusOwnerIndex = mFocusStack.size();
+ for (int index = mFocusStack.size()-1; index >= 0; index--) {
+ if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
+ lastLockedFocusOwnerIndex = index;
+ }
+ }
+ if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
+ // this should not happen, but handle it and log an error
+ Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
+ new Exception());
+ // no exclusive owner, push at top of stack, focus is granted, propagate change
+ propagateFocusLossFromGain_syncAf(nfr.getGainRequest());
+ mFocusStack.push(nfr);
+ return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+ } else {
+ mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
+ return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
+ }
+ }
+
+ /**
+ * Inner class to monitor audio focus client deaths, and remove them from the audio focus
+ * stack if necessary.
+ */
+ protected class AudioFocusDeathHandler implements IBinder.DeathRecipient {
+ private IBinder mCb; // To be notified of client's death
+
+ AudioFocusDeathHandler(IBinder cb) {
+ mCb = cb;
+ }
+
+ public void binderDied() {
+ synchronized(mAudioFocusLock) {
+ Log.w(TAG, " AudioFocus audio focus client died");
+ removeFocusStackEntryForClient(mCb);
+ }
+ }
+
+ public IBinder getBinder() {
+ return mCb;
+ }
+ }
+
+ /**
+ * Indicates whether to notify an audio focus owner when it loses focus
+ * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck.
+ * This variable being false indicates an AudioPolicy has been registered and has signaled
+ * it will handle audio ducking.
+ */
+ private boolean mNotifyFocusOwnerOnDuck = true;
+
+ protected void setDuckingInExtPolicyAvailable(boolean available) {
+ mNotifyFocusOwnerOnDuck = !available;
+ }
+
+ boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
+
+ private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
+
+ void addFocusFollower(IAudioPolicyCallback ff) {
+ if (ff == null) {
+ return;
+ }
+ synchronized(mAudioFocusLock) {
+ boolean found = false;
+ for (IAudioPolicyCallback pcb : mFocusFollowers) {
+ if (pcb.asBinder().equals(ff.asBinder())) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ return;
+ } else {
+ mFocusFollowers.add(ff);
+ notifyExtPolicyCurrentFocusAsync(ff);
+ }
+ }
+ }
+
+ void removeFocusFollower(IAudioPolicyCallback ff) {
+ if (ff == null) {
+ return;
+ }
+ synchronized(mAudioFocusLock) {
+ for (IAudioPolicyCallback pcb : mFocusFollowers) {
+ if (pcb.asBinder().equals(ff.asBinder())) {
+ mFocusFollowers.remove(pcb);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param pcb non null
+ */
+ void notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb) {
+ final IAudioPolicyCallback pcb2 = pcb;
+ final Thread thread = new Thread() {
+ @Override
+ public void run() {
+ synchronized(mAudioFocusLock) {
+ if (mFocusStack.isEmpty()) {
+ return;
+ }
+ try {
+ pcb2.notifyAudioFocusGrant(mFocusStack.peek().toAudioFocusInfo(),
+ // top of focus stack always has focus
+ AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
+ + pcb2.asBinder(), e);
+ }
+ }
+ }
+ };
+ thread.start();
+ }
+
+ /**
+ * Called synchronized on mAudioFocusLock
+ */
+ void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) {
+ for (IAudioPolicyCallback pcb : mFocusFollowers) {
+ try {
+ // oneway
+ pcb.notifyAudioFocusGrant(afi, requestResult);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
+ + pcb.asBinder(), e);
+ }
+ }
+ }
+
+ /**
+ * Called synchronized on mAudioFocusLock
+ */
+ void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) {
+ for (IAudioPolicyCallback pcb : mFocusFollowers) {
+ try {
+ // oneway
+ pcb.notifyAudioFocusLoss(afi, wasDispatched);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't call notifyAudioFocusLoss() on IAudioPolicyCallback "
+ + pcb.asBinder(), e);
+ }
+ }
+ }
+
+ protected int getCurrentAudioFocus() {
+ synchronized(mAudioFocusLock) {
+ if (mFocusStack.empty()) {
+ return AudioManager.AUDIOFOCUS_NONE;
+ } else {
+ return mFocusStack.peek().getGainRequest();
+ }
+ }
+ }
+
+ /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
+ protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
+ IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
+ Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId + " req=" + focusChangeHint +
+ "flags=0x" + Integer.toHexString(flags));
+ // we need a valid binder callback for clients
+ if (!cb.pingBinder()) {
+ Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+
+ if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
+ callingPackageName) != AppOpsManager.MODE_ALLOWED) {
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+
+ synchronized(mAudioFocusLock) {
+ boolean focusGrantDelayed = false;
+ if (!canReassignAudioFocus()) {
+ if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ } else {
+ // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
+ // granted right now, so the requester will be inserted in the focus stack
+ // to receive focus later
+ focusGrantDelayed = true;
+ }
+ }
+
+ // handle the potential premature death of the new holder of the focus
+ // (premature death == death before abandoning focus)
+ // Register for client death notification
+ AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
+ try {
+ cb.linkToDeath(afdh, 0);
+ } catch (RemoteException e) {
+ // client has already died!
+ Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death");
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+
+ if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
+ // if focus is already owned by this client and the reason for acquiring the focus
+ // hasn't changed, don't do anything
+ final FocusRequester fr = mFocusStack.peek();
+ if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
+ // unlink death handler so it can be gc'ed.
+ // linkToDeath() creates a JNI global reference preventing collection.
+ cb.unlinkToDeath(afdh, 0);
+ notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
+ AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+ }
+ // the reason for the audio focus request has changed: remove the current top of
+ // stack and respond as if we had a new focus owner
+ if (!focusGrantDelayed) {
+ mFocusStack.pop();
+ // the entry that was "popped" is the same that was "peeked" above
+ fr.release();
+ }
+ }
+
+ // focus requester might already be somewhere below in the stack, remove it
+ removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
+
+ final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
+ clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
+ if (focusGrantDelayed) {
+ // focusGrantDelayed being true implies we can't reassign focus right now
+ // which implies the focus stack is not empty.
+ final int requestResult = pushBelowLockedFocusOwners(nfr);
+ if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
+ notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
+ }
+ return requestResult;
+ } else {
+ // propagate the focus change through the stack
+ if (!mFocusStack.empty()) {
+ propagateFocusLossFromGain_syncAf(focusChangeHint);
+ }
+
+ // push focus requester at the top of the audio focus stack
+ mFocusStack.push(nfr);
+ }
+ notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
+ AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+
+ }//synchronized(mAudioFocusLock)
+
+ return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+ }
+
+ /**
+ * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
+ * */
+ protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) {
+ // AudioAttributes are currently ignored, to be used for zones
+ Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId);
+ try {
+ // this will take care of notifying the new focus owner if needed
+ synchronized(mAudioFocusLock) {
+ removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
+ }
+ } catch (java.util.ConcurrentModificationException cme) {
+ // Catching this exception here is temporary. It is here just to prevent
+ // a crash seen when the "Silent" notification is played. This is believed to be fixed
+ // but this try catch block is left just to be safe.
+ Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme);
+ cme.printStackTrace();
+ }
+
+ return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+ }
+
+
+ protected void unregisterAudioFocusClient(String clientId) {
+ synchronized(mAudioFocusLock) {
+ removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
+ }
+ }
+
+
+ //==========================================================================================
+ // RemoteControl
+ //==========================================================================================
+ /**
+ * No-op if the key code for keyEvent is not a valid media key
+ * (see {@link #isValidMediaKeyEvent(KeyEvent)})
+ * @param keyEvent the key event to send
+ */
+ protected void dispatchMediaKeyEvent(KeyEvent keyEvent) {
+ filterMediaKeyEvent(keyEvent, false /*needWakeLock*/);
+ }
+
+ /**
+ * No-op if the key code for keyEvent is not a valid media key
+ * (see {@link #isValidMediaKeyEvent(KeyEvent)})
+ * @param keyEvent the key event to send
+ */
+ protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
+ filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
+ }
+
+ private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
+ // sanity check on the incoming key event
+ if (!isValidMediaKeyEvent(keyEvent)) {
+ Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
+ return;
+ }
+ // event filtering for telephony
+ synchronized(mRingingLock) {
+ synchronized(mPRStack) {
+ if ((mMediaReceiverForCalls != null) &&
+ (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) {
+ dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
+ return;
+ }
+ }
+ }
+ // event filtering based on voice-based interactions
+ if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) {
+ filterVoiceInputKeyEvent(keyEvent, needWakeLock);
+ } else {
+ dispatchMediaKeyEvent(keyEvent, needWakeLock);
+ }
+ }
+
+ /**
+ * Handles the dispatching of the media button events to the telephony package.
+ * Precondition: mMediaReceiverForCalls != null
+ * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
+ * is dispatched.
+ */
+ private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) {
+ Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+ keyIntent.setPackage(mMediaReceiverForCalls.getPackageName());
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
+ null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Handles the dispatching of the media button events to one of the registered listeners,
+ * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
+ * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
+ * is dispatched.
+ */
+ private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ }
+ Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+ synchronized(mPRStack) {
+ if (!mPRStack.empty()) {
+ // send the intent that was registered by the client
+ try {
+ mPRStack.peek().getMediaButtonIntent().send(mContext,
+ needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/,
+ keyIntent, this, mEventHandler);
+ } catch (CanceledException e) {
+ Log.e(TAG, "Error sending pending intent " + mPRStack.peek());
+ e.printStackTrace();
+ }
+ } else {
+ // legacy behavior when nobody registered their media button event receiver
+ // through AudioManager
+ if (needWakeLock) {
+ keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
+ null, mKeyEventDone,
+ mEventHandler, Activity.RESULT_OK, null, null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+ }
+
+ /**
+ * The different actions performed in response to a voice button key event.
+ */
+ private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1;
+ private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2;
+ private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3;
+
+ private final Object mVoiceEventLock = new Object();
+ private boolean mVoiceButtonDown;
+ private boolean mVoiceButtonHandled;
+
+ /**
+ * Filter key events that may be used for voice-based interactions
+ * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported
+ * media buttons that can be used to trigger voice-based interactions.
+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
+ * is dispatched.
+ */
+ private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
+ if (DEBUG_RC) {
+ Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock);
+ }
+
+ int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS;
+ int keyAction = keyEvent.getAction();
+ synchronized (mVoiceEventLock) {
+ if (keyAction == KeyEvent.ACTION_DOWN) {
+ if (keyEvent.getRepeatCount() == 0) {
+ // initial down
+ mVoiceButtonDown = true;
+ mVoiceButtonHandled = false;
+ } else if (mVoiceButtonDown && !mVoiceButtonHandled
+ && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
+ // long-press, start voice-based interactions
+ mVoiceButtonHandled = true;
+ voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
+ }
+ } else if (keyAction == KeyEvent.ACTION_UP) {
+ if (mVoiceButtonDown) {
+ // voice button up
+ mVoiceButtonDown = false;
+ if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
+ voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
+ }
+ }
+ }
+ }//synchronized (mVoiceEventLock)
+
+ // take action after media button event filtering for voice-based interactions
+ switch (voiceButtonAction) {
+ case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS:
+ if (DEBUG_RC) Log.v(TAG, " ignore key event");
+ break;
+ case VOICEBUTTON_ACTION_START_VOICE_INPUT:
+ if (DEBUG_RC) Log.v(TAG, " start voice-based interactions");
+ // then start the voice-based interactions
+ startVoiceBasedInteractions(needWakeLock);
+ break;
+ case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS:
+ if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock);
+ sendSimulatedMediaButtonEvent(keyEvent, needWakeLock);
+ break;
+ }
+ }
+
+ private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) {
+ // send DOWN event
+ KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN);
+ dispatchMediaKeyEvent(keyEvent, needWakeLock);
+ // send UP event
+ keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP);
+ dispatchMediaKeyEvent(keyEvent, needWakeLock);
+
+ }
+
+ private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
+ if (keyEvent == null) {
+ return false;
+ }
+ return KeyEvent.isMediaKey(keyEvent.getKeyCode());
+ }
+
+ /**
+ * Checks whether the given key code is one that can trigger the launch of voice-based
+ * interactions.
+ * @param keyCode the key code associated with the key event
+ * @return true if the key is one of the supported voice-based interaction triggers
+ */
+ private static boolean isValidVoiceInputKeyCode(int keyCode) {
+ if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Tell the system to start voice-based interactions / voice commands
+ */
+ private void startVoiceBasedInteractions(boolean needWakeLock) {
+ Intent voiceIntent = null;
+ // select which type of search to launch:
+ // - screen on and device unlocked: action is ACTION_WEB_SEARCH
+ // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE
+ // with EXTRA_SECURE set to true if the device is securely locked
+ PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+ if (!isLocked && pm.isScreenOn()) {
+ voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
+ Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
+ } else {
+ voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
+ isLocked && mKeyguardManager.isKeyguardSecure());
+ Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
+ }
+ // start the search activity
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (voiceIntent != null) {
+ voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
+ }
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "No activity for search: " + e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ if (needWakeLock) {
+ mMediaEventWakeLock.release();
+ }
+ }
+ }
+
+ private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number
+
+ // only set when wakelock was acquired, no need to check value when received
+ private static final String EXTRA_WAKELOCK_ACQUIRED =
+ "android.media.AudioService.WAKELOCK_ACQUIRED";
+
+ public void onSendFinished(PendingIntent pendingIntent, Intent intent,
+ int resultCode, String resultData, Bundle resultExtras) {
+ if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) {
+ mMediaEventWakeLock.release();
+ }
+ }
+
+ BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return;
+ }
+ if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) {
+ mMediaEventWakeLock.release();
+ }
+ }
+ };
+
+ /**
+ * Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack
+ */
+ private final Object mCurrentRcLock = new Object();
+ /**
+ * The one remote control client which will receive a request for display information.
+ * This object may be null.
+ * Access protected by mCurrentRcLock.
+ */
+ private IRemoteControlClient mCurrentRcClient = null;
+ /**
+ * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant
+ * if mCurrentRcClient is null
+ */
+ private PendingIntent mCurrentRcClientIntent = null;
+
+ private final static int RC_INFO_NONE = 0;
+ private final static int RC_INFO_ALL =
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART |
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA |
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA |
+ RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE;
+
+ /**
+ * A monotonically increasing generation counter for mCurrentRcClient.
+ * Only accessed with a lock on mCurrentRcLock.
+ * No value wrap-around issues as we only act on equal values.
+ */
+ private int mCurrentRcClientGen = 0;
+
+
+ /**
+ * Internal cache for the playback information of the RemoteControlClient whose volume gets to
+ * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack
+ * every time we need this info.
+ */
+ private RemotePlaybackState mMainRemote;
+ /**
+ * Indicates whether the "main" RemoteControlClient is considered active.
+ * Use synchronized on mMainRemote.
+ */
+ private boolean mMainRemoteIsActive;
+ /**
+ * Indicates whether there is remote playback going on. True even if there is no "active"
+ * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it
+ * handles remote playback.
+ * Use synchronized on mMainRemote.
+ */
+ private boolean mHasRemotePlayback;
+
+ /**
+ * The stack of remote control event receivers.
+ * All read and write operations on mPRStack are synchronized.
+ */
+ private final Stack<PlayerRecord> mPRStack = new Stack<PlayerRecord>();
+
+ /**
+ * The component the telephony package can register so telephony calls have priority to
+ * handle media button events
+ */
+ private ComponentName mMediaReceiverForCalls = null;
+
+ /**
+ * Helper function:
+ * Display in the log the current entries in the remote control focus stack
+ */
+ private void dumpRCStack(PrintWriter pw) {
+ pw.println("\nRemote Control stack entries (last is top of stack):");
+ synchronized(mPRStack) {
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
+ while(stackIterator.hasNext()) {
+ stackIterator.next().dump(pw, true);
+ }
+ }
+ }
+
+ /**
+ * Helper function:
+ * Display in the log the current entries in the remote control stack, focusing
+ * on RemoteControlClient data
+ */
+ private void dumpRCCStack(PrintWriter pw) {
+ pw.println("\nRemote Control Client stack entries (last is top of stack):");
+ synchronized(mPRStack) {
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
+ while(stackIterator.hasNext()) {
+ stackIterator.next().dump(pw, false);
+ }
+ synchronized(mCurrentRcLock) {
+ pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen);
+ }
+ }
+ synchronized (mMainRemote) {
+ pw.println("\nRemote Volume State:");
+ pw.println(" has remote: " + mHasRemotePlayback);
+ pw.println(" is remote active: " + mMainRemoteIsActive);
+ pw.println(" rccId: " + mMainRemote.mRccId);
+ pw.println(" volume handling: "
+ + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ?
+ "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)"));
+ pw.println(" volume: " + mMainRemote.mVolume);
+ pw.println(" volume steps: " + mMainRemote.mVolumeMax);
+ }
+ }
+
+ /**
+ * Helper function:
+ * Display in the log the current entries in the list of remote control displays
+ */
+ private void dumpRCDList(PrintWriter pw) {
+ pw.println("\nRemote Control Display list entries:");
+ synchronized(mPRStack) {
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = displayIterator.next();
+ pw.println(" IRCD: " + di.mRcDisplay +
+ " -- w:" + di.mArtworkExpectedWidth +
+ " -- h:" + di.mArtworkExpectedHeight +
+ " -- wantsPosSync:" + di.mWantsPositionSync +
+ " -- " + (di.mEnabled ? "enabled" : "disabled"));
+ }
+ }
+ }
+
+ /**
+ * Helper function:
+ * Push the new media button receiver "near" the top of the PlayerRecord stack.
+ * "Near the top" is defined as:
+ * - at the top if the current PlayerRecord at the top is not playing
+ * - below the entries at the top of the stack that correspond to the playing PlayerRecord
+ * otherwise
+ * Called synchronized on mPRStack
+ * precondition: mediaIntent != null
+ * @return true if the top of mPRStack was changed, false otherwise
+ */
+ private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent,
+ ComponentName target, IBinder token) {
+ if (mPRStack.empty()) {
+ mPRStack.push(new PlayerRecord(mediaIntent, target, token));
+ return true;
+ } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) {
+ // already at top of stack
+ return false;
+ }
+ if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
+ mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
+ boolean topChanged = false;
+ PlayerRecord prse = null;
+ int lastPlayingIndex = mPRStack.size();
+ int inStackIndex = -1;
+ try {
+ // go through the stack from the top to figure out who's playing, and the position
+ // of this media button receiver (note that it may not be in the stack)
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ prse = mPRStack.elementAt(index);
+ if (prse.isPlaybackActive()) {
+ lastPlayingIndex = index;
+ }
+ if (prse.hasMatchingMediaButtonIntent(mediaIntent)) {
+ inStackIndex = index;
+ }
+ }
+
+ if (inStackIndex == -1) {
+ // is not in stack
+ prse = new PlayerRecord(mediaIntent, target, token);
+ // it's new so it's not playing (no RemoteControlClient to give a playstate),
+ // therefore it goes after the ones with active playback
+ mPRStack.add(lastPlayingIndex, prse);
+ } else {
+ // is in the stack
+ if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
+ prse = mPRStack.elementAt(inStackIndex);
+ // remove it from its old location in the stack
+ mPRStack.removeElementAt(inStackIndex);
+ if (prse.isPlaybackActive()) {
+ // and put it at the top
+ mPRStack.push(prse);
+ } else {
+ // and put it after the ones with active playback
+ if (inStackIndex > lastPlayingIndex) {
+ mPRStack.add(lastPlayingIndex, prse);
+ } else {
+ mPRStack.add(lastPlayingIndex - 1, prse);
+ }
+ }
+ }
+ }
+
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification or bad index
+ Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
+ + " size=" + mPRStack.size()
+ + " accessing media button stack", e);
+ }
+
+ return (topChanged);
+ }
+
+ /**
+ * Helper function:
+ * Remove the remote control receiver from the RC focus stack.
+ * Called synchronized on mPRStack
+ * precondition: pi != null
+ */
+ private void removeMediaButtonReceiver_syncPrs(PendingIntent pi) {
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if (prse.hasMatchingMediaButtonIntent(pi)) {
+ prse.destroy();
+ // ok to remove element while traversing the stack since we're leaving the loop
+ mPRStack.removeElementAt(index);
+ break;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
+ }
+ }
+
+ /**
+ * Helper function:
+ * Called synchronized on mPRStack
+ */
+ private boolean isCurrentRcController(PendingIntent pi) {
+ if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) {
+ return true;
+ }
+ return false;
+ }
+
+ //==========================================================================================
+ // Remote control display / client
+ //==========================================================================================
+ /**
+ * Update the remote control displays with the new "focused" client generation
+ */
+ private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
+ PendingIntent newMediaIntent, boolean clearing) {
+ synchronized(mPRStack) {
+ if (mRcDisplays.size() > 0) {
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = displayIterator.next();
+ try {
+ di.mRcDisplay.setCurrentClientId(
+ newClientGeneration, newMediaIntent, clearing);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e);
+ di.release();
+ displayIterator.remove();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the remote control clients with the new "focused" client generation
+ */
+ private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) {
+ // (using an iterator on the stack so we can safely remove an entry if needed,
+ // traversal order doesn't matter here as we update all entries)
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
+ while(stackIterator.hasNext()) {
+ PlayerRecord se = stackIterator.next();
+ if ((se != null) && (se.getRcc() != null)) {
+ try {
+ se.getRcc().setCurrentClientGenerationId(newClientGeneration);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
+ stackIterator.remove();
+ se.unlinkToRcClientDeath();
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the displays and clients with the new "focused" client generation and name
+ * @param newClientGeneration the new generation value matching a client update
+ * @param newMediaIntent the media button event receiver associated with the client.
+ * May be null, which implies there is no registered media button event receiver.
+ * @param clearing true if the new client generation value maps to a remote control update
+ * where the display should be cleared.
+ */
+ private void setNewRcClient_syncRcsCurrc(int newClientGeneration,
+ PendingIntent newMediaIntent, boolean clearing) {
+ // send the new valid client generation ID to all displays
+ setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
+ // send the new valid client generation ID to all clients
+ setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
+ }
+
+ /**
+ * Called when processing MSG_RCDISPLAY_CLEAR event
+ */
+ private void onRcDisplayClear() {
+ if (DEBUG_RC) Log.i(TAG, "Clear remote control display");
+
+ synchronized(mPRStack) {
+ synchronized(mCurrentRcLock) {
+ mCurrentRcClientGen++;
+ // synchronously update the displays and clients with the new client generation
+ setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
+ null /*newMediaIntent*/, true /*clearing*/);
+ }
+ }
+ }
+
+ /**
+ * Called when processing MSG_RCDISPLAY_UPDATE event
+ */
+ private void onRcDisplayUpdate(PlayerRecord prse, int flags /* USED ?*/) {
+ synchronized(mPRStack) {
+ synchronized(mCurrentRcLock) {
+ if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) {
+ if (DEBUG_RC) Log.i(TAG, "Display/update remote control ");
+
+ mCurrentRcClientGen++;
+ // synchronously update the displays and clients with
+ // the new client generation
+ setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
+ prse.getMediaButtonIntent() /*newMediaIntent*/,
+ false /*clearing*/);
+
+ // tell the current client that it needs to send info
+ try {
+ //TODO change name to informationRequestForAllDisplays()
+ mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Current valid remote client is dead: "+e);
+ mCurrentRcClient = null;
+ }
+ } else {
+ // the remote control display owner has changed between the
+ // the message to update the display was sent, and the time it
+ // gets to be processed (now)
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when processing MSG_RCDISPLAY_INIT_INFO event
+ * Causes the current RemoteControlClient to send its info (metadata, playstate...) to
+ * a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE.
+ */
+ private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) {
+ synchronized(mPRStack) {
+ synchronized(mCurrentRcLock) {
+ if (mCurrentRcClient != null) {
+ if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); }
+ try {
+ // synchronously update the new RCD with the current client generation
+ // and matching PendingIntent
+ newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent,
+ false);
+
+ // tell the current RCC that it needs to send info, but only to the new RCD
+ try {
+ mCurrentRcClient.informationRequestForDisplay(newRcd, w, h);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Current valid remote client is dead: ", e);
+ mCurrentRcClient = null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function:
+ * Called synchronized on mPRStack
+ */
+ private void clearRemoteControlDisplay_syncPrs() {
+ synchronized(mCurrentRcLock) {
+ mCurrentRcClient = null;
+ }
+ // will cause onRcDisplayClear() to be called in AudioService's handler thread
+ mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) );
+ }
+
+ /**
+ * Helper function for code readability: only to be called from
+ * checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for
+ * this method.
+ * Preconditions:
+ * - called synchronized on mPRStack
+ * - mPRStack.isEmpty() is false
+ */
+ private void updateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
+ PlayerRecord prse = mPRStack.peek();
+ int infoFlagsAboutToBeUsed = infoChangedFlags;
+ // this is where we enforce opt-in for information display on the remote controls
+ // with the new AudioManager.registerRemoteControlClient() API
+ if (prse.getRcc() == null) {
+ //Log.w(TAG, "Can't update remote control display with null remote control client");
+ clearRemoteControlDisplay_syncPrs();
+ return;
+ }
+ synchronized(mCurrentRcLock) {
+ if (!prse.getRcc().equals(mCurrentRcClient)) {
+ // new RC client, assume every type of information shall be queried
+ infoFlagsAboutToBeUsed = RC_INFO_ALL;
+ }
+ mCurrentRcClient = prse.getRcc();
+ mCurrentRcClientIntent = prse.getMediaButtonIntent();
+ }
+ // will cause onRcDisplayUpdate() to be called in AudioService's handler thread
+ mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
+ infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) );
+ }
+
+ /**
+ * Helper function:
+ * Called synchronized on mPRStack
+ * Check whether the remote control display should be updated, triggers the update if required
+ * @param infoChangedFlags the flags corresponding to the remote control client information
+ * that has changed, if applicable (checking for the update conditions might trigger a
+ * clear, rather than an update event).
+ */
+ private void checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
+ // determine whether the remote control display should be refreshed
+ // if the player record stack is empty, there is nothing to display, so clear the RC display
+ if (mPRStack.isEmpty()) {
+ clearRemoteControlDisplay_syncPrs();
+ return;
+ }
+
+ // this is where more rules for refresh go
+
+ // refresh conditions were verified: update the remote controls
+ // ok to call: synchronized on mPRStack, mPRStack is not empty
+ updateRemoteControlDisplay_syncPrs(infoChangedFlags);
+ }
+
+ /**
+ * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
+ * precondition: mediaIntent != null
+ */
+ protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver,
+ IBinder token) {
+ Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent);
+
+ synchronized(mPRStack) {
+ if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) {
+ // new RC client, assume every type of information shall be queried
+ checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
+ }
+ }
+ }
+
+ /**
+ * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
+ * precondition: mediaIntent != null, eventReceiver != null
+ */
+ protected void unregisterMediaButtonIntent(PendingIntent mediaIntent)
+ {
+ Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent);
+
+ synchronized(mPRStack) {
+ boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
+ removeMediaButtonReceiver_syncPrs(mediaIntent);
+ if (topOfStackWillChange) {
+ // current RC client will change, assume every type of info needs to be queried
+ checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
+ }
+ }
+ }
+
+ protected void unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent) {
+ mEventHandler.sendMessage(
+ mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0,
+ mediaIntent));
+ }
+
+ /**
+ * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c)
+ * precondition: c != null
+ */
+ protected void registerMediaButtonEventReceiverForCalls(ComponentName c) {
+ if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.e(TAG, "Invalid permissions to register media button receiver for calls");
+ return;
+ }
+ synchronized(mPRStack) {
+ mMediaReceiverForCalls = c;
+ }
+ }
+
+ /**
+ * see AudioManager.unregisterMediaButtonEventReceiverForCalls()
+ */
+ protected void unregisterMediaButtonEventReceiverForCalls() {
+ if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.e(TAG, "Invalid permissions to unregister media button receiver for calls");
+ return;
+ }
+ synchronized(mPRStack) {
+ mMediaReceiverForCalls = null;
+ }
+ }
+
+ /**
+ * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
+ * @return the unique ID of the PlayerRecord associated with the RemoteControlClient
+ * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
+ * without modifying the RC stack, but while still causing the display to refresh (will
+ * become blank as a result of this)
+ */
+ protected int registerRemoteControlClient(PendingIntent mediaIntent,
+ IRemoteControlClient rcClient, String callingPackageName) {
+ if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
+ int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
+ synchronized(mPRStack) {
+ // store the new display information
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if(prse.hasMatchingMediaButtonIntent(mediaIntent)) {
+ prse.resetControllerInfoForRcc(rcClient, callingPackageName,
+ Binder.getCallingUid());
+
+ if (rcClient == null) {
+ break;
+ }
+
+ rccId = prse.getRccId();
+
+ // there is a new (non-null) client:
+ // give the new client the displays (if any)
+ if (mRcDisplays.size() > 0) {
+ plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc());
+ }
+ break;
+ }
+ }//for
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
+ }
+
+ // if the eventReceiver is at the top of the stack
+ // then check for potential refresh of the remote controls
+ if (isCurrentRcController(mediaIntent)) {
+ checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
+ }
+ }//synchronized(mPRStack)
+ return rccId;
+ }
+
+ /**
+ * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...)
+ * rcClient is guaranteed non-null
+ */
+ protected void unregisterRemoteControlClient(PendingIntent mediaIntent,
+ IRemoteControlClient rcClient) {
+ if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient);
+ synchronized(mPRStack) {
+ boolean topRccChange = false;
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if ((prse.hasMatchingMediaButtonIntent(mediaIntent))
+ && rcClient.equals(prse.getRcc())) {
+ // we found the IRemoteControlClient to unregister
+ prse.resetControllerInfoForNoRcc();
+ topRccChange = (index == mPRStack.size()-1);
+ // there can only be one matching RCC in the RC stack, we're done
+ break;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
+ }
+ if (topRccChange) {
+ // no more RCC for the RCD, check for potential refresh of the remote controls
+ checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
+ }
+ }
+ }
+
+
+ /**
+ * A class to encapsulate all the information about a remote control display.
+ * After instanciation, init() must always be called before the object is added in the list
+ * of displays.
+ * Before being removed from the list of displays, release() must always be called (otherwise
+ * it will leak death handlers).
+ */
+ private class DisplayInfoForServer implements IBinder.DeathRecipient {
+ /** may never be null */
+ private final IRemoteControlDisplay mRcDisplay;
+ private final IBinder mRcDisplayBinder;
+ private int mArtworkExpectedWidth = -1;
+ private int mArtworkExpectedHeight = -1;
+ private boolean mWantsPositionSync = false;
+ private ComponentName mClientNotifListComp;
+ private boolean mEnabled = true;
+
+ public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
+ if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h);
+ mRcDisplay = rcd;
+ mRcDisplayBinder = rcd.asBinder();
+ mArtworkExpectedWidth = w;
+ mArtworkExpectedHeight = h;
+ }
+
+ public boolean init() {
+ try {
+ mRcDisplayBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ // remote control display is DOA, disqualify it
+ Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder);
+ return false;
+ }
+ return true;
+ }
+
+ public void release() {
+ try {
+ mRcDisplayBinder.unlinkToDeath(this, 0);
+ } catch (java.util.NoSuchElementException e) {
+ // not much we can do here, the display should have been unregistered anyway
+ Log.e(TAG, "Error in DisplaInfoForServer.relase()", e);
+ }
+ }
+
+ public void binderDied() {
+ synchronized(mPRStack) {
+ Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died");
+ // remove the display from the list
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = displayIterator.next();
+ if (di.mRcDisplay == mRcDisplay) {
+ if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
+ displayIterator.remove();
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The remote control displays.
+ * Access synchronized on mPRStack
+ */
+ private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
+
+ /**
+ * Plug each registered display into the specified client
+ * @param rcc, guaranteed non null
+ */
+ private void plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc) {
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = displayIterator.next();
+ try {
+ rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
+ di.mArtworkExpectedHeight);
+ if (di.mWantsPositionSync) {
+ rcc.setWantsSyncForDisplay(di.mRcDisplay, true);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
+ }
+ }
+ }
+
+ private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd,
+ boolean enabled) {
+ // let all the remote control clients know whether the given display is enabled
+ // (so the remote control stack traversal order doesn't matter).
+ final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
+ while(stackIterator.hasNext()) {
+ PlayerRecord prse = stackIterator.next();
+ if(prse.getRcc() != null) {
+ try {
+ prse.getRcc().enableRemoteControlDisplay(rcd, enabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error connecting RCD to client: ", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Is the remote control display interface already registered
+ * @param rcd
+ * @return true if the IRemoteControlDisplay is already in the list of displays
+ */
+ private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = displayIterator.next();
+ if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Register an IRemoteControlDisplay.
+ * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
+ * at the top of the stack to update the new display with its information.
+ * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int)
+ * @param rcd the IRemoteControlDisplay to register. No effect if null.
+ * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ * @param listenerComp the component for the listener interface, may be null if it's not needed
+ * to verify it belongs to one of the enabled notification listeners
+ */
+ private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h,
+ ComponentName listenerComp) {
+ if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
+ synchronized(mAudioFocusLock) {
+ synchronized(mPRStack) {
+ if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
+ return;
+ }
+ DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
+ di.mEnabled = true;
+ di.mClientNotifListComp = listenerComp;
+ if (!di.init()) {
+ if (DEBUG_RC) Log.e(TAG, " error registering RCD");
+ return;
+ }
+ // add RCD to list of displays
+ mRcDisplays.add(di);
+
+ // let all the remote control clients know there is a new display (so the remote
+ // control stack traversal order doesn't matter).
+ Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
+ while(stackIterator.hasNext()) {
+ PlayerRecord prse = stackIterator.next();
+ if(prse.getRcc() != null) {
+ try {
+ prse.getRcc().plugRemoteControlDisplay(rcd, w, h);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error connecting RCD to client: ", e);
+ }
+ }
+ }
+
+ // we have a new display, of which all the clients are now aware: have it be
+ // initialized wih the current gen ID and the current client info, do not
+ // reset the information for the other (existing) displays
+ sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
+ w /*arg1*/, h /*arg2*/,
+ rcd /*obj*/, 0/*delay*/);
+ }
+ }
+ }
+
+ /**
+ * Unregister an IRemoteControlDisplay.
+ * No effect if the IRemoteControlDisplay hasn't been successfully registered.
+ * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay)
+ * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
+ */
+ protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
+ if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
+ synchronized(mPRStack) {
+ if (rcd == null) {
+ return;
+ }
+
+ boolean displayWasPluggedIn = false;
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext() && !displayWasPluggedIn) {
+ final DisplayInfoForServer di = displayIterator.next();
+ if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+ displayWasPluggedIn = true;
+ di.release();
+ displayIterator.remove();
+ }
+ }
+
+ if (displayWasPluggedIn) {
+ // disconnect this remote control display from all the clients, so the remote
+ // control stack traversal order doesn't matter
+ final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
+ while(stackIterator.hasNext()) {
+ final PlayerRecord prse = stackIterator.next();
+ if(prse.getRcc() != null) {
+ try {
+ prse.getRcc().unplugRemoteControlDisplay(rcd);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error disconnecting remote control display to client: ", e);
+ }
+ }
+ }
+ } else {
+ if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD");
+ }
+ }
+ }
+
+ /**
+ * Update the size of the artwork used by an IRemoteControlDisplay.
+ * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int)
+ * @param rcd the IRemoteControlDisplay with the new artwork size requirement
+ * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
+ * display doesn't need to receive artwork.
+ */
+ protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
+ synchronized(mPRStack) {
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ boolean artworkSizeUpdate = false;
+ while (displayIterator.hasNext() && !artworkSizeUpdate) {
+ final DisplayInfoForServer di = displayIterator.next();
+ if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+ if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
+ di.mArtworkExpectedWidth = w;
+ di.mArtworkExpectedHeight = h;
+ artworkSizeUpdate = true;
+ }
+ }
+ }
+ if (artworkSizeUpdate) {
+ // RCD is currently plugged in and its artwork size has changed, notify all RCCs,
+ // stack traversal order doesn't matter
+ final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
+ while(stackIterator.hasNext()) {
+ final PlayerRecord prse = stackIterator.next();
+ if(prse.getRcc() != null) {
+ try {
+ prse.getRcc().setBitmapSizeForDisplay(rcd, w, h);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Controls whether a remote control display needs periodic checks of the RemoteControlClient
+ * playback position to verify that the estimated position has not drifted from the actual
+ * position. By default the check is not performed.
+ * The IRemoteControlDisplay must have been previously registered for this to have any effect.
+ * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
+ * or disabled. Not null.
+ * @param wantsSync if true, RemoteControlClient instances which expose their playback position
+ * to the framework will regularly compare the estimated playback position with the actual
+ * position, and will update the IRemoteControlDisplay implementation whenever a drift is
+ * detected.
+ */
+ protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
+ boolean wantsSync) {
+ synchronized(mPRStack) {
+ boolean rcdRegistered = false;
+ // store the information about this display
+ // (display stack traversal order doesn't matter).
+ final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
+ while (displayIterator.hasNext()) {
+ final DisplayInfoForServer di = displayIterator.next();
+ if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
+ di.mWantsPositionSync = wantsSync;
+ rcdRegistered = true;
+ break;
+ }
+ }
+ if (!rcdRegistered) {
+ return;
+ }
+ // notify all current RemoteControlClients
+ // (stack traversal order doesn't matter as we notify all RCCs)
+ final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
+ while (stackIterator.hasNext()) {
+ final PlayerRecord prse = stackIterator.next();
+ if (prse.getRcc() != null) {
+ try {
+ prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e);
+ }
+ }
+ }
+ }
+ }
+
+ // handler for MSG_RCC_NEW_VOLUME_OBS
+ private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
+ synchronized(mPRStack) {
+ // The stack traversal order doesn't matter because there is only one stack entry
+ // with this RCC ID, but the matching ID is more likely at the top of the stack, so
+ // start iterating from the top.
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if (prse.getRccId() == rccId) {
+ prse.mRemoteVolumeObs = rvo;
+ break;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
+ }
+ }
+ }
+
+ /**
+ * Checks if a remote client is active on the supplied stream type. Update the remote stream
+ * volume state if found and playing
+ * @param streamType
+ * @return false if no remote playing is currently playing
+ */
+ protected boolean checkUpdateRemoteStateIfActive(int streamType) {
+ synchronized(mPRStack) {
+ // iterating from top of stack as active playback is more likely on entries at the top
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
+ && isPlaystateActive(prse.mPlaybackState.mState)
+ && (prse.mPlaybackStream == streamType)) {
+ if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
+ + ", vol =" + prse.mPlaybackVolume);
+ synchronized (mMainRemote) {
+ mMainRemote.mRccId = prse.getRccId();
+ mMainRemote.mVolume = prse.mPlaybackVolume;
+ mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax;
+ mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling;
+ mMainRemoteIsActive = true;
+ }
+ return true;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
+ }
+ }
+ synchronized (mMainRemote) {
+ mMainRemoteIsActive = false;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the given playback state is considered "active", i.e. it describes a state
+ * where playback is happening, or about to
+ * @param playState the playback state to evaluate
+ * @return true if active, false otherwise (inactive or unknown)
+ */
+ protected static boolean isPlaystateActive(int playState) {
+ switch (playState) {
+ case RemoteControlClient.PLAYSTATE_PLAYING:
+ case RemoteControlClient.PLAYSTATE_BUFFERING:
+ case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
+ case RemoteControlClient.PLAYSTATE_REWINDING:
+ case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+ case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void sendVolumeUpdateToRemote(int rccId, int direction) {
+ if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
+ if (direction == 0) {
+ // only handling discrete events
+ return;
+ }
+ IRemoteVolumeObserver rvo = null;
+ synchronized (mPRStack) {
+ // The stack traversal order doesn't matter because there is only one stack entry
+ // with this RCC ID, but the matching ID is more likely at the top of the stack, so
+ // start iterating from the top.
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
+ if (prse.getRccId() == rccId) {
+ rvo = prse.mRemoteVolumeObs;
+ break;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
+ }
+ }
+ if (rvo != null) {
+ try {
+ rvo.dispatchRemoteVolumeUpdate(direction, -1);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error dispatching relative volume update", e);
+ }
+ }
+ }
+
+ protected int getRemoteStreamMaxVolume() {
+ synchronized (mMainRemote) {
+ if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
+ return 0;
+ }
+ return mMainRemote.mVolumeMax;
+ }
+ }
+
+ protected int getRemoteStreamVolume() {
+ synchronized (mMainRemote) {
+ if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
+ return 0;
+ }
+ return mMainRemote.mVolume;
+ }
+ }
+
+ protected void setRemoteStreamVolume(int vol) {
+ if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); }
+ int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
+ synchronized (mMainRemote) {
+ if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
+ return;
+ }
+ rccId = mMainRemote.mRccId;
+ }
+ IRemoteVolumeObserver rvo = null;
+ synchronized (mPRStack) {
+ // The stack traversal order doesn't matter because there is only one stack entry
+ // with this RCC ID, but the matching ID is more likely at the top of the stack, so
+ // start iterating from the top.
+ try {
+ for (int index = mPRStack.size()-1; index >= 0; index--) {
+ final PlayerRecord prse = mPRStack.elementAt(index);
+ //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
+ if (prse.getRccId() == rccId) {
+ rvo = prse.mRemoteVolumeObs;
+ break;
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // not expected to happen, indicates improper concurrent modification
+ Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
+ }
+ }
+ if (rvo != null) {
+ try {
+ rvo.dispatchRemoteVolumeUpdate(0, vol);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error dispatching absolute volume update", e);
+ }
+ }
+ }
+
+ /**
+ * Call to make AudioService reevaluate whether it's in a mode where remote players should
+ * have their volume controlled. In this implementation this is only to reset whether
+ * VolumePanel should display remote volumes
+ */
+ protected void postReevaluateRemote() {
+ sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
+ }
+
+ private void onReevaluateRemote() {
+ // TODO This was used to notify VolumePanel if there was remote playback
+ // in the stack. This is now in MediaSessionService. More code should be
+ // removed.
+ }
+
+}
diff --git a/services/core/java/com/android/server/audio/PlayerRecord.java b/services/core/java/com/android/server/audio/PlayerRecord.java
new file mode 100644
index 0000000..e98f12e
--- /dev/null
+++ b/services/core/java/com/android/server/audio/PlayerRecord.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2014 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.audio;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.media.AudioManager;
+import android.media.IRemoteControlClient;
+import android.media.IRemoteVolumeObserver;
+import android.media.RemoteControlClient;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.PrintWriter;
+
+/**
+ * @hide
+ * Class to handle all the information about a media player, encapsulating information
+ * about its use RemoteControlClient, playback type and volume... The lifecycle of each
+ * instance is managed by android.media.MediaFocusControl, from its addition to the player stack
+ * stack to its release.
+ */
+class PlayerRecord implements DeathRecipient {
+
+ // on purpose not using this classe's name, as it will only be used from MediaFocusControl
+ private static final String TAG = "MediaFocusControl";
+ private static final boolean DEBUG = false;
+
+ /**
+ * A global counter for RemoteControlClient identifiers
+ */
+ private static int sLastRccId = 0;
+
+ public static MediaFocusControl sController;
+
+ /**
+ * The target for the ACTION_MEDIA_BUTTON events.
+ * Always non null. //FIXME verify
+ */
+ final private PendingIntent mMediaIntent;
+ /**
+ * The registered media button event receiver.
+ */
+ final private ComponentName mReceiverComponent;
+
+ private int mRccId = -1;
+
+ /**
+ * A non-null token implies this record tracks a "live" player whose death is being monitored.
+ */
+ private IBinder mToken;
+ private String mCallingPackageName;
+ private int mCallingUid;
+ /**
+ * Provides access to the information to display on the remote control.
+ * May be null (when a media button event receiver is registered,
+ * but no remote control client has been registered) */
+ private IRemoteControlClient mRcClient;
+ private RcClientDeathHandler mRcClientDeathHandler;
+ /**
+ * Information only used for non-local playback
+ */
+ //FIXME private?
+ public int mPlaybackType;
+ public int mPlaybackVolume;
+ public int mPlaybackVolumeMax;
+ public int mPlaybackVolumeHandling;
+ public int mPlaybackStream;
+ public RccPlaybackState mPlaybackState;
+ public IRemoteVolumeObserver mRemoteVolumeObs;
+
+
+ protected static class RccPlaybackState {
+ public int mState;
+ public long mPositionMs;
+ public float mSpeed;
+
+ public RccPlaybackState(int state, long positionMs, float speed) {
+ mState = state;
+ mPositionMs = positionMs;
+ mSpeed = speed;
+ }
+
+ public void reset() {
+ mState = RemoteControlClient.PLAYSTATE_STOPPED;
+ mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
+ mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
+ }
+
+ @Override
+ public String toString() {
+ return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
+ }
+
+ private String posToString() {
+ if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) {
+ return "PLAYBACK_POSITION_INVALID";
+ } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
+ return "PLAYBACK_POSITION_ALWAYS_UNKNOWN";
+ } else {
+ return (String.valueOf(mPositionMs) + "ms");
+ }
+ }
+
+ private String stateToString() {
+ switch (mState) {
+ case RemoteControlClient.PLAYSTATE_NONE:
+ return "PLAYSTATE_NONE";
+ case RemoteControlClient.PLAYSTATE_STOPPED:
+ return "PLAYSTATE_STOPPED";
+ case RemoteControlClient.PLAYSTATE_PAUSED:
+ return "PLAYSTATE_PAUSED";
+ case RemoteControlClient.PLAYSTATE_PLAYING:
+ return "PLAYSTATE_PLAYING";
+ case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
+ return "PLAYSTATE_FAST_FORWARDING";
+ case RemoteControlClient.PLAYSTATE_REWINDING:
+ return "PLAYSTATE_REWINDING";
+ case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+ return "PLAYSTATE_SKIPPING_FORWARDS";
+ case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+ return "PLAYSTATE_SKIPPING_BACKWARDS";
+ case RemoteControlClient.PLAYSTATE_BUFFERING:
+ return "PLAYSTATE_BUFFERING";
+ case RemoteControlClient.PLAYSTATE_ERROR:
+ return "PLAYSTATE_ERROR";
+ default:
+ return "[invalid playstate]";
+ }
+ }
+ }
+
+
+ /**
+ * Inner class to monitor remote control client deaths, and remove the client for the
+ * remote control stack if necessary.
+ */
+ private class RcClientDeathHandler implements IBinder.DeathRecipient {
+ final private IBinder mCb; // To be notified of client's death
+ //FIXME needed?
+ final private PendingIntent mMediaIntent;
+
+ RcClientDeathHandler(IBinder cb, PendingIntent pi) {
+ mCb = cb;
+ mMediaIntent = pi;
+ }
+
+ public void binderDied() {
+ Log.w(TAG, " RemoteControlClient died");
+ // remote control client died, make sure the displays don't use it anymore
+ // by setting its remote control client to null
+ sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
+ // the dead client was maybe handling remote playback, the controller should reevaluate
+ sController.postReevaluateRemote();
+ }
+
+ public IBinder getBinder() {
+ return mCb;
+ }
+ }
+
+
+ protected static class RemotePlaybackState {
+ int mRccId;
+ int mVolume;
+ int mVolumeMax;
+ int mVolumeHandling;
+
+ protected RemotePlaybackState(int id, int vol, int volMax) {
+ mRccId = id;
+ mVolume = vol;
+ mVolumeMax = volMax;
+ mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
+ }
+ }
+
+
+ void dump(PrintWriter pw, boolean registrationInfo) {
+ if (registrationInfo) {
+ pw.println(" pi: " + mMediaIntent +
+ " -- pack: " + mCallingPackageName +
+ " -- ercvr: " + mReceiverComponent +
+ " -- client: " + mRcClient +
+ " -- uid: " + mCallingUid +
+ " -- type: " + mPlaybackType +
+ " state: " + mPlaybackState);
+ } else {
+ // emphasis on state
+ pw.println(" uid: " + mCallingUid +
+ " -- id: " + mRccId +
+ " -- type: " + mPlaybackType +
+ " -- state: " + mPlaybackState +
+ " -- vol handling: " + mPlaybackVolumeHandling +
+ " -- vol: " + mPlaybackVolume +
+ " -- volMax: " + mPlaybackVolumeMax +
+ " -- volObs: " + mRemoteVolumeObs);
+ }
+ }
+
+
+ static protected void setMediaFocusControl(MediaFocusControl mfc) {
+ sController = mfc;
+ }
+
+ /** precondition: mediaIntent != null */
+ protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)
+ {
+ mMediaIntent = mediaIntent;
+ mReceiverComponent = eventReceiver;
+ mToken = token;
+ mCallingUid = -1;
+ mRcClient = null;
+ mRccId = ++sLastRccId;
+ mPlaybackState = new RccPlaybackState(
+ RemoteControlClient.PLAYSTATE_STOPPED,
+ RemoteControlClient.PLAYBACK_POSITION_INVALID,
+ RemoteControlClient.PLAYBACK_SPEED_1X);
+
+ resetPlaybackInfo();
+ if (mToken != null) {
+ try {
+ mToken.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ sController.unregisterMediaButtonIntentAsync(mMediaIntent);
+ }
+ }
+ }
+
+ //---------------------------------------------
+ // Accessors
+ protected int getRccId() {
+ return mRccId;
+ }
+
+ protected IRemoteControlClient getRcc() {
+ return mRcClient;
+ }
+
+ protected ComponentName getMediaButtonReceiver() {
+ return mReceiverComponent;
+ }
+
+ protected PendingIntent getMediaButtonIntent() {
+ return mMediaIntent;
+ }
+
+ protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) {
+ if (mToken != null) {
+ return mMediaIntent.equals(pi);
+ } else {
+ if (mReceiverComponent != null) {
+ return mReceiverComponent.equals(pi.getIntent().getComponent());
+ } else {
+ return false;
+ }
+ }
+ }
+
+ protected boolean isPlaybackActive() {
+ return MediaFocusControl.isPlaystateActive(mPlaybackState.mState);
+ }
+
+ //---------------------------------------------
+ // Modify the records stored in the instance
+ protected void resetControllerInfoForRcc(IRemoteControlClient rcClient,
+ String callingPackageName, int uid) {
+ // already had a remote control client?
+ if (mRcClientDeathHandler != null) {
+ // stop monitoring the old client's death
+ unlinkToRcClientDeath();
+ }
+ // save the new remote control client
+ mRcClient = rcClient;
+ mCallingPackageName = callingPackageName;
+ mCallingUid = uid;
+ if (rcClient == null) {
+ // here mcse.mRcClientDeathHandler is null;
+ resetPlaybackInfo();
+ } else {
+ IBinder b = mRcClient.asBinder();
+ RcClientDeathHandler rcdh =
+ new RcClientDeathHandler(b, mMediaIntent);
+ try {
+ b.linkToDeath(rcdh, 0);
+ } catch (RemoteException e) {
+ // remote control client is DOA, disqualify it
+ Log.w(TAG, "registerRemoteControlClient() has a dead client " + b);
+ mRcClient = null;
+ }
+ mRcClientDeathHandler = rcdh;
+ }
+ }
+
+ protected void resetControllerInfoForNoRcc() {
+ // stop monitoring the RCC death
+ unlinkToRcClientDeath();
+ // reset the RCC-related fields
+ mRcClient = null;
+ mCallingPackageName = null;
+ }
+
+ public void resetPlaybackInfo() {
+ mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
+ mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
+ mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
+ mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
+ mPlaybackStream = AudioManager.STREAM_MUSIC;
+ mPlaybackState.reset();
+ mRemoteVolumeObs = null;
+ }
+
+ //---------------------------------------------
+ public void unlinkToRcClientDeath() {
+ if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) {
+ try {
+ mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0);
+ mRcClientDeathHandler = null;
+ } catch (java.util.NoSuchElementException e) {
+ // not much we can do here
+ Log.e(TAG, "Error in unlinkToRcClientDeath()", e);
+ }
+ }
+ }
+
+ // FIXME rename to "release"? (as in FocusRequester class)
+ public void destroy() {
+ unlinkToRcClientDeath();
+ if (mToken != null) {
+ mToken.unlinkToDeath(this, 0);
+ mToken = null;
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ sController.unregisterMediaButtonIntentAsync(mMediaIntent);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ destroy(); // unlink exception handled inside method
+ super.finalize();
+ }
+}
diff --git a/services/core/java/com/android/server/camera/CameraService.java b/services/core/java/com/android/server/camera/CameraService.java
new file mode 100644
index 0000000..f9b17ed
--- /dev/null
+++ b/services/core/java/com/android/server/camera/CameraService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 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.camera;
+
+import android.content.Context;
+import android.hardware.ICameraService;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.server.SystemService;
+
+/**
+ * CameraService is the system_server analog to the camera service running in mediaserver.
+ *
+ * @hide
+ */
+public class CameraService extends SystemService {
+
+ /**
+ * This must match the ICameraService.aidl definition
+ */
+ private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
+
+ // Event arguments to use with the camera service notifySystemEvent call:
+ public static final int NO_EVENT = 0; // NOOP
+ public static final int USER_SWITCHED = 1; // User changed, argument is the new user handle
+
+ public CameraService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {}
+
+ @Override
+ public void onSwitchUser(int userHandle) {
+ super.onSwitchUser(userHandle);
+
+ /**
+ * Forward the user switch event to the native camera service running in mediaserver.
+ */
+ IBinder cameraServiceBinder = getBinderService(CAMERA_SERVICE_BINDER_NAME);
+ if (cameraServiceBinder == null) {
+ return; // Camera service not active, there is no need to evict user clients.
+ }
+ ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);
+ try {
+ cameraServiceRaw.notifySystemEvent(USER_SWITCHED, userHandle);
+ } catch (RemoteException e) {
+ // Do nothing, if camera service is dead, there is no need to evict user clients.
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 3fa21d0..a9eaeee 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -22,16 +22,13 @@ import static android.net.ConnectivityManager.TYPE_WIFI;
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.NetworkAgent;
-import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.os.Handler;
import android.os.Message;
-import android.os.Messenger;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.util.Slog;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 2d1f939..8a7c902 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -71,7 +71,10 @@ public class NetworkAgentInfo {
private static final int UNVALIDATED_SCORE_PENALTY = 40;
// Score for explicitly connected network.
- private static final int EXPLICITLY_SELECTED_NETWORK_SCORE = 100;
+ //
+ // This ensures that a) the explicitly selected network is never trumped by anything else, and
+ // b) the explicitly selected network is never torn down.
+ private static final int MAXIMUM_NETWORK_SCORE = 100;
// The list of NetworkRequests being satisfied by this Network.
public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
@@ -123,13 +126,18 @@ public class NetworkAgentInfo {
// score. The NetworkScore class would provide a nice place to centralize score constants
// so they are not scattered about the transports.
- int score = currentScore;
+ // If this network is explicitly selected and the user has decided to use it even if it's
+ // unvalidated, give it the maximum score. Also give it the maximum score if it's explicitly
+ // selected and we're trying to see what its score could be. This ensures that we don't tear
+ // down an explicitly selected network before the user gets a chance to prefer it when
+ // a higher-scoring network (e.g., Ethernet) is available.
+ if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
+ return MAXIMUM_NETWORK_SCORE;
+ }
+ int score = currentScore;
if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY;
if (score < 0) score = 0;
-
- if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE;
-
return score;
}
@@ -156,7 +164,9 @@ public class NetworkAgentInfo {
networkCapabilities + "} Score{" + getCurrentScore() + "} " +
"everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " +
"created{" + created + "} " +
- "explicitlySelected{" + networkMisc.explicitlySelected + "} }";
+ "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
+ "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
+ "}";
}
public String name() {
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 76220db..7e20276 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -24,9 +24,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.ProxyInfo;
import android.net.TrafficStats;
@@ -54,7 +51,6 @@ import android.util.Log;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import com.android.server.ConnectivityService;
import com.android.server.connectivity.NetworkAgentInfo;
import java.io.IOException;
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 5ff7022..4c08960 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -40,9 +40,7 @@ import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -480,7 +478,7 @@ public class Tethering extends BaseNetworkObserver {
mTetheredNotification.flags = Notification.FLAG_ONGOING_EVENT;
mTetheredNotification.tickerText = title;
mTetheredNotification.visibility = Notification.VISIBILITY_PUBLIC;
- mTetheredNotification.color = mContext.getResources().getColor(
+ mTetheredNotification.color = mContext.getColor(
com.android.internal.R.color.system_notification_accent_color);
mTetheredNotification.setLatestEventInfo(mContext, title, message, pi);
mTetheredNotification.category = Notification.CATEGORY_STATUS;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index d9c96f2..3d478f9 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -76,6 +76,7 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnInfo;
import com.android.internal.net.VpnProfile;
import com.android.server.net.BaseNetworkObserver;
@@ -731,6 +732,7 @@ public class Vpn {
if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
mStatusIntent = null;
mVpnUsers = null;
+ mConfig = null;
mInterface = null;
if (mConnection != null) {
mContext.unbindService(mConnection);
@@ -816,6 +818,21 @@ public class Vpn {
return mConfig.underlyingNetworks;
}
+ /**
+ * This method should only be called by ConnectivityService. Because it doesn't
+ * have enough data to fill VpnInfo.primaryUnderlyingIface field.
+ */
+ public synchronized VpnInfo getVpnInfo() {
+ if (!isRunningLocked()) {
+ return null;
+ }
+
+ VpnInfo info = new VpnInfo();
+ info.ownerUid = mOwnerUID;
+ info.vpnIface = mInterface;
+ return info;
+ }
+
public synchronized boolean appliesToUid(int uid) {
if (!isRunningLocked()) {
return false;
@@ -1013,6 +1030,14 @@ public class Vpn {
public synchronized LegacyVpnInfo getLegacyVpnInfo() {
// Check if the caller is authorized.
enforceControlPermission();
+ return getLegacyVpnInfoPrivileged();
+ }
+
+ /**
+ * Return the information of the current ongoing legacy VPN.
+ * Callers are responsible for checking permissions if needed.
+ */
+ public synchronized LegacyVpnInfo getLegacyVpnInfoPrivileged() {
if (mLegacyVpnRunner == null) return null;
final LegacyVpnInfo info = new LegacyVpnInfo();
diff --git a/services/core/java/com/android/server/content/AppIdleMonitor.java b/services/core/java/com/android/server/content/AppIdleMonitor.java
new file mode 100644
index 0000000..9598de8
--- /dev/null
+++ b/services/core/java/com/android/server/content/AppIdleMonitor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 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.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.UserHandle;
+
+import com.android.server.LocalServices;
+
+/**
+ * Helper to listen for app idle and charging status changes and restart backed off
+ * sync operations.
+ */
+class AppIdleMonitor implements AppIdleStateChangeListener {
+
+ private final SyncManager mSyncManager;
+ private final UsageStatsManagerInternal mUsageStats;
+ final BatteryManager mBatteryManager;
+ /** Is the device currently plugged into power. */
+ private boolean mPluggedIn;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onPluggedIn(mBatteryManager.isCharging());
+ }
+ };
+
+ AppIdleMonitor(SyncManager syncManager, Context context) {
+ mSyncManager = syncManager;
+ mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+ mUsageStats.addAppIdleStateChangeListener(this);
+ mBatteryManager = context.getSystemService(BatteryManager.class);
+ mPluggedIn = isPowered();
+ registerReceivers(context);
+ }
+
+ private void registerReceivers(Context context) {
+ // Monitor battery charging state
+ IntentFilter filter = new IntentFilter(BatteryManager.ACTION_CHARGING);
+ filter.addAction(BatteryManager.ACTION_DISCHARGING);
+ context.registerReceiver(mReceiver, filter);
+ }
+
+ private boolean isPowered() {
+ return mBatteryManager.isCharging();
+ }
+
+ void onPluggedIn(boolean pluggedIn) {
+ if (mPluggedIn == pluggedIn) {
+ return;
+ }
+ mPluggedIn = pluggedIn;
+ if (mPluggedIn) {
+ mSyncManager.onAppNotIdle(null, UserHandle.USER_ALL);
+ }
+ }
+
+ boolean isAppIdle(String packageName, int userId) {
+ return !mPluggedIn && mUsageStats.isAppIdle(packageName, userId);
+ }
+
+ @Override
+ public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+ // Don't care if the app is becoming idle
+ if (idle) return;
+ mSyncManager.onAppNotIdle(packageName, userId);
+ }
+}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 165148b..ea461e5 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -25,7 +25,6 @@ import android.content.Context;
import android.content.IContentService;
import android.content.ISyncStatusObserver;
import android.content.PeriodicSync;
-import android.content.pm.PackageManager;
import android.content.SyncAdapterType;
import android.content.SyncInfo;
import android.content.SyncRequest;
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 6dcbc42..7cccef2 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -83,6 +83,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.accounts.AccountManagerService;
import com.android.server.content.SyncStorageEngine.AuthorityInfo;
+import com.android.server.content.SyncStorageEngine.EndPoint;
import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -107,7 +108,7 @@ import java.util.Set;
* @hide
*/
public class SyncManager {
- private static final String TAG = "SyncManager";
+ static final String TAG = "SyncManager";
/** Delay a sync due to local changes this long. In milliseconds */
private static final long LOCAL_SYNC_DELAY;
@@ -176,6 +177,7 @@ public class SyncManager {
volatile private PowerManager.WakeLock mSyncManagerWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
+ volatile private boolean mDeviceIsIdle = false;
private final NotificationManager mNotificationMgr;
private AlarmManager mAlarmService = null;
@@ -198,6 +200,8 @@ public class SyncManager {
protected SyncAdaptersCache mSyncAdapters;
+ private final AppIdleMonitor mAppIdleMonitor;
+
private BroadcastReceiver mStorageIntentReceiver =
new BroadcastReceiver() {
@Override
@@ -221,6 +225,20 @@ public class SyncManager {
}
};
+ private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ boolean idle = mPowerManager.isDeviceIdleMode();
+ mDeviceIsIdle = idle;
+ if (idle) {
+ cancelActiveSync(
+ SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
+ null /* any sync */);
+ } else {
+ sendCheckAlarmsMessage();
+ }
+ }
+ };
+
private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -412,6 +430,8 @@ public class SyncManager {
mSyncAlarmIntent = PendingIntent.getBroadcast(
mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
+ mAppIdleMonitor = new AppIdleMonitor(this, mContext);
+
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
@@ -425,6 +445,9 @@ public class SyncManager {
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
context.registerReceiver(mStorageIntentReceiver, intentFilter);
+ intentFilter = new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ context.registerReceiver(mDeviceIdleReceiver, intentFilter);
+
intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
intentFilter.setPriority(100);
context.registerReceiver(mShutdownIntentReceiver, intentFilter);
@@ -702,6 +725,11 @@ public class SyncManager {
}
for (AccountAndUser account : accounts) {
+ // If userId is specified, do not sync accounts of other users
+ if (userId >= UserHandle.USER_OWNER && account.userId >= UserHandle.USER_OWNER
+ && userId != account.userId) {
+ continue;
+ }
// 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>();
@@ -1146,6 +1174,36 @@ public class SyncManager {
}
/**
+ * Clear backoff on operations in the sync queue that match the packageName and userId.
+ * @param packageName The package that just became active. Can be null to indicate that all
+ * packages are now considered active due to being plugged in.
+ * @param userId The user for which the package has become active. Can be USER_ALL if
+ * the device just plugged in.
+ */
+ void onAppNotIdle(String packageName, int userId) {
+ synchronized (mSyncQueue) {
+ // For all sync operations in sync queue, if marked as idle, compare with package name
+ // and unmark. And clear backoff for the operation.
+ final Iterator<SyncOperation> operationIterator =
+ mSyncQueue.getOperations().iterator();
+ boolean changed = false;
+ while (operationIterator.hasNext()) {
+ final SyncOperation op = operationIterator.next();
+ if (op.appIdle
+ && (packageName == null || getPackageName(op.target).equals(packageName))
+ && (userId == UserHandle.USER_ALL || op.target.userId == userId)) {
+ op.appIdle = false;
+ clearBackoffSetting(op);
+ changed = true;
+ }
+ }
+ if (changed) {
+ sendCheckAlarmsMessage();
+ }
+ }
+ }
+
+ /**
* @hide
*/
class ActiveSyncContext extends ISyncContext.Stub
@@ -1307,6 +1365,7 @@ public class SyncManager {
pw.println();
}
pw.print("memory low: "); pw.println(mStorageIsLow);
+ pw.print("device idle: "); pw.println(mDeviceIsIdle);
final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
@@ -2353,6 +2412,13 @@ public class SyncManager {
return Long.MAX_VALUE;
}
+ if (mDeviceIsIdle) {
+ if (isLoggable) {
+ Log.v(TAG, "maybeStartNextSync: device idle, 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.
if (mRunningAccounts == INITIAL_ACCOUNTS_ARRAY) {
@@ -2416,6 +2482,19 @@ public class SyncManager {
}
continue;
}
+ String packageName = getPackageName(op.target);
+ // If app is considered idle, then skip for now and backoff
+ if (packageName != null
+ && mAppIdleMonitor.isAppIdle(packageName, op.target.userId)) {
+ increaseBackoffSetting(op);
+ op.appIdle = true;
+ if (isLoggable) {
+ Log.v(TAG, "Sync backing off idle app " + packageName);
+ }
+ continue;
+ } else {
+ op.appIdle = false;
+ }
// Add this sync to be run.
operations.add(op);
}
@@ -2979,6 +3058,7 @@ public class SyncManager {
// method to be called again
if (!mDataConnectionIsConnected) return;
if (mStorageIsLow) return;
+ if (mDeviceIsIdle) return;
// When the status bar notification should be raised
final long notificationTime =
@@ -3114,7 +3194,7 @@ public class SyncManager {
new Notification(R.drawable.stat_notify_sync_error,
mContext.getString(R.string.contentServiceSync),
System.currentTimeMillis());
- notification.color = contextForUser.getResources().getColor(
+ notification.color = contextForUser.getColor(
com.android.internal.R.color.system_notification_accent_color);
notification.setLatestEventInfo(contextForUser,
contextForUser.getString(R.string.contentServiceSyncNotificationTitle),
@@ -3162,6 +3242,21 @@ public class SyncManager {
}
}
+ String getPackageName(EndPoint endpoint) {
+ if (endpoint.target_service) {
+ return endpoint.service.getPackageName();
+ } else {
+ SyncAdapterType syncAdapterType =
+ SyncAdapterType.newKey(endpoint.provider, endpoint.account.type);
+ final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+ syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, endpoint.userId);
+ if (syncAdapterInfo == null) {
+ return null;
+ }
+ return syncAdapterInfo.componentName.getPackageName();
+ }
+ }
+
private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
for (ActiveSyncContext sync : mActiveSyncContexts) {
if (sync == activeSyncContext) {
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 35827cc..10efe81 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -90,6 +90,9 @@ public class SyncOperation implements Comparable {
/** Descriptive string key for this operation */
public String wakeLockName;
+ /** Whether this sync op was recently skipped due to the app being idle */
+ public boolean appIdle;
+
public SyncOperation(Account account, int userId, int reason, int source, String provider,
Bundle extras, long runTimeFromNow, long flexTime, long backoff,
long delayUntil, boolean allowParallelSyncs) {
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 0d5f240..f154c73 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -18,6 +18,7 @@ package com.android.server.content;
import android.accounts.Account;
import android.accounts.AccountAndUser;
+import android.app.backup.BackupManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -670,6 +671,7 @@ public class SyncStorageEngine extends Handler {
new Bundle());
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ queueBackup();
}
public int getIsSyncable(Account account, int userId, String providerName) {
@@ -1035,6 +1037,7 @@ public class SyncStorageEngine extends Handler {
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED);
+ queueBackup();
}
public boolean getMasterSyncAutomatically(int userId) {
@@ -2810,4 +2813,12 @@ public class SyncStorageEngine extends Handler {
.append(")\n");
}
}
+
+ /**
+ * Let the BackupManager know that account sync settings have changed. This will trigger
+ * {@link com.android.server.backup.SystemBackupAgent} to run.
+ */
+ public void queueBackup() {
+ BackupManager.dataChanged("android");
+ }
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index d919bf6..93d37f1 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -52,20 +52,9 @@ class AutomaticBrightnessController {
// auto-brightness adjustment setting.
private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f;
- // Light sensor event rate in milliseconds.
- private static final int LIGHT_SENSOR_RATE_MILLIS = 1000;
-
// Period of time in which to consider light samples in milliseconds.
private static final int AMBIENT_LIGHT_HORIZON = 10000;
- // Stability requirements in milliseconds for accepting a new brightness level. This is used
- // for debouncing the light sensor. Different constants are used to debounce the light sensor
- // when adapting to brighter or darker environments. This parameter controls how quickly
- // brightness changes occur in response to an observed change in light level that exceeds the
- // hysteresis threshold.
- private static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000;
- private static final long DARKENING_LIGHT_DEBOUNCE = 8000;
-
// Hysteresis constraints for brightening or darkening.
// The recent lux must have changed by at least this fraction relative to the
// current ambient lux before a change will be considered.
@@ -121,6 +110,22 @@ class AutomaticBrightnessController {
private final int mScreenBrightnessRangeMaximum;
private final float mDozeScaleFactor;
+ // Light sensor event rate in milliseconds.
+ private final int mLightSensorRate;
+
+ // Stability requirements in milliseconds for accepting a new brightness level. This is used
+ // for debouncing the light sensor. Different constants are used to debounce the light sensor
+ // when adapting to brighter or darker environments. This parameter controls how quickly
+ // brightness changes occur in response to an observed change in light level that exceeds the
+ // hysteresis threshold.
+ private final long mBrighteningLightDebounceConfig;
+ private final long mDarkeningLightDebounceConfig;
+
+ // If true immediately after the screen is turned on the controller will try to adjust the
+ // brightness based on the current sensor reads. If false, the controller will collect more data
+ // and only then decide whether to change brightness.
+ private final boolean mResetAmbientLuxAfterWarmUpConfig;
+
// Amount of time to delay auto-brightness after screen on while waiting for
// the light sensor to warm-up in milliseconds.
// May be 0 if no warm-up is required.
@@ -176,7 +181,9 @@ class AutomaticBrightnessController {
public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime,
- int brightnessMin, int brightnessMax, float dozeScaleFactor) {
+ int brightnessMin, int brightnessMax, float dozeScaleFactor,
+ int lightSensorRate, long brighteningLightDebounceConfig,
+ long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig) {
mCallbacks = callbacks;
mTwilight = LocalServices.getService(TwilightManager.class);
mSensorManager = sensorManager;
@@ -185,9 +192,13 @@ class AutomaticBrightnessController {
mScreenBrightnessRangeMaximum = brightnessMax;
mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
mDozeScaleFactor = dozeScaleFactor;
+ mLightSensorRate = lightSensorRate;
+ mBrighteningLightDebounceConfig = brighteningLightDebounceConfig;
+ mDarkeningLightDebounceConfig = darkeningLightDebounceConfig;
+ mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
mHandler = new AutomaticBrightnessHandler(looper);
- mAmbientLightRingBuffer = new AmbientLightRingBuffer();
+ mAmbientLightRingBuffer = new AmbientLightRingBuffer(mLightSensorRate);
if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
@@ -226,6 +237,9 @@ class AutomaticBrightnessController {
pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
+ pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
+ pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
+ pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
pw.println();
pw.println("Automatic Brightness Controller State:");
@@ -252,13 +266,13 @@ class AutomaticBrightnessController {
mLightSensorEnabled = true;
mLightSensorEnableTime = SystemClock.uptimeMillis();
mSensorManager.registerListener(mLightSensorListener, mLightSensor,
- LIGHT_SENSOR_RATE_MILLIS * 1000, mHandler);
+ mLightSensorRate * 1000, mHandler);
return true;
}
} else {
if (mLightSensorEnabled) {
mLightSensorEnabled = false;
- mAmbientLuxValid = false;
+ mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig;
mRecentLightSamples = 0;
mAmbientLightRingBuffer.clear();
mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
@@ -347,7 +361,7 @@ class AutomaticBrightnessController {
}
earliestValidTime = mAmbientLightRingBuffer.getTime(i);
}
- return earliestValidTime + BRIGHTENING_LIGHT_DEBOUNCE;
+ return earliestValidTime + mBrighteningLightDebounceConfig;
}
private long nextAmbientLightDarkeningTransition(long time) {
@@ -359,7 +373,7 @@ class AutomaticBrightnessController {
}
earliestValidTime = mAmbientLightRingBuffer.getTime(i);
}
- return earliestValidTime + DARKENING_LIGHT_DEBOUNCE;
+ return earliestValidTime + mDarkeningLightDebounceConfig;
}
private void updateAmbientLux() {
@@ -420,7 +434,7 @@ class AutomaticBrightnessController {
// should be enough time to decide whether we should actually transition to the new
// weighted ambient lux or not.
nextTransitionTime =
- nextTransitionTime > time ? nextTransitionTime : time + LIGHT_SENSOR_RATE_MILLIS;
+ nextTransitionTime > time ? nextTransitionTime : time + mLightSensorRate;
if (DEBUG) {
Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for "
+ nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime));
@@ -559,8 +573,6 @@ class AutomaticBrightnessController {
// Proportional extra capacity of the buffer beyond the expected number of light samples
// in the horizon
private static final float BUFFER_SLACK = 1.5f;
- private static final int DEFAULT_CAPACITY =
- (int) Math.ceil(AMBIENT_LIGHT_HORIZON * BUFFER_SLACK / LIGHT_SENSOR_RATE_MILLIS);
private float[] mRingLux;
private long[] mRingTime;
private int mCapacity;
@@ -571,8 +583,8 @@ class AutomaticBrightnessController {
private int mEnd;
private int mCount;
- public AmbientLightRingBuffer() {
- this(DEFAULT_CAPACITY);
+ public AmbientLightRingBuffer(long lightSensorRate) {
+ this((int) Math.ceil(AMBIENT_LIGHT_HORIZON * BUFFER_SLACK / lightSensorRate));
}
public AmbientLightRingBuffer(int initialCapacity) {
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 6e61e41..bb4dbc3 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -25,7 +25,6 @@ import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.SurfaceTexture;
import android.hardware.display.DisplayManagerInternal;
@@ -37,7 +36,6 @@ import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.opengl.GLES11Ext;
-import android.util.FloatMath;
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.Surface.OutOfResourcesException;
@@ -48,7 +46,6 @@ import android.view.SurfaceSession;
import libcore.io.Streams;
import com.android.server.LocalServices;
-import com.android.internal.R;
/**
* <p>
@@ -372,13 +369,13 @@ final class ColorFade {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// Draw the frame.
- float one_minus_level = 1 - level;
- float cos = FloatMath.cos((float)Math.PI * one_minus_level);
- float sign = cos < 0 ? -1 : 1;
- float opacity = -FloatMath.pow(one_minus_level, 2) + 1;
- float saturation = FloatMath.pow(level, 4);
- float scale = (-FloatMath.pow(one_minus_level, 2) + 1) * 0.1f + 0.9f;
- float gamma = (0.5f * sign * FloatMath.pow(cos, 2) + 0.5f) * 0.9f + 0.1f;
+ double one_minus_level = 1 - level;
+ double cos = Math.cos(Math.PI * one_minus_level);
+ double sign = cos < 0 ? -1 : 1;
+ float opacity = (float) -Math.pow(one_minus_level, 2) + 1;
+ float saturation = (float) Math.pow(level, 4);
+ float scale = (float) ((-Math.pow(one_minus_level, 2) + 1) * 0.1d + 0.9d);
+ float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d);
drawFaded(opacity, 1.f / gamma, saturation, scale);
if (checkGlErrors("drawFrame")) {
return false;
diff --git a/services/core/java/com/android/server/display/DisplayBlanker.java b/services/core/java/com/android/server/display/DisplayBlanker.java
index eb0ae6a..816dc13 100644
--- a/services/core/java/com/android/server/display/DisplayBlanker.java
+++ b/services/core/java/com/android/server/display/DisplayBlanker.java
@@ -20,5 +20,5 @@ package com.android.server.display;
* Interface used to update the actual display state.
*/
public interface DisplayBlanker {
- void requestDisplayState(int state);
+ void requestDisplayState(int state, int brightness);
}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 61631d4..ee36972 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -47,6 +47,10 @@ abstract class DisplayDevice {
// within a transaction from performTraversalInTransactionLocked.
private Surface mCurrentSurface;
+ // DEBUG STATE: Last device info which was written to the log, or null if none.
+ // Do not use for any other purpose.
+ DisplayDeviceInfo mDebugLastLoggedDeviceInfo;
+
public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId) {
mDisplayAdapter = displayAdapter;
mDisplayToken = displayToken;
@@ -118,10 +122,12 @@ abstract class DisplayDevice {
/**
* Sets the display state, if supported.
*
+ * @param state The new display state.
+ * @param brightness The new display brightness.
* @return A runnable containing work to be deferred until after we have
* exited the critical section, or null if none.
*/
- public Runnable requestDisplayStateLocked(int state) {
+ public Runnable requestDisplayStateLocked(int state, int brightness) {
return null;
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index d1e73f0..ebf6e4e 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -104,6 +104,16 @@ final class DisplayDeviceInfo {
public static final int TOUCH_EXTERNAL = 2;
/**
+ * Diff result: The {@link #state} fields differ.
+ */
+ public static final int DIFF_STATE = 1 << 0;
+
+ /**
+ * Diff result: Other fields differ.
+ */
+ public static final int DIFF_OTHER = 1 << 1;
+
+ /**
* Gets the name of the display device, which may be derived from EDID or
* other sources. The name may be localized and displayed to the user.
*/
@@ -238,26 +248,39 @@ final class DisplayDeviceInfo {
}
public boolean equals(DisplayDeviceInfo other) {
- return other != null
- && Objects.equal(name, other.name)
- && Objects.equal(uniqueId, other.uniqueId)
- && width == other.width
- && height == other.height
- && refreshRate == other.refreshRate
- && Arrays.equals(supportedRefreshRates, other.supportedRefreshRates)
- && densityDpi == other.densityDpi
- && xDpi == other.xDpi
- && yDpi == other.yDpi
- && appVsyncOffsetNanos == other.appVsyncOffsetNanos
- && presentationDeadlineNanos == other.presentationDeadlineNanos
- && flags == other.flags
- && touch == other.touch
- && rotation == other.rotation
- && type == other.type
- && Objects.equal(address, other.address)
- && state == other.state
- && ownerUid == other.ownerUid
- && Objects.equal(ownerPackageName, other.ownerPackageName);
+ return other != null && diff(other) == 0;
+ }
+
+ /**
+ * Computes the difference between display device infos.
+ * Assumes other is not null.
+ */
+ public int diff(DisplayDeviceInfo other) {
+ int diff = 0;
+ if (state != other.state) {
+ diff |= DIFF_STATE;
+ }
+ if (!Objects.equal(name, other.name)
+ || !Objects.equal(uniqueId, other.uniqueId)
+ || width != other.width
+ || height != other.height
+ || refreshRate != other.refreshRate
+ || !Arrays.equals(supportedRefreshRates, other.supportedRefreshRates)
+ || densityDpi != other.densityDpi
+ || xDpi != other.xDpi
+ || yDpi != other.yDpi
+ || appVsyncOffsetNanos != other.appVsyncOffsetNanos
+ || presentationDeadlineNanos != other.presentationDeadlineNanos
+ || flags != other.flags
+ || touch != other.touch
+ || rotation != other.rotation
+ || type != other.type
+ || !Objects.equal(address, other.address)
+ || ownerUid != other.ownerUid
+ || !Objects.equal(ownerPackageName, other.ownerPackageName)) {
+ diff |= DIFF_OTHER;
+ }
+ return diff;
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 09dc477..1e87433 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -40,13 +40,14 @@ import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.text.TextUtils;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -180,7 +181,11 @@ public final class DisplayManagerService extends SystemService {
// The overall display state, independent of changes that might influence one
// display or another in particular.
- private int mGlobalDisplayState = Display.STATE_UNKNOWN;
+ private int mGlobalDisplayState = Display.STATE_ON;
+
+ // The overall display brightness.
+ // For now, this only applies to the built-in display but we may split it up eventually.
+ private int mGlobalDisplayBrightness = PowerManager.BRIGHTNESS_DEFAULT;
// Set to true when there are pending display changes that have yet to be applied
// to the surface flinger state.
@@ -227,6 +232,9 @@ public final class DisplayManagerService extends SystemService {
mUiHandler = UiThread.getHandler();
mDisplayAdapterListener = new DisplayAdapterListener();
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
+
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
}
@Override
@@ -323,16 +331,34 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void requestGlobalDisplayStateInternal(int state) {
+ private void requestGlobalDisplayStateInternal(int state, int brightness) {
+ if (state == Display.STATE_UNKNOWN) {
+ state = Display.STATE_ON;
+ }
+ if (state == Display.STATE_OFF) {
+ brightness = PowerManager.BRIGHTNESS_OFF;
+ } else if (brightness < 0) {
+ brightness = PowerManager.BRIGHTNESS_DEFAULT;
+ } else if (brightness > PowerManager.BRIGHTNESS_ON) {
+ brightness = PowerManager.BRIGHTNESS_ON;
+ }
+
synchronized (mTempDisplayStateWorkQueue) {
try {
// Update the display state within the lock.
synchronized (mSyncRoot) {
- if (mGlobalDisplayState != state) {
- mGlobalDisplayState = state;
- updateGlobalDisplayStateLocked(mTempDisplayStateWorkQueue);
- scheduleTraversalLocked(false);
+ if (mGlobalDisplayState == state
+ && mGlobalDisplayBrightness == brightness) {
+ return; // no change
}
+
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestGlobalDisplayState("
+ + Display.stateToString(state)
+ + ", brightness=" + brightness + ")");
+ mGlobalDisplayState = state;
+ mGlobalDisplayBrightness = brightness;
+ updateGlobalDisplayStateLocked(mTempDisplayStateWorkQueue);
+ scheduleTraversalLocked(false);
}
// Setting the display power state can take hundreds of milliseconds
@@ -342,6 +368,7 @@ public final class DisplayManagerService extends SystemService {
for (int i = 0; i < mTempDisplayStateWorkQueue.size(); i++) {
mTempDisplayStateWorkQueue.get(i).run();
}
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
} finally {
mTempDisplayStateWorkQueue.clear();
}
@@ -641,13 +668,14 @@ public final class DisplayManagerService extends SystemService {
}
private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if (mDisplayDevices.contains(device)) {
- Slog.w(TAG, "Attempted to add already added display device: "
- + device.getDisplayDeviceInfoLocked());
+ Slog.w(TAG, "Attempted to add already added display device: " + info);
return;
}
- Slog.i(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked());
+ Slog.i(TAG, "Display device added: " + info);
+ device.mDebugLastLoggedDeviceInfo = info;
mDisplayDevices.add(device);
addLogicalDisplayLocked(device);
@@ -660,13 +688,20 @@ public final class DisplayManagerService extends SystemService {
private void handleDisplayDeviceChanged(DisplayDevice device) {
synchronized (mSyncRoot) {
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if (!mDisplayDevices.contains(device)) {
- Slog.w(TAG, "Attempted to change non-existent display device: "
- + device.getDisplayDeviceInfoLocked());
+ Slog.w(TAG, "Attempted to change non-existent display device: " + info);
return;
}
- Slog.i(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked());
+ int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
+ if (diff == DisplayDeviceInfo.DIFF_STATE) {
+ Slog.i(TAG, "Display device changed state: \"" + info.name
+ + "\", " + Display.stateToString(info.state));
+ } else if (diff != 0) {
+ Slog.i(TAG, "Display device changed: " + info);
+ }
+ device.mDebugLastLoggedDeviceInfo = info;
device.applyPendingDisplayDeviceInfoChangesLocked();
if (updateLogicalDisplaysLocked()) {
@@ -681,13 +716,14 @@ public final class DisplayManagerService extends SystemService {
}
}
private void handleDisplayDeviceRemovedLocked(DisplayDevice device) {
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if (!mDisplayDevices.remove(device)) {
- Slog.w(TAG, "Attempted to remove non-existent display device: "
- + device.getDisplayDeviceInfoLocked());
+ Slog.w(TAG, "Attempted to remove non-existent display device: " + info);
return;
}
- Slog.i(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked());
+ Slog.i(TAG, "Display device removed: " + info);
+ device.mDebugLastLoggedDeviceInfo = info;
updateLogicalDisplaysLocked();
scheduleTraversalLocked(false);
@@ -709,7 +745,7 @@ public final class DisplayManagerService extends SystemService {
// by the display power controller (if known).
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
- return device.requestDisplayStateLocked(mGlobalDisplayState);
+ return device.requestDisplayStateLocked(mGlobalDisplayState, mGlobalDisplayBrightness);
}
return null;
}
@@ -832,6 +868,24 @@ public final class DisplayManagerService extends SystemService {
}
}
+ private void setDisplayOffsetsInternal(int displayId, int x, int y) {
+ synchronized (mSyncRoot) {
+ LogicalDisplay display = mLogicalDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ if (display.getDisplayOffsetXLocked() != x
+ || display.getDisplayOffsetYLocked() != y) {
+ if (DEBUG) {
+ Slog.d(TAG, "Display " + displayId + " burn-in offset set to ("
+ + x + ", " + y + ")");
+ }
+ display.setDisplayOffsetsLocked(x, y);
+ scheduleTraversalLocked(false);
+ }
+ }
+ }
+
private void clearViewportsLocked() {
mDefaultViewport.valid = false;
mExternalTouchViewport.valid = false;
@@ -1445,16 +1499,16 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
DisplayBlanker blanker = new DisplayBlanker() {
@Override
- public void requestDisplayState(int state) {
+ public void requestDisplayState(int state, int brightness) {
// The order of operations is important for legacy reasons.
if (state == Display.STATE_OFF) {
- requestGlobalDisplayStateInternal(state);
+ requestGlobalDisplayStateInternal(state, brightness);
}
callbacks.onDisplayStateChange(state);
if (state != Display.STATE_OFF) {
- requestGlobalDisplayStateInternal(state);
+ requestGlobalDisplayStateInternal(state, brightness);
}
}
};
@@ -1513,5 +1567,10 @@ public final class DisplayManagerService extends SystemService {
float requestedRefreshRate, boolean inTraversal) {
setDisplayPropertiesInternal(displayId, hasContent, requestedRefreshRate, inTraversal);
}
+
+ @Override
+ public void setDisplayOffsets(int displayId, int x, int y) {
+ setDisplayOffsetsInternal(displayId, x, y);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 78610ff..f74601e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -19,7 +19,6 @@ package com.android.server.display;
import com.android.internal.app.IBatteryStats;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
-import com.android.server.lights.LightsManager;
import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -122,9 +121,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Battery stats.
private final IBatteryStats mBatteryStats;
- // The lights service.
- private final LightsManager mLights;
-
// The sensor manager.
private final SensorManager mSensorManager;
@@ -260,7 +256,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mCallbacks = callbacks;
mBatteryStats = BatteryStatsService.getService();
- mLights = LocalServices.getService(LightsManager.class);
mSensorManager = sensorManager;
mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
mBlanker = blanker;
@@ -302,6 +297,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
+ int lightSensorRate = resources.getInteger(
+ com.android.internal.R.integer.config_autoBrightnessLightSensorRate);
+ long brighteningLightDebounce = resources.getInteger(
+ com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
+ long darkeningLightDebounce = resources.getInteger(
+ com.android.internal.R.integer.config_autoBrightnessDarkeningLightDebounce);
+ boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+ com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
+
if (mUseSoftwareAutoBrightnessConfig) {
int[] lux = resources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevels);
@@ -336,7 +340,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAutomaticBrightnessController = new AutomaticBrightnessController(this,
handler.getLooper(), sensorManager, screenAutoBrightnessSpline,
lightSensorWarmUpTimeConfig, screenBrightnessRangeMinimum,
- mScreenBrightnessRangeMaximum, dozeScaleFactor);
+ mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate,
+ brighteningLightDebounce, darkeningLightDebounce,
+ autoBrightnessResetAmbientLuxAfterWarmUp);
}
}
@@ -432,7 +438,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Initialize the power state object for the default display.
// In the future, we might manage multiple displays independently.
mPowerState = new DisplayPowerState(mBlanker,
- mLights.getLight(LightsManager.LIGHT_ID_BACKLIGHT),
new ColorFade(Display.DEFAULT_DISPLAY));
mColorFadeOnAnimator = ObjectAnimator.ofFloat(
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index fc068af..f53ccc9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -16,14 +16,10 @@
package com.android.server.display;
-import com.android.server.lights.Light;
-
import android.content.Context;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
-import android.os.Trace;
import android.util.FloatProperty;
import android.util.IntProperty;
import android.util.Slog;
@@ -57,7 +53,6 @@ final class DisplayPowerState {
private final Handler mHandler;
private final Choreographer mChoreographer;
private final DisplayBlanker mBlanker;
- private final Light mBacklight;
private final ColorFade mColorFade;
private final PhotonicModulator mPhotonicModulator;
@@ -73,12 +68,11 @@ final class DisplayPowerState {
private Runnable mCleanListener;
- public DisplayPowerState(DisplayBlanker blanker, Light backlight, ColorFade electronBeam) {
+ public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade) {
mHandler = new Handler(true /*async*/);
mChoreographer = Choreographer.getInstance();
mBlanker = blanker;
- mBacklight = backlight;
- mColorFade = electronBeam;
+ mColorFade = colorFade;
mPhotonicModulator = new PhotonicModulator();
mPhotonicModulator.start();
@@ -349,6 +343,10 @@ final class DisplayPowerState {
private int mActualBacklight = INITIAL_BACKLIGHT;
private boolean mChangeInProgress;
+ public PhotonicModulator() {
+ super("PhotonicModulator");
+ }
+
public boolean setState(int state, int backlight) {
synchronized (mLock) {
if (state != mPendingState || backlight != mPendingBacklight) {
@@ -412,35 +410,7 @@ final class DisplayPowerState {
Slog.d(TAG, "Updating screen state: state="
+ Display.stateToString(state) + ", backlight=" + backlight);
}
- boolean suspending = Display.isSuspendedState(state);
- if (stateChanged && !suspending) {
- requestDisplayState(state);
- }
- if (backlightChanged) {
- setBrightness(backlight);
- }
- if (stateChanged && suspending) {
- requestDisplayState(state);
- }
- }
- }
-
- private void requestDisplayState(int state) {
- Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestDisplayState("
- + Display.stateToString(state) + ")");
- try {
- mBlanker.requestDisplayState(state);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_POWER);
- }
- }
-
- private void setBrightness(int backlight) {
- Trace.traceBegin(Trace.TRACE_TAG_POWER, "setBrightness(" + backlight + ")");
- try {
- mBacklight.setBrightness(backlight);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ mBlanker.requestDisplayState(state, backlight);
}
}
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 5ebe64d..e87f265 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -16,10 +16,15 @@
package com.android.server.display;
+import com.android.server.LocalServices;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
+
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.Slog;
@@ -40,6 +45,7 @@ import java.util.Arrays;
*/
final class LocalDisplayAdapter extends DisplayAdapter {
private static final String TAG = "LocalDisplayAdapter";
+ private static final boolean DEBUG = false;
private static final String UNIQUE_ID_PREFIX = "local:";
@@ -132,14 +138,17 @@ final class LocalDisplayAdapter extends DisplayAdapter {
private final int mBuiltInDisplayId;
private final SurfaceControl.PhysicalDisplayInfo mPhys;
private final int mDefaultPhysicalDisplayInfo;
+ private final Light mBacklight;
private DisplayDeviceInfo mInfo;
private boolean mHavePendingChanges;
private int mState = Display.STATE_UNKNOWN;
+ private int mBrightness = PowerManager.BRIGHTNESS_DEFAULT;
private float[] mSupportedRefreshRates;
private int[] mRefreshRateConfigIndices;
private float mLastRequestedRefreshRate;
+
public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + builtInDisplayId);
@@ -148,6 +157,13 @@ final class LocalDisplayAdapter extends DisplayAdapter {
physicalDisplayInfos[activeDisplayInfo]);
mDefaultPhysicalDisplayInfo = activeDisplayInfo;
updateSupportedRefreshRatesLocked(physicalDisplayInfos, mPhys);
+
+ if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
+ LightsManager lights = LocalServices.getService(LightsManager.class);
+ mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
+ } else {
+ mBacklight = null;
+ }
}
public boolean updatePhysicalDisplayInfoLocked(
@@ -225,28 +241,91 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
@Override
- public Runnable requestDisplayStateLocked(final int state) {
- if (mState != state) {
+ public Runnable requestDisplayStateLocked(final int state, final int brightness) {
+ // Assume that the brightness is off if the display is being turned off.
+ assert state != Display.STATE_OFF || brightness == PowerManager.BRIGHTNESS_OFF;
+
+ final boolean stateChanged = (mState != state);
+ final boolean brightnessChanged = (mBrightness != brightness) && mBacklight != null;
+ if (stateChanged || brightnessChanged) {
final int displayId = mBuiltInDisplayId;
final IBinder token = getDisplayTokenLocked();
- final int mode = getPowerModeForState(state);
- mState = state;
- updateDeviceInfoLocked();
+ final int oldState = mState;
+
+ if (stateChanged) {
+ mState = state;
+ updateDeviceInfoLocked();
+ }
- // Defer actually setting the display power mode until we have exited
+ if (brightnessChanged) {
+ mBrightness = brightness;
+ }
+
+ // Defer actually setting the display state until after we have exited
// the critical section since it can take hundreds of milliseconds
// to complete.
return new Runnable() {
@Override
public void run() {
- Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestDisplayState("
- + Display.stateToString(state) + ", id=" + displayId + ")");
+ // Exit a suspended state before making any changes.
+ int currentState = oldState;
+ if (Display.isSuspendedState(oldState)
+ || oldState == Display.STATE_UNKNOWN) {
+ if (!Display.isSuspendedState(state)) {
+ setDisplayState(state);
+ currentState = state;
+ } else if (state == Display.STATE_DOZE_SUSPEND
+ || oldState == Display.STATE_DOZE_SUSPEND) {
+ setDisplayState(Display.STATE_DOZE);
+ currentState = Display.STATE_DOZE;
+ } else {
+ return; // old state and new state is off
+ }
+ }
+
+ // Apply brightness changes given that we are in a non-suspended state.
+ if (brightnessChanged) {
+ setDisplayBrightness(brightness);
+ }
+
+ // Enter the final desired state, possibly suspended.
+ if (state != currentState) {
+ setDisplayState(state);
+ }
+ }
+
+ private void setDisplayState(int state) {
+ if (DEBUG) {
+ Slog.d(TAG, "setDisplayState("
+ + "id=" + displayId
+ + ", state=" + Display.stateToString(state) + ")");
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState("
+ + "id=" + displayId
+ + ", state=" + Display.stateToString(state) + ")");
try {
+ final int mode = getPowerModeForState(state);
SurfaceControl.setDisplayPowerMode(token, mode);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
+
+ private void setDisplayBrightness(int brightness) {
+ if (DEBUG) {
+ Slog.d(TAG, "setDisplayBrightness("
+ + "id=" + displayId + ", brightness=" + brightness + ")");
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness("
+ + "id=" + displayId + ", brightness=" + brightness + ")");
+ try {
+ mBacklight.setBrightness(brightness);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
+ }
+ }
};
}
return null;
@@ -278,6 +357,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
pw.println("mPhys=" + mPhys);
pw.println("mState=" + Display.stateToString(mState));
+ pw.println("mBrightness=" + mBrightness);
+ pw.println("mBacklight=" + mBacklight);
}
private void updateDeviceInfoLocked() {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 6c57eec..65dc72f 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -76,6 +76,10 @@ final class LogicalDisplay {
// The pending requested refresh rate. 0 if no request is pending.
private float mRequestedRefreshRate;
+ // The display offsets to apply to the display projection.
+ private int mDisplayOffsetX;
+ private int mDisplayOffsetY;
+
// Temporary rectangle used when needed.
private final Rect mTempLayerStackRect = new Rect();
private final Rect mTempDisplayRect = new Rect();
@@ -298,7 +302,10 @@ final class LogicalDisplay {
// multiplying the fractions by the product of their denominators before
// comparing them.
int displayRectWidth, displayRectHeight;
- if (physWidth * displayInfo.logicalHeight
+ if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0) {
+ displayRectWidth = displayInfo.logicalWidth;
+ displayRectHeight = displayInfo.logicalHeight;
+ } else if (physWidth * displayInfo.logicalHeight
< physHeight * displayInfo.logicalWidth) {
// Letter box.
displayRectWidth = physWidth;
@@ -313,6 +320,10 @@ final class LogicalDisplay {
mTempDisplayRect.set(displayRectLeft, displayRectTop,
displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight);
+ mTempDisplayRect.left += mDisplayOffsetX;
+ mTempDisplayRect.right += mDisplayOffsetX;
+ mTempDisplayRect.top += mDisplayOffsetY;
+ mTempDisplayRect.bottom += mDisplayOffsetY;
device.setProjectionInTransactionLocked(orientation, mTempLayerStackRect, mTempDisplayRect);
}
@@ -356,10 +367,34 @@ final class LogicalDisplay {
return mRequestedRefreshRate;
}
+ /**
+ * Gets the burn-in offset in X.
+ */
+ public int getDisplayOffsetXLocked() {
+ return mDisplayOffsetX;
+ }
+
+ /**
+ * Gets the burn-in offset in Y.
+ */
+ public int getDisplayOffsetYLocked() {
+ return mDisplayOffsetY;
+ }
+
+ /**
+ * Sets the burn-in offsets.
+ */
+ public void setDisplayOffsetsLocked(int x, int y) {
+ mDisplayOffsetX = x;
+ mDisplayOffsetY = y;
+ }
+
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mLayerStack=" + mLayerStack);
pw.println("mHasContent=" + mHasContent);
+ pw.println("mRequestedRefreshRate=" + mRequestedRefreshRate);
+ pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")");
pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ?
mPrimaryDisplayDevice.getNameLocked() : "null"));
pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo);
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 5b6f35b..af9f456 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -355,7 +355,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
if (mWindow != null) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.increaseIndent();
- DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, 200);
+ DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, "", 200);
}
}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
index 9ca5fda..3f4eab9 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
@@ -153,7 +153,7 @@ final class OverlayDisplayWindow implements DumpUtils.Dump {
}
@Override
- public void dump(PrintWriter pw) {
+ public void dump(PrintWriter pw, String prefix) {
pw.println("mWindowVisible=" + mWindowVisible);
pw.println("mWindowX=" + mWindowX);
pw.println("mWindowY=" + mWindowY);
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index f181cd5..6f59b54 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -221,7 +221,7 @@ final class VirtualDisplayAdapter extends DisplayAdapter {
}
@Override
- public Runnable requestDisplayStateLocked(int state) {
+ public Runnable requestDisplayStateLocked(int state, int brightness) {
if (state != mDisplayState) {
mDisplayState = state;
if (state == Display.STATE_OFF) {
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index c939861..f163555 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -123,7 +123,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
pw.println("mDisplayController:");
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.increaseIndent();
- DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
+ DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, "", 200);
}
}
diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
index dbb59b2..31c1eea 100644
--- a/services/core/java/com/android/server/display/WifiDisplayController.java
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -209,7 +209,7 @@ final class WifiDisplayController implements DumpUtils.Dump {
}
@Override
- public void dump(PrintWriter pw) {
+ public void dump(PrintWriter pw, String prefix) {
pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting);
pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
pw.println("mWfdEnabled=" + mWfdEnabled);
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 4521c28..458928f 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -137,10 +137,10 @@ public final class DreamManagerService extends SystemService {
DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
@Override
- public void dump(PrintWriter pw) {
+ public void dump(PrintWriter pw, String prefix) {
mController.dump(pw);
}
- }, pw, 200);
+ }, pw, "", 200);
}
private boolean isDreamingInternal() {
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 2941574..28597c1 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -16,30 +16,31 @@
package com.android.server.fingerprint;
-import android.app.Service;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
-import android.os.PowerManager;
+import android.os.Looper;
+import android.os.MessageQueue;
import android.os.RemoteException;
-import android.provider.Settings;
-import android.service.fingerprint.FingerprintManager;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.server.SystemService;
-import android.service.fingerprint.FingerprintUtils;
-import android.service.fingerprint.IFingerprintService;
-import android.service.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.fingerprint.FingerprintUtils;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.IFingerprintService;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+
+import static android.Manifest.permission.MANAGE_FINGERPRINT;
+import static android.Manifest.permission.USE_FINGERPRINT;
-import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Map.Entry;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
/**
* A service to manage multiple clients that want to access the fingerprint HAL API.
@@ -49,17 +50,33 @@ import java.util.Set;
* @hide
*/
public class FingerprintService extends SystemService {
- private final String TAG = "FingerprintService";
+ private static final String TAG = "FingerprintService";
private static final boolean DEBUG = true;
- private ArrayMap<IBinder, ClientData> mClients = new ArrayMap<IBinder, ClientData>();
+ private ClientMonitor mAuthClient = null;
+ private ClientMonitor mEnrollClient = null;
+ private ClientMonitor mRemoveClient = null;
private static final int MSG_NOTIFY = 10;
+ private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute
+
+ // Message types. Used internally to dispatch messages to the correct callback.
+ // Must agree with the list in fingerprint.h
+ private static final int FINGERPRINT_ERROR = -1;
+ private static final int FINGERPRINT_ACQUIRED = 1;
+ private static final int FINGERPRINT_TEMPLATE_ENROLLING = 3;
+ private static final int FINGERPRINT_TEMPLATE_REMOVED = 4;
+ private static final int FINGERPRINT_AUTHENTICATED = 5;
+ private static final long MS_PER_SEC = 1000;
+ private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000;
+ private static final int MAX_FAILED_ATTEMPTS = 5;
+
Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_NOTIFY:
- handleNotify(msg.arg1, msg.arg2, (Integer) msg.obj);
+ FpHalMsg m = (FpHalMsg) msg.obj;
+ handleNotify(m.type, m.arg1, m.arg2, m.arg3);
break;
default:
@@ -68,261 +85,530 @@ public class FingerprintService extends SystemService {
}
};
private Context mContext;
+ private int mHalDeviceId;
+ private int mFailedAttempts;
+ private final Runnable mLockoutReset = new Runnable() {
+ @Override
+ public void run() {
+ resetFailedAttempts();
+ }
+ };
- private static final int STATE_IDLE = 0;
- private static final int STATE_LISTENING = 1;
- private static final int STATE_ENROLLING = 2;
- private static final int STATE_REMOVING = 3;
- private static final long MS_PER_SEC = 1000;
- public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT";
- public static final String ENROLL_FINGERPRINT = "android.permission.ENROLL_FINGERPRINT";
+ public FingerprintService(Context context) {
+ super(context);
+ mContext = context;
+ nativeInit(Looper.getMainLooper().getQueue(), this);
+ }
- private static final class ClientData {
- public IFingerprintServiceReceiver receiver;
- int state;
- int userId;
- public TokenWatcher tokenWatcher;
- IBinder getToken() { return tokenWatcher.getToken(); }
+ // TODO: Move these into separate process
+ // JNI methods to communicate from FingerprintService to HAL
+ static native int nativeEnroll(byte [] token, int groupId, int timeout);
+ static native long nativePreEnroll();
+ static native int nativeStopEnrollment();
+ static native int nativeAuthenticate(long sessionId, int groupId);
+ static native int nativeStopAuthentication();
+ static native int nativeRemove(int fingerId, int groupId);
+ static native int nativeOpenHal();
+ static native int nativeCloseHal();
+ static native void nativeInit(MessageQueue queue, FingerprintService service);
+ static native long nativeGetAuthenticatorId();
+
+ static final class FpHalMsg {
+ int type; // Type of the message. One of the constants in fingerprint.h
+ int arg1; // optional arguments
+ int arg2;
+ int arg3;
+
+ FpHalMsg(int type, int arg1, int arg2, int arg3) {
+ this.type = type;
+ this.arg1 = arg1;
+ this.arg2 = arg2;
+ this.arg3 = arg3;
+ }
}
- private class TokenWatcher implements IBinder.DeathRecipient {
- WeakReference<IBinder> token;
+ /**
+ * Called from JNI to communicate messages from fingerprint HAL.
+ */
+ void notify(int type, int arg1, int arg2, int arg3) {
+ mHandler.obtainMessage(MSG_NOTIFY, new FpHalMsg(type, arg1, arg2, arg3)).sendToTarget();
+ }
- TokenWatcher(IBinder token) {
- this.token = new WeakReference<IBinder>(token);
+ void handleNotify(int type, int arg1, int arg2, int arg3) {
+ Slog.v(TAG, "handleNotify(type=" + type + ", arg1=" + arg1 + ", arg2=" + arg2 + ")"
+ + ", mAuthClients = " + mAuthClient + ", mEnrollClient = " + mEnrollClient);
+ if (mEnrollClient != null) {
+ final IBinder token = mEnrollClient.token;
+ if (dispatchNotify(mEnrollClient, type, arg1, arg2, arg3)) {
+ stopEnrollment(token, false);
+ removeClient(mEnrollClient);
+ }
}
-
- IBinder getToken() { return token.get(); }
- public void binderDied() {
- mClients.remove(token);
- this.token = null;
+ if (mAuthClient != null) {
+ final IBinder token = mAuthClient.token;
+ if (dispatchNotify(mAuthClient, type, arg1, arg2, arg3)) {
+ stopAuthentication(token, false);
+ removeClient(mAuthClient);
+ }
}
+ if (mRemoveClient != null) {
+ if (dispatchNotify(mRemoveClient, type, arg1, arg2, arg3)) {
+ removeClient(mRemoveClient);
+ }
+ }
+ }
- protected void finalize() throws Throwable {
- try {
- if (token != null) {
- if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token);
- mClients.remove(token);
+ /*
+ * Dispatch notify events to clients.
+ *
+ * @return true if the operation is done, i.e. authentication completed
+ */
+ boolean dispatchNotify(ClientMonitor clientMonitor, int type, int arg1, int arg2, int arg3) {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ boolean operationCompleted = false;
+ int fpId;
+ int groupId;
+ int remaining;
+ int acquireInfo;
+ switch (type) {
+ case FINGERPRINT_ERROR:
+ fpId = arg1;
+ operationCompleted = clientMonitor.sendError(fpId);
+ break;
+ case FINGERPRINT_ACQUIRED:
+ acquireInfo = arg1;
+ operationCompleted = clientMonitor.sendAcquired(acquireInfo);
+ break;
+ case FINGERPRINT_AUTHENTICATED:
+ fpId = arg1;
+ groupId = arg2;
+ operationCompleted = clientMonitor.sendAuthenticated(fpId, groupId);
+ break;
+ case FINGERPRINT_TEMPLATE_ENROLLING:
+ fpId = arg1;
+ groupId = arg2;
+ remaining = arg3;
+ operationCompleted = clientMonitor.sendEnrollResult(fpId, groupId, remaining);
+ if (remaining == 0) {
+ addTemplateForUser(clientMonitor, contentResolver, fpId);
+ operationCompleted = true; // enroll completed
}
- } finally {
- super.finalize();
- }
+ break;
+ case FINGERPRINT_TEMPLATE_REMOVED:
+ fpId = arg1;
+ groupId = arg2;
+ operationCompleted = clientMonitor.sendRemoved(fpId, groupId);
+ if (fpId != 0) {
+ removeTemplateForUser(clientMonitor, contentResolver, fpId);
+ }
+ break;
}
+ return operationCompleted;
}
- public FingerprintService(Context context) {
- super(context);
- mContext = context;
- nativeInit(this);
+ private void removeClient(ClientMonitor clientMonitor) {
+ if (clientMonitor == null) return;
+ clientMonitor.destroy();
+ if (clientMonitor == mAuthClient) {
+ mAuthClient = null;
+ } else if (clientMonitor == mEnrollClient) {
+ mEnrollClient = null;
+ } else if (clientMonitor == mRemoveClient) {
+ mRemoveClient = null;
+ }
}
- // TODO: Move these into separate process
- // JNI methods to communicate from FingerprintManagerService to HAL
- native int nativeEnroll(int timeout);
- native int nativeEnrollCancel();
- native int nativeRemove(int fingerprintId);
- native int nativeOpenHal();
- native int nativeCloseHal();
- native void nativeInit(FingerprintService service);
-
- // JNI methods for communicating from HAL to clients
- void notify(int msg, int arg1, int arg2) {
- mHandler.obtainMessage(MSG_NOTIFY, msg, arg1, arg2).sendToTarget();
+ private boolean inLockoutMode() {
+ return mFailedAttempts > MAX_FAILED_ATTEMPTS;
}
- void handleNotify(int msg, int arg1, int arg2) {
- Slog.v(TAG, "handleNotify(msg=" + msg + ", arg1=" + arg1 + ", arg2=" + arg2 + ")");
- for (int i = 0; i < mClients.size(); i++) {
- ClientData clientData = mClients.valueAt(i);
- if (clientData == null || clientData.receiver == null) {
- if (DEBUG) Slog.v(TAG, "clientData at " + i + " is invalid!!");
- continue;
- }
- switch (msg) {
- case FingerprintManager.FINGERPRINT_ERROR: {
- final int error = arg1;
- try {
- clientData.receiver.onError(error);
- } catch (RemoteException e) {
- Slog.e(TAG, "can't send message to client. Did it die?", e);
- mClients.remove(mClients.keyAt(i));
- }
- }
- break;
- case FingerprintManager.FINGERPRINT_ACQUIRED: {
- final int acquireInfo = arg1;
- try {
- clientData.receiver.onAcquired(acquireInfo);
- } catch (RemoteException e) {
- Slog.e(TAG, "can't send message to client. Did it die?", e);
- mClients.remove(mClients.keyAt(i));
- }
- break;
- }
- case FingerprintManager.FINGERPRINT_PROCESSED: {
- final int fingerId = arg1;
- try {
- clientData.receiver.onProcessed(fingerId);
- } catch (RemoteException e) {
- Slog.e(TAG, "can't send message to client. Did it die?", e);
- mClients.remove(mClients.keyAt(i));
- }
- break;
- }
- case FingerprintManager.FINGERPRINT_TEMPLATE_ENROLLING: {
- final int fingerId = arg1;
- final int remaining = arg2;
- if (clientData.state == STATE_ENROLLING) {
- // Only send enroll updates to clients that are actually enrolling
- try {
- clientData.receiver.onEnrollResult(fingerId, remaining);
- } catch (RemoteException e) {
- Slog.e(TAG, "can't send message to client. Did it die?", e);
- mClients.remove(mClients.keyAt(i));
- }
- // Update the database with new finger id.
- // TODO: move to client code (Settings)
- if (remaining == 0) {
- FingerprintUtils.addFingerprintIdForUser(fingerId,
- mContext.getContentResolver(), clientData.userId);
- clientData.state = STATE_IDLE; // Nothing left to do
- }
- } else {
- if (DEBUG) Slog.w(TAG, "Client not enrolling");
- break;
- }
- break;
- }
- case FingerprintManager.FINGERPRINT_TEMPLATE_REMOVED: {
- int fingerId = arg1;
- if (fingerId == 0) throw new IllegalStateException("Got illegal id from HAL");
- FingerprintUtils.removeFingerprintIdForUser(fingerId,
- mContext.getContentResolver(), clientData.userId);
- if (clientData.receiver != null) {
- try {
- clientData.receiver.onRemoved(fingerId);
- } catch (RemoteException e) {
- Slog.e(TAG, "can't send message to client. Did it die?", e);
- mClients.remove(mClients.keyAt(i));
- }
- }
- clientData.state = STATE_LISTENING;
- }
- break;
+ private void resetFailedAttempts() {
+ if (DEBUG && inLockoutMode()) {
+ Slog.v(TAG, "Reset fingerprint lockout");
+ }
+ mFailedAttempts = 0;
+ }
+
+ private boolean handleFailedAttempt(ClientMonitor clientMonitor) {
+ mFailedAttempts++;
+ if (mFailedAttempts > MAX_FAILED_ATTEMPTS) {
+ // Failing multiple times will continue to push out the lockout time.
+ mHandler.removeCallbacks(mLockoutReset);
+ mHandler.postDelayed(mLockoutReset, FAIL_LOCKOUT_TIMEOUT_MS);
+ if (clientMonitor != null
+ && !clientMonitor.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) {
+ Slog.w(TAG, "Cannot send lockout message to client");
}
+ return true;
+ }
+ return false;
+ }
+
+ private void removeTemplateForUser(ClientMonitor clientMonitor, ContentResolver contentResolver,
+ final int fingerId) {
+ FingerprintUtils.removeFingerprintIdForUser(fingerId, contentResolver,
+ clientMonitor.userId);
+ }
+
+ private void addTemplateForUser(ClientMonitor clientMonitor, ContentResolver contentResolver,
+ final int fingerId) {
+ FingerprintUtils.addFingerprintIdForUser(contentResolver, fingerId,
+ clientMonitor.userId);
+ }
+
+ void startEnrollment(IBinder token, byte[] cryptoToken, int groupId,
+ IFingerprintServiceReceiver receiver, int flags) {
+ stopPendingOperations();
+ mEnrollClient = new ClientMonitor(token, receiver, groupId);
+ final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
+ final int result = nativeEnroll(cryptoToken, groupId, timeout);
+ if (result != 0) {
+ Slog.w(TAG, "startEnroll failed, result=" + result);
}
}
- void startEnroll(IBinder token, long timeout, int userId) {
- ClientData clientData = mClients.get(token);
- if (clientData != null) {
- if (clientData.userId != userId) throw new IllegalStateException("Bad user");
- clientData.state = STATE_ENROLLING;
- nativeEnroll((int) (timeout / MS_PER_SEC));
- } else {
- Slog.w(TAG, "enroll(): No listener registered");
+ public long startPreEnroll(IBinder token) {
+ return nativePreEnroll();
+ }
+
+ private void stopPendingOperations() {
+ if (mEnrollClient != null) {
+ stopEnrollment(mEnrollClient.token, true);
+ }
+ if (mAuthClient != null) {
+ stopAuthentication(mAuthClient.token, true);
}
+ // mRemoveClient is allowed to continue
}
- void startEnrollCancel(IBinder token, int userId) {
- ClientData clientData = mClients.get(token);
- if (clientData != null) {
- if (clientData.userId != userId) throw new IllegalStateException("Bad user");
- clientData.state = STATE_LISTENING;
- nativeEnrollCancel();
- } else {
- Slog.w(TAG, "enrollCancel(): No listener registered");
+ void stopEnrollment(IBinder token, boolean notify) {
+ final ClientMonitor client = mEnrollClient;
+ if (client == null || client.token != token) return;
+ int result = nativeStopEnrollment();
+ if (notify) {
+ client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED);
+ }
+ removeClient(mEnrollClient);
+ if (result != 0) {
+ Slog.w(TAG, "startEnrollCancel failed, result=" + result);
}
}
- // Remove all fingerprints for the given user.
- void startRemove(IBinder token, int fingerId, int userId) {
- ClientData clientData = mClients.get(token);
- if (clientData != null) {
- if (clientData.userId != userId) throw new IllegalStateException("Bad user");
- clientData.state = STATE_REMOVING;
- // The fingerprint id will be removed when we get confirmation from the HAL
- int result = nativeRemove(fingerId);
- if (result != 0) {
- Slog.w(TAG, "Error removing fingerprint with id = " + fingerId);
+ void startAuthentication(IBinder token, long opId, int groupId,
+ IFingerprintServiceReceiver receiver, int flags) {
+ stopPendingOperations();
+ mAuthClient = new ClientMonitor(token, receiver, groupId);
+ if (inLockoutMode()) {
+ Slog.v(TAG, "In lockout mode; disallowing authentication");
+ if (!mAuthClient.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) {
+ Slog.w(TAG, "Cannot send timeout message to client");
}
- } else {
- Slog.w(TAG, "remove(" + token + "): No listener registered");
+ mAuthClient = null;
+ return;
+ }
+ final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
+ final int result = nativeAuthenticate(opId, groupId);
+ if (result != 0) {
+ Slog.w(TAG, "startAuthentication failed, result=" + result);
+ }
+ }
+
+ void stopAuthentication(IBinder token, boolean notify) {
+ final ClientMonitor client = mAuthClient;
+ if (client == null || client.token != token) return;
+ int result = nativeStopAuthentication();
+ if (notify) {
+ client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED);
+ }
+ removeClient(mAuthClient);
+ if (result != 0) {
+ Slog.w(TAG, "stopAuthentication failed, result=" + result);
+ }
+ }
+
+ void startRemove(IBinder token, int fingerId, int userId,
+ IFingerprintServiceReceiver receiver) {
+ mRemoveClient = new ClientMonitor(token, receiver, userId);
+ // The fingerprint template ids will be removed when we get confirmation from the HAL
+ final int result = nativeRemove(fingerId, userId);
+ if (result != 0) {
+ Slog.w(TAG, "startRemove with id = " + fingerId + " failed with result=" + result);
}
}
- void addListener(IBinder token, IFingerprintServiceReceiver receiver, int userId) {
- if (DEBUG) Slog.v(TAG, "startListening(" + receiver + ")");
- if (mClients.get(token) == null) {
- ClientData clientData = new ClientData();
- clientData.state = STATE_LISTENING;
- clientData.receiver = receiver;
- clientData.userId = userId;
- clientData.tokenWatcher = new TokenWatcher(token);
+ public List<Fingerprint> getEnrolledFingerprints(int groupId) {
+ ContentResolver resolver = mContext.getContentResolver();
+ int[] ids = FingerprintUtils.getFingerprintIdsForUser(resolver, groupId);
+ List<Fingerprint> result = new ArrayList<Fingerprint>();
+ for (int i = 0; i < ids.length; i++) {
+ // TODO: persist names in Settings
+ CharSequence name = "Finger" + ids[i];
+ final int group = 0; // TODO
+ final int fingerId = ids[i];
+ final long deviceId = 0; // TODO
+ Fingerprint item = new Fingerprint(name, 0, ids[i], 0);
+ result.add(item);
+ }
+ return result;
+ }
+
+ public boolean hasEnrolledFingerprints(int groupId) {
+ ContentResolver resolver = mContext.getContentResolver();
+ return FingerprintUtils.getFingerprintIdsForUser(resolver, groupId).length > 0;
+ }
+
+ void checkPermission(String permission) {
+ getContext().enforceCallingOrSelfPermission(permission,
+ "Must have " + permission + " permission.");
+ }
+
+ private class ClientMonitor implements IBinder.DeathRecipient {
+ IBinder token;
+ WeakReference<IFingerprintServiceReceiver> receiver;
+ int userId;
+
+ public ClientMonitor(IBinder token, IFingerprintServiceReceiver receiver, int userId) {
+ this.token = token;
+ this.receiver = new WeakReference<IFingerprintServiceReceiver>(receiver);
+ this.userId = userId;
try {
- token.linkToDeath(clientData.tokenWatcher, 0);
- mClients.put(token, clientData);
+ token.linkToDeath(this, 0);
} catch (RemoteException e) {
Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
}
- } else {
- if (DEBUG) Slog.v(TAG, "listener already registered for " + token);
}
- }
- void removeListener(IBinder token, int userId) {
- if (DEBUG) Slog.v(TAG, "stopListening(" + token + ")");
- ClientData clientData = mClients.get(token);
- if (clientData != null) {
- token.unlinkToDeath(clientData.tokenWatcher, 0);
- mClients.remove(token);
- } else {
- if (DEBUG) Slog.v(TAG, "listener not registered: " + token);
+ public void destroy() {
+ if (token != null) {
+ token.unlinkToDeath(this, 0);
+ token = null;
+ }
+ receiver = null;
+ }
+
+ public void binderDied() {
+ token = null;
+ removeClient(this);
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ if (token != null) {
+ if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token);
+ removeClient(this);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /*
+ * @return true if we're done.
+ */
+ private boolean sendRemoved(int fingerId, int groupId) {
+ IFingerprintServiceReceiver rx = receiver.get();
+ if (rx == null) return true; // client not listening
+ try {
+ rx.onRemoved(mHalDeviceId, fingerId, groupId);
+ return fingerId == 0;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify Removed:", e);
+ }
+ return false;
+ }
+
+ /*
+ * @return true if we're done.
+ */
+ private boolean sendEnrollResult(int fpId, int groupId, int remaining) {
+ IFingerprintServiceReceiver rx = receiver.get();
+ if (rx == null) return true; // client not listening
+ FingerprintUtils.vibrateFingerprintSuccess(getContext());
+ try {
+ rx.onEnrollResult(mHalDeviceId, fpId, groupId, remaining);
+ return remaining == 0;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify EnrollResult:", e);
+ return true;
+ }
+ }
+
+ /*
+ * @return true if we're done.
+ */
+ private boolean sendAuthenticated(int fpId, int groupId) {
+ IFingerprintServiceReceiver rx = receiver.get();
+ boolean result = false;
+ if (rx != null) {
+ try {
+ rx.onAuthenticated(mHalDeviceId, fpId, groupId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify Authenticated:", e);
+ result = true; // client failed
+ }
+ } else {
+ result = true; // client not listening
+ }
+ if (fpId <= 0) {
+ FingerprintUtils.vibrateFingerprintError(getContext());
+ result |= handleFailedAttempt(this);
+ } else {
+ FingerprintUtils.vibrateFingerprintSuccess(getContext());
+ result |= true; // we have a valid fingerprint
+ mLockoutReset.run();
+ }
+ return result;
+ }
+
+ /*
+ * @return true if we're done.
+ */
+ private boolean sendAcquired(int acquiredInfo) {
+ IFingerprintServiceReceiver rx = receiver.get();
+ if (rx == null) return true; // client not listening
+ try {
+ rx.onAcquired(mHalDeviceId, acquiredInfo);
+ return false; // acquisition continues...
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke sendAcquired:", e);
+ return true; // client failed
+ }
}
- mClients.remove(token);
- }
- void checkPermission(String permisison) {
- // TODO
+ /*
+ * @return true if we're done.
+ */
+ private boolean sendError(int error) {
+ IFingerprintServiceReceiver rx = receiver.get();
+ if (rx != null) {
+ try {
+ rx.onError(mHalDeviceId, error);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to invoke sendError:", e);
+ }
+ }
+ return true; // errors always terminate progress
+ }
}
private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
- @Override // Binder call
- public void enroll(IBinder token, long timeout, int userId) {
- checkPermission(ENROLL_FINGERPRINT);
- startEnroll(token, timeout, userId);
+ @Override
+ public long preEnroll(IBinder token) {
+ checkPermission(MANAGE_FINGERPRINT);
+ return startPreEnroll(token);
+ }
+
+ @Override
+ // Binder call
+ public void enroll(final IBinder token, final byte[] cryptoToken, final int groupId,
+ final IFingerprintServiceReceiver receiver, final int flags) {
+ checkPermission(MANAGE_FINGERPRINT);
+ final byte [] cryptoClone = Arrays.copyOf(cryptoToken, cryptoToken.length);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ startEnrollment(token, cryptoClone, groupId, receiver, flags);
+ }
+ });
+ }
+
+ @Override
+ // Binder call
+ public void cancelEnrollment(final IBinder token) {
+ checkPermission(MANAGE_FINGERPRINT);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ stopEnrollment(token, true);
+ }
+ });
}
- @Override // Binder call
- public void enrollCancel(IBinder token,int userId) {
- checkPermission(ENROLL_FINGERPRINT);
- startEnrollCancel(token, userId);
+ @Override
+ // Binder call
+ public void authenticate(final IBinder token, final long opId, final int groupId,
+ final IFingerprintServiceReceiver receiver, final int flags) {
+ checkPermission(USE_FINGERPRINT);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ startAuthentication(token, opId, groupId, receiver, flags);
+ }
+ });
+ }
+
+ @Override
+
+ // Binder call
+ public void cancelAuthentication(final IBinder token) {
+ checkPermission(USE_FINGERPRINT);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ stopAuthentication(token, true);
+ }
+ });
+ }
+
+ @Override
+ // Binder call
+ public void remove(final IBinder token, final int fingerId, final int groupId,
+ final IFingerprintServiceReceiver receiver) {
+ checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ startRemove(token, fingerId, groupId, receiver);
+ }
+ });
+
+ }
+
+ @Override
+ // Binder call
+ public boolean isHardwareDetected(long deviceId) {
+ checkPermission(USE_FINGERPRINT);
+ return mHalDeviceId != 0; // TODO
}
- @Override // Binder call
- public void remove(IBinder token, int fingerprintId, int userId) {
- checkPermission(ENROLL_FINGERPRINT); // TODO: Maybe have another permission
- startRemove(token, fingerprintId, userId);
+ @Override
+ // Binder call
+ public void rename(final int fingerId, final int groupId, final String name) {
+ checkPermission(MANAGE_FINGERPRINT);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Slog.w(TAG, "rename id=" + fingerId + ",gid=" + groupId + ",name=" + name);
+ }
+ });
+ }
+
+ @Override
+ // Binder call
+ public List<Fingerprint> getEnrolledFingerprints(int groupId) {
+ checkPermission(USE_FINGERPRINT);
+ return FingerprintService.this.getEnrolledFingerprints(groupId);
}
- @Override // Binder call
- public void startListening(IBinder token, IFingerprintServiceReceiver receiver, int userId)
- {
+ @Override
+ // Binder call
+ public boolean hasEnrolledFingerprints(int groupId) {
checkPermission(USE_FINGERPRINT);
- addListener(token, receiver, userId);
+ return FingerprintService.this.hasEnrolledFingerprints(groupId);
}
- @Override // Binder call
- public void stopListening(IBinder token, int userId) {
+ @Override
+ public long getAuthenticatorId() {
checkPermission(USE_FINGERPRINT);
- removeListener(token, userId);
+ return nativeGetAuthenticatorId();
}
}
@Override
public void onStart() {
- publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
- nativeOpenHal();
+ publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
+ mHalDeviceId = nativeOpenHal();
+ if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + mHalDeviceId);
}
}
diff --git a/services/core/java/com/android/server/firewall/SenderFilter.java b/services/core/java/com/android/server/firewall/SenderFilter.java
index c0eee69..0074119 100644
--- a/services/core/java/com/android/server/firewall/SenderFilter.java
+++ b/services/core/java/com/android/server/firewall/SenderFilter.java
@@ -45,7 +45,8 @@ class SenderFilter {
IPackageManager pm = AppGlobals.getPackageManager();
try {
- return (pm.getFlagsForUid(callerUid) & ApplicationInfo.FLAG_PRIVILEGED) != 0;
+ return (pm.getPrivateFlagsForUid(callerUid) & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+ != 0;
} catch (RemoteException ex) {
Slog.e(IntentFirewall.TAG, "Remote exception while retrieving uid flags",
ex);
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
index 7f48768..01547c1 100644
--- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -68,8 +68,12 @@ final class ActiveSourceHandler {
}
if (!tv.isProhibitMode()) {
+ ActiveSource old = ActiveSource.of(tv.getActiveSource());
tv.updateActiveSource(newActive);
boolean notifyInputChange = (mCallback == null);
+ if (!old.equals(newActive)) {
+ tv.setPrevPortId(tv.getActivePortId());
+ }
tv.updateActiveInput(newActive.physicalAddress, notifyInputChange);
invokeCallback(HdmiControlManager.RESULT_SUCCESS);
} else {
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 0c86aed..e434f39 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -214,6 +214,10 @@ final class Constants {
// values which denotes the device type in HDMI Spec 1.4.
static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type";
+ // Set to false to allow playback device to go to suspend mode even
+ // when it's an active source. True by default.
+ static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake";
+
static final int RECORDING_TYPE_DIGITAL_RF = 1;
static final int RECORDING_TYPE_ANALOGUE_RF = 2;
static final int RECORDING_TYPE_EXTERNAL_PHYSICAL_ADDRESS = 3;
diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
index 77ffe0b..2c1a7d5 100644
--- a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
+++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
@@ -17,8 +17,6 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiDeviceInfo;
-import android.util.Slog;
-
import java.util.ArrayList;
import java.util.Iterator;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index d17e9b3..8031c05 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -71,6 +71,9 @@ abstract class HdmiCecLocalDevice {
logicalAddress = logical;
physicalAddress = physical;
}
+ public static ActiveSource of(ActiveSource source) {
+ return new ActiveSource(source.logicalAddress, source.physicalAddress);
+ }
public static ActiveSource of(int logical, int physical) {
return new ActiveSource(logical, physical);
}
@@ -102,10 +105,10 @@ abstract class HdmiCecLocalDevice {
StringBuffer s = new StringBuffer();
String logicalAddressString = (logicalAddress == Constants.ADDR_INVALID)
? "invalid" : String.format("0x%02x", logicalAddress);
- s.append("logical_address: ").append(logicalAddressString);
+ s.append("(").append(logicalAddressString);
String physicalAddressString = (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS)
? "invalid" : String.format("0x%04x", physicalAddress);
- s.append(", physical_address: ").append(physicalAddressString);
+ s.append(", ").append(physicalAddressString).append(")");
return s.toString();
}
}
@@ -173,6 +176,7 @@ abstract class HdmiCecLocalDevice {
void init() {
assertRunOnServiceThread();
mPreferredAddress = getPreferredAddress();
+ mPendingActionClearedCallback = null;
}
/**
@@ -636,7 +640,7 @@ abstract class HdmiCecLocalDevice {
void addAndStartAction(final HdmiCecFeatureAction action) {
assertRunOnServiceThread();
mActions.add(action);
- if (mService.isPowerStandbyOrTransient()) {
+ if (mService.isPowerStandby()) {
Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action);
return;
}
@@ -834,16 +838,16 @@ abstract class HdmiCecLocalDevice {
*
* @param initiatedByCec true if this sequence is initiated
* by the reception the CEC messages like &lt;Standby&gt;
- * @param origialCallback callback interface to get notified when all pending actions are
+ * @param originalCallback callback interface to get notified when all pending actions are
* cleared
*/
protected void disableDevice(boolean initiatedByCec,
- final PendingActionClearedCallback origialCallback) {
+ final PendingActionClearedCallback originalCallback) {
mPendingActionClearedCallback = new PendingActionClearedCallback() {
@Override
public void onCleared(HdmiCecLocalDevice device) {
mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
- origialCallback.onCleared(device);
+ originalCallback.onCleared(device);
}
};
mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT),
@@ -862,6 +866,9 @@ abstract class HdmiCecLocalDevice {
action.finish(false);
iter.remove();
}
+ if (mPendingActionClearedCallback != null) {
+ mPendingActionClearedCallback.onCleared(this);
+ }
}
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 8034809..89ffe45 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -38,9 +38,11 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
// Used to keep the device awake while it is the active source. For devices that
// cannot wake up via CEC commands, this address the inconvenience of having to
- // turn them on.
+ // turn them on. True by default, and can be disabled (i.e. device can go to sleep
+ // in active device status) by explicitly setting the system property
+ // persist.sys.hdmi.keep_awake to false.
// Lazily initialized - should call getWakeLock() to get the instance.
- private WakeLock mWakeLock;
+ private ActiveWakeLock mWakeLock;
HdmiCecLocalDevicePlayback(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
@@ -142,19 +144,30 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
mIsActiveSource = on;
if (on) {
getWakeLock().acquire();
- HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
} else {
getWakeLock().release();
- HdmiLogger.debug("Wake lock released");
}
}
@ServiceThreadOnly
- private WakeLock getWakeLock() {
+ private ActiveWakeLock getWakeLock() {
assertRunOnServiceThread();
if (mWakeLock == null) {
- mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mWakeLock.setReferenceCounted(false);
+ if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) {
+ mWakeLock = new SystemWakeLock();
+ } else {
+ // Create a dummy lock object that doesn't do anything about wake lock,
+ // hence allows the device to go to sleep even if it's the active source.
+ mWakeLock = new ActiveWakeLock() {
+ @Override
+ public void acquire() { }
+ @Override
+ public void release() { }
+ @Override
+ public boolean isHeld() { return false; }
+ };
+ HdmiLogger.debug("No wakelock is used to keep the display on.");
+ }
}
return mWakeLock;
}
@@ -253,6 +266,16 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
@Override
@ServiceThreadOnly
+ protected void sendStandby(int deviceId) {
+ assertRunOnServiceThread();
+
+ // Playback device can send <Standby> to TV only. Ignore the parameter.
+ int targetAddress = Constants.ADDR_TV;
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
+ }
+
+ @Override
+ @ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
super.disableDevice(initiatedByCec, callback);
@@ -270,4 +293,36 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
super.dump(pw);
pw.println("mIsActiveSource: " + mIsActiveSource);
}
+
+ // Wrapper interface over PowerManager.WakeLock
+ private interface ActiveWakeLock {
+ void acquire();
+ void release();
+ boolean isHeld();
+ }
+
+ private class SystemWakeLock implements ActiveWakeLock {
+ private final WakeLock mWakeLock;
+ public SystemWakeLock() {
+ mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.setReferenceCounted(false);
+ }
+
+ @Override
+ public void acquire() {
+ mWakeLock.acquire();
+ HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
+ }
+
+ @Override
+ public void release() {
+ mWakeLock.release();
+ HdmiLogger.debug("Wake lock released");
+ }
+
+ @Override
+ public boolean isHeld() {
+ return mWakeLock.isHeld();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 8241cdc..5ac027d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -30,8 +30,6 @@ import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANAL
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
-import android.annotation.Nullable;
-import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
@@ -41,22 +39,19 @@ import android.hardware.hdmi.IHdmiControlCallback;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.provider.Settings.Global;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
-import com.android.server.SystemService;
-
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -77,9 +72,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@ServiceThreadOnly
private boolean mArcEstablished = false;
- // Whether ARC feature is enabled or not. The default value is true.
- // TODO: once adding system setting for it, read the value to it.
- private boolean mArcFeatureEnabled = true;
+ // Stores whether ARC feature is enabled per port. True by default for all the ARC-enabled ports.
+ private final SparseBooleanArray mArcFeatureEnabled = new SparseBooleanArray();
// Whether System audio mode is activated or not.
// This becomes true only when all system audio sequences are finished.
@@ -196,6 +190,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@ServiceThreadOnly
protected void onAddressAllocated(int logicalAddress, int reason) {
assertRunOnServiceThread();
+ List<HdmiPortInfo> ports = mService.getPortInfo();
+ for (HdmiPortInfo port : ports) {
+ mArcFeatureEnabled.put(port.getId(), port.isArcSupported());
+ }
mService.registerTvInputCallback(mTvInputCallback);
mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
mAddress, mService.getPhysicalAddress(), mDeviceType));
@@ -208,7 +206,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
reason != HdmiControlService.INITIATED_BY_BOOT_UP);
mLocalDeviceAddresses = initLocalDeviceAddresses();
launchDeviceDiscovery();
- startQueuedActions();
}
@@ -258,7 +255,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
int targetAddress = targetDevice.getLogicalAddress();
ActiveSource active = getActiveSource();
- if (active.isValid() && targetAddress == active.logicalAddress) {
+ if (targetDevice.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON
+ && active.isValid()
+ && targetAddress == active.logicalAddress) {
invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
return;
}
@@ -348,7 +347,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
void updateActiveInput(int path, boolean notifyInputChange) {
assertRunOnServiceThread();
// Seq #15
- setPrevPortId(getActivePortId());
setActivePath(path);
// TODO: Handle PAP/PIP case.
// Show OSD port change banner
@@ -787,6 +785,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
HdmiDeviceInfo avr = getAvrDeviceInfo();
if (avr != null) {
onNewAvrAdded(avr);
+ } else {
+ setSystemAudioMode(false, true);
}
}
});
@@ -799,7 +799,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
if (getSystemAudioModeSetting() && !isSystemAudioActivated()) {
addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
}
- if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) {
+ if (isArcFeatureEnabled(avr.getPortId())
+ && !hasAction(SetArcTransmissionStateAction.class)) {
startArcAction(true);
}
}
@@ -881,7 +882,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
boolean oldStatus = mArcEstablished;
// 1. Enable/disable ARC circuit.
- mService.setAudioReturnChannel(getAvrDeviceInfo().getPortId(), enabled);
+ setAudioReturnChannel(enabled);
// 2. Notify arc status to audio service.
notifyArcStatusToAudioService(enabled);
// 3. Update arc status;
@@ -889,39 +890,66 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
return oldStatus;
}
+ /**
+ * Switch hardware ARC circuit in the system.
+ */
+ @ServiceThreadOnly
+ void setAudioReturnChannel(boolean enabled) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo avr = getAvrDeviceInfo();
+ if (avr != null) {
+ mService.setAudioReturnChannel(avr.getPortId(), enabled);
+ }
+ }
+
@ServiceThreadOnly
private void updateArcFeatureStatus(int portId, boolean isConnected) {
assertRunOnServiceThread();
+ HdmiDeviceInfo avr = getAvrDeviceInfo();
+ if (avr == null) {
+ return;
+ }
// HEAC 2.4, HEACT 5-15
// Should not activate ARC if +5V status is false.
HdmiPortInfo portInfo = mService.getPortInfo(portId);
- if (portInfo.isArcSupported()) {
- changeArcFeatureEnabled(isConnected);
+ if (avr.getPortId() == portId && portInfo.isArcSupported()) {
+ changeArcFeatureEnabled(portId, isConnected);
}
}
+ @ServiceThreadOnly
+ boolean isConnected(int portId) {
+ assertRunOnServiceThread();
+ return mService.isConnected(portId);
+ }
+
private void notifyArcStatusToAudioService(boolean enabled) {
// Note that we don't set any name to ARC.
mService.getAudioManager().setWiredDeviceConnectionState(
AudioSystem.DEVICE_OUT_HDMI_ARC,
- enabled ? 1 : 0, "");
+ enabled ? 1 : 0, "", "");
}
/**
- * Returns whether ARC is enabled or not.
+ * Returns true if ARC is currently established on a certain port.
*/
@ServiceThreadOnly
- boolean isArcEstabilished() {
+ boolean isArcEstablished() {
assertRunOnServiceThread();
- return mArcFeatureEnabled && mArcEstablished;
+ if (mArcEstablished) {
+ for (int i = 0; i < mArcFeatureEnabled.size(); i++) {
+ if (mArcFeatureEnabled.valueAt(i)) return true;
+ }
+ }
+ return false;
}
@ServiceThreadOnly
- void changeArcFeatureEnabled(boolean enabled) {
+ void changeArcFeatureEnabled(int portId, boolean enabled) {
assertRunOnServiceThread();
- if (mArcFeatureEnabled != enabled) {
- mArcFeatureEnabled = enabled;
+ if (mArcFeatureEnabled.get(portId) != enabled) {
+ mArcFeatureEnabled.put(portId, enabled);
if (enabled) {
if (!mArcEstablished) {
startArcAction(true);
@@ -935,9 +963,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
@ServiceThreadOnly
- boolean isArcFeatureEnabled() {
+ boolean isArcFeatureEnabled(int portId) {
assertRunOnServiceThread();
- return mArcFeatureEnabled;
+ return mArcFeatureEnabled.get(portId);
}
@ServiceThreadOnly
@@ -1072,7 +1100,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
&& isConnectedToArcPort(avr.getPhysicalAddress())
&& isDirectConnectAddress(avr.getPhysicalAddress())) {
if (shouldCheckArcFeatureEnabled) {
- return isArcFeatureEnabled();
+ return isArcFeatureEnabled(avr.getPortId());
} else {
return true;
}
@@ -1085,11 +1113,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@ServiceThreadOnly
protected boolean handleTerminateArc(HdmiCecMessage message) {
assertRunOnServiceThread();
- // In cast of termination, do not check ARC configuration in that AVR device
- // might be removed already.
-
- // In case where <Terminate Arc> is started by <Request ARC Termination>
- // need to clean up RequestArcInitiationAction.
+ if (mService .isPowerStandbyOrTransient()) {
+ setArcStatus(false);
+ return true;
+ }
+ // Do not check ARC configuration since the AVR might have been already removed.
+ // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
+ // <Request ARC Termination>.
removeAction(RequestArcTerminationAction.class);
SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
message.getSource(), false);
@@ -1565,7 +1595,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
@Override
@ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
- super.disableDevice(initiatedByCec, callback);
assertRunOnServiceThread();
mService.unregisterTvInputCallback(mTvInputCallback);
// Remove any repeated working actions.
@@ -1581,6 +1610,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
disableSystemAudioIfExist();
disableArcIfExist();
+
+ super.disableDevice(initiatedByCec, callback);
clearDeviceInfoList();
checkIfPendingActionsCleared();
}
@@ -1598,10 +1629,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
removeAction(SystemAudioAutoInitiationAction.class);
removeAction(SystemAudioStatusAction.class);
removeAction(VolumeControlAction.class);
-
- // Turn off the mode but do not write it the settings, so that the next time TV powers on
- // the system audio mode setting can be restored automatically.
- setSystemAudioMode(false, false);
}
@ServiceThreadOnly
@@ -1614,7 +1641,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
// Seq #44.
removeAction(RequestArcInitiationAction.class);
- if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
+ if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 49a96d8..2cbc1b9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -780,6 +780,12 @@ public final class HdmiControlService extends SystemService {
return false;
}
+ @ServiceThreadOnly
+ boolean isConnected(int portId) {
+ assertRunOnServiceThread();
+ return mCecController.isConnected(portId);
+ }
+
void runOnServiceThread(Runnable runnable) {
mHandler.post(runnable);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java b/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java
index 3883200..f19b19b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java
+++ b/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java
@@ -20,7 +20,6 @@ import android.hardware.hdmi.HdmiPortInfo;
import android.util.SparseArray;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
/**
* A handler class for MHL control command. It converts user's command into MHL command and pass it
diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
index 1bbd038..5f2d651 100644
--- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -156,10 +156,13 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
int index = -1;
while ((index = removed.nextSetBit(index + 1)) != -1) {
if (index == Constants.ADDR_AUDIO_SYSTEM) {
- ++mAvrStatusCount;
- Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount);
- if (mAvrStatusCount < AVR_COUNT_MAX) {
- continue;
+ HdmiDeviceInfo avr = tv().getAvrDeviceInfo();
+ if (avr != null && tv().isConnected(avr.getPortId())) {
+ ++mAvrStatusCount;
+ Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount);
+ if (mAvrStatusCount < AVR_COUNT_MAX) {
+ continue;
+ }
}
}
Slog.v(TAG, "Remove device by hot-plug detection:" + index);
@@ -261,7 +264,8 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction {
// Turn off system audio mode and update settings.
tv().setSystemAudioMode(false, true);
- if (tv().isArcEstabilished()) {
+ if (tv().isArcEstablished()) {
+ tv().setAudioReturnChannel(false);
addAndStartAction(new RequestArcTerminationAction(localDevice(), address));
}
}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java
index 31322a9..75a79cb 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java
@@ -17,7 +17,6 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiDeviceInfo;
-import android.util.Slog;
/**
* Base feature action class for &lt;Request ARC Initiation&gt;/&lt;Request ARC Termination&gt;.
@@ -59,14 +58,16 @@ abstract class RequestArcAction extends HdmiCecFeatureAction {
// received without <Request ARC Initiation> or <Request ARC Termination>.
case Constants.MESSAGE_FEATURE_ABORT:
int originalOpcode = cmd.getParams()[0] & 0xFF;
- if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION
- || originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) {
+ if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) {
disableArcTransmission();
finish();
return true;
- } else {
- return false;
+ } else if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION) {
+ tv().setArcStatus(false);
+ finish();
+ return true;
}
+ return false;
}
return false;
}
@@ -83,7 +84,7 @@ abstract class RequestArcAction extends HdmiCecFeatureAction {
if (mState != state || state != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE) {
return;
}
- HdmiLogger.debug("[T]RequestArcAction.");
+ HdmiLogger.debug("[T] RequestArcAction.");
disableArcTransmission();
finish();
}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
index d9e1f24..f69f975 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
@@ -35,6 +35,7 @@ final class RequestArcInitiationAction extends RequestArcAction {
@Override
boolean start() {
+ // Seq #38
mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
@@ -44,9 +45,8 @@ final class RequestArcInitiationAction extends RequestArcAction {
@Override
public void onSendCompleted(int error) {
if (error != Constants.SEND_RESULT_SUCCESS) {
- // If failed to send <Request ARC Initiation>, start "Disabled"
- // ARC transmission action.
- disableArcTransmission();
+ // Turn off ARC status if <Request ARC Initiation> fails.
+ tv().setArcStatus(false);
finish();
}
}
diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
index ce5b9ab..6c8694e 100644
--- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java
+++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java
@@ -119,7 +119,7 @@ final class RoutingControlAction extends HdmiCecFeatureAction {
private void handleReportPowerStatus(int devicePowerStatus) {
if (isPowerOnOrTransient(getTvPowerStatus())) {
- tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
+ updateActiveInput();
if (isPowerOnOrTransient(devicePowerStatus)) {
sendSetStreamPath();
}
@@ -127,6 +127,12 @@ final class RoutingControlAction extends HdmiCecFeatureAction {
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
}
+ private void updateActiveInput() {
+ HdmiCecLocalDeviceTv tv = tv();
+ tv.setPrevPortId(tv.getActivePortId());
+ tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
+ }
+
private int getTvPowerStatus() {
return tv().getPowerStatus();
}
@@ -165,13 +171,13 @@ final class RoutingControlAction extends HdmiCecFeatureAction {
}
});
} else {
- tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
+ updateActiveInput();
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
}
return;
case STATE_WAIT_FOR_REPORT_POWER_STATUS:
if (isPowerOnOrTransient(getTvPowerStatus())) {
- tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
+ updateActiveInput();
sendSetStreamPath();
}
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
@@ -189,7 +195,7 @@ final class RoutingControlAction extends HdmiCecFeatureAction {
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS);
} else {
- tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange);
+ updateActiveInput();
sendSetStreamPath();
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
}
diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
index bffa854..9b4950b 100644
--- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
+++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
@@ -52,8 +52,9 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction {
@Override
boolean start() {
+ // Seq #37.
if (mEnabled) {
- // Enable ARC status immediately after sending <Report Arc Initiated>.
+ // Enable ARC status immediately before sending <Report Arc Initiated>.
// If AVR responds with <Feature Abort>, disable ARC status again.
// This is different from spec that says that turns ARC status to
// "Enabled" if <Report ARC Initiated> is acknowledged and no
@@ -79,12 +80,21 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction {
sendCommand(command, new HdmiControlService.SendMessageCallback() {
@Override
public void onSendCompleted(int error) {
- if (error != Constants.SEND_RESULT_SUCCESS) {
- // If fails to send <Report ARC Initiated>, disable ARC and
- // send <Report ARC Terminated> directly.
- setArcStatus(false);
- HdmiLogger.debug("Failed to send <Report Arc Initiated>.");
- finish();
+ switch (error) {
+ case Constants.SEND_RESULT_SUCCESS:
+ case Constants.SEND_RESULT_BUSY:
+ case Constants.SEND_RESULT_FAILURE:
+ // The result of the command transmission, unless it is an obvious
+ // failure indicated by the target device (or lack thereof), should
+ // not affect the ARC status. Ignores it silently.
+ break;
+ case Constants.SEND_RESULT_NAK:
+ // If <Report ARC Initiated> is negatively ack'ed, disable ARC and
+ // send <Report ARC Terminated> directly.
+ setArcStatus(false);
+ HdmiLogger.debug("Failed to send <Report Arc Initiated>.");
+ finish();
+ break;
}
}
});
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 15dcd44..17b4f9c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -796,7 +796,7 @@ public class InputManagerService extends IInputManager.Stub
.setContentIntent(keyboardLayoutIntent)
.setSmallIcon(R.drawable.ic_settings_language)
.setPriority(Notification.PRIORITY_LOW)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color))
.build();
mNotificationManager.notifyAsUser(null,
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 83d6986..ecda36a 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
@@ -40,6 +41,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -49,6 +51,7 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.app.IBatteryStats;
+import com.android.server.job.controllers.AppIdleController;
import com.android.server.job.controllers.BatteryController;
import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.IdleController;
@@ -72,7 +75,8 @@ public class JobSchedulerService extends com.android.server.SystemService
implements StateChangedListener, JobCompletedListener {
static final boolean DEBUG = false;
/** The number of concurrent jobs we run at one time. */
- private static final int MAX_JOB_CONTEXTS_COUNT = 3;
+ private static final int MAX_JOB_CONTEXTS_COUNT
+ = ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
static final String TAG = "JobSchedulerService";
/** Master list of jobs. */
final JobStore mJobs;
@@ -107,21 +111,22 @@ public class JobSchedulerService extends com.android.server.SystemService
* Track Services that have currently active or pending jobs. The index is provided by
* {@link JobStatus#getServiceToken()}
*/
- final List<JobServiceContext> mActiveServices = new ArrayList<JobServiceContext>();
+ final List<JobServiceContext> mActiveServices = new ArrayList<>();
/** List of controllers that will notify this service of updates to jobs. */
List<StateController> mControllers;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
*/
- final ArrayList<JobStatus> mPendingJobs = new ArrayList<JobStatus>();
+ final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
- final ArrayList<Integer> mStartedUsers = new ArrayList();
+ final ArrayList<Integer> mStartedUsers = new ArrayList<>();
final JobHandler mHandler;
final JobSchedulerStub mJobSchedulerStub;
IBatteryStats mBatteryStats;
+ PowerManager mPowerManager;
/**
* Set to true once we are allowed to run third party apps.
@@ -129,6 +134,11 @@ public class JobSchedulerService extends com.android.server.SystemService
boolean mReadyToRock;
/**
+ * True when in device idle mode, so we don't want to schedule any jobs.
+ */
+ boolean mDeviceIdleMode;
+
+ /**
* Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
* still clean up. On reinstall the package will have a new uid.
*/
@@ -152,6 +162,8 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.d(TAG, "Removing jobs for user: " + userId);
}
cancelJobsForUser(userId);
+ } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
+ updateIdleMode(mPowerManager != null ? mPowerManager.isDeviceIdleMode() : false);
}
}
};
@@ -197,7 +209,7 @@ public class JobSchedulerService extends com.android.server.SystemService
return outList;
}
- private void cancelJobsForUser(int userHandle) {
+ void cancelJobsForUser(int userHandle) {
List<JobStatus> jobsForUser;
synchronized (mJobs) {
jobsForUser = mJobs.getJobsByUser(userHandle);
@@ -255,6 +267,40 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
+ void updateIdleMode(boolean enabled) {
+ boolean changed = false;
+ boolean rocking;
+ synchronized (mJobs) {
+ if (mDeviceIdleMode != enabled) {
+ changed = true;
+ }
+ rocking = mReadyToRock;
+ }
+ if (changed) {
+ if (rocking) {
+ for (int i=0; i<mControllers.size(); i++) {
+ mControllers.get(i).deviceIdleModeChanged(enabled);
+ }
+ }
+ synchronized (mJobs) {
+ mDeviceIdleMode = enabled;
+ if (enabled) {
+ // When becoming idle, make sure no jobs are actively running.
+ for (int i=0; i<mActiveServices.size(); i++) {
+ JobServiceContext jsc = mActiveServices.get(i);
+ final JobStatus executing = jsc.getRunningJob();
+ if (executing != null) {
+ jsc.cancelExecutingJob();
+ }
+ }
+ } else {
+ // When coming out of idle, allow thing to start back up.
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
+ }
+ }
+ }
+
/**
* Initializes the system service.
* <p>
@@ -272,6 +318,7 @@ public class JobSchedulerService extends com.android.server.SystemService
mControllers.add(TimeController.get(this));
mControllers.add(IdleController.get(this));
mControllers.add(BatteryController.get(this));
+ mControllers.add(AppIdleController.get(this));
mHandler = new JobHandler(context.getMainLooper());
mJobSchedulerStub = new JobSchedulerStub();
@@ -292,8 +339,10 @@ public class JobSchedulerService extends com.android.server.SystemService
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
+ userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
+ mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mJobs) {
// Let's go!
@@ -311,6 +360,7 @@ public class JobSchedulerService extends com.android.server.SystemService
for (int i=0; i<jobs.size(); i++) {
JobStatus job = jobs.valueAt(i);
for (int controller=0; controller<mControllers.size(); controller++) {
+ mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode);
mControllers.get(controller).maybeStartTrackingJob(job);
}
}
@@ -640,7 +690,6 @@ public class JobSchedulerService extends com.android.server.SystemService
final boolean jobPending = mPendingJobs.contains(job);
final boolean jobActive = isCurrentlyActiveLocked(job);
final boolean userRunning = mStartedUsers.contains(job.getUserId());
-
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+ " ready=" + jobReady + " pending=" + jobPending
@@ -665,6 +714,10 @@ public class JobSchedulerService extends com.android.server.SystemService
*/
private void maybeRunPendingJobsH() {
synchronized (mJobs) {
+ if (mDeviceIdleMode) {
+ // If device is idle, we will not schedule jobs to run.
+ return;
+ }
Iterator<JobStatus> it = mPendingJobs.iterator();
if (DEBUG) {
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
@@ -686,6 +739,10 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
if (availableContext != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "About to run job "
+ + nextPending.getJob().getService().toString());
+ }
if (!availableContext.executeRunnableJob(nextPending)) {
if (DEBUG) {
Slog.d(TAG, "Error executing " + nextPending);
@@ -876,6 +933,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
pw.println();
pw.print("mReadyToRock="); pw.println(mReadyToRock);
+ pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
}
pw.println();
}
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 63c8d92..53ceb2e 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -153,8 +153,9 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
}
mRunningJob = job;
- mParams = new JobParameters(this, job.getJobId(), job.getExtras(),
- !job.isConstraintsSatisfied());
+ final boolean isDeadlineExpired =
+ job.getLatestRunTimeElapsed() >= SystemClock.elapsedRealtime();
+ mParams = new JobParameters(this, job.getJobId(), job.getExtras(), isDeadlineExpired);
mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
mVerb = VERB_BINDING;
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
new file mode 100644
index 0000000..98fb11b
--- /dev/null
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 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.job.controllers;
+
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.StateChangedListener;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Controls when apps are considered idle and if jobs pertaining to those apps should
+ * be executed. Apps that haven't been actively launched or accessed from a foreground app
+ * for a certain amount of time (maybe hours or days) are considered idle. When the app comes
+ * out of idle state, it will be allowed to run scheduled jobs.
+ */
+public class AppIdleController extends StateController
+ implements UsageStatsManagerInternal.AppIdleStateChangeListener {
+
+ private static final String LOG_TAG = "AppIdleController";
+ private static final boolean DEBUG = false;
+
+ // Singleton factory
+ private static Object sCreationLock = new Object();
+ private static volatile AppIdleController sController;
+ final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
+ private final UsageStatsManagerInternal mUsageStatsInternal;
+ private final BatteryManager mBatteryManager;
+ private boolean mPluggedIn;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ onPluggedIn(mBatteryManager.isCharging());
+ }
+ };
+
+ public static AppIdleController get(JobSchedulerService service) {
+ synchronized (sCreationLock) {
+ if (sController == null) {
+ sController = new AppIdleController(service, service.getContext());
+ }
+ return sController;
+ }
+ }
+
+ private AppIdleController(StateChangedListener stateChangedListener, Context context) {
+ super(stateChangedListener, context);
+ mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
+ mBatteryManager = context.getSystemService(BatteryManager.class);
+ mPluggedIn = mBatteryManager.isCharging();
+ mUsageStatsInternal.addAppIdleStateChangeListener(this);
+ registerReceivers();
+ }
+
+ private void registerReceivers() {
+ // Monitor battery charging state
+ IntentFilter filter = new IntentFilter(BatteryManager.ACTION_CHARGING);
+ filter.addAction(BatteryManager.ACTION_DISCHARGING);
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ @Override
+ public void maybeStartTrackingJob(JobStatus jobStatus) {
+ synchronized (mTrackedTasks) {
+ mTrackedTasks.add(jobStatus);
+ String packageName = jobStatus.job.getService().getPackageName();
+ final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName,
+ jobStatus.getUserId());
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Start tracking, setting idle state of "
+ + packageName + " to " + appIdle);
+ }
+ jobStatus.appNotIdleConstraintSatisfied.set(!appIdle);
+ }
+ }
+
+ @Override
+ public void maybeStopTrackingJob(JobStatus jobStatus) {
+ synchronized (mTrackedTasks) {
+ mTrackedTasks.remove(jobStatus);
+ }
+ }
+
+ @Override
+ public void dumpControllerState(PrintWriter pw) {
+ pw.println("AppIdle");
+ pw.println("Plugged In: " + mPluggedIn);
+ synchronized (mTrackedTasks) {
+ for (JobStatus task : mTrackedTasks) {
+ pw.print(task.job.getService().getPackageName());
+ pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get());
+ pw.print(", ");
+ }
+ pw.println();
+ }
+ }
+
+ @Override
+ public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+ boolean changed = false;
+ synchronized (mTrackedTasks) {
+ // If currently plugged in, we don't care about app idle state
+ if (mPluggedIn) {
+ return;
+ }
+ for (JobStatus task : mTrackedTasks) {
+ if (task.job.getService().getPackageName().equals(packageName)
+ && task.getUserId() == userId) {
+ if (task.appNotIdleConstraintSatisfied.get() != !idle) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "App Idle state changed, setting idle state of "
+ + packageName + " to " + idle);
+ }
+ task.appNotIdleConstraintSatisfied.set(!idle);
+ changed = true;
+ }
+ }
+ }
+ }
+ if (changed) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+
+ void onPluggedIn(boolean pluggedIn) {
+ // Flag if any app's idle state has changed
+ boolean changed = false;
+ synchronized (mTrackedTasks) {
+ if (mPluggedIn == pluggedIn) {
+ return;
+ }
+ mPluggedIn = pluggedIn;
+ for (JobStatus task : mTrackedTasks) {
+ String packageName = task.job.getService().getPackageName();
+ final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName,
+ task.getUserId());
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Plugged in " + pluggedIn + ", setting idle state of "
+ + packageName + " to " + appIdle);
+ }
+ if (task.appNotIdleConstraintSatisfied.get() == appIdle) {
+ task.appNotIdleConstraintSatisfied.set(!appIdle);
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 309e034..7c2aead 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -47,10 +47,6 @@ public class BatteryController extends StateController {
private static final Object sCreationLock = new Object();
private static volatile BatteryController sController;
- private static final String ACTION_CHARGING_STABLE =
- "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE";
- /** Wait this long after phone is plugged in before doing any work. */
- private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes.
private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
private ChargingTracker mChargeTracker;
@@ -91,9 +87,6 @@ public class BatteryController extends StateController {
taskStatus.chargingConstraintSatisfied.set(isOnStablePower);
}
}
- if (isOnStablePower) {
- mChargeTracker.setStableChargingAlarm();
- }
}
@Override
@@ -131,8 +124,6 @@ public class BatteryController extends StateController {
}
public class ChargingTracker extends BroadcastReceiver {
- private final AlarmManager mAlarm;
- private final PendingIntent mStableChargingTriggerIntent;
/**
* Track whether we're "charging", where charging means that we're ready to commit to
* doing work.
@@ -142,9 +133,6 @@ public class BatteryController extends StateController {
private boolean mBatteryHealthy;
public ChargingTracker() {
- mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(ACTION_CHARGING_STABLE);
- mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
}
public void startTracking() {
@@ -154,10 +142,8 @@ public class BatteryController extends StateController {
filter.addAction(Intent.ACTION_BATTERY_LOW);
filter.addAction(Intent.ACTION_BATTERY_OKAY);
// Charging/not charging.
- filter.addAction(Intent.ACTION_POWER_CONNECTED);
- filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- // Charging stable.
- filter.addAction(ACTION_CHARGING_STABLE);
+ filter.addAction(BatteryManager.ACTION_CHARGING);
+ filter.addAction(BatteryManager.ACTION_DISCHARGING);
mContext.registerReceiver(this, filter);
// Initialise tracker state.
@@ -195,44 +181,20 @@ public class BatteryController extends StateController {
}
mBatteryHealthy = true;
maybeReportNewChargingState();
- } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+ } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
if (DEBUG) {
- Slog.d(TAG, "Received charging intent, setting alarm for "
- + STABLE_CHARGING_THRESHOLD_MILLIS);
+ Slog.d(TAG, "Received charging intent, fired @ "
+ + SystemClock.elapsedRealtime());
}
- // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks
- // here if the user unplugs the phone immediately.
- setStableChargingAlarm();
mCharging = true;
- } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ maybeReportNewChargingState();
+ } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
- Slog.d(TAG, "Disconnected from power, cancelling any set alarms.");
+ Slog.d(TAG, "Disconnected from power.");
}
- // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted.
- mAlarm.cancel(mStableChargingTriggerIntent);
mCharging = false;
maybeReportNewChargingState();
- }else if (ACTION_CHARGING_STABLE.equals(action)) {
- // Here's where we actually do the notify for a task being ready.
- if (DEBUG) {
- Slog.d(TAG, "Stable charging fired @ " + SystemClock.elapsedRealtime()
- + " charging: " + mCharging);
- }
- if (mCharging) { // Should never receive this intent if mCharging is false.
- maybeReportNewChargingState();
- }
- }
- }
-
- void setStableChargingAlarm() {
- final long alarmTriggerElapsed =
- SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS;
- if (DEBUG) {
- Slog.d(TAG, "Setting stable alarm to go off in " +
- (STABLE_CHARGING_THRESHOLD_MILLIS / 1000) + "s");
}
- mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTriggerElapsed,
- mStableChargingTriggerIntent);
}
}
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 7b71027..8e2ca18 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -22,7 +22,6 @@ import java.util.ArrayList;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index e3c55b6..69c63f3 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -54,6 +54,7 @@ public class JobStatus {
final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean unmeteredConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean();
+ final AtomicBoolean appNotIdleConstraintSatisfied = new AtomicBoolean();
/**
* Earliest point in the future at which this job will be eligible to run. A value of 0
@@ -199,8 +200,11 @@ public class JobStatus {
* the constraints are satisfied <strong>or</strong> the deadline on the job has expired.
*/
public synchronized boolean isReady() {
- return isConstraintsSatisfied()
- || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get());
+ // Deadline constraint trumps other constraints
+ // AppNotIdle implicit constraint trumps all!
+ return (isConstraintsSatisfied()
+ || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get()))
+ && appNotIdleConstraintSatisfied.get();
}
/**
@@ -229,6 +233,7 @@ public class JobStatus {
+ ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging()
+ ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
+ ",P=" + job.isPersisted()
+ + ",ANI=" + appNotIdleConstraintSatisfied.get()
+ (isReady() ? "(READY)" : "")
+ "]";
}
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index ca56886..cda7c32 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -18,7 +18,6 @@ package com.android.server.job.controllers;
import android.content.Context;
-import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
@@ -32,15 +31,20 @@ public abstract class StateController {
protected static final boolean DEBUG = false;
protected Context mContext;
protected StateChangedListener mStateChangedListener;
+ protected boolean mDeviceIdleMode;
public StateController(StateChangedListener stateChangedListener, Context context) {
mStateChangedListener = stateChangedListener;
mContext = context;
}
+ public void deviceIdleModeChanged(boolean enabled) {
+ mDeviceIdleMode = enabled;
+ }
+
/**
* Implement the logic here to decide whether a job should be tracked by this controller.
- * This logic is put here so the JobManger can be completely agnostic of Controller logic.
+ * This logic is put here so the JobManager can be completely agnostic of Controller logic.
* Also called when updating a task, so implementing controllers have to be aware of
* preexisting tasks.
*/
@@ -51,5 +55,4 @@ public abstract class StateController {
public abstract void maybeStopTrackingJob(JobStatus jobStatus);
public abstract void dumpControllerState(PrintWriter pw);
-
}
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index 4c6cb17..b3d7287 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -91,14 +91,20 @@ public class TimeController extends StateController {
public synchronized void maybeStartTrackingJob(JobStatus job) {
if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
maybeStopTrackingJob(job);
+ boolean isInsert = false;
ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
while (it.hasPrevious()) {
JobStatus ts = it.previous();
if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) {
// Insert
+ isInsert = true;
break;
}
}
+ if(isInsert)
+ {
+ it.next();
+ }
it.add(job);
maybeUpdateAlarms(
job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 9dcc529..ed884ef 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -19,16 +19,11 @@ package com.android.server.lights;
import com.android.server.SystemService;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Handler;
-import android.os.IHardwareService;
import android.os.Message;
import android.os.Trace;
import android.util.Slog;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-
public class LightsService extends SystemService {
static final String TAG = "LightsService";
static final boolean DEBUG = false;
@@ -106,7 +101,8 @@ public class LightsService extends SystemService {
mMode = mode;
mOnMS = onMS;
mOffMS = offMS;
- Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", " + color + ")");
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x"
+ + Integer.toHexString(color) + ")");
try {
setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
} finally {
@@ -123,46 +119,6 @@ public class LightsService extends SystemService {
private boolean mFlashing;
}
- /* This class implements an obsolete API that was removed after eclair and re-added during the
- * final moments of the froyo release to support flashlight apps that had been using the private
- * IHardwareService API. This is expected to go away in the next release.
- */
- private final IHardwareService.Stub mLegacyFlashlightHack = new IHardwareService.Stub() {
-
- private static final String FLASHLIGHT_FILE = "/sys/class/leds/spotlight/brightness";
-
- public boolean getFlashlightEnabled() {
- try {
- FileInputStream fis = new FileInputStream(FLASHLIGHT_FILE);
- int result = fis.read();
- fis.close();
- return (result != '0');
- } catch (Exception e) {
- return false;
- }
- }
-
- public void setFlashlightEnabled(boolean on) {
- final Context context = getContext();
- if (context.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT)
- != PackageManager.PERMISSION_GRANTED &&
- context.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires FLASHLIGHT or HARDWARE_TEST permission");
- }
- try {
- FileOutputStream fos = new FileOutputStream(FLASHLIGHT_FILE);
- byte[] bytes = new byte[2];
- bytes[0] = (byte)(on ? '1' : '0');
- bytes[1] = '\n';
- fos.write(bytes);
- fos.close();
- } catch (Exception e) {
- // fail silently
- }
- }
- };
-
public LightsService(Context context) {
super(context);
@@ -175,13 +131,12 @@ public class LightsService extends SystemService {
@Override
public void onStart() {
- publishBinderService("hardware", mLegacyFlashlightHack);
publishLocalService(LightsManager.class, mService);
}
private final LightsManager mService = new LightsManager() {
@Override
- public com.android.server.lights.Light getLight(int id) {
+ public Light getLight(int id) {
if (id < LIGHT_ID_COUNT) {
return mLights[id];
} else {
diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java
index 530ad4b..1fb22be 100644
--- a/services/core/java/com/android/server/location/FlpHardwareProvider.java
+++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java
@@ -42,8 +42,13 @@ import android.util.Log;
* {@hide}
*/
public class FlpHardwareProvider {
+ private static final int FIRST_VERSION_WITH_FLUSH_LOCATIONS = 2;
private GeofenceHardwareImpl mGeofenceHardwareSink = null;
private IFusedLocationHardwareSink mLocationSink = null;
+ // Capabilities provided by FlpCallbacks
+ private boolean mHaveBatchingCapabilities;
+ private int mBatchingCapabilities;
+ private int mVersion;
private static FlpHardwareProvider sSingletonInstance = null;
@@ -124,6 +129,52 @@ public class FlpHardwareProvider {
}
}
+ private void onBatchingCapabilities(int capabilities) {
+ synchronized (mLocationSinkLock) {
+ mHaveBatchingCapabilities = true;
+ mBatchingCapabilities = capabilities;
+ }
+
+ maybeSendCapabilities();
+ }
+
+ private void onBatchingStatus(int status) {
+ IFusedLocationHardwareSink sink;
+ synchronized (mLocationSinkLock) {
+ sink = mLocationSink;
+ }
+ try {
+ if (sink != null) {
+ sink.onStatusChanged(status);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling onBatchingStatus");
+ }
+ }
+
+ private void setVersion(int version) {
+ mVersion = version;
+ getGeofenceHardwareSink().setVersion(version);
+ }
+
+ private void maybeSendCapabilities() {
+ IFusedLocationHardwareSink sink;
+ boolean haveBatchingCapabilities;
+ int batchingCapabilities;
+ synchronized (mLocationSinkLock) {
+ sink = mLocationSink;
+ haveBatchingCapabilities = mHaveBatchingCapabilities;
+ batchingCapabilities = mBatchingCapabilities;
+ }
+ try {
+ if (sink != null && haveBatchingCapabilities) {
+ sink.onCapabilities(batchingCapabilities);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling onLocationAvailable");
+ }
+ }
+
// FlpDiagnosticCallbacks members
private void onDataReport(String data) {
IFusedLocationHardwareSink sink;
@@ -209,6 +260,10 @@ public class FlpHardwareProvider {
translateToGeofenceHardwareStatus(result));
}
+ private void onGeofencingCapabilities(int capabilities) {
+ getGeofenceHardwareSink().onCapabilities(capabilities);
+ }
+
/**
* Private native methods accessing FLP HAL.
*/
@@ -225,6 +280,7 @@ public class FlpHardwareProvider {
private native void nativeUpdateBatchingOptions(int requestId, FusedBatchOptions optionsObject);
private native void nativeStopBatching(int id);
private native void nativeRequestBatchedLocation(int lastNLocations);
+ private native void nativeFlushBatchedLocations();
private native void nativeInjectLocation(Location location);
// TODO [Fix] sort out the lifetime of the instance
private native void nativeCleanup();
@@ -277,6 +333,7 @@ public class FlpHardwareProvider {
mLocationSink = eventSink;
}
+ maybeSendCapabilities();
}
@Override
@@ -315,6 +372,16 @@ public class FlpHardwareProvider {
}
@Override
+ public void flushBatchedLocations() {
+ if (mVersion >= FIRST_VERSION_WITH_FLUSH_LOCATIONS) {
+ nativeFlushBatchedLocations();
+ } else {
+ Log.wtf(TAG,
+ "Tried to call flushBatchedLocations on an unsupported implementation");
+ }
+ }
+
+ @Override
public boolean supportsDiagnosticDataInjection() {
return nativeIsDiagnosticSupported();
}
@@ -333,6 +400,11 @@ public class FlpHardwareProvider {
public void injectDeviceContext(int deviceEnabledContext) {
nativeInjectDeviceContext(deviceEnabledContext);
}
+
+ @Override
+ public int getVersion() {
+ return mVersion;
+ }
};
private final IFusedGeofenceHardware mGeofenceHardwareService =
diff --git a/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java b/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java
index 389bd24..a08d326 100644
--- a/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java
+++ b/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java
@@ -116,4 +116,16 @@ public class FusedLocationHardwareSecure extends IFusedLocationHardware.Stub {
checkPermissions();
mLocationHardware.injectDeviceContext(deviceEnabledContext);
}
+
+ @Override
+ public void flushBatchedLocations() throws RemoteException {
+ checkPermissions();
+ mLocationHardware.flushBatchedLocations();
+ }
+
+ @Override
+ public int getVersion() throws RemoteException {
+ checkPermissions();
+ return mLocationHardware.getVersion();
+ }
}
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index 5ae6300..d3240ec 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -22,10 +22,8 @@ import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
-import com.android.internal.R;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
import android.app.AlarmManager;
import android.app.AppOpsManager;
@@ -72,7 +70,6 @@ import android.provider.Settings;
import android.provider.Telephony.Carriers;
import android.provider.Telephony.Sms.Intents;
import android.telephony.SmsMessage;
-import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
@@ -91,7 +88,6 @@ import java.io.StringReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Date;
-import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
@@ -201,6 +197,8 @@ public class GpsLocationProvider implements LocationProviderInterface {
private static final int REMOVE_LISTENER = 9;
private static final int INJECT_NTP_TIME_FINISHED = 10;
private static final int DOWNLOAD_XTRA_DATA_FINISHED = 11;
+ private static final int SUBSCRIPTION_OR_SIM_CHANGED = 12;
+ private static final int INITIALIZE_HANDLER = 13;
// Request setid
private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1;
@@ -342,8 +340,12 @@ public class GpsLocationProvider implements LocationProviderInterface {
// True if gps should be disabled (used to support battery saver mode in settings).
private boolean mDisableGps = false;
- // properties loaded from PROPERTIES_FILE
+ /**
+ * Properties loaded from PROPERTIES_FILE.
+ * It must be accessed only inside {@link #mHandler}.
+ */
private Properties mProperties;
+
private String mSuplServerHost;
private int mSuplServerPort = TCP_MIN_PORT;
private String mC2KServerHost;
@@ -395,7 +397,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
@Override
- public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
+ public void addGpsStatusListener(IGpsStatusListener listener) {
mListenerHelper.addListener(listener);
}
@@ -434,7 +436,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
checkSmsSuplInit(intent);
} else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) {
checkWapSuplInit(intent);
- } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE)) {
+ } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
// retrieve NetworkInfo result for this UID
NetworkInfo info =
intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
@@ -466,7 +468,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
- subscriptionOrSimChanged(mContext);
+ sendMessage(SUBSCRIPTION_OR_SIM_CHANGED, 0, null);
}
};
@@ -548,14 +550,19 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
}
- try {
- // Convert properties to string contents and send it to HAL.
- ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
- properties.store(baos, null);
- native_configuration_update(baos.toString());
- Log.d(TAG, "final config = " + baos.toString());
- } catch (IOException ex) {
- Log.w(TAG, "failed to dump properties contents");
+ if (native_is_gnss_configuration_supported()) {
+ try {
+ // Convert properties to string contents and send it to HAL.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
+ properties.store(baos, null);
+ native_configuration_update(baos.toString());
+ Log.d(TAG, "final config = " + baos.toString());
+ } catch (IOException ex) {
+ Log.w(TAG, "failed to dump properties contents");
+ }
+ } else if (DEBUG) {
+ Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not"
+ + " supported");
}
// SUPL_ES configuration.
@@ -631,57 +638,26 @@ public class GpsLocationProvider implements LocationProviderInterface {
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
- // Load GPS configuration.
+ // Construct internal handler
+ mHandler = new ProviderHandler(looper);
+
+ // Load GPS configuration and register listeners in the background:
+ // some operations, such as opening files and registering broadcast receivers, can take a
+ // relative long time, so the ctor() is kept to create objects needed by this instance,
+ // while IO initialization and registration is delegated to our internal handler
+ // this approach is just fine because events are posted to our handler anyway
mProperties = new Properties();
- reloadGpsProperties(mContext, mProperties);
+ sendMessage(INITIALIZE_HANDLER, 0, null);
// Create a GPS net-initiated handler.
mNIHandler = new GpsNetInitiatedHandler(context,
mNetInitiatedListener,
mSuplEsEnabled);
- // TODO: When this object "finishes" we should unregister by invoking
- // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener);
- // This is not strictly necessary because it will be unregistered if the
- // notification fails but it is good form.
-
- // Register for SubscriptionInfo list changes which is guaranteed
- // to invoke onSubscriptionsChanged the first time.
- SubscriptionManager.from(mContext)
- .addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
-
- // construct handler, listen for events
- mHandler = new ProviderHandler(looper);
- listenForBroadcasts();
-
- // also listen for PASSIVE_PROVIDER updates
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- LocationManager locManager =
- (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
- final long minTime = 0;
- final float minDistance = 0;
- final boolean oneShot = false;
- LocationRequest request = LocationRequest.createFromDeprecatedProvider(
- LocationManager.PASSIVE_PROVIDER,
- minTime,
- minDistance,
- oneShot);
- // Don't keep track of this request since it's done on behalf of other clients
- // (which are kept track of separately).
- request.setHideFromAppOps(true);
- locManager.requestLocationUpdates(
- request,
- new NetworkLocationListener(),
- mHandler.getLooper());
- }
- });
-
mListenerHelper = new GpsStatusListenerHelper(mHandler) {
@Override
protected boolean isAvailableInPlatform() {
- return GpsLocationProvider.isSupported();
+ return isSupported();
}
@Override
@@ -735,33 +711,6 @@ public class GpsLocationProvider implements LocationProviderInterface {
};
}
- private void listenForBroadcasts() {
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
- intentFilter.addDataScheme("sms");
- intentFilter.addDataAuthority("localhost","7275");
- mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler);
-
- intentFilter = new IntentFilter();
- intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
- try {
- intentFilter.addDataType("application/vnd.omaloc-supl-init");
- } catch (IntentFilter.MalformedMimeTypeException e) {
- Log.w(TAG, "Malformed SUPL init mime type");
- }
- mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler);
-
- intentFilter = new IntentFilter();
- intentFilter.addAction(ALARM_WAKEUP);
- intentFilter.addAction(ALARM_TIMEOUT);
- intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE);
- intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
- intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
- intentFilter.addAction(Intent.ACTION_SCREEN_ON);
- intentFilter.addAction(SIM_STATE_CHANGED);
- mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler);
- }
-
/**
* Returns the name of this provider.
*/
@@ -788,16 +737,21 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
if (info != null) {
- boolean dataEnabled = TelephonyManager.getDefault().getDataEnabled();
- boolean networkAvailable = info.isAvailable() && dataEnabled;
- String defaultApn = getSelectedApn();
- if (defaultApn == null) {
- defaultApn = "dummy-apn";
- }
+ if (native_is_agps_ril_supported()) {
+ boolean dataEnabled = TelephonyManager.getDefault().getDataEnabled();
+ boolean networkAvailable = info.isAvailable() && dataEnabled;
+ String defaultApn = getSelectedApn();
+ if (defaultApn == null) {
+ defaultApn = "dummy-apn";
+ }
- native_update_network_state(info.isConnected(), info.getType(),
- info.isRoaming(), networkAvailable,
- info.getExtraInfo(), defaultApn);
+ native_update_network_state(info.isConnected(), info.getType(),
+ info.isRoaming(), networkAvailable,
+ info.getExtraInfo(), defaultApn);
+ } else if (DEBUG) {
+ Log.d(TAG, "Skipped network state update because AGPS-RIL in GPS HAL is not"
+ + " supported");
+ }
}
if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL
@@ -914,7 +868,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
- GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mContext, mProperties);
+ GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mProperties);
byte[] data = xtraDownloader.downloadXtraData();
if (data != null) {
if (DEBUG) {
@@ -1025,6 +979,9 @@ public class GpsLocationProvider implements LocationProviderInterface {
if (mC2KServerHost != null) {
native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort);
}
+
+ mGpsMeasurementsProvider.onGpsEnabledChanged();
+ mGpsNavigationMessageProvider.onGpsEnabledChanged();
} else {
synchronized (mLock) {
mEnabled = false;
@@ -1058,6 +1015,9 @@ public class GpsLocationProvider implements LocationProviderInterface {
// do this before releasing wakelock
native_cleanup();
+
+ mGpsMeasurementsProvider.onGpsEnabledChanged();
+ mGpsNavigationMessageProvider.onGpsEnabledChanged();
}
@Override
@@ -1477,9 +1437,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
if (wasNavigating != mNavigating) {
- mListenerHelper.onGpsEnabledChanged(mNavigating);
- mGpsMeasurementsProvider.onGpsEnabledChanged(mNavigating);
- mGpsNavigationMessageProvider.onGpsEnabledChanged(mNavigating);
+ mListenerHelper.onStatusChanged(mNavigating);
// send an intent to notify that the GPS has been enabled or disabled
Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
@@ -1804,7 +1762,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
// NI Client support
//=============================================================
private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() {
- // Sends a response for an NI reqeust to HAL.
+ // Sends a response for an NI request to HAL.
@Override
public boolean sendNiResponse(int notificationId, int userResponse)
{
@@ -1895,7 +1853,7 @@ public class GpsLocationProvider implements LocationProviderInterface {
private void requestSetID(int flags) {
TelephonyManager phone = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
- int type = AGPS_SETID_TYPE_NONE;
+ int type = AGPS_SETID_TYPE_NONE;
String data = "";
if ((flags & AGPS_RIL_REQUEST_SETID_IMSI) == AGPS_RIL_REQUEST_SETID_IMSI) {
@@ -2012,13 +1970,91 @@ public class GpsLocationProvider implements LocationProviderInterface {
case UPDATE_LOCATION:
handleUpdateLocation((Location)msg.obj);
break;
+ case SUBSCRIPTION_OR_SIM_CHANGED:
+ subscriptionOrSimChanged(mContext);
+ break;
+ case INITIALIZE_HANDLER:
+ initialize();
+ break;
}
if (msg.arg2 == 1) {
// wakelock was taken for this message, release it
mWakeLock.release();
}
}
- };
+
+ /**
+ * This method is bound to {@link #GpsLocationProvider(Context, ILocationManager, Looper)}.
+ * It is in charge of loading properties and registering for events that will be posted to
+ * this handler.
+ */
+ private void initialize() {
+ // load default GPS configuration
+ // (this configuration might change in the future based on SIM changes)
+ reloadGpsProperties(mContext, mProperties);
+
+ // TODO: When this object "finishes" we should unregister by invoking
+ // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener);
+ // This is not strictly necessary because it will be unregistered if the
+ // notification fails but it is good form.
+
+ // Register for SubscriptionInfo list changes which is guaranteed
+ // to invoke onSubscriptionsChanged the first time.
+ SubscriptionManager.from(mContext)
+ .addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+
+ // listen for events
+ IntentFilter intentFilter;
+ if (native_is_agps_ril_supported()) {
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
+ intentFilter.addDataScheme("sms");
+ intentFilter.addDataAuthority("localhost", "7275");
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this);
+
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
+ try {
+ intentFilter.addDataType("application/vnd.omaloc-supl-init");
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ Log.w(TAG, "Malformed SUPL init mime type");
+ }
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this);
+ } else if (DEBUG) {
+ Log.d(TAG, "Skipped registration for SMS/WAP-PUSH messages because AGPS Ril in GPS"
+ + " HAL is not supported");
+ }
+
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(ALARM_WAKEUP);
+ intentFilter.addAction(ALARM_TIMEOUT);
+ intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(SIM_STATE_CHANGED);
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this);
+
+ // listen for PASSIVE_PROVIDER updates
+ LocationManager locManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ long minTime = 0;
+ float minDistance = 0;
+ boolean oneShot = false;
+ LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+ LocationManager.PASSIVE_PROVIDER,
+ minTime,
+ minDistance,
+ oneShot);
+ // Don't keep track of this request since it's done on behalf of other clients
+ // (which are kept track of separately).
+ request.setHideFromAppOps(true);
+ locManager.requestLocationUpdates(
+ request,
+ new NetworkLocationListener(),
+ getLooper());
+ }
+ }
private final class NetworkLocationListener implements LocationListener {
@Override
@@ -2167,6 +2203,8 @@ public class GpsLocationProvider implements LocationProviderInterface {
static { class_init_native(); }
private static native void class_init_native();
private static native boolean native_is_supported();
+ private static native boolean native_is_agps_ril_supported();
+ private static native boolean native_is_gnss_configuration_supported();
private native boolean native_init();
private native void native_cleanup();
diff --git a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java
index 0514e0c..b327ca2 100644
--- a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java
@@ -33,7 +33,7 @@ public abstract class GpsMeasurementsProvider
extends RemoteListenerHelper<IGpsMeasurementsListener> {
private static final String TAG = "GpsMeasurementsProvider";
- public GpsMeasurementsProvider(Handler handler) {
+ protected GpsMeasurementsProvider(Handler handler) {
super(handler, TAG);
}
@@ -49,15 +49,19 @@ public abstract class GpsMeasurementsProvider
}
public void onCapabilitiesUpdated(boolean isGpsMeasurementsSupported) {
- int status = isGpsMeasurementsSupported ?
- GpsMeasurementsEvent.STATUS_READY :
- GpsMeasurementsEvent.STATUS_NOT_SUPPORTED;
- setSupported(isGpsMeasurementsSupported, new StatusChangedOperation(status));
+ setSupported(isGpsMeasurementsSupported);
+ updateResult();
+ }
+
+ public void onGpsEnabledChanged() {
+ if (tryUpdateRegistrationWithService()) {
+ updateResult();
+ }
}
@Override
protected ListenerOperation<IGpsMeasurementsListener> getHandlerOperation(int result) {
- final int status;
+ int status;
switch (result) {
case RESULT_SUCCESS:
status = GpsMeasurementsEvent.STATUS_READY;
@@ -70,6 +74,8 @@ public abstract class GpsMeasurementsProvider
case RESULT_GPS_LOCATION_DISABLED:
status = GpsMeasurementsEvent.STATUS_GPS_LOCATION_DISABLED;
break;
+ case RESULT_UNKNOWN:
+ return null;
default:
Log.v(TAG, "Unhandled addListener result: " + result);
return null;
@@ -77,15 +83,8 @@ public abstract class GpsMeasurementsProvider
return new StatusChangedOperation(status);
}
- @Override
- protected void handleGpsEnabledChanged(boolean enabled) {
- int status = enabled ?
- GpsMeasurementsEvent.STATUS_READY :
- GpsMeasurementsEvent.STATUS_GPS_LOCATION_DISABLED;
- foreach(new StatusChangedOperation(status));
- }
-
- private class StatusChangedOperation implements ListenerOperation<IGpsMeasurementsListener> {
+ private static class StatusChangedOperation
+ implements ListenerOperation<IGpsMeasurementsListener> {
private final int mStatus;
public StatusChangedOperation(int status) {
diff --git a/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java b/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java
index 13d22fc..e6bbe56 100644
--- a/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java
@@ -33,7 +33,7 @@ public abstract class GpsNavigationMessageProvider
extends RemoteListenerHelper<IGpsNavigationMessageListener> {
private static final String TAG = "GpsNavigationMessageProvider";
- public GpsNavigationMessageProvider(Handler handler) {
+ protected GpsNavigationMessageProvider(Handler handler) {
super(handler, TAG);
}
@@ -50,15 +50,19 @@ public abstract class GpsNavigationMessageProvider
}
public void onCapabilitiesUpdated(boolean isGpsNavigationMessageSupported) {
- int status = isGpsNavigationMessageSupported ?
- GpsNavigationMessageEvent.STATUS_READY :
- GpsNavigationMessageEvent.STATUS_NOT_SUPPORTED;
- setSupported(isGpsNavigationMessageSupported, new StatusChangedOperation(status));
+ setSupported(isGpsNavigationMessageSupported);
+ updateResult();
+ }
+
+ public void onGpsEnabledChanged() {
+ if (tryUpdateRegistrationWithService()) {
+ updateResult();
+ }
}
@Override
protected ListenerOperation<IGpsNavigationMessageListener> getHandlerOperation(int result) {
- final int status;
+ int status;
switch (result) {
case RESULT_SUCCESS:
status = GpsNavigationMessageEvent.STATUS_READY;
@@ -71,6 +75,8 @@ public abstract class GpsNavigationMessageProvider
case RESULT_GPS_LOCATION_DISABLED:
status = GpsNavigationMessageEvent.STATUS_GPS_LOCATION_DISABLED;
break;
+ case RESULT_UNKNOWN:
+ return null;
default:
Log.v(TAG, "Unhandled addListener result: " + result);
return null;
@@ -78,15 +84,7 @@ public abstract class GpsNavigationMessageProvider
return new StatusChangedOperation(status);
}
- @Override
- protected void handleGpsEnabledChanged(boolean enabled) {
- int status = enabled ?
- GpsNavigationMessageEvent.STATUS_READY :
- GpsNavigationMessageEvent.STATUS_GPS_LOCATION_DISABLED;
- foreach(new StatusChangedOperation(status));
- }
-
- private class StatusChangedOperation
+ private static class StatusChangedOperation
implements ListenerOperation<IGpsNavigationMessageListener> {
private final int mStatus;
diff --git a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java
index 376b4a5..53ff6c2 100644
--- a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java
+++ b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java
@@ -24,14 +24,9 @@ import android.os.RemoteException;
* Implementation of a handler for {@link IGpsStatusListener}.
*/
abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusListener> {
- public GpsStatusListenerHelper(Handler handler) {
+ protected GpsStatusListenerHelper(Handler handler) {
super(handler, "GpsStatusListenerHelper");
-
- Operation nullOperation = new Operation() {
- @Override
- public void execute(IGpsStatusListener iGpsStatusListener) throws RemoteException {}
- };
- setSupported(GpsLocationProvider.isSupported(), nullOperation);
+ setSupported(GpsLocationProvider.isSupported());
}
@Override
@@ -47,10 +42,9 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusLi
return null;
}
- @Override
- protected void handleGpsEnabledChanged(boolean enabled) {
+ public void onStatusChanged(boolean isNavigating) {
Operation operation;
- if (enabled) {
+ if (isNavigating) {
operation = new Operation() {
@Override
public void execute(IGpsStatusListener listener) throws RemoteException {
@@ -114,5 +108,5 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusLi
foreach(operation);
}
- private abstract class Operation implements ListenerOperation<IGpsStatusListener> { }
+ private interface Operation extends ListenerOperation<IGpsStatusListener> {}
}
diff --git a/services/core/java/com/android/server/location/GpsXtraDownloader.java b/services/core/java/com/android/server/location/GpsXtraDownloader.java
index a5eef6a..3585049 100644
--- a/services/core/java/com/android/server/location/GpsXtraDownloader.java
+++ b/services/core/java/com/android/server/location/GpsXtraDownloader.java
@@ -16,21 +16,13 @@
package com.android.server.location;
-import android.content.Context;
-import android.net.Proxy;
-import android.net.http.AndroidHttpClient;
import android.text.TextUtils;
import android.util.Log;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.conn.params.ConnRouteParams;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import libcore.io.Streams;
-import java.io.DataInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Random;
@@ -46,15 +38,12 @@ public class GpsXtraDownloader {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String DEFAULT_USER_AGENT = "Android";
- private final Context mContext;
private final String[] mXtraServers;
// to load balance our server requests
private int mNextServerIndex;
private final String mUserAgent;
- GpsXtraDownloader(Context context, Properties properties) {
- mContext = context;
-
+ GpsXtraDownloader(Properties properties) {
// read XTRA servers from the Properties object
int count = 0;
String server1 = properties.getProperty("XTRA_SERVER_1");
@@ -75,7 +64,6 @@ public class GpsXtraDownloader {
if (count == 0) {
Log.e(TAG, "No XTRA servers were specified in the GPS configuration");
mXtraServers = null;
- return;
} else {
mXtraServers = new String[count];
count = 0;
@@ -90,9 +78,6 @@ public class GpsXtraDownloader {
}
byte[] downloadXtraData() {
- String proxyHost = Proxy.getHost(mContext);
- int proxyPort = Proxy.getPort(mContext);
- boolean useProxy = (proxyHost != null && proxyPort != -1);
byte[] result = null;
int startIndex = mNextServerIndex;
@@ -102,7 +87,7 @@ public class GpsXtraDownloader {
// load balance our requests among the available servers
while (result == null) {
- result = doDownload(mXtraServers[mNextServerIndex], useProxy, proxyHost, proxyPort);
+ result = doDownload(mXtraServers[mNextServerIndex]);
// increment mNextServerIndex and wrap around if necessary
mNextServerIndex++;
@@ -116,65 +101,32 @@ public class GpsXtraDownloader {
return result;
}
- protected byte[] doDownload(String url, boolean isProxySet,
- String proxyHost, int proxyPort) {
+ protected byte[] doDownload(String url) {
if (DEBUG) Log.d(TAG, "Downloading XTRA data from " + url);
- AndroidHttpClient client = null;
+ HttpURLConnection connection = null;
try {
- if (DEBUG) Log.d(TAG, "XTRA user agent: " + mUserAgent);
- client = AndroidHttpClient.newInstance(mUserAgent);
- HttpUriRequest req = new HttpGet(url);
-
- if (isProxySet) {
- HttpHost proxy = new HttpHost(proxyHost, proxyPort);
- ConnRouteParams.setDefaultProxy(req.getParams(), proxy);
- }
-
- req.addHeader(
+ connection = (HttpURLConnection) (new URL(url)).openConnection();
+ connection.setRequestProperty(
"Accept",
"*/*, application/vnd.wap.mms-message, application/vnd.wap.sic");
-
- req.addHeader(
+ connection.setRequestProperty(
"x-wap-profile",
"http://www.openmobilealliance.org/tech/profiles/UAPROF/ccppschema-20021212#");
- HttpResponse response = client.execute(req);
- StatusLine status = response.getStatusLine();
- if (status.getStatusCode() != 200) { // HTTP 200 is success.
- if (DEBUG) Log.d(TAG, "HTTP error: " + status.getReasonPhrase());
+ connection.connect();
+ int statusCode = connection.getResponseCode();
+ if (statusCode != HttpURLConnection.HTTP_OK) {
+ if (DEBUG) Log.d(TAG, "HTTP error downloading gps XTRA: " + statusCode);
return null;
}
- HttpEntity entity = response.getEntity();
- byte[] body = null;
- if (entity != null) {
- try {
- if (entity.getContentLength() > 0) {
- body = new byte[(int) entity.getContentLength()];
- DataInputStream dis = new DataInputStream(entity.getContent());
- try {
- dis.readFully(body);
- } finally {
- try {
- dis.close();
- } catch (IOException e) {
- Log.e(TAG, "Unexpected IOException.", e);
- }
- }
- }
- } finally {
- if (entity != null) {
- entity.consumeContent();
- }
- }
- }
- return body;
- } catch (Exception e) {
- if (DEBUG) Log.d(TAG, "error " + e);
+ return Streams.readFully(connection.getInputStream());
+ } catch (IOException ioe) {
+ if (DEBUG) Log.d(TAG, "Error downloading gps XTRA: ", ioe);
} finally {
- if (client != null) {
- client.close();
+ if (connection != null) {
+ connection.disconnect();
}
}
return null;
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index 402b601..ec2828b 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -26,26 +26,31 @@ import android.os.RemoteException;
import android.util.Log;
import java.util.HashMap;
+import java.util.Map;
/**
* A helper class, that handles operations in remote listeners, and tracks for remote process death.
*/
abstract class RemoteListenerHelper<TListener extends IInterface> {
+
protected static final int RESULT_SUCCESS = 0;
protected static final int RESULT_NOT_AVAILABLE = 1;
protected static final int RESULT_NOT_SUPPORTED = 2;
protected static final int RESULT_GPS_LOCATION_DISABLED = 3;
protected static final int RESULT_INTERNAL_ERROR = 4;
+ protected static final int RESULT_UNKNOWN = 5;
private final Handler mHandler;
private final String mTag;
- private final HashMap<IBinder, LinkedListener> mListenerMap = new HashMap<>();
+ private final Map<IBinder, LinkedListener> mListenerMap = new HashMap<>();
private boolean mIsRegistered;
private boolean mHasIsSupported;
private boolean mIsSupported;
+ private int mLastReportedResult = RESULT_UNKNOWN;
+
protected RemoteListenerHelper(Handler handler, String name) {
Preconditions.checkNotNull(name);
mHandler = handler;
@@ -110,33 +115,11 @@ abstract class RemoteListenerHelper<TListener extends IInterface> {
}
}
- public void onGpsEnabledChanged(boolean enabled) {
- // handle first the sub-class implementation, so any error in registration can take
- // precedence
- handleGpsEnabledChanged(enabled);
- synchronized (mListenerMap) {
- if (!enabled) {
- tryUnregister();
- return;
- }
- if (mListenerMap.isEmpty()) {
- return;
- }
- if (tryRegister()) {
- // registration was successful, there is no need to update the state
- return;
- }
- ListenerOperation<TListener> operation = getHandlerOperation(RESULT_INTERNAL_ERROR);
- foreachUnsafe(operation);
- }
- }
-
protected abstract boolean isAvailableInPlatform();
protected abstract boolean isGpsEnabled();
protected abstract boolean registerWithService();
protected abstract void unregisterFromService();
protected abstract ListenerOperation<TListener> getHandlerOperation(int result);
- protected abstract void handleGpsEnabledChanged(boolean enabled);
protected interface ListenerOperation<TListener extends IInterface> {
void execute(TListener listener) throws RemoteException;
@@ -148,11 +131,40 @@ abstract class RemoteListenerHelper<TListener extends IInterface> {
}
}
- protected void setSupported(boolean value, ListenerOperation<TListener> notifier) {
+ protected void setSupported(boolean value) {
synchronized (mListenerMap) {
mHasIsSupported = true;
mIsSupported = value;
- foreachUnsafe(notifier);
+ }
+ }
+
+ protected boolean tryUpdateRegistrationWithService() {
+ synchronized (mListenerMap) {
+ if (!isGpsEnabled()) {
+ tryUnregister();
+ return true;
+ }
+ if (mListenerMap.isEmpty()) {
+ return true;
+ }
+ if (tryRegister()) {
+ // registration was successful, there is no need to update the state
+ return true;
+ }
+ ListenerOperation<TListener> operation = getHandlerOperation(RESULT_INTERNAL_ERROR);
+ foreachUnsafe(operation);
+ return false;
+ }
+ }
+
+ protected void updateResult() {
+ synchronized (mListenerMap) {
+ int newResult = calculateCurrentResultUnsafe();
+ if (mLastReportedResult == newResult) {
+ return;
+ }
+ foreachUnsafe(getHandlerOperation(newResult));
+ mLastReportedResult = newResult;
}
}
@@ -183,6 +195,24 @@ abstract class RemoteListenerHelper<TListener extends IInterface> {
mIsRegistered = false;
}
+ private int calculateCurrentResultUnsafe() {
+ // update statuses we already know about, starting from the ones that will never change
+ if (!isAvailableInPlatform()) {
+ return RESULT_NOT_AVAILABLE;
+ }
+ if (!mHasIsSupported || mListenerMap.isEmpty()) {
+ // we'll update once we have a supported status available
+ return RESULT_UNKNOWN;
+ }
+ if (!mIsSupported) {
+ return RESULT_NOT_SUPPORTED;
+ }
+ if (!isGpsEnabled()) {
+ return RESULT_GPS_LOCATION_DISABLED;
+ }
+ return RESULT_SUCCESS;
+ }
+
private class LinkedListener implements IBinder.DeathRecipient {
private final TListener mListener;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 53ae1ab..09d0501 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -17,7 +17,6 @@
package com.android.server.media;
import android.app.PendingIntent;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
@@ -28,6 +27,9 @@ import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.media.routing.IMediaRouter;
+import android.media.routing.IMediaRouterDelegate;
+import android.media.routing.IMediaRouterStateCallback;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.ISessionController;
@@ -35,7 +37,6 @@ import android.media.session.ISessionControllerCallback;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
import android.media.session.ParcelableVolumeInfo;
import android.media.session.PlaybackState;
import android.media.AudioAttributes;
@@ -58,7 +59,6 @@ import com.android.server.LocalServices;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.UUID;
/**
* This is the system implementation of a Session. Apps will interact with the
@@ -91,7 +91,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
private final SessionStub mSession;
private final SessionCb mSessionCb;
private final MediaSessionService mService;
- private final boolean mUseMasterVolume;
private final Object mLock = new Object();
private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
@@ -141,8 +140,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
- mUseMasterVolume = service.getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
}
/**
@@ -246,77 +243,30 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
flags &= ~AudioManager.FLAG_PLAY_SOUND;
}
- boolean isMute = direction == MediaSessionManager.DIRECTION_MUTE;
- if (direction > 1) {
- direction = 1;
- } else if (direction < -1) {
- direction = -1;
- }
if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
- if (mUseMasterVolume) {
- // If this device only uses master volume and playback is local
- // just adjust the master volume and return.
- boolean isMasterMute = mAudioManager.isMasterMute();
- if (isMute) {
- mAudioManagerInternal.setMasterMuteForUid(!isMasterMute,
- flags, packageName, mService.mICallback, uid);
- } else {
- mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName,
- uid);
- if (isMasterMute) {
- mAudioManagerInternal.setMasterMuteForUid(false,
- flags, packageName, mService.mICallback, uid);
- }
- }
- return;
- }
int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
- boolean isStreamMute = mAudioManager.isStreamMute(stream);
if (useSuggested) {
if (AudioSystem.isStreamActive(stream, 0)) {
- if (isMute) {
- mAudioManager.setStreamMute(stream, !isStreamMute);
- } else {
- mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
- flags, packageName, uid);
- if (isStreamMute && direction != 0) {
- mAudioManager.setStreamMute(stream, false);
- }
- }
+ mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
+ flags, packageName, uid);
} else {
flags |= previousFlagPlaySound;
- isStreamMute =
- mAudioManager.isStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE);
- if (isMute) {
- mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE,
- !isStreamMute);
- } else {
- mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
- AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName,
- uid);
- if (isStreamMute && direction != 0) {
- mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE,
- false);
- }
- }
+ mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
+ AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName,
+ uid);
}
} else {
- if (isMute) {
- mAudioManager.setStreamMute(stream, !isStreamMute);
- } else {
- mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
- packageName, uid);
- if (isStreamMute && direction != 0) {
- mAudioManager.setStreamMute(stream, false);
- }
- }
+ mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
+ packageName, uid);
}
} else {
if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
// Nothing to do, the volume cannot be changed
return;
}
- if (isMute) {
+ if (direction == AudioManager.ADJUST_TOGGLE_MUTE
+ || direction == AudioManager.ADJUST_MUTE
+ || direction == AudioManager.ADJUST_UNMUTE) {
Log.w(TAG, "Muting remote playback is not supported");
return;
}
@@ -768,6 +718,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
+ public void setMediaRouter(IMediaRouter router) {
+ mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
+ }
+
+ @Override
public void setMediaButtonReceiver(PendingIntent pi) {
mMediaButtonReceiver = pi;
}
@@ -854,6 +809,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
if (typeChanged) {
mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
+ mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
}
}
@@ -868,6 +824,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
if (typeChanged) {
mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
+ mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
}
}
}
@@ -931,6 +888,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
}
+ public void playFromUri(Uri uri, Bundle extras) {
+ try {
+ mCb.onPlayFromUri(uri, extras);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in playFromUri.", e);
+ }
+ }
+
public void skipToTrack(long id) {
try {
mCb.onSkipToTrack(id);
@@ -1147,6 +1112,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
+ public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
+ mSessionCb.playFromUri(uri, extras);
+ }
+
+ @Override
public void skipToQueueItem(long id) {
mSessionCb.skipToTrack(id);
}
@@ -1239,6 +1209,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
public boolean isTransportControlEnabled() {
return MediaSessionRecord.this.isTransportControlEnabled();
}
+
+ @Override
+ public IMediaRouterDelegate createMediaRouterDelegate(
+ IMediaRouterStateCallback callback) {
+ // todo
+ return null;
+ }
}
private class MessageHandler extends Handler {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 72e4b4b..65949bf 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -31,6 +31,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.media.AudioManager;
+import android.media.AudioManagerInternal;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
@@ -38,9 +39,7 @@ import android.media.session.IActiveSessionsListener;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.ISessionManager;
-import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -60,6 +59,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyEvent;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.Watchdog.Monitor;
@@ -90,11 +90,10 @@ public class MediaSessionService extends SystemService implements Monitor {
private final Object mLock = new Object();
private final MessageHandler mHandler = new MessageHandler();
private final PowerManager.WakeLock mMediaEventWakeLock;
- private final boolean mUseMasterVolume;
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
- private AudioManager mAudioManager;
+ private AudioManagerInternal mAudioManagerInternal;
private ContentResolver mContentResolver;
private SettingsObserver mSettingsObserver;
@@ -110,22 +109,21 @@ public class MediaSessionService extends SystemService implements Monitor {
mPriorityStack = new MediaSessionStack();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
- mUseMasterVolume = context.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
}
@Override
public void onStart() {
publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
Watchdog.getInstance().addMonitor(this);
- updateUser();
mKeyguardManager =
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
mAudioService = getAudioService();
- mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
mContentResolver = getContext().getContentResolver();
mSettingsObserver = new SettingsObserver();
mSettingsObserver.observe();
+
+ updateUser();
}
private IAudioService getAudioService() {
@@ -334,6 +332,7 @@ public class MediaSessionService extends SystemService implements Monitor {
*/
private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
int resolvedUserId) {
+ if (isCurrentVolumeController(uid)) return;
if (getContext()
.checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
!= PackageManager.PERMISSION_GRANTED
@@ -343,7 +342,18 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
- private void enforceStatusBarPermission(String action, int pid, int uid) {
+ private boolean isCurrentVolumeController(int uid) {
+ if (mAudioManagerInternal != null) {
+ final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
+ if (vcuid > 0 && uid == vcuid) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void enforceSystemUiPermission(String action, int pid, int uid) {
+ if (isCurrentVolumeController(uid)) return;
if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
pid, uid) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Only system ui may " + action);
@@ -454,11 +464,6 @@ public class MediaSessionService extends SystemService implements Monitor {
return -1;
}
- private boolean isSessionDiscoverable(MediaSessionRecord record) {
- // TODO probably want to check more than if it's active.
- return record.isActive();
- }
-
private void pushSessionsChanged(int userId) {
synchronized (mLock) {
List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
@@ -502,6 +507,12 @@ public class MediaSessionService extends SystemService implements Monitor {
UserRecord user = mUserRecords.get(record.getUserId());
if (receiver != null && user != null) {
user.mLastMediaButtonReceiver = receiver;
+ ComponentName component = receiver.getIntent().getComponent();
+ if (component != null && record.getPackageName().equals(component.getPackageName())) {
+ Settings.Secure.putStringForUser(mContentResolver,
+ Settings.System.MEDIA_BUTTON_RECEIVER, component.flattenToString(),
+ record.getUserId());
+ }
}
}
@@ -512,14 +523,17 @@ public class MediaSessionService extends SystemService implements Monitor {
final class UserRecord {
private final int mUserId;
private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+ private final Context mContext;
private PendingIntent mLastMediaButtonReceiver;
+ private ComponentName mRestoredMediaButtonReceiver;
public UserRecord(Context context, int userId) {
+ mContext = context;
mUserId = userId;
+ restoreMediaButtonReceiver();
}
public void startLocked() {
- // nothing for now
}
public void stopLocked() {
@@ -549,6 +563,7 @@ public class MediaSessionService extends SystemService implements Monitor {
pw.println(prefix + "Record for user " + mUserId);
String indent = prefix + " ";
pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
+ pw.println(indent + "Restored ButtonReceiver:" + mRestoredMediaButtonReceiver);
int size = mSessions.size();
pw.println(indent + size + " Sessions:");
for (int i = 0; i < size; i++) {
@@ -557,6 +572,19 @@ public class MediaSessionService extends SystemService implements Monitor {
pw.println(indent + mSessions.get(i).toString());
}
}
+
+ private void restoreMediaButtonReceiver() {
+ String receiverName = Settings.Secure.getStringForUser(mContentResolver,
+ Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT);
+ if (!TextUtils.isEmpty(receiverName)) {
+ ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName);
+ if (eventReceiver == null) {
+ // an invalid name was persisted
+ return;
+ }
+ mRestoredMediaButtonReceiver = eventReceiver;
+ }
+ }
}
final class SessionsListenerRecord implements IBinder.DeathRecipient {
@@ -721,6 +749,10 @@ public class MediaSessionService extends SystemService implements Monitor {
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
+ + keyEvent);
+ }
if (!isUserSetupComplete()) {
// Global media key handling can have the side-effect of starting new
// activities which is undesirable while setup is in progress.
@@ -732,8 +764,9 @@ public class MediaSessionService extends SystemService implements Monitor {
synchronized (mLock) {
// If we don't have a media button receiver to fall back on
// include non-playing sessions for dispatching
- boolean useNotPlayingSessions = mUserRecords.get(
- ActivityManager.getCurrentUser()).mLastMediaButtonReceiver == null;
+ UserRecord ur = mUserRecords.get(ActivityManager.getCurrentUser());
+ boolean useNotPlayingSessions = ur.mLastMediaButtonReceiver == null
+ && ur.mRestoredMediaButtonReceiver == null;
MediaSessionRecord session = mPriorityStack
.getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions);
if (isVoiceKey(keyEvent.getKeyCode())) {
@@ -769,7 +802,7 @@ public class MediaSessionService extends SystemService implements Monitor {
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
- enforceStatusBarPermission("listen for volume changes", pid, uid);
+ enforceSystemUiPermission("listen for volume changes", pid, uid);
mRvc = rvc;
} finally {
Binder.restoreCallingIdentity(token);
@@ -807,7 +840,7 @@ public class MediaSessionService extends SystemService implements Monitor {
pw.println("User Records:");
count = mUserRecords.size();
for (int i = 0; i < count; i++) {
- UserRecord user = mUserRecords.get(i);
+ UserRecord user = mUserRecords.get(mUserRecords.keyAt(i));
user.dumpLocked(pw, "");
}
}
@@ -855,32 +888,8 @@ public class MediaSessionService extends SystemService implements Monitor {
}
try {
String packageName = getContext().getOpPackageName();
- if (mUseMasterVolume) {
- boolean isMasterMute = mAudioService.isMasterMute();
- if (direction == MediaSessionManager.DIRECTION_MUTE) {
- mAudioService.setMasterMute(!isMasterMute, flags, packageName, mICallback);
- } else {
- mAudioService.adjustMasterVolume(direction, flags, packageName);
- // Do not call setMasterMute when direction = 0 which is used just to
- // show the UI.
- if (isMasterMute && direction != 0) {
- mAudioService.setMasterMute(false, flags, packageName, mICallback);
- }
- }
- } else {
- boolean isStreamMute = mAudioService.isStreamMute(suggestedStream);
- if (direction == MediaSessionManager.DIRECTION_MUTE) {
- mAudioManager.setStreamMute(suggestedStream, !isStreamMute);
- } else {
- mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
- flags, packageName);
- // Do not call setStreamMute when direction = 0 which is used just to
- // show the UI.
- if (isStreamMute && direction != 0) {
- mAudioManager.setStreamMute(suggestedStream, false);
- }
- }
- }
+ mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
+ flags, packageName, TAG);
} catch (RemoteException e) {
Log.e(TAG, "Error adjusting default volume.", e);
}
@@ -938,9 +947,12 @@ public class MediaSessionService extends SystemService implements Monitor {
// Launch the last PendingIntent we had with priority
int userId = ActivityManager.getCurrentUser();
UserRecord user = mUserRecords.get(userId);
- if (user.mLastMediaButtonReceiver != null) {
+ if (user.mLastMediaButtonReceiver != null
+ || user.mRestoredMediaButtonReceiver != null) {
if (DEBUG) {
- Log.d(TAG, "Sending media key to last known PendingIntent");
+ Log.d(TAG, "Sending media key to last known PendingIntent "
+ + user.mLastMediaButtonReceiver + " or restored Intent "
+ + user.mRestoredMediaButtonReceiver);
}
if (needWakeLock) {
mKeyEventReceiver.aquireWakeLockLocked();
@@ -948,9 +960,15 @@ public class MediaSessionService extends SystemService implements Monitor {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
try {
- user.mLastMediaButtonReceiver.send(getContext(),
- needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
- mediaButtonIntent, mKeyEventReceiver, null);
+ if (user.mLastMediaButtonReceiver != null) {
+ user.mLastMediaButtonReceiver.send(getContext(),
+ needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
+ mediaButtonIntent, mKeyEventReceiver, null);
+ } else {
+ mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver);
+ getContext().sendBroadcastAsUser(mediaButtonIntent,
+ new UserHandle(userId));
+ }
} catch (CanceledException e) {
Log.i(TAG, "Error sending key event to media button receiver "
+ user.mLastMediaButtonReceiver, e);
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index bfdc400..a029b0e 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -33,9 +33,7 @@ import android.media.projection.MediaProjectionManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -45,9 +43,6 @@ import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
import java.util.Map;
/**
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index b5a450d..90e26d8 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -24,25 +24,19 @@ import android.net.NetworkUtils;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import com.android.server.net.DelayedDiskWrite;
import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Inet4Address;
-import java.util.Map;
public class IpConfigStore {
private static final String TAG = "IpConfigStore";
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 752614f..6ffe6ac 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -341,7 +341,7 @@ public class LockdownVpnTracker {
.setOngoing(true)
.addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset),
mResetIntent)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
NotificationManager.from(mContext).notify(TAG, 0, builder.build());
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 2be591b..818f0aa 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -27,10 +27,8 @@ import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_UID;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
-import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.ConnectivityManager.isNetworkTypeMobile;
import static android.net.NetworkPolicy.CYCLE_NONE;
@@ -46,7 +44,6 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkPolicyManager.dumpPolicy;
import static android.net.NetworkPolicyManager.dumpRules;
-import static android.net.NetworkTemplate.MATCH_ETHERNET;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
@@ -60,7 +57,6 @@ import static android.net.wifi.WifiManager.EXTRA_CHANGE_REASON;
import static android.net.wifi.WifiManager.EXTRA_NETWORK_INFO;
import static android.net.wifi.WifiManager.EXTRA_WIFI_CONFIGURATION;
import static android.net.wifi.WifiManager.EXTRA_WIFI_INFO;
-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;
@@ -75,7 +71,10 @@ import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UP
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.Manifest;
import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.IProcessObserver;
@@ -87,6 +86,7 @@ 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.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
@@ -122,7 +122,6 @@ import android.os.UserManager;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.text.format.Formatter;
import android.text.format.Time;
import android.util.ArrayMap;
@@ -138,6 +137,7 @@ import android.util.SparseIntArray;
import android.util.TrustedTime;
import android.util.Xml;
+import com.android.server.AppOpsService;
import libcore.io.IoUtils;
import com.android.internal.R;
@@ -248,9 +248,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final Object mRulesLock = new Object();
+ volatile boolean mSystemReady;
volatile boolean mScreenOn;
volatile boolean mRestrictBackground;
volatile boolean mRestrictPower;
+ volatile boolean mDeviceIdleMode;
private final boolean mSuppressDefaultPolicy;
@@ -292,6 +294,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private final AtomicFile mPolicyFile;
+ private final AppOpsManager mAppOps;
+
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
@@ -326,6 +330,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mSuppressDefaultPolicy = suppressDefaultPolicy;
mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"));
+
+ mAppOps = context.getSystemService(AppOpsManager.class);
}
public void bindConnectivityManager(IConnectivityManager connManager) {
@@ -372,11 +378,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
});
mRestrictPower = mPowerManagerInternal.getLowPowerModeEnabled();
+ mSystemReady = true;
// read policy from disk
readPolicyLocked();
- if (mRestrictBackground || mRestrictPower) {
+ if (mRestrictBackground || mRestrictPower || mDeviceIdleMode) {
updateRulesForGlobalChangeLocked(true);
updateNotificationsLocked();
}
@@ -400,7 +407,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
mContext.registerReceiver(mScreenReceiver, screenFilter);
// watch for network interfaces to be claimed
- final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE);
+ final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
// listen for package changes to update policy
@@ -794,7 +801,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final Notification.Builder builder = new Notification.Builder(mContext);
builder.setOnlyAlertOnce(true);
builder.setWhen(0L);
- builder.setColor(mContext.getResources().getColor(
+ builder.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
final Resources res = mContext.getResources();
@@ -921,7 +928,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
builder.setTicker(title);
builder.setContentTitle(title);
builder.setContentText(body);
- builder.setColor(mContext.getResources().getColor(
+ builder.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
final Intent intent = buildAllowBackgroundDataIntent();
@@ -1036,7 +1043,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// will not have a bandwidth limit. Also only do this if restrict
// background data use is *not* enabled, since that takes precendence
// use over those networks can have a cost associated with it).
- final boolean powerSave = mRestrictPower && !mRestrictBackground;
+ final boolean powerSave = (mRestrictPower || mDeviceIdleMode) && !mRestrictBackground;
// First, generate identities of all connected networks so we can
// quickly compare them against all defined policies below.
@@ -1592,16 +1599,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
void addNetworkPolicyLocked(NetworkPolicy policy) {
- NetworkPolicy[] policies = getNetworkPolicies();
+ NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName());
policies = ArrayUtils.appendElement(NetworkPolicy.class, policies, policy);
setNetworkPolicies(policies);
}
@Override
- public NetworkPolicy[] getNetworkPolicies() {
+ public NetworkPolicy[] getNetworkPolicies(String callingPackage) {
mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, TAG);
+ if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return new NetworkPolicy[0];
+ }
+
synchronized (mRulesLock) {
final int size = mNetworkPolicy.size();
final NetworkPolicy[] policies = new NetworkPolicy[size];
@@ -1613,7 +1625,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
private void normalizePoliciesLocked() {
- normalizePoliciesLocked(getNetworkPolicies());
+ normalizePoliciesLocked(getNetworkPolicies(mContext.getOpPackageName()));
}
private void normalizePoliciesLocked(NetworkPolicy[] policies) {
@@ -1701,6 +1713,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
+ @Override
+ public void setDeviceIdleMode(boolean enabled) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ synchronized (mRulesLock) {
+ if (mDeviceIdleMode != enabled) {
+ mDeviceIdleMode = enabled;
+ if (mSystemReady) {
+ updateRulesForGlobalChangeLocked(true);
+ }
+ }
+ }
+ }
+
private NetworkPolicy findPolicyForNetworkLocked(NetworkIdentity ident) {
for (int i = mNetworkPolicy.size()-1; i >= 0; i--) {
NetworkPolicy policy = mNetworkPolicy.valueAt(i);
@@ -1806,8 +1832,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
return;
}
+ fout.print("System ready: "); fout.println(mSystemReady);
fout.print("Restrict background: "); fout.println(mRestrictBackground);
fout.print("Restrict power: "); fout.println(mRestrictPower);
+ fout.print("Device idle: "); fout.println(mDeviceIdleMode);
fout.print("Current foreground state: "); fout.println(mCurForegroundState);
fout.println("Network policies:");
fout.increaseIndent();
@@ -1957,8 +1985,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
/**
- * Update rules that might be changed by {@link #mRestrictBackground}
- * or {@link #mRestrictPower} value.
+ * Update rules that might be changed by {@link #mRestrictBackground},
+ * {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
*/
void updateRulesForGlobalChangeLocked(boolean restrictedNetworksChanged) {
final PackageManager pm = mContext.getPackageManager();
@@ -1967,7 +1995,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// If we are in restrict power mode, we allow all important apps
// to have data access. Otherwise, we restrict data access to only
// the top apps.
- mCurForegroundState = (!mRestrictBackground && mRestrictPower)
+ mCurForegroundState = (!mRestrictBackground && (mRestrictPower || mDeviceIdleMode))
? ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
: ActivityManager.PROCESS_STATE_TOP;
@@ -2007,6 +2035,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
void updateRulesForUidLocked(int uid) {
if (!isUidValidForRules(uid)) return;
+ // quick check: if this uid doesn't have INTERNET permission, it doesn't have
+ // network access anyway, so it is a waste to mess with it here.
+ final IPackageManager ipm = AppGlobals.getPackageManager();
+ try {
+ if (ipm.checkUidPermission(Manifest.permission.INTERNET, uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ } catch (RemoteException e) {
+ }
+
final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
final boolean uidForeground = isUidForegroundLocked(uid);
@@ -2020,7 +2059,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// uid in background, and global background disabled
uidRules = RULE_REJECT_METERED;
}
- } else if (mRestrictPower) {
+ } else if (mRestrictPower || mDeviceIdleMode) {
final boolean whitelisted = mPowerSaveWhitelistAppIds.get(UserHandle.getAppId(uid));
if (!whitelisted && !uidForeground
&& (uidPolicy & POLICY_ALLOW_BACKGROUND_BATTERY_SAVE) == 0) {
@@ -2262,4 +2301,29 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
fout.print("]");
}
+
+ @Override
+ public void factoryReset(String subscriber) {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ // Turn mobile data limit off
+ NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName());
+ NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriber);
+ for (NetworkPolicy policy : policies) {
+ if (policy.template.equals(template)) {
+ policy.limitBytes = NetworkPolicy.LIMIT_DISABLED;
+ policy.inferred = false;
+ policy.clearSnooze();
+ }
+ }
+ setNetworkPolicies(policies);
+
+ // Turn restrict background data off
+ setRestrictBackground(false);
+
+ // Remove app's "restrict background data" flag
+ for (int uid : getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND)) {
+ setUidPolicy(uid, POLICY_NONE);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index 3ac20f7..15b68c7 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -22,6 +22,7 @@ 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_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
@@ -31,14 +32,18 @@ import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TrafficStats;
+import android.os.Binder;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.AtomicFile;
+import android.util.IntArray;
import libcore.io.IoUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FileRotator;
import com.android.internal.util.IndentingPrintWriter;
+
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -131,6 +136,23 @@ public class NetworkStatsCollection implements FileRotator.Reader {
return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
}
+ public int[] getRelevantUids() {
+ final int callerUid = Binder.getCallingUid();
+ IntArray uids = new IntArray();
+ for (int i = 0; i < mStats.size(); i++) {
+ final Key key = mStats.keyAt(i);
+ if (isAccessibleToUser(key.uid, callerUid)) {
+ int j = uids.binarySearch(key.uid);
+
+ if (j < 0) {
+ j = ~j;
+ uids.add(j, key.uid);
+ }
+ }
+ }
+ return uids.toArray();
+ }
+
/**
* Combine all {@link NetworkStatsHistory} in this collection which match
* the requested parameters.
@@ -146,12 +168,21 @@ public class NetworkStatsCollection implements FileRotator.Reader {
*/
public NetworkStatsHistory getHistory(
NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) {
+ final int callerUid = Binder.getCallingUid();
+ if (!isAccessibleToUser(uid, callerUid)) {
+ throw new SecurityException("Network stats history of uid " + uid
+ + " is forbidden for caller " + callerUid);
+ }
+
final NetworkStatsHistory combined = new NetworkStatsHistory(
- mBucketDuration, estimateBuckets(), fields);
+ mBucketDuration, start == end ? 1 : estimateBuckets(), fields);
+
+ // shortcut when we know stats will be empty
+ if (start == end) return combined;
+
for (int i = 0; i < mStats.size(); i++) {
final Key key = mStats.keyAt(i);
- final boolean setMatches = set == SET_ALL || key.set == set;
- if (key.uid == uid && setMatches && key.tag == tag
+ if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag
&& templateMatches(template, key.ident)) {
final NetworkStatsHistory value = mStats.valueAt(i);
combined.recordHistory(value, start, end);
@@ -168,15 +199,17 @@ public class NetworkStatsCollection implements FileRotator.Reader {
final long now = System.currentTimeMillis();
final NetworkStats stats = new NetworkStats(end - start, 24);
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- NetworkStatsHistory.Entry historyEntry = null;
-
// shortcut when we know stats will be empty
if (start == end) return stats;
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ NetworkStatsHistory.Entry historyEntry = null;
+
+ final int callerUid = Binder.getCallingUid();
for (int i = 0; i < mStats.size(); i++) {
final Key key = mStats.keyAt(i);
- if (templateMatches(template, key.ident)) {
+ if (templateMatches(template, key.ident) && isAccessibleToUser(key.uid, callerUid)
+ && key.set < NetworkStats.SET_DEBUG_START) {
final NetworkStatsHistory value = mStats.valueAt(i);
historyEntry = value.getValues(start, end, now, historyEntry);
@@ -509,6 +542,7 @@ public class NetworkStatsCollection implements FileRotator.Reader {
final NetworkStatsHistory value = mStats.valueAt(i);
if (!templateMatches(groupTemplate, key.ident)) continue;
+ if (key.set >= NetworkStats.SET_DEBUG_START) continue;
final Key groupKey = new Key(null, key.uid, key.set, key.tag);
NetworkStatsHistory groupHistory = grouped.get(groupKey);
@@ -536,6 +570,12 @@ public class NetworkStatsCollection implements FileRotator.Reader {
}
}
+ private static boolean isAccessibleToUser(int uid, int callerUid) {
+ return UserHandle.getAppId(callerUid) == android.os.Process.SYSTEM_UID ||
+ uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED || uid == UID_TETHERING
+ || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
+ }
+
/**
* Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
* in the given {@link NetworkIdentitySet}.
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
index d5d7667..6490865 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -31,6 +31,7 @@ import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
+import com.android.internal.net.VpnInfo;
import com.android.internal.util.FileRotator;
import com.android.internal.util.IndentingPrintWriter;
import com.google.android.collect.Sets;
@@ -163,7 +164,8 @@ public class NetworkStatsRecorder {
* snapshot is considered bootstrap, and is not counted as delta.
*/
public void recordSnapshotLocked(NetworkStats snapshot,
- Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
+ Map<String, NetworkIdentitySet> ifaceIdent, VpnInfo[] vpnArray,
+ long currentTimeMillis) {
final HashSet<String> unknownIfaces = Sets.newHashSet();
// skip recording when snapshot missing
@@ -182,6 +184,12 @@ public class NetworkStatsRecorder {
final long end = currentTimeMillis;
final long start = end - delta.getElapsedRealtime();
+ if (vpnArray != null) {
+ for (VpnInfo info : vpnArray) {
+ delta.migrateTun(info.ownerUid, info.vpnIface, info.primaryUnderlyingIface);
+ }
+ }
+
NetworkStats.Entry entry = null;
for (int i = 0; i < delta.size(); i++) {
entry = delta.getValues(i, entry);
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 856a076..50e03a2 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -62,9 +62,13 @@ import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
+import android.Manifest;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.IAlarmManager;
import android.app.PendingIntent;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -93,7 +97,9 @@ import android.os.HandlerThread;
import android.os.INetworkManagementService;
import android.os.Message;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -111,10 +117,12 @@ import android.util.SparseIntArray;
import android.util.TrustedTime;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.VpnInfo;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FileRotator;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.EventLogTags;
+import com.android.server.LocalServices;
import com.android.server.connectivity.Tethering;
import java.io.File;
@@ -428,7 +436,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@Override
public INetworkStatsSession openSession() {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+ return openSessionForUsageStats(null);
+ }
+
+ @Override
+ public INetworkStatsSession openSessionForUsageStats(final String callingPackage) {
assertBandwidthControlEnabled();
// return an IBinder which holds strong references to any loaded stats
@@ -437,6 +449,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
return new INetworkStatsSession.Stub() {
private NetworkStatsCollection mUidComplete;
private NetworkStatsCollection mUidTagComplete;
+ private String mCallingPackage = callingPackage;
private NetworkStatsCollection getUidComplete() {
synchronized (mStatsLock) {
@@ -457,8 +470,29 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
}
@Override
+ public int[] getRelevantUids() {
+ enforcePermissionForManagedAdmin(mCallingPackage);
+ return getUidComplete().getRelevantUids();
+ }
+
+ @Override
+ public NetworkStats getDeviceSummaryForNetwork(NetworkTemplate template, long start,
+ long end) {
+ enforcePermission(mCallingPackage);
+ NetworkStats result = new NetworkStats(end - start, 1);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ result.combineAllValues(internalGetSummaryForNetwork(template, start, end));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return result;
+ }
+
+ @Override
public NetworkStats getSummaryForNetwork(
NetworkTemplate template, long start, long end) {
+ enforcePermission(mCallingPackage);
return internalGetSummaryForNetwork(template, start, end);
}
@@ -470,6 +504,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@Override
public NetworkStats getSummaryForAllUid(
NetworkTemplate template, long start, long end, boolean includeTags) {
+ enforcePermissionForManagedAdmin(mCallingPackage);
final NetworkStats stats = getUidComplete().getSummary(template, start, end);
if (includeTags) {
final NetworkStats tagStats = getUidTagComplete()
@@ -482,6 +517,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
@Override
public NetworkStatsHistory getHistoryForUid(
NetworkTemplate template, int uid, int set, int tag, int fields) {
+ enforcePermissionForManagedAdmin(mCallingPackage);
if (tag == TAG_NONE) {
return getUidComplete().getHistory(template, uid, set, tag, fields);
} else {
@@ -497,6 +533,53 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
};
}
+ private boolean hasAppOpsPermission(String callingPackage) {
+ final int callingUid = Binder.getCallingUid();
+ boolean appOpsAllow = false;
+ if (callingPackage != null) {
+ AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
+ Context.APP_OPS_SERVICE);
+
+ final int mode = appOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS,
+ callingUid, callingPackage);
+ if (mode == AppOpsManager.MODE_DEFAULT) {
+ // The default behavior here is to check if PackageManager has given the app
+ // permission.
+ final int permissionCheck = mContext.checkCallingPermission(
+ Manifest.permission.PACKAGE_USAGE_STATS);
+ appOpsAllow = permissionCheck == PackageManager.PERMISSION_GRANTED;
+ }
+ appOpsAllow = (mode == AppOpsManager.MODE_ALLOWED);
+ }
+ return appOpsAllow;
+ }
+
+ private void enforcePermissionForManagedAdmin(String callingPackage) {
+ boolean hasPermission = hasAppOpsPermission(callingPackage);
+ if (!hasPermission) {
+ // Profile and device owners are exempt from permission checking.
+ final int callingUid = Binder.getCallingUid();
+ final DevicePolicyManagerInternal dpmi = LocalServices.getService(
+ DevicePolicyManagerInternal.class);
+ if (dpmi.isActiveAdminWithPolicy(callingUid, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)
+ || dpmi.isActiveAdminWithPolicy(callingUid,
+ DeviceAdminInfo.USES_POLICY_DEVICE_OWNER)) {
+ return;
+ }
+ }
+ if (!hasPermission) {
+ mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+ }
+ }
+
+ private void enforcePermission(String callingPackage) {
+ boolean appOpsAllow = hasAppOpsPermission(callingPackage);
+ if (!appOpsAllow) {
+ mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+ }
+ }
+
+
/**
* Return network summary, splicing between DEV and XT stats when
* appropriate.
@@ -855,6 +938,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
return ident;
}
+ private void recordSnapshotLocked(long currentTime) throws RemoteException {
+ // snapshot and record current counters; read UID stats first to
+ // avoid overcounting dev stats.
+ final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
+ final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt();
+ final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
+
+ VpnInfo[] vpnArray = mConnManager.getAllVpnInfo();
+ mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, null, currentTime);
+ mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, null, currentTime);
+ mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
+ mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
+ }
+
/**
* Bootstrap initial stats snapshot, usually during {@link #systemReady()}
* so we have baseline values without double-counting.
@@ -864,17 +961,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
: System.currentTimeMillis();
try {
- // snapshot and record current counters; read UID stats first to
- // avoid overcounting dev stats.
- final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
- final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt();
- final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
-
- mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
- mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime);
- mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
- mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
-
+ recordSnapshotLocked(currentTime);
} catch (IllegalStateException e) {
Slog.w(TAG, "problem reading network stats: " + e);
} catch (RemoteException e) {
@@ -918,17 +1005,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
: System.currentTimeMillis();
try {
- // snapshot and record current counters; read UID stats first to
- // avoid overcounting dev stats.
- final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
- final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt();
- final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
-
- mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
- mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime);
- mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
- mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime);
-
+ recordSnapshotLocked(currentTime);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem reading network stats", e);
return;
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index ab53fbc..b36fcd2 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -25,12 +25,10 @@ import android.os.IInterface;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -41,50 +39,44 @@ import com.android.server.notification.NotificationManagerService.DumpFilter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Objects;
public class ConditionProviders extends ManagedServices {
- private static final Condition[] NO_CONDITIONS = new Condition[0];
-
- private final ZenModeHelper mZenModeHelper;
- private final ArrayMap<IBinder, IConditionListener> mListeners
- = new ArrayMap<IBinder, IConditionListener>();
- private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
- private final ArraySet<String> mSystemConditionProviders;
- private final CountdownConditionProvider mCountdown;
- private final DowntimeConditionProvider mDowntime;
- private final NextAlarmConditionProvider mNextAlarm;
- private final NextAlarmTracker mNextAlarmTracker;
-
- private Condition mExitCondition;
- private ComponentName mExitConditionComponent;
-
- public ConditionProviders(Context context, Handler handler,
- UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
+ private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
+ private final ArrayMap<IBinder, IConditionListener> mListeners = new ArrayMap<>();
+ private final ArraySet<String> mSystemConditionProviderNames;
+ private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
+ = new ArraySet<>();
+
+ private Callback mCallback;
+
+ public ConditionProviders(Context context, Handler handler, UserProfiles userProfiles) {
super(context, handler, new Object(), userProfiles);
- mZenModeHelper = zenModeHelper;
- mZenModeHelper.addCallback(new ZenModeHelperCallback());
- mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext,
+ mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
"system.condition.providers",
R.array.config_system_condition_providers));
- final boolean countdown = mSystemConditionProviders.contains(ZenModeConfig.COUNTDOWN_PATH);
- final boolean downtime = mSystemConditionProviders.contains(ZenModeConfig.DOWNTIME_PATH);
- final boolean nextAlarm = mSystemConditionProviders.contains(ZenModeConfig.NEXT_ALARM_PATH);
- mNextAlarmTracker = (downtime || nextAlarm) ? new NextAlarmTracker(mContext) : null;
- mCountdown = countdown ? new CountdownConditionProvider() : null;
- mDowntime = downtime ? new DowntimeConditionProvider(this, mNextAlarmTracker,
- mZenModeHelper) : null;
- mNextAlarm = nextAlarm ? new NextAlarmConditionProvider(mNextAlarmTracker) : null;
- loadZenConfig();
}
- public boolean isSystemConditionProviderEnabled(String path) {
- return mSystemConditionProviders.contains(path);
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public boolean isSystemProviderEnabled(String path) {
+ return mSystemConditionProviderNames.contains(path);
+ }
+
+ public void addSystemProvider(SystemConditionProviderService service) {
+ mSystemConditionProviders.add(service);
+ service.attachBase(mContext);
+ registerService(service.asInterface(), service.getComponent(), UserHandle.USER_OWNER);
+ }
+
+ public Iterable<SystemConditionProviderService> getSystemProviders() {
+ return mSystemConditionProviders;
}
@Override
protected Config getConfig() {
- Config c = new Config();
+ final Config c = new Config();
c.caption = "condition provider";
c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
@@ -98,12 +90,6 @@ public class ConditionProviders extends ManagedServices {
public void dump(PrintWriter pw, DumpFilter filter) {
super.dump(pw, filter);
synchronized(mMutex) {
- if (filter == null) {
- pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
- for (int i = 0; i < mListeners.size(); i++) {
- pw.print(" "); pw.println(mListeners.keyAt(i));
- }
- }
pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):");
for (int i = 0; i < mRecords.size(); i++) {
final ConditionRecord r = mRecords.get(i);
@@ -115,18 +101,15 @@ public class ConditionProviders extends ManagedServices {
}
}
}
- pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviders);
- if (mCountdown != null) {
- mCountdown.dump(pw, filter);
- }
- if (mDowntime != null) {
- mDowntime.dump(pw, filter);
- }
- if (mNextAlarm != null) {
- mNextAlarm.dump(pw, filter);
+ if (filter == null) {
+ pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
+ for (int i = 0; i < mListeners.size(); i++) {
+ pw.print(" "); pw.println(mListeners.keyAt(i));
+ }
}
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.dump(pw, filter);
+ pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
+ for (int i = 0; i < mSystemConditionProviders.size(); i++) {
+ mSystemConditionProviders.valueAt(i).dump(pw, filter);
}
}
@@ -138,31 +121,16 @@ public class ConditionProviders extends ManagedServices {
@Override
public void onBootPhaseAppsCanStart() {
super.onBootPhaseAppsCanStart();
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.init();
- }
- if (mCountdown != null) {
- mCountdown.attachBase(mContext);
- registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
- }
- if (mDowntime != null) {
- mDowntime.attachBase(mContext);
- registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
- }
- if (mNextAlarm != null) {
- mNextAlarm.attachBase(mContext);
- registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
+ if (mCallback != null) {
+ mCallback.onBootComplete();
}
}
@Override
public void onUserSwitched() {
super.onUserSwitched();
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.onUserSwitched();
+ if (mCallback != null) {
+ mCallback.onUserSwitched();
}
}
@@ -174,23 +142,8 @@ public class ConditionProviders extends ManagedServices {
} catch (RemoteException e) {
// we tried
}
- synchronized (mMutex) {
- if (info.component.equals(mExitConditionComponent)) {
- // ensure record exists, we'll wire it up and subscribe below
- final ConditionRecord manualRecord =
- getRecordLocked(mExitCondition.id, mExitConditionComponent);
- manualRecord.isManual = true;
- }
- final int N = mRecords.size();
- for(int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (!r.component.equals(info.component)) continue;
- r.info = info;
- // if automatic or manual, auto-subscribe
- if (r.isAutomatic || r.isManual) {
- subscribeLocked(r);
- }
- }
+ if (mCallback != null) {
+ mCallback.onServiceAdded(info.component);
}
}
@@ -200,15 +153,6 @@ public class ConditionProviders extends ManagedServices {
for (int i = mRecords.size() - 1; i >= 0; i--) {
final ConditionRecord r = mRecords.get(i);
if (!r.component.equals(removed.component)) continue;
- if (r.isManual) {
- // removing the current manual condition, exit zen
- onManualConditionClearing();
- mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved");
- }
- if (r.isAutomatic) {
- // removing an automatic condition, exit zen
- mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved");
- }
mRecords.remove(i);
}
}
@@ -219,9 +163,9 @@ public class ConditionProviders extends ManagedServices {
}
}
- public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ public void requestConditions(IConditionListener callback, int relevance) {
synchronized(mMutex) {
- if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
+ if (DEBUG) Slog.d(TAG, "requestConditions callback=" + callback
+ " relevance=" + Condition.relevanceToString(relevance));
if (callback == null) return;
relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
@@ -262,7 +206,8 @@ public class ConditionProviders extends ManagedServices {
return rt;
}
- private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
+ private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
+ if (id == null || component == null) return null;
final int N = mRecords.size();
for (int i = 0; i < N; i++) {
final ConditionRecord r = mRecords.get(i);
@@ -270,9 +215,12 @@ public class ConditionProviders extends ManagedServices {
return r;
}
}
- final ConditionRecord r = new ConditionRecord(id, component);
- mRecords.add(r);
- return r;
+ if (create) {
+ final ConditionRecord r = new ConditionRecord(id, component);
+ mRecords.add(r);
+ return r;
+ }
+ return null;
}
public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
@@ -291,99 +239,58 @@ public class ConditionProviders extends ManagedServices {
}
for (int i = 0; i < N; i++) {
final Condition c = conditions[i];
- final ConditionRecord r = getRecordLocked(c.id, info.component);
- final Condition oldCondition = r.condition;
- final boolean conditionUpdate = oldCondition != null && !oldCondition.equals(c);
+ final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
r.info = info;
r.condition = c;
- // if manual, exit zen if false (or failed), update if true (and changed)
- if (r.isManual) {
- if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
- final boolean failed = c.state == Condition.STATE_ERROR;
- if (failed) {
- Slog.w(TAG, "Exit zen: manual condition failed: " + c);
- } else if (DEBUG) {
- Slog.d(TAG, "Exit zen: manual condition false: " + c);
- }
- onManualConditionClearing();
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
- "manualConditionExit");
- unsubscribeLocked(r);
- r.isManual = false;
- } else if (c.state == Condition.STATE_TRUE && conditionUpdate) {
- if (DEBUG) Slog.d(TAG, "Current condition updated, still true. old="
- + oldCondition + " new=" + c);
- setZenModeCondition(c, "conditionUpdate");
- }
- }
- // if automatic, exit zen if false (or failed), enter zen if true
- if (r.isAutomatic) {
- if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
- final boolean failed = c.state == Condition.STATE_ERROR;
- if (failed) {
- Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
- } else if (DEBUG) {
- Slog.d(TAG, "Exit zen: automatic condition false: " + c);
- }
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
- "automaticConditionExit");
- } else if (c.state == Condition.STATE_TRUE) {
- Slog.d(TAG, "Enter zen: automatic condition true: " + c);
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- "automaticConditionEnter");
- }
+ if (mCallback != null) {
+ mCallback.onConditionChanged(c.id, c);
}
}
}
}
- private void ensureRecordExists(Condition condition, IConditionProvider provider,
- ComponentName component) {
+ public IConditionProvider findConditionProvider(ComponentName component) {
+ if (component == null) return null;
+ for (ManagedServiceInfo service : mServices) {
+ if (component.equals(service.component)) {
+ return provider(service);
+ }
+ }
+ return null;
+ }
+
+ public void ensureRecordExists(ComponentName component, Uri conditionId,
+ IConditionProvider provider) {
// constructed by convention, make sure the record exists...
- final ConditionRecord r = getRecordLocked(condition.id, component);
+ final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
if (r.info == null) {
// ... and is associated with the in-process service
r.info = checkServiceTokenLocked(provider);
}
}
- public void setZenModeCondition(Condition condition, String reason) {
- if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition + " reason=" + reason);
- synchronized(mMutex) {
- ComponentName conditionComponent = null;
- if (condition != null) {
- if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) {
- ensureRecordExists(condition, mCountdown.asInterface(),
- CountdownConditionProvider.COMPONENT);
- }
- if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
- ensureRecordExists(condition, mDowntime.asInterface(),
- DowntimeConditionProvider.COMPONENT);
- }
- }
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- final boolean idEqual = condition != null && r.id.equals(condition.id);
- if (r.isManual && !idEqual) {
- // was previous manual condition, unsubscribe
- unsubscribeLocked(r);
- r.isManual = false;
- } else if (idEqual && !r.isManual) {
- // is new manual condition, subscribe
- subscribeLocked(r);
- r.isManual = true;
- }
- if (idEqual) {
- conditionComponent = r.component;
- }
+ public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
+ synchronized (mMutex) {
+ final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+ if (r == null) {
+ Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
+ return false;
}
- if (!Objects.equals(mExitCondition, condition)) {
- mExitCondition = condition;
- mExitConditionComponent = conditionComponent;
- ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason);
- saveZenConfigLocked();
+ if (r.subscribed) return true;
+ subscribeLocked(r);
+ return r.subscribed;
+ }
+ }
+
+ public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
+ synchronized (mMutex) {
+ final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+ if (r == null) {
+ Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
+ return;
}
+ if (!r.subscribed) return;
+ unsubscribeLocked(r);;
}
}
@@ -393,8 +300,9 @@ public class ConditionProviders extends ManagedServices {
RemoteException re = null;
if (provider != null) {
try {
- Slog.d(TAG, "Subscribing to " + r.id + " with " + provider);
+ Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
provider.onSubscribe(r.id);
+ r.subscribed = true;
} catch (RemoteException e) {
Slog.w(TAG, "Error subscribing to " + r, e);
re = e;
@@ -417,53 +325,6 @@ public class ConditionProviders extends ManagedServices {
return rt;
}
- public void setAutomaticZenModeConditions(Uri[] conditionIds) {
- setAutomaticZenModeConditions(conditionIds, true /*save*/);
- }
-
- private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
- if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
- + (conditionIds == null ? null : Arrays.asList(conditionIds)));
- synchronized(mMutex) {
- final ArraySet<Uri> newIds = safeSet(conditionIds);
- final int N = mRecords.size();
- boolean changed = false;
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- final boolean automatic = newIds.contains(r.id);
- if (!r.isAutomatic && automatic) {
- // subscribe to new automatic
- subscribeLocked(r);
- r.isAutomatic = true;
- changed = true;
- } else if (r.isAutomatic && !automatic) {
- // unsubscribe from old automatic
- unsubscribeLocked(r);
- r.isAutomatic = false;
- changed = true;
- }
- }
- if (save && changed) {
- saveZenConfigLocked();
- }
- }
- }
-
- public Condition[] getAutomaticZenModeConditions() {
- synchronized(mMutex) {
- final int N = mRecords.size();
- ArrayList<Condition> rt = null;
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (r.isAutomatic && r.condition != null) {
- if (rt == null) rt = new ArrayList<Condition>();
- rt.add(r.condition);
- }
- }
- return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
- }
- }
-
private void unsubscribeLocked(ConditionRecord r) {
if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
final IConditionProvider provider = provider(r);
@@ -475,6 +336,7 @@ public class ConditionProviders extends ManagedServices {
Slog.w(TAG, "Error unsubscribing to " + r, e);
re = e;
}
+ r.subscribed = false;
}
ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
}
@@ -495,7 +357,7 @@ public class ConditionProviders extends ManagedServices {
for (int i = mRecords.size() - 1; i >= 0; i--) {
final ConditionRecord r = mRecords.get(i);
if (r.info != info) continue;
- if (r.isManual || r.isAutomatic) continue;
+ if (r.subscribed) continue;
mRecords.remove(i);
}
try {
@@ -506,103 +368,12 @@ public class ConditionProviders extends ManagedServices {
}
}
- private void loadZenConfig() {
- final ZenModeConfig config = mZenModeHelper.getConfig();
- if (config == null) {
- if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
- return;
- }
- synchronized (mMutex) {
- final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition);
- mExitCondition = config.exitCondition;
- mExitConditionComponent = config.exitConditionComponent;
- if (changingExit) {
- ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config");
- }
- if (mDowntime != null) {
- mDowntime.setConfig(config);
- }
- if (config.conditionComponents == null || config.conditionIds == null
- || config.conditionComponents.length != config.conditionIds.length) {
- if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
- setAutomaticZenModeConditions(null, false /*save*/);
- return;
- }
- final ArraySet<Uri> newIds = new ArraySet<Uri>();
- final int N = config.conditionComponents.length;
- for (int i = 0; i < N; i++) {
- final ComponentName component = config.conditionComponents[i];
- final Uri id = config.conditionIds[i];
- if (component != null && id != null) {
- getRecordLocked(id, component); // ensure record exists
- newIds.add(id);
- }
- }
- if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
- setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
- }
- }
-
- private void saveZenConfigLocked() {
- ZenModeConfig config = mZenModeHelper.getConfig();
- if (config == null) return;
- config = config.copy();
- final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
- final int automaticN = mRecords.size();
- for (int i = 0; i < automaticN; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (r.isAutomatic) {
- automatic.add(r);
- }
- }
- if (automatic.isEmpty()) {
- config.conditionComponents = null;
- config.conditionIds = null;
- } else {
- final int N = automatic.size();
- config.conditionComponents = new ComponentName[N];
- config.conditionIds = new Uri[N];
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = automatic.get(i);
- config.conditionComponents[i] = r.component;
- config.conditionIds[i] = r.id;
- }
- }
- config.exitCondition = mExitCondition;
- config.exitConditionComponent = mExitConditionComponent;
- if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
- mZenModeHelper.setConfig(config);
- }
-
- private void onManualConditionClearing() {
- if (mDowntime != null) {
- mDowntime.onManualConditionClearing();
- }
- }
-
- private class ZenModeHelperCallback extends ZenModeHelper.Callback {
- @Override
- void onConfigChanged() {
- loadZenConfig();
- }
-
- @Override
- void onZenModeChanged() {
- final int mode = mZenModeHelper.getZenMode();
- if (mode == Global.ZEN_MODE_OFF) {
- // ensure any manual condition is cleared
- setZenModeCondition(null, "zenOff");
- }
- }
- }
-
private static class ConditionRecord {
public final Uri id;
public final ComponentName component;
public Condition condition;
public ManagedServiceInfo info;
- public boolean isAutomatic;
- public boolean isManual;
+ public boolean subscribed;
private ConditionRecord(Uri id, ComponentName component) {
this.id = id;
@@ -612,10 +383,17 @@ public class ConditionProviders extends ManagedServices {
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
- .append(id).append(",component=").append(component);
- if (isAutomatic) sb.append(",automatic");
- if (isManual) sb.append(",manual");
+ .append(id).append(",component=").append(component)
+ .append(",subscribed=").append(subscribed);
return sb.append(']').toString();
}
}
+
+ public interface Callback {
+ void onBootComplete();
+ void onServiceAdded(ComponentName component);
+ void onConditionChanged(Uri id, Condition condition);
+ void onUserSwitched();
+ }
+
}
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index 37aacaa..6a04688 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -25,7 +25,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
import android.text.format.DateUtils;
@@ -38,8 +37,8 @@ import java.io.PrintWriter;
import java.util.Date;
/** Built-in zen condition provider for simple time-based conditions */
-public class CountdownConditionProvider extends ConditionProviderService {
- private static final String TAG = "CountdownConditions";
+public class CountdownConditionProvider extends SystemConditionProviderService {
+ private static final String TAG = "ConditionProviders";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final ComponentName COMPONENT =
@@ -59,6 +58,27 @@ public class CountdownConditionProvider extends ConditionProviderService {
if (DEBUG) Slog.d(TAG, "new CountdownConditionProvider()");
}
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+
+ @Override
+ public boolean isValidConditionid(Uri id) {
+ return ZenModeConfig.isValidCountdownConditionId(id);
+ }
+
+ @Override
+ public void attachBase(Context base) {
+ attachBaseContext(base);
+ }
+
+ @Override
+ public IConditionProvider asInterface() {
+ return (IConditionProvider) onBind(null);
+ }
+
+ @Override
public void dump(PrintWriter pw, DumpFilter filter) {
pw.println(" CountdownConditionProvider:");
pw.print(" mConnected="); pw.println(mConnected);
@@ -154,11 +174,4 @@ public class CountdownConditionProvider extends ConditionProviderService {
return new Date(time) + " (" + time + ")";
}
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
}
diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
deleted file mode 100644
index df4ecfd..0000000
--- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/**
- * Copyright (c) 2014, 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.notification;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.provider.Settings.Global;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
-import android.text.format.DateFormat;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.TimeZone;
-
-/** Built-in zen condition provider for managing downtime */
-public class DowntimeConditionProvider extends ConditionProviderService {
- private static final String TAG = "DowntimeConditions";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- public static final ComponentName COMPONENT =
- new ComponentName("android", DowntimeConditionProvider.class.getName());
-
- private static final String ENTER_ACTION = TAG + ".enter";
- private static final int ENTER_CODE = 100;
- private static final String EXIT_ACTION = TAG + ".exit";
- private static final int EXIT_CODE = 101;
- private static final String EXTRA_TIME = "time";
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long HOURS = 60 * MINUTES;
-
- private final Context mContext = this;
- private final DowntimeCalendar mCalendar = new DowntimeCalendar();
- private final FiredAlarms mFiredAlarms = new FiredAlarms();
- private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
- private final ConditionProviders mConditionProviders;
- private final NextAlarmTracker mTracker;
- private final ZenModeHelper mZenModeHelper;
-
- private boolean mConnected;
- private long mLookaheadThreshold;
- private ZenModeConfig mConfig;
- private boolean mDowntimed;
- private boolean mConditionClearing;
- private boolean mRequesting;
-
- public DowntimeConditionProvider(ConditionProviders conditionProviders,
- NextAlarmTracker tracker, ZenModeHelper zenModeHelper) {
- if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()");
- mConditionProviders = conditionProviders;
- mTracker = tracker;
- mZenModeHelper = zenModeHelper;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" DowntimeConditionProvider:");
- pw.print(" mConnected="); pw.println(mConnected);
- pw.print(" mSubscriptions="); pw.println(mSubscriptions);
- pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
- pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
- pw.print(" mCalendar="); pw.println(mCalendar);
- pw.print(" mFiredAlarms="); pw.println(mFiredAlarms);
- pw.print(" mDowntimed="); pw.println(mDowntimed);
- pw.print(" mConditionClearing="); pw.println(mConditionClearing);
- pw.print(" mRequesting="); pw.println(mRequesting);
- }
-
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
- @Override
- public void onConnected() {
- if (DEBUG) Slog.d(TAG, "onConnected");
- mConnected = true;
- mLookaheadThreshold = PropConfig.getInt(mContext, "downtime.condition.lookahead",
- R.integer.config_downtime_condition_lookahead_threshold_hrs) * HOURS;
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ENTER_ACTION);
- filter.addAction(EXIT_ACTION);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- mContext.registerReceiver(mReceiver, filter);
- mTracker.addCallback(mTrackerCallback);
- mZenModeHelper.addCallback(mZenCallback);
- init();
- }
-
- @Override
- public void onDestroy() {
- if (DEBUG) Slog.d(TAG, "onDestroy");
- mTracker.removeCallback(mTrackerCallback);
- mZenModeHelper.removeCallback(mZenCallback);
- mConnected = false;
- }
-
- @Override
- public void onRequestConditions(int relevance) {
- if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
- if (!mConnected) return;
- mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
- evaluateSubscriptions();
- }
-
- @Override
- public void onSubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId);
- final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId);
- if (downtime == null) return;
- mFiredAlarms.clear();
- mSubscriptions.add(conditionId);
- notifyCondition(downtime);
- }
-
- private boolean shouldShowCondition() {
- final long now = System.currentTimeMillis();
- if (DEBUG) Slog.d(TAG, "shouldShowCondition now=" + mCalendar.isInDowntime(now)
- + " lookahead="
- + (mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold)));
- return mCalendar.isInDowntime(now)
- || mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold);
- }
-
- private void notifyCondition(DowntimeInfo downtime) {
- if (mConfig == null) {
- // we don't know yet
- notifyCondition(createCondition(downtime, Condition.STATE_UNKNOWN));
- return;
- }
- if (!downtime.equals(mConfig.toDowntimeInfo())) {
- // not the configured downtime, consider it false
- notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
- return;
- }
- if (!shouldShowCondition()) {
- // configured downtime, but not within the time range
- notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
- return;
- }
- if (isZenNone() && mFiredAlarms.findBefore(System.currentTimeMillis())) {
- // within the configured time range, but wake up if none and the next alarm is fired
- notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
- return;
- }
- // within the configured time range, condition still valid
- notifyCondition(createCondition(downtime, Condition.STATE_TRUE));
- }
-
- private boolean isZenNone() {
- return mZenModeHelper.getZenMode() == Global.ZEN_MODE_NO_INTERRUPTIONS;
- }
-
- private boolean isZenOff() {
- return mZenModeHelper.getZenMode() == Global.ZEN_MODE_OFF;
- }
-
- private void evaluateSubscriptions() {
- ArraySet<Uri> conditions = mSubscriptions;
- if (mConfig != null && mRequesting && shouldShowCondition()) {
- final Uri id = ZenModeConfig.toDowntimeConditionId(mConfig.toDowntimeInfo());
- if (!conditions.contains(id)) {
- conditions = new ArraySet<Uri>(conditions);
- conditions.add(id);
- }
- }
- for (Uri conditionId : conditions) {
- final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId);
- if (downtime != null) {
- notifyCondition(downtime);
- }
- }
- }
-
- @Override
- public void onUnsubscribe(Uri conditionId) {
- final boolean current = mSubscriptions.contains(conditionId);
- if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId + " current=" + current);
- mSubscriptions.remove(conditionId);
- mFiredAlarms.clear();
- }
-
- public void setConfig(ZenModeConfig config) {
- if (Objects.equals(mConfig, config)) return;
- final boolean downtimeChanged = mConfig == null || config == null
- || !mConfig.toDowntimeInfo().equals(config.toDowntimeInfo());
- mConfig = config;
- if (DEBUG) Slog.d(TAG, "setConfig downtimeChanged=" + downtimeChanged);
- if (mConnected && downtimeChanged) {
- mDowntimed = false;
- init();
- }
- // when active, mark downtime as entered for today
- if (mConfig != null && mConfig.exitCondition != null
- && ZenModeConfig.isValidDowntimeConditionId(mConfig.exitCondition.id)) {
- mDowntimed = true;
- }
- }
-
- public void onManualConditionClearing() {
- mConditionClearing = true;
- }
-
- private Condition createCondition(DowntimeInfo downtime, int state) {
- if (downtime == null) return null;
- final Uri id = ZenModeConfig.toDowntimeConditionId(downtime);
- final String skeleton = DateFormat.is24HourFormat(mContext) ? "Hm" : "hma";
- final Locale locale = Locale.getDefault();
- final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
- final long now = System.currentTimeMillis();
- long endTime = mCalendar.getNextTime(now, downtime.endHour, downtime.endMinute);
- if (isZenNone()) {
- final AlarmClockInfo nextAlarm = mTracker.getNextAlarm();
- final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
- if (nextAlarmTime > now && nextAlarmTime < endTime) {
- endTime = nextAlarmTime;
- }
- }
- final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(endTime));
- final String summary = mContext.getString(R.string.downtime_condition_summary, formatted);
- final String line1 = mContext.getString(R.string.downtime_condition_line_one);
- return new Condition(id, summary, line1, formatted, 0, state, Condition.FLAG_RELEVANT_NOW);
- }
-
- private void init() {
- mCalendar.setDowntimeInfo(mConfig != null ? mConfig.toDowntimeInfo() : null);
- evaluateSubscriptions();
- updateAlarms();
- evaluateAutotrigger();
- }
-
- private void updateAlarms() {
- if (mConfig == null) return;
- updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute);
- updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute);
- }
-
-
- private void updateAlarm(String action, int requestCode, int hr, int min) {
- final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final long now = System.currentTimeMillis();
- final long time = mCalendar.getNextTime(now, hr, min);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode,
- new Intent(action)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_TIME, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
- alarms.cancel(pendingIntent);
- if (mConfig.sleepMode != null) {
- if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, in %s, now=%s",
- action, ts(time), NextAlarmTracker.formatDuration(time - now), ts(now)));
- alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- }
- }
-
- private static String ts(long time) {
- return new Date(time) + " (" + time + ")";
- }
-
- private void onEvaluateNextAlarm(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- if (!booted) return; // we don't know yet
- if (DEBUG) Slog.d(TAG, "onEvaluateNextAlarm " + mTracker.formatAlarmDebug(nextAlarm));
- if (nextAlarm != null && wakeupTime > 0 && System.currentTimeMillis() > wakeupTime) {
- if (DEBUG) Slog.d(TAG, "Alarm fired: " + mTracker.formatAlarmDebug(wakeupTime));
- mFiredAlarms.add(wakeupTime);
- }
- evaluateSubscriptions();
- }
-
- private void evaluateAutotrigger() {
- String skipReason = null;
- if (mConfig == null) {
- skipReason = "no config";
- } else if (mDowntimed) {
- skipReason = "already downtimed";
- } else if (mZenModeHelper.getZenMode() != Global.ZEN_MODE_OFF) {
- skipReason = "already in zen";
- } else if (!mCalendar.isInDowntime(System.currentTimeMillis())) {
- skipReason = "not in downtime";
- }
- if (skipReason != null) {
- ZenLog.traceDowntimeAutotrigger("Autotrigger skipped: " + skipReason);
- return;
- }
- ZenLog.traceDowntimeAutotrigger("Autotrigger fired");
- mZenModeHelper.setZenMode(mConfig.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
- : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtime");
- final Condition condition = createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE);
- mConditionProviders.setZenModeCondition(condition, "downtime");
- }
-
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final long now = System.currentTimeMillis();
- if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) {
- final long schTime = intent.getLongExtra(EXTRA_TIME, 0);
- if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s",
- action, ts(schTime), ts(now), now - schTime));
- if (ENTER_ACTION.equals(action)) {
- evaluateAutotrigger();
- } else /*EXIT_ACTION*/ {
- mDowntimed = false;
- }
- mFiredAlarms.clear();
- } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
- if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault());
- mCalendar.setTimeZone(TimeZone.getDefault());
- mFiredAlarms.clear();
- } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
- if (DEBUG) Slog.d(TAG, "time changed to " + now);
- mFiredAlarms.clear();
- } else {
- if (DEBUG) Slog.d(TAG, action + " fired at " + now);
- }
- evaluateSubscriptions();
- updateAlarms();
- }
- };
-
- private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
- @Override
- public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- DowntimeConditionProvider.this.onEvaluateNextAlarm(nextAlarm, wakeupTime, booted);
- }
- };
-
- private final ZenModeHelper.Callback mZenCallback = new ZenModeHelper.Callback() {
- @Override
- void onZenModeChanged() {
- if (mConditionClearing && isZenOff()) {
- evaluateAutotrigger();
- }
- mConditionClearing = false;
- evaluateSubscriptions();
- }
- };
-
- private class FiredAlarms {
- private final ArraySet<Long> mFiredAlarms = new ArraySet<Long>();
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mFiredAlarms.size(); i++) {
- if (i > 0) sb.append(',');
- sb.append(mTracker.formatAlarmDebug(mFiredAlarms.valueAt(i)));
- }
- return sb.toString();
- }
-
- public void add(long firedAlarm) {
- mFiredAlarms.add(firedAlarm);
- }
-
- public void clear() {
- mFiredAlarms.clear();
- }
-
- public boolean findBefore(long time) {
- for (int i = 0; i < mFiredAlarms.size(); i++) {
- if (mFiredAlarms.valueAt(i) < time) {
- return true;
- }
- }
- return false;
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 0c7d71b..9ccb2ea 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -18,10 +18,12 @@ package com.android.server.notification;
import android.app.ActivityManager;
import android.app.PendingIntent;
+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.PackageManager;
@@ -51,6 +53,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -74,6 +77,7 @@ abstract public class ManagedServices {
private final UserProfiles mUserProfiles;
private final SettingsObserver mSettingsObserver;
private final Config mConfig;
+ private ArraySet<String> mRestored;
// contains connections to all connected services, including app services
// and system services
@@ -91,6 +95,8 @@ abstract public class ManagedServices {
// user change).
private int[] mLastSeenProfileIds;
+ private final BroadcastReceiver mRestoreReceiver;
+
public ManagedServices(Context context, Handler handler, Object mutex,
UserProfiles userProfiles) {
mContext = context;
@@ -98,6 +104,24 @@ abstract public class ManagedServices {
mUserProfiles = userProfiles;
mConfig = getConfig();
mSettingsObserver = new SettingsObserver(handler);
+
+ mRestoreReceiver = new SettingRestoredReceiver();
+ IntentFilter filter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
+ context.registerReceiver(mRestoreReceiver, filter);
+ }
+
+ class SettingRestoredReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) {
+ String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+ if (Objects.equals(element, mConfig.secureSettingName)) {
+ String prevValue = intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE);
+ String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE);
+ settingRestored(element, prevValue, newValue, getSendingUserId());
+ }
+ }
+ }
}
abstract protected Config getConfig();
@@ -140,6 +164,31 @@ abstract public class ManagedServices {
}
}
+ // By convention, restored settings are replicated to another settings
+ // entry, named similarly but with a disambiguation suffix.
+ public static final String restoredSettingName(Config config) {
+ return config.secureSettingName + ":restored";
+ }
+
+ // The OS has done a restore of this service's saved state. We clone it to the
+ // 'restored' reserve, and then once we return and the actual write to settings is
+ // performed, our observer will do the work of maintaining the restored vs live
+ // settings data.
+ public void settingRestored(String element, String oldValue, String newValue, int userid) {
+ if (DEBUG) Slog.d(TAG, "Restored managed service setting: " + element
+ + " ovalue=" + oldValue + " nvalue=" + newValue);
+ if (mConfig.secureSettingName.equals(element)) {
+ if (element != null) {
+ mRestored = null;
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ restoredSettingName(mConfig),
+ newValue,
+ userid);
+ disableNonexistentServices(userid);
+ }
+ }
+ }
+
public void onPackagesChanged(boolean queryReplace, String[] pkgList) {
if (DEBUG) Slog.d(TAG, "onPackagesChanged queryReplace=" + queryReplace
+ " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
@@ -211,8 +260,23 @@ abstract public class ManagedServices {
}
private void disableNonexistentServices(int userId) {
+ final ContentResolver cr = mContext.getContentResolver();
+ boolean restoredChanged = false;
+ if (mRestored == null) {
+ String restoredSetting = Settings.Secure.getStringForUser(
+ cr,
+ restoredSettingName(mConfig),
+ userId);
+ if (!TextUtils.isEmpty(restoredSetting)) {
+ if (DEBUG) Slog.d(TAG, "restored: " + restoredSetting);
+ String[] restored = restoredSetting.split(ENABLED_SERVICES_SEPARATOR);
+ mRestored = new ArraySet<String>(Arrays.asList(restored));
+ } else {
+ mRestored = new ArraySet<String>();
+ }
+ }
String flatIn = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
+ cr,
mConfig.secureSettingName,
userId);
if (!TextUtils.isEmpty(flatIn)) {
@@ -228,14 +292,16 @@ abstract public class ManagedServices {
ResolveInfo resolveInfo = installedServices.get(i);
ServiceInfo info = resolveInfo.serviceInfo;
+ ComponentName component = new ComponentName(info.packageName, info.name);
if (!mConfig.bindPermission.equals(info.permission)) {
Slog.w(TAG, "Skipping " + getCaption() + " service "
+ info.packageName + "/" + info.name
+ ": it does not require the permission "
+ mConfig.bindPermission);
+ restoredChanged |= mRestored.remove(component.flattenToString());
continue;
}
- installed.add(new ComponentName(info.packageName, info.name));
+ installed.add(component);
}
String flatOut = "";
@@ -246,16 +312,27 @@ abstract public class ManagedServices {
ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
if (installed.contains(enabledComponent)) {
remaining.add(enabled[i]);
+ restoredChanged |= mRestored.remove(enabled[i]);
}
}
+ remaining.addAll(mRestored);
flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining);
}
if (DEBUG) Slog.v(TAG, "flat after: " + flatOut);
if (!flatIn.equals(flatOut)) {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(cr,
mConfig.secureSettingName,
flatOut, userId);
}
+ if (restoredChanged) {
+ if (DEBUG) Slog.d(TAG, "restored changed; rewriting");
+ final String flatRestored = TextUtils.join(ENABLED_SERVICES_SEPARATOR,
+ mRestored.toArray());
+ Settings.Secure.putStringForUser(cr,
+ restoredSettingName(mConfig),
+ flatRestored,
+ userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
deleted file mode 100644
index 1634c65..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2014 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.notification;
-
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-
-/**
- * Built-in zen condition provider for alarm-clock-based conditions.
- *
- * <p>If the user's next alarm is within a lookahead threshold (config, default 12hrs), advertise
- * it as an exit condition for zen mode.
- *
- * <p>The next alarm is defined as {@link AlarmManager#getNextAlarmClock(int)}, which does not
- * survive a reboot. Maintain the illusion of a consistent next alarm value by holding on to
- * a persisted condition until we receive the first value after reboot, or timeout with no value.
- */
-public class NextAlarmConditionProvider extends ConditionProviderService {
- private static final String TAG = "NextAlarmConditions";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long HOURS = 60 * MINUTES;
-
- private static final long BAD_CONDITION = -1;
-
- public static final ComponentName COMPONENT =
- new ComponentName("android", NextAlarmConditionProvider.class.getName());
-
- private final Context mContext = this;
- private final NextAlarmTracker mTracker;
- private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
-
- private boolean mConnected;
- private long mLookaheadThreshold;
- private boolean mRequesting;
-
- public NextAlarmConditionProvider(NextAlarmTracker tracker) {
- if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()");
- mTracker = tracker;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" NextAlarmConditionProvider:");
- pw.print(" mConnected="); pw.println(mConnected);
- pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
- pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
- pw.print(" mSubscriptions="); pw.println(mSubscriptions);
- pw.print(" mRequesting="); pw.println(mRequesting);
- }
-
- @Override
- public void onConnected() {
- if (DEBUG) Slog.d(TAG, "onConnected");
- mLookaheadThreshold = PropConfig.getInt(mContext, "nextalarm.condition.lookahead",
- R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
- mConnected = true;
- mTracker.addCallback(mTrackerCallback);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (DEBUG) Slog.d(TAG, "onDestroy");
- mTracker.removeCallback(mTrackerCallback);
- mConnected = false;
- }
-
- @Override
- public void onRequestConditions(int relevance) {
- if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
- if (!mConnected) return;
- mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
- mTracker.evaluate();
- }
-
- @Override
- public void onSubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
- if (tryParseNextAlarmCondition(conditionId) == BAD_CONDITION) {
- notifyCondition(conditionId, null, Condition.STATE_FALSE, "badCondition");
- return;
- }
- mSubscriptions.add(conditionId);
- mTracker.evaluate();
- }
-
- @Override
- public void onUnsubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
- mSubscriptions.remove(conditionId);
- }
-
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
- private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) {
- if (alarm == null) return false;
- final long delta = NextAlarmTracker.getEarlyTriggerTime(alarm) - System.currentTimeMillis();
- return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold);
- }
-
- private void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) {
- final String formattedAlarm = alarm == null ? "" : mTracker.formatAlarm(alarm);
- if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
- + " alarm=" + formattedAlarm + " reason=" + reason);
- notifyCondition(new Condition(id,
- mContext.getString(R.string.zen_mode_next_alarm_summary, formattedAlarm),
- mContext.getString(R.string.zen_mode_next_alarm_line_one),
- formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW));
- }
-
- private Uri newConditionId(AlarmClockInfo nextAlarm) {
- return new Uri.Builder().scheme(Condition.SCHEME)
- .authority(ZenModeConfig.SYSTEM_AUTHORITY)
- .appendPath(ZenModeConfig.NEXT_ALARM_PATH)
- .appendPath(Integer.toString(mTracker.getCurrentUserId()))
- .appendPath(Long.toString(nextAlarm.getTriggerTime()))
- .build();
- }
-
- private long tryParseNextAlarmCondition(Uri conditionId) {
- return conditionId != null && conditionId.getScheme().equals(Condition.SCHEME)
- && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
- && conditionId.getPathSegments().size() == 3
- && conditionId.getPathSegments().get(0).equals(ZenModeConfig.NEXT_ALARM_PATH)
- && conditionId.getPathSegments().get(1)
- .equals(Integer.toString(mTracker.getCurrentUserId()))
- ? tryParseLong(conditionId.getPathSegments().get(2), BAD_CONDITION)
- : BAD_CONDITION;
- }
-
- private static long tryParseLong(String value, long defValue) {
- if (TextUtils.isEmpty(value)) return defValue;
- try {
- return Long.valueOf(value);
- } catch (NumberFormatException e) {
- return defValue;
- }
- }
-
- private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm);
- final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
- if (DEBUG) Slog.d(TAG, "onEvaluate mSubscriptions=" + mSubscriptions
- + " nextAlarmTime=" + mTracker.formatAlarmDebug(nextAlarmTime)
- + " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime)
- + " withinThreshold=" + withinThreshold
- + " booted=" + booted);
-
- ArraySet<Uri> conditions = mSubscriptions;
- if (mRequesting && nextAlarm != null && withinThreshold) {
- final Uri id = newConditionId(nextAlarm);
- if (!conditions.contains(id)) {
- conditions = new ArraySet<Uri>(conditions);
- conditions.add(id);
- }
- }
- for (Uri conditionId : conditions) {
- final long time = tryParseNextAlarmCondition(conditionId);
- if (time == BAD_CONDITION) {
- notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "badCondition");
- } else if (!booted) {
- // we don't know yet
- if (mSubscriptions.contains(conditionId)) {
- notifyCondition(conditionId, nextAlarm, Condition.STATE_UNKNOWN, "!booted");
- }
- } else if (time != nextAlarmTime) {
- // next alarm changed since subscription, consider obsolete
- notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "changed");
- } else if (!withinThreshold) {
- // next alarm outside threshold or in the past, condition = false
- notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "!within");
- } else {
- // next alarm within threshold and in the future, condition = true
- notifyCondition(conditionId, nextAlarm, Condition.STATE_TRUE, "within");
- }
- }
- }
-
- private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
- @Override
- public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted);
- }
- };
-}
diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java
deleted file mode 100644
index 234f545..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmTracker.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2014 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.notification;
-
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-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.Message;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.text.format.DateFormat;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Locale;
-
-/** Helper for tracking updates to the current user's next alarm. */
-public class NextAlarmTracker {
- private static final String TAG = "NextAlarmTracker";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String ACTION_TRIGGER = TAG + ".trigger";
- private static final String EXTRA_TRIGGER = "trigger";
- private static final int REQUEST_CODE = 100;
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long NEXT_ALARM_UPDATE_DELAY = 1 * SECONDS; // treat clear+set as update
- private static final long EARLY = 5 * SECONDS; // fire early, ensure alarm stream is unmuted
- private static final long WAIT_AFTER_INIT = 5 * MINUTES;// for initial alarm re-registration
- private static final long WAIT_AFTER_BOOT = 20 * SECONDS; // for initial alarm re-registration
-
- private final Context mContext;
- private final H mHandler = new H();
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-
- private long mInit;
- private boolean mRegistered;
- private AlarmManager mAlarmManager;
- private int mCurrentUserId;
- private long mScheduledAlarmTime;
- private long mBootCompleted;
- private PowerManager.WakeLock mWakeLock;
-
- public NextAlarmTracker(Context context) {
- mContext = context;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" NextAlarmTracker:");
- pw.print(" len(mCallbacks)="); pw.println(mCallbacks.size());
- pw.print(" mRegistered="); pw.println(mRegistered);
- pw.print(" mInit="); pw.println(mInit);
- pw.print(" mBootCompleted="); pw.println(mBootCompleted);
- pw.print(" mCurrentUserId="); pw.println(mCurrentUserId);
- pw.print(" mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime));
- pw.print(" mWakeLock="); pw.println(mWakeLock);
- }
-
- public void addCallback(Callback callback) {
- mCallbacks.add(callback);
- }
-
- public void removeCallback(Callback callback) {
- mCallbacks.remove(callback);
- }
-
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- public AlarmClockInfo getNextAlarm() {
- return mAlarmManager.getNextAlarmClock(mCurrentUserId);
- }
-
- public void onUserSwitched() {
- reset();
- }
-
- public void init() {
- mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mInit = System.currentTimeMillis();
- reset();
- }
-
- public void reset() {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- }
- mCurrentUserId = ActivityManager.getCurrentUser();
- final IntentFilter filter = new IntentFilter();
- filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- filter.addAction(ACTION_TRIGGER);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- filter.addAction(Intent.ACTION_BOOT_COMPLETED);
- mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null,
- null);
- mRegistered = true;
- evaluate();
- }
-
- public void destroy() {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- mRegistered = false;
- }
- }
-
- public void evaluate() {
- mHandler.postEvaluate(0);
- }
-
- private void fireEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- for (Callback callback : mCallbacks) {
- callback.onEvaluate(nextAlarm, wakeupTime, booted);
- }
- }
-
- private void handleEvaluate() {
- final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId);
- final long triggerTime = getEarlyTriggerTime(nextAlarm);
- final long now = System.currentTimeMillis();
- final boolean alarmUpcoming = triggerTime > now;
- final boolean booted = isDoneWaitingAfterBoot(now);
- if (DEBUG) Slog.d(TAG, "handleEvaluate nextAlarm=" + formatAlarmDebug(triggerTime)
- + " alarmUpcoming=" + alarmUpcoming
- + " booted=" + booted);
- fireEvaluate(nextAlarm, triggerTime, booted);
- if (!booted) {
- // recheck after boot
- final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT;
- rescheduleAlarm(recheckTime);
- return;
- }
- if (alarmUpcoming) {
- // wake up just before the next alarm
- rescheduleAlarm(triggerTime);
- }
- }
-
- public static long getEarlyTriggerTime(AlarmClockInfo alarm) {
- return alarm != null ? (alarm.getTriggerTime() - EARLY) : 0;
- }
-
- private boolean isDoneWaitingAfterBoot(long time) {
- if (mBootCompleted > 0) return (time - mBootCompleted) > WAIT_AFTER_BOOT;
- if (mInit > 0) return (time - mInit) > WAIT_AFTER_INIT;
- return true;
- }
-
- public static String formatDuration(long millis) {
- final StringBuilder sb = new StringBuilder();
- TimeUtils.formatDuration(millis, sb);
- return sb.toString();
- }
-
- public String formatAlarm(AlarmClockInfo alarm) {
- return alarm != null ? formatAlarm(alarm.getTriggerTime()) : null;
- }
-
- private String formatAlarm(long time) {
- return formatAlarm(time, "Hm", "hma");
- }
-
- private String formatAlarm(long time, String skeleton24, String skeleton12) {
- final String skeleton = DateFormat.is24HourFormat(mContext) ? skeleton24 : skeleton12;
- final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- return DateFormat.format(pattern, time).toString();
- }
-
- public String formatAlarmDebug(AlarmClockInfo alarm) {
- return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0);
- }
-
- public String formatAlarmDebug(long time) {
- if (time <= 0) return Long.toString(time);
- return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa"));
- }
-
- private void rescheduleAlarm(long time) {
- if (DEBUG) Slog.d(TAG, "rescheduleAlarm " + time);
- final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
- new Intent(ACTION_TRIGGER)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_TRIGGER, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
- alarms.cancel(pendingIntent);
- mScheduledAlarmTime = time;
- if (time > 0) {
- if (DEBUG) Slog.d(TAG, String.format("Scheduling alarm for %s (in %s)",
- formatAlarmDebug(time), formatDuration(time - System.currentTimeMillis())));
- alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- }
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (DEBUG) Slog.d(TAG, "onReceive " + action);
- long delay = 0;
- if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
- delay = NEXT_ALARM_UPDATE_DELAY;
- if (DEBUG) Slog.d(TAG, String.format(" next alarm for user %s: %s",
- mCurrentUserId,
- formatAlarmDebug(mAlarmManager.getNextAlarmClock(mCurrentUserId))));
- } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- mBootCompleted = System.currentTimeMillis();
- }
- mHandler.postEvaluate(delay);
- mWakeLock.acquire(delay + 5000); // stay awake during evaluate
- }
- };
-
- private class H extends Handler {
- private static final int MSG_EVALUATE = 1;
-
- public void postEvaluate(long delay) {
- removeMessages(MSG_EVALUATE);
- sendEmptyMessageDelayed(MSG_EVALUATE, delay);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_EVALUATE) {
- handleEvaluate();
- }
- }
- }
-
- public interface Callback {
- void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted);
- }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
index 1335706..22cdd58 100644
--- a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
@@ -18,6 +18,7 @@ package com.android.server.notification;
import android.app.Notification;
import android.content.Context;
+import android.util.Log;
import android.util.Slog;
/**
@@ -25,8 +26,8 @@ import android.util.Slog;
* notifications and marks them to get a temporary ranking bump.
*/
public class NotificationIntrusivenessExtractor implements NotificationSignalExtractor {
- private static final String TAG = "NotificationNoiseExtractor";
- private static final boolean DBG = false;
+ private static final String TAG = "IntrusivenessExtractor";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
/** Length of time (in milliseconds) that an intrusive or noisy notification will stay at
the top of the ranking order, before it falls back to its natural position. */
@@ -48,7 +49,7 @@ public class NotificationIntrusivenessExtractor implements NotificationSignalExt
(notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
notification.sound != null ||
notification.fullScreenIntent != null) {
- record.setRecentlyIntusive(true);
+ record.setRecentlyIntrusive(true);
}
return new RankingReconsideration(record.getKey(), HANG_TIME_MS) {
@@ -59,7 +60,7 @@ public class NotificationIntrusivenessExtractor implements NotificationSignalExt
@Override
public void applyChangesLocked(NotificationRecord record) {
- record.setRecentlyIntusive(false);
+ record.setRecentlyIntrusive(false);
}
};
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f49d77d..1008653 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -29,9 +29,11 @@ import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
+import android.app.INotificationManagerCallback;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
@@ -50,10 +52,12 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioAttributes;
import android.media.AudioManager;
+import android.media.AudioManagerInternal;
import android.media.AudioSystem;
import android.media.IRingtonePlayer;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -64,6 +68,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
@@ -124,6 +129,8 @@ import java.util.Objects;
public class NotificationManagerService extends SystemService {
static final String TAG = "NotificationService";
static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+ public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE
+ && SystemProperties.getBoolean("debug.child_notifs", false);
static final int MAX_PACKAGE_NOTIFICATIONS = 50;
@@ -179,6 +186,7 @@ public class NotificationManagerService extends SystemService {
private IActivityManager mAm;
AudioManager mAudioManager;
+ AudioManagerInternal mAudioManagerInternal;
StatusBarManagerInternal mStatusBar;
Vibrator mVibrator;
@@ -207,7 +215,7 @@ public class NotificationManagerService extends SystemService {
private final ArraySet<ManagedServiceInfo> mListenersDisablingEffects = new ArraySet<>();
private ComponentName mEffectsSuppressor;
private int mListenerHints; // right now, all hints are global
- private int mInterruptionFilter; // current ZEN mode as communicated to listeners
+ private int mInterruptionFilter = NotificationListenerService.INTERRUPTION_FILTER_UNKNOWN;
// for enabling and disabling notification pulse behavior
private boolean mScreenOn = true;
@@ -221,6 +229,8 @@ public class NotificationManagerService extends SystemService {
new ArrayMap<String, NotificationRecord>();
final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
+ private final ArrayMap<String, Policy.Token> mPolicyTokens = new ArrayMap<>();
+
// The last key in this list owns the hardware.
ArrayList<String> mLights = new ArrayList<>();
@@ -873,7 +883,8 @@ public class NotificationManagerService extends SystemService {
mRankingHelper = new RankingHelper(getContext(),
new RankingWorkerHandler(mRankingThread.getLooper()),
extractorNames);
- mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper());
+ mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
+ mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
public void onConfigChanged() {
@@ -886,6 +897,13 @@ public class NotificationManagerService extends SystemService {
updateInterruptionFilterLocked();
}
}
+
+ @Override
+ void onPolicyChanged() {
+ getContext().sendBroadcast(
+ new Intent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY));
+ }
});
final File systemDir = new File(Environment.getDataDirectory(), "system");
mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml"));
@@ -894,8 +912,6 @@ public class NotificationManagerService extends SystemService {
importOldBlockDb();
mListeners = new NotificationListeners();
- mConditionProviders = new ConditionProviders(getContext(),
- mHandler, mUserProfiles, mZenModeHelper);
mStatusBar = getLocalService(StatusBarManagerInternal.class);
mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -930,7 +946,7 @@ public class NotificationManagerService extends SystemService {
Settings.Global.DEVICE_PROVISIONED, 0)) {
mDisableNotificationEffects = true;
}
- mZenModeHelper.readZenModeFromSetting();
+ mZenModeHelper.initZenMode();
mInterruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter();
mUserProfiles.updateCache(getContext());
@@ -997,6 +1013,7 @@ public class NotificationManagerService extends SystemService {
// Grab our optional AudioService
mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
mZenModeHelper.onSystemReady();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
@@ -1023,6 +1040,7 @@ public class NotificationManagerService extends SystemService {
private void updateListenerHintsLocked() {
final int hints = mListenersDisablingEffects.isEmpty() ? 0 : HINT_HOST_DISABLE_EFFECTS;
if (hints == mListenerHints) return;
+ ZenLog.traceListenerHintsChanged(mListenerHints, hints, mListenersDisablingEffects.size());
mListenerHints = hints;
scheduleListenerHintsChanged(hints);
}
@@ -1031,6 +1049,7 @@ public class NotificationManagerService extends SystemService {
final ComponentName suppressor = !mListenersDisablingEffects.isEmpty()
? mListenersDisablingEffects.valueAt(0).component : null;
if (Objects.equals(suppressor, mEffectsSuppressor)) return;
+ ZenLog.traceEffectsSuppressorChanged(mEffectsSuppressor, suppressor);
mEffectsSuppressor = suppressor;
mZenModeHelper.setEffectsSuppressed(suppressor != null);
getContext().sendBroadcast(new Intent(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)
@@ -1207,6 +1226,19 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public void setPackagePeekable(String pkg, int uid, boolean peekable) {
+ checkCallerIsSystem();
+
+ mRankingHelper.setPackagePeekable(pkg, uid, peekable);
+ }
+
+ @Override
+ public boolean getPackagePeekable(String pkg, int uid) {
+ checkCallerIsSystem();
+ return mRankingHelper.getPackagePeekable(pkg, uid);
+ }
+
+ @Override
public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
checkCallerIsSystem();
mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
@@ -1250,6 +1282,44 @@ public class NotificationManagerService extends SystemService {
}
/**
+ * Public API for getting a list of current notifications for the calling package/uid.
+ *
+ * @returns A list of all the package's notifications, in natural order.
+ */
+ @Override
+ public ParceledListSlice<StatusBarNotification> getAppActiveNotifications(String pkg,
+ int incomingUserId) {
+ checkCallerIsSystemOrSameApp(pkg);
+ int userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), incomingUserId, true, false,
+ "getAppActiveNotifications", pkg);
+
+ final int N = mNotificationList.size();
+ final ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>(N);
+
+ synchronized (mNotificationList) {
+ for (int i = 0; i < N; i++) {
+ final StatusBarNotification sbn = mNotificationList.get(i).sbn;
+ if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId) {
+ // We could pass back a cloneLight() but clients might get confused and
+ // try to send this thing back to notify() again, which would not work
+ // very well.
+ final StatusBarNotification sbnOut = new StatusBarNotification(
+ sbn.getPackageName(),
+ sbn.getOpPkg(),
+ sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
+ 0, // hide score from apps
+ sbn.getNotification().clone(),
+ sbn.getUser(), sbn.getPostTime());
+ list.add(sbnOut);
+ }
+ }
+ }
+
+ return new ParceledListSlice<StatusBarNotification>(list);
+ }
+
+ /**
* System-only API for getting a list of recent (cleared, no longer shown) notifications.
*
* Requires ACCESS_NOTIFICATIONS which is signature|system.
@@ -1468,57 +1538,60 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public int getZenMode() {
+ return mZenModeHelper.getZenMode();
+ }
+
+ @Override
public ZenModeConfig getZenModeConfig() {
- enforceSystemOrSystemUI("INotificationManager.getZenModeConfig");
+ enforceSystemOrSystemUIOrVolume("INotificationManager.getZenModeConfig");
return mZenModeHelper.getConfig();
}
@Override
- public boolean setZenModeConfig(ZenModeConfig config) {
+ public boolean setZenModeConfig(ZenModeConfig config, String reason) {
checkCallerIsSystem();
- return mZenModeHelper.setConfig(config);
+ return mZenModeHelper.setConfig(config, reason);
}
@Override
- public void notifyConditions(String pkg, IConditionProvider provider,
- Condition[] conditions) {
- final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider);
- checkCallerIsSystemOrSameApp(pkg);
+ public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
+ enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
final long identity = Binder.clearCallingIdentity();
try {
- mConditionProviders.notifyConditions(pkg, info, conditions);
+ mZenModeHelper.setManualZenMode(mode, conditionId, reason);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public void requestZenModeConditions(IConditionListener callback, int relevance) {
- enforceSystemOrSystemUI("INotificationManager.requestZenModeConditions");
- mConditionProviders.requestZenModeConditions(callback, relevance);
- }
-
- @Override
- public void setZenModeCondition(Condition condition) {
- enforceSystemOrSystemUI("INotificationManager.setZenModeCondition");
+ public void notifyConditions(String pkg, IConditionProvider provider,
+ Condition[] conditions) {
+ final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider);
+ checkCallerIsSystemOrSameApp(pkg);
final long identity = Binder.clearCallingIdentity();
try {
- mConditionProviders.setZenModeCondition(condition, "binderCall");
+ mConditionProviders.notifyConditions(pkg, info, conditions);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public void setAutomaticZenModeConditions(Uri[] conditionIds) {
- enforceSystemOrSystemUI("INotificationManager.setAutomaticZenModeConditions");
- mConditionProviders.setAutomaticZenModeConditions(conditionIds);
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions");
+ mZenModeHelper.requestZenModeConditions(callback, relevance);
}
- @Override
- public Condition[] getAutomaticZenModeConditions() {
- enforceSystemOrSystemUI("INotificationManager.getAutomaticZenModeConditions");
- return mConditionProviders.getAutomaticZenModeConditions();
+ private void enforceSystemOrSystemUIOrVolume(String message) {
+ if (mAudioManagerInternal != null) {
+ final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
+ if (vcuid > 0 && Binder.getCallingUid() == vcuid) {
+ return;
+ }
+ }
+ enforceSystemOrSystemUI(message);
}
private void enforceSystemOrSystemUI(String message) {
@@ -1527,6 +1600,18 @@ public class NotificationManagerService extends SystemService {
message);
}
+ private void enforcePolicyToken(Policy.Token token, String method) {
+ if (!checkPolicyToken(token)) {
+ Slog.w(TAG, "Invalid notification policy token calling " + method);
+ throw new SecurityException("Invalid notification policy token");
+ }
+ }
+
+ private boolean checkPolicyToken(Policy.Token token) {
+ return mPolicyTokens.containsValue(token)
+ || mListeners.mPolicyTokens.containsValue(token);
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -1542,7 +1627,7 @@ public class NotificationManagerService extends SystemService {
@Override
public ComponentName getEffectsSuppressor() {
- enforceSystemOrSystemUI("INotificationManager.getEffectsSuppressor");
+ enforceSystemOrSystemUIOrVolume("INotificationManager.getEffectsSuppressor");
return mEffectsSuppressor;
}
@@ -1559,27 +1644,88 @@ public class NotificationManagerService extends SystemService {
@Override
public boolean isSystemConditionProviderEnabled(String path) {
- enforceSystemOrSystemUI("INotificationManager.isSystemConditionProviderEnabled");
- return mConditionProviders.isSystemConditionProviderEnabled(path);
+ enforceSystemOrSystemUIOrVolume("INotificationManager.isSystemConditionProviderEnabled");
+ return mConditionProviders.isSystemProviderEnabled(path);
}
- };
- private String[] getActiveNotificationKeys(INotificationListener token) {
- final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
- final ArrayList<String> keys = new ArrayList<String>();
- if (info.isEnabledForCurrentProfiles()) {
- synchronized (mNotificationList) {
- final int N = mNotificationList.size();
- for (int i = 0; i < N; i++) {
- final StatusBarNotification sbn = mNotificationList.get(i).sbn;
- if (info.enabledAndUserMatches(sbn.getUserId())) {
- keys.add(sbn.getKey());
+ // Backup/restore interface
+ @Override
+ public byte[] getBackupPayload(int user) {
+ // TODO: build a payload of whatever is appropriate
+ return null;
+ }
+
+ @Override
+ public void applyRestore(byte[] payload, int user) {
+ // TODO: apply the restored payload as new current state
+ }
+
+ @Override
+ public Policy.Token getPolicyTokenFromListener(INotificationListener listener) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mListeners.getPolicyToken(listener);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void requestNotificationPolicyToken(String pkg,
+ INotificationManagerCallback callback) throws RemoteException {
+ if (callback == null) {
+ Slog.w(TAG, "requestNotificationPolicyToken: no callback specified");
+ return;
+ }
+ if (pkg == null) {
+ Slog.w(TAG, "requestNotificationPolicyToken denied: no package specified");
+ callback.onPolicyToken(null);
+ return;
+ }
+ Policy.Token token = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mNotificationList) {
+ token = mPolicyTokens.get(pkg);
+ if (token == null) {
+ token = new Policy.Token(new Binder());
+ mPolicyTokens.put(pkg, token);
}
+ if (DBG) Slog.w(TAG, "requestNotificationPolicyToken granted for " + pkg);
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
+ callback.onPolicyToken(token);
}
- return keys.toArray(new String[keys.size()]);
- }
+
+ @Override
+ public boolean isNotificationPolicyTokenValid(String pkg, Policy.Token token) {
+ return checkPolicyToken(token);
+ }
+
+ @Override
+ public Policy getNotificationPolicy(Policy.Token token) {
+ enforcePolicyToken(token, "getNotificationPolicy");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mZenModeHelper.getNotificationPolicy();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setNotificationPolicy(Policy.Token token, Policy policy) {
+ enforcePolicyToken(token, "setNotificationPolicy");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mZenModeHelper.setNotificationPolicy(policy);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ };
private String disableNotificationEffects(NotificationRecord record) {
if (mDisableNotificationEffects) {
@@ -1694,6 +1840,10 @@ public class NotificationManagerService extends SystemService {
pw.print(listener.component);
}
pw.println(')');
+ pw.print(" mPolicyTokens.keys: ");
+ pw.println(TextUtils.join(",", mPolicyTokens.keySet()));
+ pw.print(" mListeners.mPolicyTokens.keys: ");
+ pw.println(TextUtils.join(",", mListeners.mPolicyTokens.keySet()));
}
pw.println("\n Condition providers:");
@@ -1815,6 +1965,14 @@ public class NotificationManagerService extends SystemService {
notification.priority = Notification.PRIORITY_HIGH;
}
}
+ // force no heads up per package config
+ if (!mRankingHelper.getPackagePeekable(pkg, callingUid)) {
+ if (notification.extras == null) {
+ notification.extras = new Bundle();
+ }
+ notification.extras.putInt(Notification.EXTRA_AS_HEADS_UP,
+ Notification.HEADS_UP_NEVER);
+ }
// 1. initial score: buckets of 10, around the app [-20..20]
final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER;
@@ -1981,35 +2139,37 @@ public class NotificationManagerService extends SystemService {
*/
private boolean removeUnusedGroupedNotificationLocked(NotificationRecord r,
NotificationRecord old, int callingUid, int callingPid) {
- // No optimizations are possible if listeners want groups.
- if (mListeners.notificationGroupsDesired()) {
- return false;
- }
+ if (!ENABLE_CHILD_NOTIFICATIONS) {
+ // No optimizations are possible if listeners want groups.
+ if (mListeners.notificationGroupsDesired()) {
+ return false;
+ }
- StatusBarNotification sbn = r.sbn;
- String group = sbn.getGroupKey();
- boolean isSummary = sbn.getNotification().isGroupSummary();
- boolean isChild = sbn.getNotification().isGroupChild();
+ StatusBarNotification sbn = r.sbn;
+ String group = sbn.getGroupKey();
+ boolean isSummary = sbn.getNotification().isGroupSummary();
+ boolean isChild = sbn.getNotification().isGroupChild();
- NotificationRecord summary = mSummaryByGroupKey.get(group);
- if (isChild && summary != null) {
- // Child with an active summary -> ignore
- if (DBG) {
- Slog.d(TAG, "Ignoring group child " + sbn.getKey() + " due to existing summary "
- + summary.getKey());
- }
- // Make sure we don't leave an old version of the notification around.
- if (old != null) {
+ NotificationRecord summary = mSummaryByGroupKey.get(group);
+ if (isChild && summary != null) {
+ // Child with an active summary -> ignore
if (DBG) {
- Slog.d(TAG, "Canceling old version of ignored group child " + sbn.getKey());
+ Slog.d(TAG, "Ignoring group child " + sbn.getKey() + " due to existing summary "
+ + summary.getKey());
}
- cancelNotificationLocked(old, false, REASON_GROUP_OPTIMIZATION);
+ // Make sure we don't leave an old version of the notification around.
+ if (old != null) {
+ if (DBG) {
+ Slog.d(TAG, "Canceling old version of ignored group child " + sbn.getKey());
+ }
+ cancelNotificationLocked(old, false, REASON_GROUP_OPTIMIZATION);
+ }
+ return true;
+ } else if (isSummary) {
+ // Summary -> cancel children
+ cancelGroupChildrenLocked(r, callingUid, callingPid, null,
+ REASON_GROUP_OPTIMIZATION);
}
- return true;
- } else if (isSummary) {
- // Summary -> cancel children
- cancelGroupChildrenLocked(r, callingUid, callingPid, null,
- REASON_GROUP_OPTIMIZATION);
}
return false;
}
@@ -2541,7 +2701,8 @@ public class NotificationManagerService extends SystemService {
// Save it for users of getHistoricalNotifications()
mArchive.record(r.sbn);
- EventLogTags.writeNotificationCanceled(canceledKey, reason);
+ int lifespan = (int) (System.currentTimeMillis() - r.getCreationTimeMs());
+ EventLogTags.writeNotificationCanceled(canceledKey, reason, lifespan);
}
/**
@@ -2560,8 +2721,8 @@ public class NotificationManagerService extends SystemService {
@Override
public void run() {
String listenerName = listener == null ? null : listener.component.toShortString();
- EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag, userId,
- mustHaveFlags, mustNotHaveFlags, reason, listenerName);
+ if (DBG) EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag,
+ userId, mustHaveFlags, mustNotHaveFlags, reason, listenerName);
synchronized (mNotificationList) {
int index = indexOfNotificationLocked(pkg, tag, id, userId);
@@ -2936,12 +3097,18 @@ public class NotificationManagerService extends SystemService {
public class NotificationListeners extends ManagedServices {
private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
+ private final ArrayMap<ComponentName, Policy.Token> mPolicyTokens = new ArrayMap<>();
private boolean mNotificationGroupsDesired;
public NotificationListeners() {
super(getContext(), mHandler, mNotificationList, mUserProfiles);
}
+ public Policy.Token getPolicyToken(INotificationListener listener) {
+ final ManagedServiceInfo info = checkServiceTokenLocked(listener);
+ return info == null ? null : mPolicyTokens.get(info.component);
+ }
+
@Override
protected Config getConfig() {
Config c = new Config();
@@ -2966,6 +3133,7 @@ public class NotificationManagerService extends SystemService {
synchronized (mNotificationList) {
updateNotificationGroupsDesiredLocked();
update = makeRankingUpdateLocked(info);
+ mPolicyTokens.put(info.component, new Policy.Token(new Binder()));
}
try {
listener.onListenerConnected(update);
@@ -2982,6 +3150,7 @@ public class NotificationManagerService extends SystemService {
}
mLightTrimListeners.remove(removed);
updateNotificationGroupsDesiredLocked();
+ mPolicyTokens.remove(removed.component);
}
public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index ea6f2db..5569a09 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -62,6 +62,9 @@ public final class NotificationRecord {
// The timestamp used for ranking.
private long mRankingTimeMs;
+ // The first post time, stable across updates.
+ private long mCreationTimeMs;
+
// Is this record an update of an old record?
public boolean isUpdate;
private int mPackagePriority;
@@ -77,6 +80,7 @@ public final class NotificationRecord {
this.score = score;
mOriginalFlags = sbn.getNotification().flags;
mRankingTimeMs = calculateRankingTimeMs(0L);
+ mCreationTimeMs = sbn.getPostTime();
}
// copy any notes that the ranking system may have made before the update
@@ -87,6 +91,7 @@ public final class NotificationRecord {
mPackageVisibility = previous.mPackageVisibility;
mIntercept = previous.mIntercept;
mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
+ mCreationTimeMs = previous.mCreationTimeMs;
// Don't copy mGlobalSortKey, recompute it.
}
@@ -167,6 +172,7 @@ public final class NotificationRecord {
pw.println(prefix + " mIntercept=" + mIntercept);
pw.println(prefix + " mGlobalSortKey=" + mGlobalSortKey);
pw.println(prefix + " mRankingTimeMs=" + mRankingTimeMs);
+ pw.println(prefix + " mCreationTimeMs=" + mCreationTimeMs);
}
@@ -209,7 +215,7 @@ public final class NotificationRecord {
return mContactAffinity;
}
- public void setRecentlyIntusive(boolean recentlyIntrusive) {
+ public void setRecentlyIntrusive(boolean recentlyIntrusive) {
mRecentlyIntrusive = recentlyIntrusive;
}
@@ -263,6 +269,13 @@ public final class NotificationRecord {
}
/**
+ * Returns the timestamp of the first post, ignoring updates.
+ */
+ public long getCreationTimeMs() {
+ return mCreationTimeMs;
+ }
+
+ /**
* @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
* of the previous notification record, 0 otherwise
*/
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index 8278c4f..4696771 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -28,6 +28,7 @@ import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import com.android.internal.logging.MetricsLogger;
import com.android.server.notification.NotificationManagerService.DumpFilter;
import java.io.PrintWriter;
@@ -49,15 +50,17 @@ public class NotificationUsageStats {
// WARNING: Aggregated stats can grow unboundedly with pkg+id+tag.
// Don't enable on production builds.
private static final boolean ENABLE_AGGREGATED_IN_MEMORY_STATS = false;
- private static final boolean ENABLE_SQLITE_LOG = false;
+ private static final boolean ENABLE_SQLITE_LOG = true;
private static final AggregatedStats[] EMPTY_AGGREGATED_STATS = new AggregatedStats[0];
// Guarded by synchronized(this).
private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>();
private final SQLiteLog mSQLiteLog;
+ private final Context mContext;
public NotificationUsageStats(Context context) {
+ mContext = context;
mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null;
}
@@ -103,6 +106,8 @@ public class NotificationUsageStats {
* Called when the user dismissed the notification via the UI.
*/
public synchronized void registerDismissedByUser(NotificationRecord notification) {
+ MetricsLogger.histogram(mContext, "note_dismiss_longevity",
+ (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onDismiss();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.numDismissedByUser++;
@@ -117,6 +122,8 @@ public class NotificationUsageStats {
* Called when the user clicked the notification in the UI.
*/
public synchronized void registerClickedByUser(NotificationRecord notification) {
+ MetricsLogger.histogram(mContext, "note_click_longevity",
+ (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onClick();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.numClickedByUser++;
@@ -222,7 +229,7 @@ public class NotificationUsageStats {
public void collect(SingleNotificationStats singleNotificationStats) {
posttimeMs.addSample(
- SystemClock.elapsedRealtime() - singleNotificationStats.posttimeElapsedMs);
+ SystemClock.elapsedRealtime() - singleNotificationStats.posttimeElapsedMs);
if (singleNotificationStats.posttimeToDismissMs >= 0) {
posttimeToDismissMs.addSample(singleNotificationStats.posttimeToDismissMs);
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index aea137b..803db10 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -20,6 +20,10 @@ public interface RankingConfig {
void setPackagePriority(String packageName, int uid, int priority);
+ boolean getPackagePeekable(String packageName, int uid);
+
+ void setPackagePeekable(String packageName, int uid, boolean peekable);
+
int getPackageVisibilityOverride(String packageName, int uid);
void setPackageVisibilityOverride(String packageName, int uid, int visibility);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 6a96f85..88055ba 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -23,10 +23,8 @@ import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
import android.util.Slog;
-import android.util.SparseIntArray;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -35,12 +33,10 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Set;
import java.util.concurrent.TimeUnit;
public class RankingHelper implements RankingConfig {
private static final String TAG = "RankingHelper";
- private static final boolean DEBUG = false;
private static final int XML_VERSION = 1;
@@ -51,16 +47,20 @@ public class RankingHelper implements RankingConfig {
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
private static final String ATT_PRIORITY = "priority";
+ private static final String ATT_PEEKABLE = "peekable";
private static final String ATT_VISIBILITY = "visibility";
+ private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
+ private static final boolean DEFAULT_PEEKABLE = true;
+ private static final int DEFAULT_VISIBILITY =
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
+
private final NotificationSignalExtractor[] mSignalExtractors;
private final NotificationComparator mPreliminaryComparator = new NotificationComparator();
private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
- // Package name to uid, to priority. Would be better as Table<String, Int, Int>
- private final ArrayMap<String, SparseIntArray> mPackagePriorities;
- private final ArrayMap<String, SparseIntArray> mPackageVisibilities;
- private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp;
+ private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
+ private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
private final Context mContext;
private final Handler mRankingHandler;
@@ -68,8 +68,6 @@ public class RankingHelper implements RankingConfig {
public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) {
mContext = context;
mRankingHandler = rankingHandler;
- mPackagePriorities = new ArrayMap<String, SparseIntArray>();
- mPackageVisibilities = new ArrayMap<String, SparseIntArray>();
final int N = extractorNames.length;
mSignalExtractors = new NotificationSignalExtractor[N];
@@ -89,9 +87,9 @@ public class RankingHelper implements RankingConfig {
Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
}
}
- mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>();
}
+ @SuppressWarnings("unchecked")
public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
final int N = mSignalExtractors.length;
for (int i = 0; i < N; i++) {
@@ -126,8 +124,7 @@ public class RankingHelper implements RankingConfig {
if (type != XmlPullParser.START_TAG) return;
String tag = parser.getName();
if (!TAG_RANKING.equals(tag)) return;
- mPackagePriorities.clear();
- final int version = safeInt(parser, ATT_VERSION, XML_VERSION);
+ mRecords.clear();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
@@ -136,27 +133,20 @@ public class RankingHelper implements RankingConfig {
if (type == XmlPullParser.START_TAG) {
if (TAG_PACKAGE.equals(tag)) {
int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL);
- int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT);
- int vis = safeInt(parser, ATT_VISIBILITY,
- NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+ int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+ boolean peekable = safeBool(parser, ATT_PEEKABLE, DEFAULT_PEEKABLE);
+ int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
String name = parser.getAttributeValue(null, ATT_NAME);
if (!TextUtils.isEmpty(name)) {
- if (priority != Notification.PRIORITY_DEFAULT) {
- SparseIntArray priorityByUid = mPackagePriorities.get(name);
- if (priorityByUid == null) {
- priorityByUid = new SparseIntArray();
- mPackagePriorities.put(name, priorityByUid);
- }
- priorityByUid.put(uid, priority);
+ if (priority != DEFAULT_PRIORITY) {
+ getOrCreateRecord(name, uid).priority = priority;
+ }
+ if (peekable != DEFAULT_PEEKABLE) {
+ getOrCreateRecord(name, uid).peekable = peekable;
}
- if (vis != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
- SparseIntArray visibilityByUid = mPackageVisibilities.get(name);
- if (visibilityByUid == null) {
- visibilityByUid = new SparseIntArray();
- mPackageVisibilities.put(name, visibilityByUid);
- }
- visibilityByUid.put(uid, vis);
+ if (vis != DEFAULT_VISIBILITY) {
+ getOrCreateRecord(name, uid).visibility = vis;
}
}
}
@@ -165,49 +155,53 @@ public class RankingHelper implements RankingConfig {
throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
+ private static String recordKey(String pkg, int uid) {
+ return pkg + "|" + uid;
+ }
+
+ private Record getOrCreateRecord(String pkg, int uid) {
+ final String key = recordKey(pkg, uid);
+ Record r = mRecords.get(key);
+ if (r == null) {
+ r = new Record();
+ r.pkg = pkg;
+ r.uid = uid;
+ mRecords.put(key, r);
+ }
+ return r;
+ }
+
+ private void removeDefaultRecords() {
+ final int N = mRecords.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final Record r = mRecords.valueAt(i);
+ if (r.priority == DEFAULT_PRIORITY && r.peekable == DEFAULT_PEEKABLE
+ && r.visibility == DEFAULT_VISIBILITY) {
+ mRecords.remove(i);
+ }
+ }
+ }
+
public void writeXml(XmlSerializer out) throws IOException {
out.startTag(null, TAG_RANKING);
out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
- final Set<String> packageNames = new ArraySet<>(mPackagePriorities.size()
- + mPackageVisibilities.size());
- packageNames.addAll(mPackagePriorities.keySet());
- packageNames.addAll(mPackageVisibilities.keySet());
- final Set<Integer> packageUids = new ArraySet<>();
- for (String packageName : packageNames) {
- packageUids.clear();
- SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
- SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
- if (priorityByUid != null) {
- final int M = priorityByUid.size();
- for (int j = 0; j < M; j++) {
- packageUids.add(priorityByUid.keyAt(j));
- }
+ final int N = mRecords.size();
+ for (int i = 0; i < N; i++) {
+ final Record r = mRecords.valueAt(i);
+ out.startTag(null, TAG_PACKAGE);
+ out.attribute(null, ATT_NAME, r.pkg);
+ if (r.priority != DEFAULT_PRIORITY) {
+ out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
}
- if (visibilityByUid != null) {
- final int M = visibilityByUid.size();
- for (int j = 0; j < M; j++) {
- packageUids.add(visibilityByUid.keyAt(j));
- }
+ if (r.peekable != DEFAULT_PEEKABLE) {
+ out.attribute(null, ATT_PEEKABLE, Boolean.toString(r.peekable));
}
- for (Integer uid : packageUids) {
- out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATT_NAME, packageName);
- if (priorityByUid != null) {
- final int priority = priorityByUid.get(uid);
- if (priority != Notification.PRIORITY_DEFAULT) {
- out.attribute(null, ATT_PRIORITY, Integer.toString(priority));
- }
- }
- if (visibilityByUid != null) {
- final int visibility = visibilityByUid.get(uid);
- if (visibility != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
- out.attribute(null, ATT_VISIBILITY, Integer.toString(visibility));
- }
- }
- out.attribute(null, ATT_UID, Integer.toString(uid));
- out.endTag(null, TAG_PACKAGE);
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
}
+ out.attribute(null, ATT_UID, Integer.toString(r.uid));
+ out.endTag(null, TAG_PACKAGE);
}
out.endTag(null, TAG_RANKING);
}
@@ -296,14 +290,20 @@ public class RankingHelper implements RankingConfig {
}
}
+ private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ return tryParseBool(val, defValue);
+ }
+
+ private static boolean tryParseBool(String value, boolean defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ return Boolean.valueOf(value);
+ }
+
@Override
public int getPackagePriority(String packageName, int uid) {
- int priority = Notification.PRIORITY_DEFAULT;
- SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
- if (priorityByUid != null) {
- priority = priorityByUid.get(uid, Notification.PRIORITY_DEFAULT);
- }
- return priority;
+ final Record r = mRecords.get(recordKey(packageName, uid));
+ return r != null ? r.priority : DEFAULT_PRIORITY;
}
@Override
@@ -311,24 +311,31 @@ public class RankingHelper implements RankingConfig {
if (priority == getPackagePriority(packageName, uid)) {
return;
}
- SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
- if (priorityByUid == null) {
- priorityByUid = new SparseIntArray();
- mPackagePriorities.put(packageName, priorityByUid);
+ getOrCreateRecord(packageName, uid).priority = priority;
+ removeDefaultRecords();
+ updateConfig();
+ }
+
+ @Override
+ public boolean getPackagePeekable(String packageName, int uid) {
+ final Record r = mRecords.get(recordKey(packageName, uid));
+ return r != null ? r.peekable : DEFAULT_PEEKABLE;
+ }
+
+ @Override
+ public void setPackagePeekable(String packageName, int uid, boolean peekable) {
+ if (peekable == getPackagePeekable(packageName, uid)) {
+ return;
}
- priorityByUid.put(uid, priority);
+ getOrCreateRecord(packageName, uid).peekable = peekable;
+ removeDefaultRecords();
updateConfig();
}
@Override
public int getPackageVisibilityOverride(String packageName, int uid) {
- int visibility = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
- SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
- if (visibilityByUid != null) {
- visibility = visibilityByUid.get(uid,
- NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
- }
- return visibility;
+ final Record r = mRecords.get(recordKey(packageName, uid));
+ return r != null ? r.visibility : DEFAULT_VISIBILITY;
}
@Override
@@ -336,12 +343,8 @@ public class RankingHelper implements RankingConfig {
if (visibility == getPackageVisibilityOverride(packageName, uid)) {
return;
}
- SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
- if (visibilityByUid == null) {
- visibilityByUid = new SparseIntArray();
- mPackageVisibilities.put(packageName, visibilityByUid);
- }
- visibilityByUid.put(uid, visibility);
+ getOrCreateRecord(packageName, uid).visibility = visibility;
+ removeDefaultRecords();
updateConfig();
}
@@ -357,28 +360,42 @@ public class RankingHelper implements RankingConfig {
pw.println(mSignalExtractors[i]);
}
}
- final int N = mPackagePriorities.size();
if (filter == null) {
pw.print(prefix);
- pw.println("package priorities:");
+ pw.println("per-package config:");
}
+ final int N = mRecords.size();
for (int i = 0; i < N; i++) {
- String name = mPackagePriorities.keyAt(i);
- if (filter == null || filter.matches(name)) {
- SparseIntArray priorityByUid = mPackagePriorities.get(name);
- final int M = priorityByUid.size();
- for (int j = 0; j < M; j++) {
- int uid = priorityByUid.keyAt(j);
- int priority = priorityByUid.get(uid);
- pw.print(prefix);
- pw.print(" ");
- pw.print(name);
- pw.print(" (");
- pw.print(uid);
- pw.print(") has priority: ");
- pw.println(priority);
+ final Record r = mRecords.valueAt(i);
+ if (filter == null || filter.matches(r.pkg)) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(r.pkg);
+ pw.print(" (");
+ pw.print(r.uid);
+ pw.print(')');
+ if (r.priority != DEFAULT_PRIORITY) {
+ pw.print(" priority=");
+ pw.print(Notification.priorityToString(r.priority));
+ }
+ if (r.peekable != DEFAULT_PEEKABLE) {
+ pw.print(" peekable=");
+ pw.print(r.peekable);
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ pw.print(" visibility=");
+ pw.print(Notification.visibilityToString(r.visibility));
}
+ pw.println();
}
}
}
+
+ private static class Record {
+ String pkg;
+ int uid;
+ int priority = DEFAULT_PRIORITY;
+ boolean peekable = DEFAULT_PEEKABLE;
+ int visibility = DEFAULT_VISIBILITY;
+ }
}
diff --git a/services/core/java/com/android/server/notification/DowntimeCalendar.java b/services/core/java/com/android/server/notification/ScheduleCalendar.java
index d14fd40..cea611d 100644
--- a/services/core/java/com/android/server/notification/DowntimeCalendar.java
+++ b/services/core/java/com/android/server/notification/ScheduleCalendar.java
@@ -16,38 +16,36 @@
package com.android.server.notification;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+
import java.util.Calendar;
import java.util.Objects;
import java.util.TimeZone;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
-import android.util.ArraySet;
-
-public class DowntimeCalendar {
-
+public class ScheduleCalendar {
private final ArraySet<Integer> mDays = new ArraySet<Integer>();
private final Calendar mCalendar = Calendar.getInstance();
- private DowntimeInfo mInfo;
+ private ScheduleInfo mSchedule;
@Override
public String toString() {
- return "DowntimeCalendar[mDays=" + mDays + "]";
+ return "ScheduleCalendar[mDays=" + mDays + "]";
}
- public void setDowntimeInfo(DowntimeInfo info) {
- if (Objects.equals(mInfo, info)) return;
- mInfo = info;
+ public void setSchedule(ScheduleInfo schedule) {
+ if (Objects.equals(mSchedule, schedule)) return;
+ mSchedule = schedule;
updateDays();
}
- public long nextDowntimeStart(long time) {
- if (mInfo == null || mDays.size() == 0) return Long.MAX_VALUE;
- final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
+ public long nextScheduleStart(long time) {
+ if (mSchedule == null || mDays.size() == 0) return Long.MAX_VALUE;
+ final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
for (int i = 0; i < Calendar.SATURDAY; i++) {
final long t = addDays(start, i);
- if (t > time && isInDowntime(t)) {
+ if (t > time && isInSchedule(t)) {
return t;
}
}
@@ -58,7 +56,14 @@ public class DowntimeCalendar {
mCalendar.setTimeZone(tz);
}
- public long getNextTime(long now, int hr, int min) {
+ public long getNextChangeTime(long now) {
+ if (mSchedule == null) return 0;
+ final long nextStart = getNextTime(now, mSchedule.startHour, mSchedule.startMinute);
+ final long nextEnd = getNextTime(now, mSchedule.endHour, mSchedule.endMinute);
+ return Math.min(nextStart, nextEnd);
+ }
+
+ private long getNextTime(long now, int hr, int min) {
final long time = getTime(now, hr, min);
return time <= now ? addDays(time, 1) : time;
}
@@ -72,17 +77,17 @@ public class DowntimeCalendar {
return mCalendar.getTimeInMillis();
}
- public boolean isInDowntime(long time) {
- if (mInfo == null || mDays.size() == 0) return false;
- final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
- long end = getTime(time, mInfo.endHour, mInfo.endMinute);
+ public boolean isInSchedule(long time) {
+ if (mSchedule == null || mDays.size() == 0) return false;
+ final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
+ long end = getTime(time, mSchedule.endHour, mSchedule.endMinute);
if (end <= start) {
end = addDays(end, 1);
}
- return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end);
+ return isInSchedule(-1, time, start, end) || isInSchedule(0, time, start, end);
}
- private boolean isInDowntime(int daysOffset, long time, long start, long end) {
+ private boolean isInSchedule(int daysOffset, long time, long start, long end) {
final int n = Calendar.SATURDAY;
final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1;
start = addDays(start, daysOffset);
@@ -97,10 +102,9 @@ public class DowntimeCalendar {
private void updateDays() {
mDays.clear();
- if (mInfo != null) {
- final int[] days = ZenModeConfig.tryParseDays(mInfo.mode);
- for (int i = 0; days != null && i < days.length; i++) {
- mDays.add(days[i]);
+ if (mSchedule != null && mSchedule.days != null) {
+ for (int i = 0; i < mSchedule.days.length; i++) {
+ mDays.add(mSchedule.days[i]);
}
}
}
@@ -110,4 +114,4 @@ public class DowntimeCalendar {
mCalendar.add(Calendar.DATE, days);
return mCalendar.getTimeInMillis();
}
-}
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
new file mode 100644
index 0000000..383d56c
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2015 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.notification;
+
+import android.app.AlarmManager;
+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.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionProvider;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Built-in zen condition provider for daily scheduled time-based conditions.
+ */
+public class ScheduleConditionProvider extends SystemConditionProviderService {
+ private static final String TAG = "ConditionProviders";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final ComponentName COMPONENT =
+ new ComponentName("android", ScheduleConditionProvider.class.getName());
+ private static final String NOT_SHOWN = "...";
+ private static final String ACTION_EVALUATE = TAG + ".EVALUATE";
+ private static final int REQUEST_CODE_EVALUATE = 1;
+ private static final String EXTRA_TIME = "time";
+
+ private final Context mContext = this;
+ private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
+
+ private boolean mConnected;
+ private boolean mRegistered;
+
+ public ScheduleConditionProvider() {
+ if (DEBUG) Slog.d(TAG, "new ScheduleConditionProvider()");
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+
+ @Override
+ public boolean isValidConditionid(Uri id) {
+ return ZenModeConfig.isValidScheduleConditionId(id);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, DumpFilter filter) {
+ pw.println(" ScheduleConditionProvider:");
+ pw.print(" mConnected="); pw.println(mConnected);
+ pw.print(" mRegistered="); pw.println(mRegistered);
+ pw.println(" mSubscriptions=");
+ final long now = System.currentTimeMillis();
+ for (Uri conditionId : mSubscriptions) {
+ pw.print(" ");
+ pw.print(meetsSchedule(conditionId, now) ? "* " : " ");
+ pw.println(conditionId);
+ }
+ }
+
+ @Override
+ public void onConnected() {
+ if (DEBUG) Slog.d(TAG, "onConnected");
+ mConnected = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (DEBUG) Slog.d(TAG, "onDestroy");
+ mConnected = false;
+ }
+
+ @Override
+ public void onRequestConditions(int relevance) {
+ if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
+ // does not advertise conditions
+ }
+
+ @Override
+ public void onSubscribe(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
+ if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
+ notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition");
+ return;
+ }
+ mSubscriptions.add(conditionId);
+ evaluateSubscriptions();
+ }
+
+ @Override
+ public void onUnsubscribe(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
+ mSubscriptions.remove(conditionId);
+ evaluateSubscriptions();
+ }
+
+ @Override
+ public void attachBase(Context base) {
+ attachBaseContext(base);
+ }
+
+ @Override
+ public IConditionProvider asInterface() {
+ return (IConditionProvider) onBind(null);
+ }
+
+ private void evaluateSubscriptions() {
+ setRegistered(!mSubscriptions.isEmpty());
+ final long now = System.currentTimeMillis();
+ long nextAlarmTime = 0;
+ for (Uri conditionId : mSubscriptions) {
+ final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+ if (cal != null && cal.isInSchedule(now)) {
+ notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
+ } else {
+ notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
+ }
+ if (cal != null) {
+ final long nextChangeTime = cal.getNextChangeTime(now);
+ if (nextChangeTime > 0 && nextChangeTime > now) {
+ if (nextAlarmTime == 0 || nextChangeTime < nextAlarmTime) {
+ nextAlarmTime = nextChangeTime;
+ }
+ }
+ }
+ }
+ updateAlarm(now, nextAlarmTime);
+ }
+
+ private void updateAlarm(long now, long time) {
+ final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_EVALUATE,
+ new Intent(ACTION_EVALUATE)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_TIME, time),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ alarms.cancel(pendingIntent);
+ if (time > now) {
+ if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
+ ts(time), formatDuration(time - now), ts(now)));
+ alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
+ } else {
+ if (DEBUG) Slog.d(TAG, "Not scheduling evaluate");
+ }
+ }
+
+ private static String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ private static String formatDuration(long millis) {
+ final StringBuilder sb = new StringBuilder();
+ TimeUtils.formatDuration(millis, sb);
+ return sb.toString();
+ }
+
+ private static boolean meetsSchedule(Uri conditionId, long time) {
+ final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+ return cal != null && cal.isInSchedule(time);
+ }
+
+ private static ScheduleCalendar toScheduleCalendar(Uri conditionId) {
+ final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId);
+ if (schedule == null || schedule.days == null || schedule.days.length == 0) return null;
+ final ScheduleCalendar sc = new ScheduleCalendar();
+ sc.setSchedule(schedule);
+ sc.setTimeZone(TimeZone.getDefault());
+ return sc;
+ }
+
+ private void setRegistered(boolean registered) {
+ if (mRegistered == registered) return;
+ if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
+ mRegistered = registered;
+ if (mRegistered) {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(ACTION_EVALUATE);
+ registerReceiver(mReceiver, filter);
+ } else {
+ unregisterReceiver(mReceiver);
+ }
+ }
+
+ private void notifyCondition(Uri conditionId, int state, String reason) {
+ if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
+ + " reason=" + reason);
+ notifyCondition(createCondition(conditionId, state));
+ }
+
+ private Condition createCondition(Uri id, int state) {
+ final String summary = NOT_SHOWN;
+ final String line1 = NOT_SHOWN;
+ final String line2 = NOT_SHOWN;
+ return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
+ }
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
+ evaluateSubscriptions();
+ }
+ };
+
+}
diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
new file mode 100644
index 0000000..a217623
--- /dev/null
+++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 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.notification;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.service.notification.ConditionProviderService;
+import android.service.notification.IConditionProvider;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+
+public abstract class SystemConditionProviderService extends ConditionProviderService {
+
+ abstract public void dump(PrintWriter pw, DumpFilter filter);
+ abstract public void attachBase(Context context);
+ abstract public IConditionProvider asInterface();
+ abstract public ComponentName getComponent();
+ abstract public boolean isValidConditionid(Uri id);
+}
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 11d00cf..10f1696 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -34,6 +34,7 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -219,7 +220,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
return null;
}
- if (INFO) Slog.i(TAG, "Validating: " + key);
+ if (INFO) Slog.i(TAG, "Validating: " + key + " for " + context.getUserId());
final LinkedList<String> pendingLookups = new LinkedList<String>();
for (int personIdx = 0; personIdx < people.length && personIdx < MAX_PEOPLE; personIdx++) {
final String handle = people[personIdx];
@@ -244,6 +245,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
if (pendingLookups.isEmpty()) {
if (INFO) Slog.i(TAG, "final affinity: " + affinity);
+ if (affinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
return null;
}
@@ -443,13 +445,18 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
final String cacheKey = getCacheKey(mContext.getUserId(), handle);
mPeopleCache.put(cacheKey, lookupResult);
}
+ if (DEBUG) Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
+ } else {
+ if (DEBUG) Slog.d(TAG, "lookupResult is null");
}
}
if (DEBUG) {
Slog.d(TAG, "Validation finished in " + (System.currentTimeMillis() - timeStartMs) +
"ms");
}
+
+ if (mContactAffinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
}
@Override
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index dda0b37..1e318ef 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -24,8 +24,8 @@ import android.os.RemoteException;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.IConditionProvider;
+import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
-import android.util.ArraySet;
import android.util.Slog;
import java.io.PrintWriter;
@@ -57,6 +57,8 @@ public class ZenLog {
private static final int TYPE_CONFIG = 11;
private static final int TYPE_NOT_INTERCEPTED = 12;
private static final int TYPE_DISABLE_EFFECTS = 13;
+ private static final int TYPE_SUPPRESSOR_CHANGED = 14;
+ private static final int TYPE_LISTENER_HINTS_CHANGED = 15;
private static int sNext;
private static int sSize;
@@ -121,6 +123,17 @@ public class ZenLog {
append(TYPE_DISABLE_EFFECTS, record.getKey() + "," + reason);
}
+ public static void traceEffectsSuppressorChanged(ComponentName oldSuppressor,
+ ComponentName newSuppressor) {
+ append(TYPE_SUPPRESSOR_CHANGED, componentToString(oldSuppressor) + "->"
+ + componentToString(newSuppressor));
+ }
+
+ public static void traceListenerHintsChanged(int oldHints, int newHints, int listenerCount) {
+ append(TYPE_LISTENER_HINTS_CHANGED, hintsToString(oldHints) + "->"
+ + hintsToString(newHints) + ",listeners=" + listenerCount);
+ }
+
private static String subscribeResult(IConditionProvider provider, RemoteException e) {
return provider == null ? "no provider" : e != null ? e.getMessage() : "ok";
}
@@ -140,6 +153,8 @@ public class ZenLog {
case TYPE_CONFIG: return "config";
case TYPE_NOT_INTERCEPTED: return "not_intercepted";
case TYPE_DISABLE_EFFECTS: return "disable_effects";
+ case TYPE_SUPPRESSOR_CHANGED: return "suppressor_changed";
+ case TYPE_LISTENER_HINTS_CHANGED: return "listener_hints_changed";
default: return "unknown";
}
}
@@ -157,11 +172,20 @@ public class ZenLog {
switch (zenMode) {
case Global.ZEN_MODE_OFF: return "off";
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return "important_interruptions";
+ case Global.ZEN_MODE_ALARMS: return "alarms";
case Global.ZEN_MODE_NO_INTERRUPTIONS: return "no_interruptions";
default: return "unknown";
}
}
+ private static String hintsToString(int hints) {
+ switch (hints) {
+ case 0 : return "none";
+ case NotificationListenerService.HINT_HOST_DISABLE_EFFECTS : return "disable_effects";
+ default: return Integer.toString(hints);
+ }
+ }
+
private static String componentToString(ComponentName component) {
return component != null ? component.toShortString() : null;
}
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
new file mode 100644
index 0000000..766d6c5
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -0,0 +1,188 @@
+/**
+ * Copyright (c) 2015, 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.notification;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.IConditionProvider;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+public class ZenModeConditions implements ConditionProviders.Callback {
+ private static final String TAG = ZenModeHelper.TAG;
+ private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+ private final ZenModeHelper mHelper;
+ private final ConditionProviders mConditionProviders;
+ private final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>();
+
+ private CountdownConditionProvider mCountdown;
+ private ScheduleConditionProvider mSchedule;
+ private boolean mFirstEvaluation = true;
+
+ public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
+ mHelper = helper;
+ mConditionProviders = conditionProviders;
+ if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.COUNTDOWN_PATH)) {
+ mCountdown = new CountdownConditionProvider();
+ mConditionProviders.addSystemProvider(mCountdown);
+ }
+ if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.SCHEDULE_PATH)) {
+ mSchedule = new ScheduleConditionProvider();
+ mConditionProviders.addSystemProvider(mSchedule);
+ }
+ mConditionProviders.setCallback(this);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mSubscriptions="); pw.println(mSubscriptions);
+ }
+
+ public void requestConditions(IConditionListener callback, int relevance) {
+ mConditionProviders.requestConditions(callback, relevance);
+ }
+
+ public void evaluateConfig(ZenModeConfig config) {
+ if (config == null) return;
+ if (config.manualRule != null && config.manualRule.condition != null
+ && !config.manualRule.isTrueOrUnknown()) {
+ if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule");
+ config.manualRule = null;
+ }
+ final ArraySet<Uri> current = new ArraySet<>();
+ evaluateRule(config.manualRule, current);
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ evaluateRule(automaticRule, current);
+ updateSnoozing(automaticRule);
+ }
+ final int N = mSubscriptions.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final Uri id = mSubscriptions.keyAt(i);
+ final ComponentName component = mSubscriptions.valueAt(i);
+ if (!current.contains(id)) {
+ mConditionProviders.unsubscribeIfNecessary(component, id);
+ mSubscriptions.removeAt(i);
+ }
+ }
+ mFirstEvaluation = false;
+ }
+
+ @Override
+ public void onBootComplete() {
+ // noop
+ }
+
+ @Override
+ public void onUserSwitched() {
+ // noop
+ }
+
+ @Override
+ public void onServiceAdded(ComponentName component) {
+ if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
+ if (isAutomaticActive(component)) {
+ mHelper.setConfig(mHelper.getConfig(), "zmc.onServiceAdded");
+ }
+ }
+
+ @Override
+ public void onConditionChanged(Uri id, Condition condition) {
+ if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
+ ZenModeConfig config = mHelper.getConfig();
+ if (config == null) return;
+ config = config.copy();
+ boolean updated = updateCondition(id, condition, config.manualRule);
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ updated |= updateCondition(id, condition, automaticRule);
+ updated |= updateSnoozing(automaticRule);
+ }
+ if (updated) {
+ mHelper.setConfig(config, "conditionChanged");
+ }
+ }
+
+ private void evaluateRule(ZenRule rule, ArraySet<Uri> current) {
+ if (rule == null || rule.conditionId == null) return;
+ final Uri id = rule.conditionId;
+ boolean isSystemCondition = false;
+ for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
+ if (sp.isValidConditionid(id)) {
+ mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface());
+ rule.component = sp.getComponent();
+ isSystemCondition = true;
+ }
+ }
+ if (!isSystemCondition) {
+ final IConditionProvider cp = mConditionProviders.findConditionProvider(rule.component);
+ if (DEBUG) Log.d(TAG, "Ensure external rule exists: " + (cp != null) + " for " + id);
+ if (cp != null) {
+ mConditionProviders.ensureRecordExists(rule.component, id, cp);
+ }
+ }
+ if (rule.component == null) {
+ Log.w(TAG, "No component found for automatic rule: " + rule.conditionId);
+ rule.enabled = false;
+ return;
+ }
+ if (current != null) {
+ current.add(id);
+ }
+ if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) {
+ mSubscriptions.put(rule.conditionId, rule.component);
+ } else {
+ if (DEBUG) Log.d(TAG, "zmc failed to subscribe");
+ }
+ }
+
+ private boolean isAutomaticActive(ComponentName component) {
+ if (component == null) return false;
+ final ZenModeConfig config = mHelper.getConfig();
+ if (config == null) return false;
+ for (ZenRule rule : config.automaticRules.values()) {
+ if (component.equals(rule.component) && rule.isAutomaticActive()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean updateSnoozing(ZenRule rule) {
+ if (rule != null && rule.snoozing && (mFirstEvaluation || !rule.isTrueOrUnknown())) {
+ rule.snoozing = false;
+ if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean updateCondition(Uri id, Condition condition, ZenRule rule) {
+ if (id == null || rule == null || rule.conditionId == null) return false;
+ if (!rule.conditionId.equals(id)) return false;
+ if (Objects.equals(condition, rule.condition)) return false;
+ rule.condition = condition;
+ return true;
+ }
+
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
new file mode 100644
index 0000000..2aaeb9d
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -0,0 +1,279 @@
+/**
+ * Copyright (c) 2015, 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.notification;
+
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
+import android.service.notification.ZenModeConfig;
+import android.telecom.TelecomManager;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.Objects;
+
+public class ZenModeFiltering {
+ private static final String TAG = ZenModeHelper.TAG;
+ private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+ static final RepeatCallers REPEAT_CALLERS = new RepeatCallers();
+
+ private final Context mContext;
+
+ private ComponentName mDefaultPhoneApp;
+
+ public ZenModeFiltering(Context context) {
+ mContext = context;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
+ pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
+ pw.println(REPEAT_CALLERS.mThresholdMinutes);
+ synchronized (REPEAT_CALLERS) {
+ if (!REPEAT_CALLERS.mCalls.isEmpty()) {
+ pw.print(prefix); pw.println("RepeatCallers.mCalls=");
+ for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
+ pw.print(" at ");
+ pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
+ }
+ }
+ }
+ }
+
+ private static String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ /**
+ * @param extras extras of the notification with EXTRA_PEOPLE populated
+ * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
+ * @param timeoutAffinity affinity to return when the timeout specified via
+ * <code>contactsTimeoutMs</code> is hit
+ */
+ public static boolean matchesCallFilter(Context context, int zen, ZenModeConfig config,
+ UserHandle userHandle, Bundle extras, ValidateNotificationPeople validator,
+ int contactsTimeoutMs, float timeoutAffinity) {
+ if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
+ if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
+ if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+ if (config.allowRepeatCallers && REPEAT_CALLERS.isRepeat(context, extras)) return true;
+ if (!config.allowCalls) return false; // no other calls get through
+ if (validator != null) {
+ final float contactAffinity = validator.getContactAffinity(userHandle, extras,
+ contactsTimeoutMs, timeoutAffinity);
+ return audienceMatches(config, contactAffinity);
+ }
+ }
+ return true;
+ }
+
+ private static Bundle extras(NotificationRecord record) {
+ return record != null && record.sbn != null && record.sbn.getNotification() != null
+ ? record.sbn.getNotification().extras : null;
+ }
+
+ public boolean shouldIntercept(int zen, ZenModeConfig config, NotificationRecord record) {
+ if (isSystem(record)) {
+ return false;
+ }
+ switch (zen) {
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ // #notevenalarms
+ ZenLog.traceIntercepted(record, "none");
+ return true;
+ case Global.ZEN_MODE_ALARMS:
+ if (isAlarm(record)) {
+ // Alarms only
+ return false;
+ }
+ ZenLog.traceIntercepted(record, "alarmsOnly");
+ return true;
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ if (isAlarm(record)) {
+ // Alarms are always priority
+ return false;
+ }
+ // allow user-prioritized packages through in priority mode
+ if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
+ ZenLog.traceNotIntercepted(record, "priorityApp");
+ return false;
+ }
+ if (isCall(record)) {
+ if (config.allowRepeatCallers
+ && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
+ ZenLog.traceNotIntercepted(record, "repeatCaller");
+ return false;
+ }
+ if (!config.allowCalls) {
+ ZenLog.traceIntercepted(record, "!allowCalls");
+ return true;
+ }
+ return shouldInterceptAudience(config, record);
+ }
+ if (isMessage(record)) {
+ if (!config.allowMessages) {
+ ZenLog.traceIntercepted(record, "!allowMessages");
+ return true;
+ }
+ return shouldInterceptAudience(config, record);
+ }
+ if (isEvent(record)) {
+ if (!config.allowEvents) {
+ ZenLog.traceIntercepted(record, "!allowEvents");
+ return true;
+ }
+ return false;
+ }
+ if (isReminder(record)) {
+ if (!config.allowReminders) {
+ ZenLog.traceIntercepted(record, "!allowReminders");
+ return true;
+ }
+ return false;
+ }
+ ZenLog.traceIntercepted(record, "!priority");
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean shouldInterceptAudience(ZenModeConfig config,
+ NotificationRecord record) {
+ if (!audienceMatches(config, record.getContactAffinity())) {
+ ZenLog.traceIntercepted(record, "!audienceMatches");
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isSystem(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_SYSTEM);
+ }
+
+ private static boolean isAlarm(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_ALARM)
+ || record.isAudioStream(AudioManager.STREAM_ALARM)
+ || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
+ }
+
+ private static boolean isEvent(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_EVENT);
+ }
+
+ private static boolean isReminder(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_REMINDER);
+ }
+
+ public boolean isCall(NotificationRecord record) {
+ return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
+ || record.isCategory(Notification.CATEGORY_CALL));
+ }
+
+ private boolean isDefaultPhoneApp(String pkg) {
+ if (mDefaultPhoneApp == null) {
+ final TelecomManager telecomm =
+ (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
+ if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
+ }
+ return pkg != null && mDefaultPhoneApp != null
+ && pkg.equals(mDefaultPhoneApp.getPackageName());
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean isDefaultMessagingApp(NotificationRecord record) {
+ final int userId = record.getUserId();
+ if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
+ final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
+ Secure.SMS_DEFAULT_APPLICATION, userId);
+ return Objects.equals(defaultApp, record.sbn.getPackageName());
+ }
+
+ private boolean isMessage(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
+ }
+
+ private static boolean audienceMatches(ZenModeConfig config, float contactAffinity) {
+ switch (config.allowFrom) {
+ case ZenModeConfig.SOURCE_ANYONE:
+ return true;
+ case ZenModeConfig.SOURCE_CONTACT:
+ return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
+ case ZenModeConfig.SOURCE_STAR:
+ return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
+ default:
+ Slog.w(TAG, "Encountered unknown source: " + config.allowFrom);
+ return true;
+ }
+ }
+
+ private static class RepeatCallers {
+ private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
+ private int mThresholdMinutes;
+
+ private synchronized boolean isRepeat(Context context, Bundle extras) {
+ if (mThresholdMinutes <= 0) {
+ mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
+ .config_zen_repeat_callers_threshold);
+ }
+ if (mThresholdMinutes <= 0 || extras == null) return false;
+ final String peopleString = peopleString(extras);
+ if (peopleString == null) return false;
+ final long now = System.currentTimeMillis();
+ final int N = mCalls.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final long time = mCalls.valueAt(i);
+ if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
+ mCalls.removeAt(i);
+ }
+ }
+ final boolean isRepeat = mCalls.containsKey(peopleString);
+ mCalls.put(peopleString, now);
+ return isRepeat;
+ }
+
+ private static String peopleString(Bundle extras) {
+ final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+ if (extraPeople == null || extraPeople.length == 0) return null;
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < extraPeople.length; i++) {
+ String extraPerson = extraPeople[i];
+ if (extraPerson == null) continue;
+ extraPerson = extraPerson.trim();
+ if (extraPerson.isEmpty()) continue;
+ if (sb.length() > 0) {
+ sb.append('|');
+ }
+ sb.append(extraPerson);
+ }
+ return sb.length() == 0 ? null : sb.toString();
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 841fc21..9cb8af5 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -21,16 +21,16 @@ import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import android.app.AppOpsManager;
-import android.app.Notification;
+import android.app.NotificationManager.Policy;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
-import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
+import android.media.VolumePolicy;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -38,12 +38,13 @@ import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
+import android.service.notification.IConditionListener;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
-import android.telecom.TelecomManager;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArraySet;
import android.util.Log;
-import android.util.Slog;
import com.android.internal.R;
import com.android.server.LocalServices;
@@ -62,9 +63,9 @@ import java.util.Objects;
/**
* NotificationManagerService helper for functionality related to zen mode.
*/
-public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
- private static final String TAG = "ZenModeHelper";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+public class ZenModeHelper {
+ static final String TAG = "ZenModeHelper";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
private final H mHandler;
@@ -72,38 +73,46 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
private final AppOpsManager mAppOps;
private final ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final ZenModeFiltering mFiltering;
+ private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate();
+ private final ZenModeConditions mConditions;
- private ComponentName mDefaultPhoneApp;
private int mZenMode;
private ZenModeConfig mConfig;
private AudioManagerInternal mAudioManager;
private int mPreviousRingerMode = -1;
private boolean mEffectsSuppressed;
- public ZenModeHelper(Context context, Looper looper) {
+ public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
mContext = context;
mHandler = new H(looper);
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mDefaultConfig = readDefaultConfig(context.getResources());
+ appendDefaultScheduleRules(mDefaultConfig);
mConfig = mDefaultConfig;
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
+ mFiltering = new ZenModeFiltering(mContext);
+ mConditions = new ZenModeConditions(this, conditionProviders);
}
- public static ZenModeConfig readDefaultConfig(Resources resources) {
- XmlResourceParser parser = null;
- try {
- parser = resources.getXml(R.xml.default_zen_mode_config);
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- final ZenModeConfig config = ZenModeConfig.readXml(parser);
- if (config != null) return config;
- }
- } catch (Exception e) {
- Slog.w(TAG, "Error reading default zen mode config from resource", e);
- } finally {
- IoUtils.closeQuietly(parser);
- }
- return new ZenModeConfig();
+ @Override
+ public String toString() {
+ return TAG;
+ }
+
+ public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
+ ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+ return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle, extras,
+ validator, contactsTimeoutMs, timeoutAffinity);
+ }
+
+ public boolean isCall(NotificationRecord record) {
+ return mFiltering.isCall(record);
+ }
+
+ public boolean shouldIntercept(NotificationRecord record) {
+ return mFiltering.shouldIntercept(mZenMode, mConfig, record);
}
public void addCallback(Callback callback) {
@@ -114,44 +123,32 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
mCallbacks.remove(callback);
}
+ public void initZenMode() {
+ if (DEBUG) Log.d(TAG, "initZenMode");
+ evaluateZenMode("init", true /*setRingerMode*/);
+ }
+
public void onSystemReady() {
+ if (DEBUG) Log.d(TAG, "onSystemReady");
mAudioManager = LocalServices.getService(AudioManagerInternal.class);
if (mAudioManager != null) {
- mAudioManager.setRingerModeDelegate(this);
+ mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
}
}
- public int getZenModeListenerInterruptionFilter() {
- switch (mZenMode) {
- case Global.ZEN_MODE_OFF:
- return NotificationListenerService.INTERRUPTION_FILTER_ALL;
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- return NotificationListenerService.INTERRUPTION_FILTER_NONE;
- default:
- return 0;
- }
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ mConditions.requestConditions(callback, relevance);
}
- private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
- int defValue) {
- switch (listenerInterruptionFilter) {
- case NotificationListenerService.INTERRUPTION_FILTER_ALL:
- return Global.ZEN_MODE_OFF;
- case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
- return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- case NotificationListenerService.INTERRUPTION_FILTER_NONE:
- return Global.ZEN_MODE_NO_INTERRUPTIONS;
- default:
- return defValue;
- }
+ public int getZenModeListenerInterruptionFilter() {
+ return getZenModeListenerInterruptionFilter(mZenMode);
}
public void requestFromListener(ComponentName name, int interruptionFilter) {
final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1);
if (newZen != -1) {
- setZenMode(newZen, "listener:" + (name != null ? name.flattenToShortString() : null));
+ setManualZenMode(newZen, null,
+ "listener:" + (name != null ? name.flattenToShortString() : null));
}
}
@@ -161,86 +158,163 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
applyRestrictions();
}
- public boolean shouldIntercept(NotificationRecord record) {
- if (isSystem(record)) {
- return false;
- }
- switch (mZenMode) {
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- // #notevenalarms
- ZenLog.traceIntercepted(record, "none");
- return true;
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- if (isAlarm(record)) {
- // Alarms are always priority
- return false;
- }
- // allow user-prioritized packages through in priority mode
- if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
- ZenLog.traceNotIntercepted(record, "priorityApp");
- return false;
- }
- if (isCall(record)) {
- if (!mConfig.allowCalls) {
- ZenLog.traceIntercepted(record, "!allowCalls");
- return true;
- }
- return shouldInterceptAudience(record);
- }
- if (isMessage(record)) {
- if (!mConfig.allowMessages) {
- ZenLog.traceIntercepted(record, "!allowMessages");
- return true;
- }
- return shouldInterceptAudience(record);
- }
- if (isEvent(record)) {
- if (!mConfig.allowEvents) {
- ZenLog.traceIntercepted(record, "!allowEvents");
- return true;
- }
- return false;
+ public int getZenMode() {
+ return mZenMode;
+ }
+
+ public void setManualZenMode(int zenMode, Uri conditionId, String reason) {
+ setManualZenMode(zenMode, conditionId, reason, true /*setRingerMode*/);
+ }
+
+ private void setManualZenMode(int zenMode, Uri conditionId, String reason,
+ boolean setRingerMode) {
+ if (mConfig == null) return;
+ if (!Global.isValidZenMode(zenMode)) return;
+ if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+ + " conditionId=" + conditionId + " reason=" + reason
+ + " setRingerMode=" + setRingerMode);
+ final ZenModeConfig newConfig = mConfig.copy();
+ if (zenMode == Global.ZEN_MODE_OFF) {
+ newConfig.manualRule = null;
+ for (ZenRule automaticRule : newConfig.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive()) {
+ automaticRule.snoozing = true;
}
- ZenLog.traceIntercepted(record, "!priority");
- return true;
- default:
- return false;
+ }
+ } else {
+ final ZenRule newRule = new ZenRule();
+ newRule.enabled = true;
+ newRule.zenMode = zenMode;
+ newRule.conditionId = conditionId;
+ newConfig.manualRule = newRule;
+ }
+ setConfig(newConfig, reason, setRingerMode);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mZenMode=");
+ pw.println(Global.zenModeToString(mZenMode));
+ dump(pw, prefix, "mConfig", mConfig);
+ dump(pw, prefix, "mDefaultConfig", mDefaultConfig);
+ pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
+ pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
+ mFiltering.dump(pw, prefix);
+ mConditions.dump(pw, prefix);
+ }
+
+ private static void dump(PrintWriter pw, String prefix, String var, ZenModeConfig config) {
+ pw.print(prefix); pw.print(var); pw.print('=');
+ if (config == null) {
+ pw.println(config);
+ return;
+ }
+ pw.printf("allow(calls=%s,repeatCallers=%s,events=%s,from=%s,messages=%s,reminders=%s)\n",
+ config.allowCalls, config.allowRepeatCallers, config.allowEvents, config.allowFrom,
+ config.allowMessages, config.allowReminders);
+ pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule);
+ if (config.automaticRules.isEmpty()) return;
+ final int N = config.automaticRules.size();
+ for (int i = 0; i < N; i++) {
+ pw.print(prefix); pw.print(i == 0 ? " automaticRules=" : " ");
+ pw.println(config.automaticRules.valueAt(i));
}
}
- private boolean shouldInterceptAudience(NotificationRecord record) {
- if (!audienceMatches(record.getContactAffinity())) {
- ZenLog.traceIntercepted(record, "!audienceMatches");
- return true;
+ public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ if (config != null) {
+ if (DEBUG) Log.d(TAG, "readXml");
+ setConfig(config, "readXml");
}
- return false;
}
- public int getZenMode() {
- return mZenMode;
+ public void writeXml(XmlSerializer out) throws IOException {
+ mConfig.writeXml(out);
+ }
+
+ public Policy getNotificationPolicy() {
+ return getNotificationPolicy(mConfig);
+ }
+
+ private static Policy getNotificationPolicy(ZenModeConfig config) {
+ return config == null ? null : config.toNotificationPolicy();
+ }
+
+ public void setNotificationPolicy(Policy policy) {
+ if (policy == null || mConfig == null) return;
+ final ZenModeConfig newConfig = mConfig.copy();
+ newConfig.applyNotificationPolicy(policy);
+ setConfig(newConfig, "setNotificationPolicy");
+ }
+
+ public ZenModeConfig getConfig() {
+ return mConfig;
+ }
+
+ public boolean setConfig(ZenModeConfig config, String reason) {
+ return setConfig(config, reason, true /*setRingerMode*/);
+ }
+
+ private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
+ if (config == null || !config.isValid()) {
+ Log.w(TAG, "Invalid config in setConfig; " + config);
+ return false;
+ }
+ mConditions.evaluateConfig(config); // may modify config
+ if (config.equals(mConfig)) return true;
+ if (DEBUG) Log.d(TAG, "setConfig reason=" + reason);
+ ZenLog.traceConfig(mConfig, config);
+ final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
+ getNotificationPolicy(config));
+ mConfig = config;
+ dispatchOnConfigChanged();
+ if (policyChanged){
+ dispatchOnPolicyChanged();
+ }
+ final String val = Integer.toString(mConfig.hashCode());
+ Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
+ if (!evaluateZenMode(reason, setRingerMode)) {
+ applyRestrictions(); // evaluateZenMode will also apply restrictions if changed
+ }
+ return true;
}
- public void setZenMode(int zenMode, String reason) {
- setZenMode(zenMode, reason, true);
+ private int getZenModeSetting() {
+ return Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF);
}
- private void setZenMode(int zenMode, String reason, boolean setRingerMode) {
- ZenLog.traceSetZenMode(zenMode, reason);
- if (mZenMode == zenMode) return;
- ZenLog.traceUpdateZenMode(mZenMode, zenMode);
- mZenMode = zenMode;
- Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, mZenMode);
+ private void setZenModeSetting(int zen) {
+ Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
+ }
+
+ private boolean evaluateZenMode(String reason, boolean setRingerMode) {
+ if (DEBUG) Log.d(TAG, "evaluateZenMode");
+ final ArraySet<ZenRule> automaticRules = new ArraySet<ZenRule>();
+ final int zen = computeZenMode(automaticRules);
+ if (zen == mZenMode) return false;
+ ZenLog.traceSetZenMode(zen, reason);
+ mZenMode = zen;
+ setZenModeSetting(mZenMode);
if (setRingerMode) {
applyZenToRingerMode();
}
applyRestrictions();
mHandler.postDispatchOnZenModeChanged();
+ return true;
}
- public void readZenModeFromSetting() {
- final int newMode = Global.getInt(mContext.getContentResolver(),
- Global.ZEN_MODE, Global.ZEN_MODE_OFF);
- setZenMode(newMode, "setting");
+ private int computeZenMode(ArraySet<ZenRule> automaticRulesOut) {
+ if (mConfig == null) return Global.ZEN_MODE_OFF;
+ if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
+ int zen = Global.ZEN_MODE_OFF;
+ for (ZenRule automaticRule : mConfig.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive()) {
+ if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
+ zen = automaticRule.zenMode;
+ }
+ }
+ }
+ return zen;
}
private void applyRestrictions() {
@@ -251,7 +325,8 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
applyRestrictions(muteNotifications, USAGE_NOTIFICATION);
// call restrictions
- final boolean muteCalls = zen && !mConfig.allowCalls || mEffectsSuppressed;
+ final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
+ || mEffectsSuppressed;
applyRestrictions(muteCalls, USAGE_NOTIFICATION_RINGTONE);
// alarm restrictions
@@ -269,43 +344,6 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
exceptionPackages);
}
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mZenMode=");
- pw.println(Global.zenModeToString(mZenMode));
- pw.print(prefix); pw.print("mConfig="); pw.println(mConfig);
- pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig);
- pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
- pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
- pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
- }
-
- public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
- final ZenModeConfig config = ZenModeConfig.readXml(parser);
- if (config != null) {
- setConfig(config);
- }
- }
-
- public void writeXml(XmlSerializer out) throws IOException {
- mConfig.writeXml(out);
- }
-
- public ZenModeConfig getConfig() {
- return mConfig;
- }
-
- public boolean setConfig(ZenModeConfig config) {
- if (config == null || !config.isValid()) return false;
- if (config.equals(mConfig)) return true;
- ZenLog.traceConfig(mConfig, config);
- mConfig = config;
- dispatchOnConfigChanged();
- final String val = Integer.toString(mConfig.hashCode());
- Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
- applyRestrictions();
- return true;
- }
-
private void applyZenToRingerMode() {
if (mAudioManager == null) return;
// force the ringer mode into compliance
@@ -313,6 +351,7 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
int newRingerModeInternal = ringerModeInternal;
switch (mZenMode) {
case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ case Global.ZEN_MODE_ALARMS:
if (ringerModeInternal != AudioManager.RINGER_MODE_SILENT) {
mPreviousRingerMode = ringerModeInternal;
newRingerModeInternal = AudioManager.RINGER_MODE_SILENT;
@@ -332,82 +371,15 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
}
}
- @Override // RingerModeDelegate
- public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeExternal) {
- final boolean isChange = ringerModeOld != ringerModeNew;
-
- int ringerModeExternalOut = ringerModeNew;
-
- int newZen = -1;
- switch (ringerModeNew) {
- case AudioManager.RINGER_MODE_SILENT:
- if (isChange) {
- if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS) {
- newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
- }
- }
- break;
- case AudioManager.RINGER_MODE_VIBRATE:
- case AudioManager.RINGER_MODE_NORMAL:
- if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
- && mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
- newZen = Global.ZEN_MODE_OFF;
- } else if (mZenMode != Global.ZEN_MODE_OFF) {
- ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
- }
- break;
- }
- if (newZen != -1) {
- setZenMode(newZen, "ringerModeInternal", false /*setRingerMode*/);
- }
-
- if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
- ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
- ringerModeExternal, ringerModeExternalOut);
- }
- return ringerModeExternalOut;
- }
-
- @Override // RingerModeDelegate
- public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeInternal) {
- int ringerModeInternalOut = ringerModeNew;
- final boolean isChange = ringerModeOld != ringerModeNew;
- final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
-
- int newZen = -1;
- switch (ringerModeNew) {
- case AudioManager.RINGER_MODE_SILENT:
- if (isChange) {
- if (mZenMode == Global.ZEN_MODE_OFF) {
- newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- }
- ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
- : AudioManager.RINGER_MODE_NORMAL;
- } else {
- ringerModeInternalOut = ringerModeInternal;
- }
- break;
- case AudioManager.RINGER_MODE_VIBRATE:
- case AudioManager.RINGER_MODE_NORMAL:
- if (mZenMode != Global.ZEN_MODE_OFF) {
- newZen = Global.ZEN_MODE_OFF;
- }
- break;
- }
- if (newZen != -1) {
- setZenMode(newZen, "ringerModeExternal", false /*setRingerMode*/);
+ private void dispatchOnConfigChanged() {
+ for (Callback callback : mCallbacks) {
+ callback.onConfigChanged();
}
-
- ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, ringerModeInternal,
- ringerModeInternalOut);
- return ringerModeInternalOut;
}
- private void dispatchOnConfigChanged() {
+ private void dispatchOnPolicyChanged() {
for (Callback callback : mCallbacks) {
- callback.onConfigChanged();
+ callback.onPolicyChanged();
}
}
@@ -417,89 +389,210 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
}
}
- private static boolean isSystem(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_SYSTEM);
+ private static int getZenModeListenerInterruptionFilter(int zen) {
+ switch (zen) {
+ case Global.ZEN_MODE_OFF:
+ return NotificationListenerService.INTERRUPTION_FILTER_ALL;
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
+ case Global.ZEN_MODE_ALARMS:
+ return NotificationListenerService.INTERRUPTION_FILTER_ALARMS;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ return NotificationListenerService.INTERRUPTION_FILTER_NONE;
+ default:
+ return 0;
+ }
}
- private static boolean isAlarm(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_ALARM)
- || record.isAudioStream(AudioManager.STREAM_ALARM)
- || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
+ private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
+ int defValue) {
+ switch (listenerInterruptionFilter) {
+ case NotificationListenerService.INTERRUPTION_FILTER_ALL:
+ return Global.ZEN_MODE_OFF;
+ case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
+ return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ case NotificationListenerService.INTERRUPTION_FILTER_ALARMS:
+ return Global.ZEN_MODE_ALARMS;
+ case NotificationListenerService.INTERRUPTION_FILTER_NONE:
+ return Global.ZEN_MODE_NO_INTERRUPTIONS;
+ default:
+ return defValue;
+ }
}
- private static boolean isEvent(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_EVENT);
+ private ZenModeConfig readDefaultConfig(Resources resources) {
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getXml(R.xml.default_zen_mode_config);
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ if (config != null) return config;
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Error reading default zen mode config from resource", e);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ }
+ return new ZenModeConfig();
}
- public boolean isCall(NotificationRecord record) {
- return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
- || record.isCategory(Notification.CATEGORY_CALL));
+ private void appendDefaultScheduleRules(ZenModeConfig config) {
+ if (config == null) return;
+
+ final ScheduleInfo weeknights = new ScheduleInfo();
+ weeknights.days = ZenModeConfig.WEEKNIGHT_DAYS;
+ weeknights.startHour = 22;
+ weeknights.endHour = 7;
+ final ZenRule rule1 = new ZenRule();
+ rule1.enabled = false;
+ rule1.name = mContext.getResources()
+ .getString(R.string.zen_mode_default_weeknights_name);
+ rule1.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+ rule1.zenMode = Global.ZEN_MODE_ALARMS;
+ config.automaticRules.put(config.newRuleId(), rule1);
+
+ final ScheduleInfo weekends = new ScheduleInfo();
+ weekends.days = ZenModeConfig.WEEKEND_DAYS;
+ weekends.startHour = 23;
+ weekends.startMinute = 30;
+ weekends.endHour = 10;
+ final ZenRule rule2 = new ZenRule();
+ rule2.enabled = false;
+ rule2.name = mContext.getResources()
+ .getString(R.string.zen_mode_default_weekends_name);
+ rule2.conditionId = ZenModeConfig.toScheduleConditionId(weekends);
+ rule2.zenMode = Global.ZEN_MODE_ALARMS;
+ config.automaticRules.put(config.newRuleId(), rule2);
+ }
+
+ private static int zenSeverity(int zen) {
+ switch (zen) {
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return 1;
+ case Global.ZEN_MODE_ALARMS: return 2;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS: return 3;
+ default: return 0;
+ }
}
- private boolean isDefaultPhoneApp(String pkg) {
- if (mDefaultPhoneApp == null) {
- final TelecomManager telecomm =
- (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
- if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
+ private final ZenModeConfig.Migration mConfigMigration = new ZenModeConfig.Migration() {
+ @Override
+ public ZenModeConfig migrate(ZenModeConfig.XmlV1 v1) {
+ if (v1 == null) return null;
+ final ZenModeConfig rt = new ZenModeConfig();
+ rt.allowCalls = v1.allowCalls;
+ rt.allowEvents = v1.allowEvents;
+ rt.allowFrom = v1.allowFrom;
+ rt.allowMessages = v1.allowMessages;
+ rt.allowReminders = v1.allowReminders;
+ // don't migrate current exit condition
+ final int[] days = ZenModeConfig.XmlV1.tryParseDays(v1.sleepMode);
+ if (days != null && days.length > 0) {
+ Log.i(TAG, "Migrating existing V1 downtime to single schedule");
+ final ScheduleInfo schedule = new ScheduleInfo();
+ schedule.days = days;
+ schedule.startHour = v1.sleepStartHour;
+ schedule.startMinute = v1.sleepStartMinute;
+ schedule.endHour = v1.sleepEndHour;
+ schedule.endMinute = v1.sleepEndMinute;
+ final ZenRule rule = new ZenRule();
+ rule.enabled = true;
+ rule.name = mContext.getResources()
+ .getString(R.string.zen_mode_downtime_feature_name);
+ rule.conditionId = ZenModeConfig.toScheduleConditionId(schedule);
+ rule.zenMode = v1.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
+ : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rt.automaticRules.put(rt.newRuleId(), rule);
+ } else {
+ Log.i(TAG, "No existing V1 downtime found, generating default schedules");
+ appendDefaultScheduleRules(rt);
+ }
+ return rt;
}
- return pkg != null && mDefaultPhoneApp != null
- && pkg.equals(mDefaultPhoneApp.getPackageName());
- }
+ };
- private boolean isDefaultMessagingApp(NotificationRecord record) {
- final int userId = record.getUserId();
- if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
- final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
- Secure.SMS_DEFAULT_APPLICATION, userId);
- return Objects.equals(defaultApp, record.sbn.getPackageName());
- }
+ private final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
+ @Override
+ public String toString() {
+ return TAG;
+ }
- private boolean isMessage(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
- }
+ @Override
+ public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
+ int ringerModeExternal, VolumePolicy policy) {
+ final boolean isChange = ringerModeOld != ringerModeNew;
+
+ int ringerModeExternalOut = ringerModeNew;
+
+ int newZen = -1;
+ switch (ringerModeNew) {
+ case AudioManager.RINGER_MODE_SILENT:
+ if (isChange && policy.doNotDisturbWhenSilent) {
+ if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
+ && mZenMode != Global.ZEN_MODE_ALARMS) {
+ newZen = Global.ZEN_MODE_ALARMS;
+ }
+ }
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ case AudioManager.RINGER_MODE_NORMAL:
+ if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
+ && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+ || mZenMode == Global.ZEN_MODE_ALARMS)) {
+ newZen = Global.ZEN_MODE_OFF;
+ } else if (mZenMode != Global.ZEN_MODE_OFF) {
+ ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
+ }
+ break;
+ }
+ if (newZen != -1) {
+ setManualZenMode(newZen, null, "ringerModeInternal", false /*setRingerMode*/);
+ }
- /**
- * @param extras extras of the notification with EXTRA_PEOPLE populated
- * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
- * @param timeoutAffinity affinity to return when the timeout specified via
- * <code>contactsTimeoutMs</code> is hit
- */
- public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
- ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
- final int zen = mZenMode;
- if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
- if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
- if (!mConfig.allowCalls) return false; // no calls get through
- if (validator != null) {
- final float contactAffinity = validator.getContactAffinity(userHandle, extras,
- contactsTimeoutMs, timeoutAffinity);
- return audienceMatches(contactAffinity);
+ if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
+ ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
+ ringerModeExternal, ringerModeExternalOut);
}
+ return ringerModeExternalOut;
}
- return true;
- }
- @Override
- public String toString() {
- return TAG;
- }
+ @Override
+ public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
+ int ringerModeInternal, VolumePolicy policy) {
+ int ringerModeInternalOut = ringerModeNew;
+ final boolean isChange = ringerModeOld != ringerModeNew;
+ final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+
+ int newZen = -1;
+ switch (ringerModeNew) {
+ case AudioManager.RINGER_MODE_SILENT:
+ if (isChange) {
+ if (mZenMode == Global.ZEN_MODE_OFF) {
+ newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ }
+ ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
+ : AudioManager.RINGER_MODE_NORMAL;
+ } else {
+ ringerModeInternalOut = ringerModeInternal;
+ }
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ case AudioManager.RINGER_MODE_NORMAL:
+ if (mZenMode != Global.ZEN_MODE_OFF) {
+ newZen = Global.ZEN_MODE_OFF;
+ }
+ break;
+ }
+ if (newZen != -1) {
+ setManualZenMode(newZen, null, "ringerModeExternal", false /*setRingerMode*/);
+ }
- private boolean audienceMatches(float contactAffinity) {
- switch (mConfig.allowFrom) {
- case ZenModeConfig.SOURCE_ANYONE:
- return true;
- case ZenModeConfig.SOURCE_CONTACT:
- return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
- case ZenModeConfig.SOURCE_STAR:
- return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
- default:
- Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom);
- return true;
+ ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller,
+ ringerModeInternal, ringerModeInternalOut);
+ return ringerModeInternalOut;
}
}
- private class SettingsObserver extends ContentObserver {
+ private final class SettingsObserver extends ContentObserver {
private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
public SettingsObserver(Handler handler) {
@@ -519,12 +612,15 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
public void update(Uri uri) {
if (ZEN_MODE.equals(uri)) {
- readZenModeFromSetting();
+ if (mZenMode != getZenModeSetting()) {
+ if (DEBUG) Log.d(TAG, "Fixing zen mode setting");
+ setZenModeSetting(mZenMode);
+ }
}
}
}
- private class H extends Handler {
+ private final class H extends Handler {
private static final int MSG_DISPATCH = 1;
private H(Looper looper) {
@@ -549,5 +645,7 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
public static class Callback {
void onConfigChanged() {}
void onZenModeChanged() {}
+ void onPolicyChanged() {}
}
+
}
diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java
index 4f27408..30f8b37 100644
--- a/services/core/java/com/android/server/pm/BasePermission.java
+++ b/services/core/java/com/android/server/pm/BasePermission.java
@@ -18,6 +18,9 @@ package com.android.server.pm;
import android.content.pm.PackageParser;
import android.content.pm.PermissionInfo;
+import android.os.UserHandle;
+
+import com.android.internal.util.ArrayUtils;
final class BasePermission {
final static int TYPE_NORMAL = 0;
@@ -40,9 +43,17 @@ final class BasePermission {
PermissionInfo pendingInfo;
+ /** UID that owns the definition of this permission */
int uid;
- int[] gids;
+ /** Additional GIDs given to apps granted this permission */
+ private int[] gids;
+
+ /**
+ * Flag indicating that {@link #gids} should be adjusted based on the
+ * {@link UserHandle} the granted app is running as.
+ */
+ private boolean perUser;
BasePermission(String _name, String _sourcePackage, int _type) {
name = _name;
@@ -52,8 +63,31 @@ final class BasePermission {
protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
}
+ @Override
public String toString() {
return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
+ "}";
}
+
+ public void setGids(int[] gids, boolean perUser) {
+ this.gids = gids;
+ this.perUser = perUser;
+ }
+
+ public int[] computeGids(int userId) {
+ if (perUser) {
+ final int[] userGids = new int[gids.length];
+ for (int i = 0; i < gids.length; i++) {
+ userGids[i] = UserHandle.getUid(userId, gids[i]);
+ }
+ return userGids;
+ } else {
+ return gids;
+ }
+ }
+
+ public boolean isRuntime() {
+ return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+ == PermissionInfo.PROTECTION_DANGEROUS;
+ }
}
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 6d18531..ff4049b 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -33,7 +33,6 @@ import android.os.UserHandle;
class CrossProfileIntentFilter extends IntentFilter {
private static final String ATTR_TARGET_USER_ID = "targetUserId";
private static final String ATTR_FLAGS = "flags";
- private static final String ATTR_OWNER_USER_ID = "ownerUserId";
private static final String ATTR_OWNER_PACKAGE = "ownerPackage";
private static final String ATTR_FILTER = "filter";
@@ -41,15 +40,13 @@ class CrossProfileIntentFilter extends IntentFilter {
// If the intent matches the IntentFilter, then it can be forwarded to this userId.
final int mTargetUserId;
- final int mOwnerUserId; // userId of the app which has set this CrossProfileIntentFilter.
final String mOwnerPackage; // packageName of the app.
final int mFlags;
- CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int ownerUserId,
- int targetUserId, int flags) {
+ CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int targetUserId,
+ int flags) {
super(filter);
mTargetUserId = targetUserId;
- mOwnerUserId = ownerUserId;
mOwnerPackage = ownerPackage;
mFlags = flags;
}
@@ -62,17 +59,12 @@ class CrossProfileIntentFilter extends IntentFilter {
return mFlags;
}
- public int getOwnerUserId() {
- return mOwnerUserId;
- }
-
public String getOwnerPackage() {
return mOwnerPackage;
}
CrossProfileIntentFilter(XmlPullParser parser) throws XmlPullParserException, IOException {
mTargetUserId = getIntFromXml(parser, ATTR_TARGET_USER_ID, UserHandle.USER_NULL);
- mOwnerUserId = getIntFromXml(parser, ATTR_OWNER_USER_ID, UserHandle.USER_NULL);
mOwnerPackage = getStringFromXml(parser, ATTR_OWNER_PACKAGE, "");
mFlags = getIntFromXml(parser, ATTR_FLAGS, 0);
@@ -129,7 +121,6 @@ class CrossProfileIntentFilter extends IntentFilter {
public void writeToXml(XmlSerializer serializer) throws IOException {
serializer.attribute(null, ATTR_TARGET_USER_ID, Integer.toString(mTargetUserId));
serializer.attribute(null, ATTR_FLAGS, Integer.toString(mFlags));
- serializer.attribute(null, ATTR_OWNER_USER_ID, Integer.toString(mOwnerUserId));
serializer.attribute(null, ATTR_OWNER_PACKAGE, mOwnerPackage);
serializer.startTag(null, ATTR_FILTER);
super.writeToXml(serializer);
@@ -144,7 +135,6 @@ class CrossProfileIntentFilter extends IntentFilter {
boolean equalsIgnoreFilter(CrossProfileIntentFilter other) {
return mTargetUserId == other.mTargetUserId
- && mOwnerUserId == other.mOwnerUserId
&& mOwnerPackage.equals(other.mOwnerPackage)
&& mFlags == other.mFlags;
}
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
index a335d3a..0e0096d 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
@@ -18,7 +18,6 @@
package com.android.server.pm;
-import java.io.PrintWriter;
import com.android.server.IntentResolver;
import java.util.List;
diff --git a/services/core/java/com/android/server/pm/GrantedPermissions.java b/services/core/java/com/android/server/pm/GrantedPermissions.java
deleted file mode 100644
index 8f0f935..0000000
--- a/services/core/java/com/android/server/pm/GrantedPermissions.java
+++ /dev/null
@@ -1,50 +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.pm;
-
-import android.content.pm.ApplicationInfo;
-import android.util.ArraySet;
-
-class GrantedPermissions {
- int pkgFlags;
-
- ArraySet<String> grantedPermissions = new ArraySet<String>();
-
- int[] gids;
-
- GrantedPermissions(int pkgFlags) {
- setFlags(pkgFlags);
- }
-
- @SuppressWarnings("unchecked")
- GrantedPermissions(GrantedPermissions base) {
- pkgFlags = base.pkgFlags;
- grantedPermissions = new ArraySet<>(base.grantedPermissions);
-
- if (base.gids != null) {
- gids = base.gids.clone();
- }
- }
-
- void setFlags(int pkgFlags) {
- this.pkgFlags = pkgFlags
- & (ApplicationInfo.FLAG_SYSTEM
- | ApplicationInfo.FLAG_PRIVILEGED
- | ApplicationInfo.FLAG_FORWARD_LOCK
- | ApplicationInfo.FLAG_EXTERNAL_STORAGE);
- }
-}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 31c604f..0f3b4e6 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,10 +16,13 @@
package com.android.server.pm;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
+import android.text.TextUtils;
import android.util.Slog;
+
import dalvik.system.VMRuntime;
import com.android.internal.os.InstallerConnection;
@@ -41,9 +44,27 @@ public final class Installer extends SystemService {
ping();
}
+ private static String escapeNull(String arg) {
+ if (TextUtils.isEmpty(arg)) {
+ return "!";
+ } else {
+ if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) {
+ throw new IllegalArgumentException(arg);
+ }
+ return arg;
+ }
+ }
+
+ @Deprecated
public int install(String name, int uid, int gid, String seinfo) {
+ return install(null, name, uid, gid, seinfo);
+ }
+
+ public int install(String uuid, String name, int uid, int gid, String seinfo) {
StringBuilder builder = new StringBuilder("install");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -54,42 +75,26 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
- public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet) {
- if (!isValidInstructionSet(instructionSet)) {
- Slog.e(TAG, "Invalid instruction set: " + instructionSet);
- return -1;
- }
-
- return mInstaller.patchoat(apkPath, uid, isPublic, pkgName, instructionSet);
- }
-
- public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
- if (!isValidInstructionSet(instructionSet)) {
- Slog.e(TAG, "Invalid instruction set: " + instructionSet);
- return -1;
- }
-
- return mInstaller.patchoat(apkPath, uid, isPublic, instructionSet);
- }
-
- public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
+ public int dexopt(String apkPath, int uid, boolean isPublic,
+ String instructionSet, int dexoptNeeded) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
- return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet);
+ return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet, dexoptNeeded);
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode) {
+ String instructionSet, int dexoptNeeded, boolean vmSafeMode,
+ boolean debuggable, @Nullable String outputPath) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
-
- return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, instructionSet, vmSafeMode);
+ return mInstaller.dexopt(apkPath, uid, isPublic, pkgName,
+ instructionSet, dexoptNeeded, vmSafeMode,
+ debuggable, outputPath);
}
public int idmap(String targetApkPath, String overlayApkPath, int uid) {
@@ -133,9 +138,26 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ /**
+ * Removes packageDir or its subdirectory
+ */
+ public int rmPackageDir(String packageDir) {
+ StringBuilder builder = new StringBuilder("rmpackagedir");
+ builder.append(' ');
+ builder.append(packageDir);
+ return mInstaller.execute(builder.toString());
+ }
+
+ @Deprecated
public int remove(String name, int userId) {
+ return remove(null, name, userId);
+ }
+
+ public int remove(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("remove");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
@@ -151,9 +173,16 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int fixUid(String name, int uid, int gid) {
+ return fixUid(null, name, uid, gid);
+ }
+
+ public int fixUid(String uuid, String name, int uid, int gid) {
StringBuilder builder = new StringBuilder("fixuid");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -162,27 +191,48 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int deleteCacheFiles(String name, int userId) {
+ return deleteCacheFiles(null, name, userId);
+ }
+
+ public int deleteCacheFiles(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmcache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int deleteCodeCacheFiles(String name, int userId) {
+ return deleteCodeCacheFiles(null, name, userId);
+ }
+
+ public int deleteCodeCacheFiles(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmcodecache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int createUserData(String name, int uid, int userId, String seinfo) {
+ return createUserData(null, name, uid, userId, seinfo);
+ }
+
+ public int createUserData(String uuid, String name, int uid, int userId, String seinfo) {
StringBuilder builder = new StringBuilder("mkuserdata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -200,16 +250,46 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int removeUserDataDirs(int userId) {
+ return removeUserDataDirs(null, userId);
+ }
+
+ public int removeUserDataDirs(String uuid, int userId) {
StringBuilder builder = new StringBuilder("rmuser");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ public int moveUserDataDirs(String fromUuid, String toUuid, String packageName, int appId,
+ String seinfo) {
+ StringBuilder builder = new StringBuilder("mvuserdata");
+ builder.append(' ');
+ builder.append(escapeNull(fromUuid));
+ builder.append(' ');
+ builder.append(escapeNull(toUuid));
+ builder.append(' ');
+ builder.append(packageName);
+ builder.append(' ');
+ builder.append(appId);
+ builder.append(' ');
+ builder.append(seinfo);
+ return mInstaller.execute(builder.toString());
+ }
+
+ @Deprecated
public int clearUserData(String name, int userId) {
+ return clearUserData(null, name, userId);
+ }
+
+ public int clearUserData(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmuserdata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
@@ -236,15 +316,30 @@ public final class Installer extends SystemService {
}
}
+ @Deprecated
public int freeCache(long freeStorageSize) {
+ return freeCache(null, freeStorageSize);
+ }
+
+ public int freeCache(String uuid, long freeStorageSize) {
StringBuilder builder = new StringBuilder("freecache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(String.valueOf(freeStorageSize));
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath,
String fwdLockApkPath, String asecPath, String[] instructionSets, PackageStats pStats) {
+ return getSizeInfo(null, pkgName, persona, apkPath, libDirPath, fwdLockApkPath, asecPath,
+ instructionSets, pStats);
+ }
+
+ public int getSizeInfo(String uuid, String pkgName, int persona, String apkPath,
+ String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets,
+ PackageStats pStats) {
for (String instructionSet : instructionSets) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
@@ -254,6 +349,8 @@ public final class Installer extends SystemService {
StringBuilder builder = new StringBuilder("getsize");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(persona);
@@ -293,6 +390,11 @@ public final class Installer extends SystemService {
return mInstaller.execute("movefiles");
}
+ @Deprecated
+ public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) {
+ return linkNativeLibraryDirectory(null, dataPath, nativeLibPath32, userId);
+ }
+
/**
* Links the 32 bit native library directory in an application's data directory to the
* real location for backward compatibility. Note that no such symlink is created for
@@ -300,7 +402,8 @@ public final class Installer extends SystemService {
*
* @return -1 on error
*/
- public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) {
+ public int linkNativeLibraryDirectory(String uuid, String dataPath, String nativeLibPath32,
+ int userId) {
if (dataPath == null) {
Slog.e(TAG, "linkNativeLibraryDirectory dataPath is null");
return -1;
@@ -309,7 +412,10 @@ public final class Installer extends SystemService {
return -1;
}
- StringBuilder builder = new StringBuilder("linklib ");
+ StringBuilder builder = new StringBuilder("linklib");
+ builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(dataPath);
builder.append(' ');
builder.append(nativeLibPath32);
@@ -319,9 +425,16 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public boolean restoreconData(String pkgName, String seinfo, int uid) {
+ return restoreconData(null, pkgName, seinfo, uid);
+ }
+
+ public boolean restoreconData(String uuid, String pkgName, String seinfo, int uid) {
StringBuilder builder = new StringBuilder("restorecondata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(seinfo != null ? seinfo : "!");
@@ -330,6 +443,15 @@ public final class Installer extends SystemService {
return (mInstaller.execute(builder.toString()) == 0);
}
+ public int createOatDir(String oatDir, String dexInstructionSet) {
+ StringBuilder builder = new StringBuilder("createoatdir");
+ builder.append(' ');
+ builder.append(oatDir);
+ builder.append(' ');
+ builder.append(dexInstructionSet);
+ return mInstaller.execute(builder.toString());
+ }
+
/**
* Returns true iff. {@code instructionSet} is a valid instruction set.
*/
diff --git a/services/core/java/com/android/server/pm/InstructionSets.java b/services/core/java/com/android/server/pm/InstructionSets.java
new file mode 100644
index 0000000..5092ebf
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstructionSets.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import dalvik.system.VMRuntime;
+
+/**
+ * Provides various methods for obtaining and converting of instruction sets.
+ *
+ * @hide
+ */
+public class InstructionSets {
+ private static final String PREFERRED_INSTRUCTION_SET =
+ VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);;
+ public static String[] getAppDexInstructionSets(ApplicationInfo info) {
+ if (info.primaryCpuAbi != null) {
+ if (info.secondaryCpuAbi != null) {
+ return new String[] {
+ VMRuntime.getInstructionSet(info.primaryCpuAbi),
+ VMRuntime.getInstructionSet(info.secondaryCpuAbi) };
+ } else {
+ return new String[] {
+ VMRuntime.getInstructionSet(info.primaryCpuAbi) };
+ }
+ }
+
+ return new String[] { getPreferredInstructionSet() };
+ }
+
+ public static String[] getAppDexInstructionSets(PackageSetting ps) {
+ if (ps.primaryCpuAbiString != null) {
+ if (ps.secondaryCpuAbiString != null) {
+ return new String[] {
+ VMRuntime.getInstructionSet(ps.primaryCpuAbiString),
+ VMRuntime.getInstructionSet(ps.secondaryCpuAbiString) };
+ } else {
+ return new String[] {
+ VMRuntime.getInstructionSet(ps.primaryCpuAbiString) };
+ }
+ }
+
+ return new String[] { getPreferredInstructionSet() };
+ }
+
+ public static String getPreferredInstructionSet() {
+ return PREFERRED_INSTRUCTION_SET;
+ }
+
+ /**
+ * Returns the instruction set that should be used to compile dex code. In the presence of
+ * a native bridge this might be different than the one shared libraries use.
+ */
+ public static String getDexCodeInstructionSet(String sharedLibraryIsa) {
+ // TODO b/19550105 Build mapping once instead of querying each time
+ String dexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + sharedLibraryIsa);
+ return TextUtils.isEmpty(dexCodeIsa) ? sharedLibraryIsa : dexCodeIsa;
+ }
+
+ public static String[] getDexCodeInstructionSets(String[] instructionSets) {
+ ArraySet<String> dexCodeInstructionSets = new ArraySet<String>(instructionSets.length);
+ for (String instructionSet : instructionSets) {
+ dexCodeInstructionSets.add(getDexCodeInstructionSet(instructionSet));
+ }
+ return dexCodeInstructionSets.toArray(new String[dexCodeInstructionSets.size()]);
+ }
+
+ /**
+ * Returns deduplicated list of supported instructions for dex code.
+ */
+ public static String[] getAllDexCodeInstructionSets() {
+ String[] supportedInstructionSets = new String[Build.SUPPORTED_ABIS.length];
+ for (int i = 0; i < supportedInstructionSets.length; i++) {
+ String abi = Build.SUPPORTED_ABIS[i];
+ supportedInstructionSets[i] = VMRuntime.getInstructionSet(abi);
+ }
+ return getDexCodeInstructionSets(supportedInstructionSets);
+ }
+
+ public static List<String> getAllInstructionSets() {
+ final String[] allAbis = Build.SUPPORTED_ABIS;
+ final List<String> allInstructionSets = new ArrayList<String>(allAbis.length);
+
+ for (String abi : allAbis) {
+ final String instructionSet = VMRuntime.getInstructionSet(abi);
+ if (!allInstructionSets.contains(instructionSet)) {
+ allInstructionSets.add(instructionSet);
+ }
+ }
+
+ return allInstructionSets;
+ }
+
+ public static String getPrimaryInstructionSet(ApplicationInfo info) {
+ if (info.primaryCpuAbi == null) {
+ return getPreferredInstructionSet();
+ }
+
+ return VMRuntime.getInstructionSet(info.primaryCpuAbi);
+ }
+
+}
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java b/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java
new file mode 100644
index 0000000..399b03c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import java.util.Arrays;
+
+/**
+ * This is the key for the map of {@link android.content.pm.IntentFilterVerificationInfo}s
+ * maintained by the {@link com.android.server.pm.PackageManagerService}
+ */
+class IntentFilterVerificationKey {
+ public String domains;
+ public String packageName;
+ public String className;
+
+ public IntentFilterVerificationKey(String[] domains, String packageName, String className) {
+ StringBuilder sb = new StringBuilder();
+ for (String host : domains) {
+ sb.append(host);
+ }
+ this.domains = sb.toString();
+ this.packageName = packageName;
+ this.className = className;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ IntentFilterVerificationKey that = (IntentFilterVerificationKey) o;
+
+ if (domains != null ? !domains.equals(that.domains) : that.domains != null) return false;
+ if (className != null ? !className.equals(that.className) : that.className != null)
+ return false;
+ if (packageName != null ? !packageName.equals(that.packageName) : that.packageName != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = domains != null ? domains.hashCode() : 0;
+ result = 31 * result + (packageName != null ? packageName.hashCode() : 0);
+ result = 31 * result + (className != null ? className.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java b/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java
new file mode 100644
index 0000000..ead399b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+
+import java.util.List;
+
+/* package private */ class IntentFilterVerificationResponse {
+ public final int callerUid;
+ public final int code;
+ public final List<String> failedDomains;
+
+ public IntentFilterVerificationResponse(int callerUid, int code, List<String> failedDomains) {
+ this.callerUid = callerUid;
+ this.code = code;
+ this.failedDomains = failedDomains;
+ }
+
+ public String getFailedDomainsString() {
+ StringBuilder sb = new StringBuilder();
+ for (String domain : failedDomains) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append(domain);
+ }
+ return sb.toString();
+ }
+}
diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationState.java b/services/core/java/com/android/server/pm/IntentFilterVerificationState.java
new file mode 100644
index 0000000..c09d6ae
--- /dev/null
+++ b/services/core/java/com/android/server/pm/IntentFilterVerificationState.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class IntentFilterVerificationState {
+ static final String TAG = IntentFilterVerificationState.class.getName();
+
+ public final static int STATE_UNDEFINED = 0;
+ public final static int STATE_VERIFICATION_PENDING = 1;
+ public final static int STATE_VERIFICATION_SUCCESS = 2;
+ public final static int STATE_VERIFICATION_FAILURE = 3;
+
+ private int mRequiredVerifierUid = 0;
+
+ private int mState;
+
+ private ArrayList<PackageParser.ActivityIntentInfo> mFilters = new ArrayList<>();
+ private ArraySet<String> mHosts = new ArraySet<>();
+ private int mUserId;
+
+ private String mPackageName;
+ private boolean mVerificationComplete;
+
+ public IntentFilterVerificationState(int verifierUid, int userId, String packageName) {
+ mRequiredVerifierUid = verifierUid;
+ mUserId = userId;
+ mPackageName = packageName;
+ mState = STATE_UNDEFINED;
+ mVerificationComplete = false;
+ }
+
+ public void setState(int state) {
+ if (state > STATE_VERIFICATION_FAILURE || state < STATE_UNDEFINED) {
+ mState = STATE_UNDEFINED;
+ } else {
+ mState = state;
+ }
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ public void setPendingState() {
+ setState(STATE_VERIFICATION_PENDING);
+ }
+
+ public ArrayList<PackageParser.ActivityIntentInfo> getFilters() {
+ return mFilters;
+ }
+
+ public boolean isVerificationComplete() {
+ return mVerificationComplete;
+ }
+
+ public boolean isVerified() {
+ if (mVerificationComplete) {
+ return (mState == STATE_VERIFICATION_SUCCESS);
+ }
+ return false;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public String getHostsString() {
+ StringBuilder sb = new StringBuilder();
+ final int count = mHosts.size();
+ for (int i=0; i<count; i++) {
+ if (i > 0) {
+ sb.append(" ");
+ }
+ sb.append(mHosts.valueAt(i));
+ }
+ return sb.toString();
+ }
+
+ public boolean setVerifierResponse(int callerUid, int code) {
+ if (mRequiredVerifierUid == callerUid) {
+ int state = STATE_UNDEFINED;
+ if (code == PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS) {
+ state = STATE_VERIFICATION_SUCCESS;
+ } else if (code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
+ state = STATE_VERIFICATION_FAILURE;
+ }
+ mVerificationComplete = true;
+ setState(state);
+ return true;
+ }
+ Log.d(TAG, "Cannot set verifier response with callerUid:" + callerUid + " and code:" +
+ code + " as required verifierUid is:" + mRequiredVerifierUid);
+ return false;
+ }
+
+ public void addFilter(PackageParser.ActivityIntentInfo filter) {
+ mFilters.add(filter);
+ mHosts.addAll(filter.getHostsList());
+ }
+}
diff --git a/services/core/java/com/android/server/pm/KeySetHandle.java b/services/core/java/com/android/server/pm/KeySetHandle.java
index 640feb3..f34bd60 100644
--- a/services/core/java/com/android/server/pm/KeySetHandle.java
+++ b/services/core/java/com/android/server/pm/KeySetHandle.java
@@ -18,5 +18,46 @@ package com.android.server.pm;
import android.os.Binder;
-public class KeySetHandle extends Binder {
-} \ No newline at end of file
+class KeySetHandle extends Binder{
+ private final long mId;
+ private int mRefCount;
+
+ protected KeySetHandle(long id) {
+ mId = id;
+ mRefCount = 1;
+ }
+
+ /*
+ * Only used when reading state from packages.xml
+ */
+ protected KeySetHandle(long id, int refCount) {
+ mId = id;
+ mRefCount = refCount;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ protected int getRefCountLPr() {
+ return mRefCount;
+ }
+
+ /*
+ * Only used when reading state from packages.xml
+ */
+ protected void setRefCountLPw(int newCount) {
+ mRefCount = newCount;
+ return;
+ }
+
+ protected void incrRefCountLPw() {
+ mRefCount++;
+ return;
+ }
+
+ protected int decrRefCountLPw() {
+ mRefCount--;
+ return mRefCount;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 4a8e318..c8e5c3a 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -17,7 +17,7 @@
package com.android.server.pm;
import android.content.pm.PackageParser;
-import android.os.Binder;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Base64;
import android.util.Slog;
@@ -26,7 +26,6 @@ import android.util.LongSparseArray;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.PublicKey;
-import java.util.Map;
import java.util.Set;
import org.xmlpull.v1.XmlPullParser;
@@ -53,19 +52,61 @@ public class KeySetManagerService {
private final LongSparseArray<KeySetHandle> mKeySets;
- private final LongSparseArray<PublicKey> mPublicKeys;
+ private final LongSparseArray<PublicKeyHandle> mPublicKeys;
protected final LongSparseArray<ArraySet<Long>> mKeySetMapping;
- private final Map<String, PackageSetting> mPackages;
+ private final ArrayMap<String, PackageSetting> mPackages;
private static long lastIssuedKeySetId = 0;
private static long lastIssuedKeyId = 0;
- public KeySetManagerService(Map<String, PackageSetting> packages) {
+ class PublicKeyHandle {
+ private final PublicKey mKey;
+ private final long mId;
+ private int mRefCount;
+
+ public PublicKeyHandle(long id, PublicKey key) {
+ mId = id;
+ mRefCount = 1;
+ mKey = key;
+ }
+
+ /*
+ * Only used when reading state from packages.xml
+ */
+ private PublicKeyHandle(long id, int refCount, PublicKey key) {
+ mId = id;
+ mRefCount = refCount;
+ mKey = key;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public PublicKey getKey() {
+ return mKey;
+ }
+
+ public int getRefCountLPr() {
+ return mRefCount;
+ }
+
+ public void incrRefCountLPw() {
+ mRefCount++;
+ return;
+ }
+ public long decrRefCountLPw() {
+ mRefCount--;
+ return mRefCount;
+ }
+ }
+
+ public KeySetManagerService(ArrayMap<String, PackageSetting> packages) {
mKeySets = new LongSparseArray<KeySetHandle>();
- mPublicKeys = new LongSparseArray<PublicKey>();
+ mPublicKeys = new LongSparseArray<PublicKeyHandle>();
mKeySetMapping = new LongSparseArray<ArraySet<Long>>();
mPackages = packages;
}
@@ -93,7 +134,9 @@ public class KeySetManagerService {
if (id == KEYSET_NOT_FOUND) {
return false;
}
- return pkg.keySetData.packageIsSignedBy(id);
+ ArraySet<Long> pkgKeys = mKeySetMapping.get(pkg.keySetData.getProperSigningKeySet());
+ ArraySet<Long> testKeys = mKeySetMapping.get(id);
+ return pkgKeys.containsAll(testKeys);
}
/**
@@ -114,81 +157,45 @@ public class KeySetManagerService {
|| pkg.keySetData.getProperSigningKeySet()
== PackageKeySetData.KEYSET_UNASSIGNED) {
throw new NullPointerException("Package has no KeySet data");
- }
+ }
long id = getIdByKeySetLPr(ks);
- return pkg.keySetData.getProperSigningKeySet() == id;
- }
-
- /**
- * This informs the system that the given package has defined a KeySet
- * in its manifest that a) contains the given keys and b) is named
- * alias by that package.
- */
- public void addDefinedKeySetToPackageLPw(String packageName,
- ArraySet<PublicKey> keys, String alias) {
- if ((packageName == null) || (keys == null) || (alias == null)) {
- Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
- return;
- }
- PackageSetting pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new NullPointerException("Unknown package");
- }
- // Add to KeySets, then to package
- KeySetHandle ks = addKeySetLPw(keys);
- long id = getIdByKeySetLPr(ks);
- pkg.keySetData.addDefinedKeySet(id, alias);
- }
-
- /**
- * This informs the system that the given package has defined a KeySet
- * alias in its manifest to be an upgradeKeySet. This must be called
- * after all of the defined KeySets have been added.
- */
- public void addUpgradeKeySetToPackageLPw(String packageName, String alias) {
- if ((packageName == null) || (alias == null)) {
- Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
- return;
- }
- PackageSetting pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new NullPointerException("Unknown package");
+ if (id == KEYSET_NOT_FOUND) {
+ return false;
}
- pkg.keySetData.addUpgradeKeySet(alias);
+ ArraySet<Long> pkgKeys = mKeySetMapping.get(pkg.keySetData.getProperSigningKeySet());
+ ArraySet<Long> testKeys = mKeySetMapping.get(id);
+ return pkgKeys.equals(testKeys);
}
/**
- * Similar to the above, this informs the system that the given package
- * was signed by the provided KeySet.
+ * Informs the system that the given package was signed by the provided KeySet.
*/
public void addSigningKeySetToPackageLPw(String packageName,
ArraySet<PublicKey> signingKeys) {
- if ((packageName == null) || (signingKeys == null)) {
- Slog.w(TAG, "Got null argument for a signing keyset, ignoring!");
- return;
- }
- // add the signing KeySet
- KeySetHandle ks = addKeySetLPw(signingKeys);
- long id = getIdByKeySetLPr(ks);
- ArraySet<Long> publicKeyIds = mKeySetMapping.get(id);
- if (publicKeyIds == null) {
- throw new NullPointerException("Got invalid KeySet id");
- }
- // attach it to the package
+
+ /* check existing keyset for reuse or removal */
PackageSetting pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new NullPointerException("No such package!");
- }
- pkg.keySetData.setProperSigningKeySet(id);
- // for each KeySet which is a subset of the one above, add the
- // KeySet id to the package's signing KeySets
- for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
- long keySetID = mKeySets.keyAt(keySetIndex);
- ArraySet<Long> definedKeys = mKeySetMapping.get(keySetID);
- if (publicKeyIds.containsAll(definedKeys)) {
- pkg.keySetData.addSigningKeySet(keySetID);
+ long signingKeySetId = pkg.keySetData.getProperSigningKeySet();
+
+ if (signingKeySetId != PackageKeySetData.KEYSET_UNASSIGNED) {
+ ArraySet<PublicKey> existingKeys = getPublicKeysFromKeySetLPr(signingKeySetId);
+ if (existingKeys.equals(signingKeys)) {
+
+ /* no change in signing keys, leave PackageSetting alone */
+ return;
+ } else {
+
+ /* old keyset no longer valid, remove ref */
+ KeySetHandle ksh = mKeySets.get(signingKeySetId);
+ decrementKeySetLPw(signingKeySetId);
}
}
+
+ /* create and add a new keyset */
+ KeySetHandle ks = addKeySetLPw(signingKeys);
+ long id = ks.getId();
+ pkg.keySetData.setProperSigningKeySet(id);
+ return;
}
/**
@@ -205,25 +212,63 @@ public class KeySetManagerService {
return KEYSET_NOT_FOUND;
}
+ /*
+ * Inform the system that the given package defines the given KeySets.
+ * Remove any KeySets the package no longer defines.
+ */
+ public void addDefinedKeySetsToPackageLPw(String packageName,
+ ArrayMap<String, ArraySet<PublicKey>> definedMapping) {
+ PackageSetting pkg = mPackages.get(packageName);
+ ArrayMap<String, Long> prevDefinedKeySets = pkg.keySetData.getAliases();
+
+ /* add all of the newly defined KeySets */
+ ArrayMap<String, Long> newKeySetAliases = new ArrayMap<String, Long>();
+ final int defMapSize = definedMapping.size();
+ for (int i = 0; i < defMapSize; i++) {
+ String alias = definedMapping.keyAt(i);
+ ArraySet<PublicKey> pubKeys = definedMapping.valueAt(i);
+ if (alias != null && pubKeys != null && pubKeys.size() > 0) {
+ KeySetHandle ks = addKeySetLPw(pubKeys);
+ newKeySetAliases.put(alias, ks.getId());
+ }
+ }
+
+ /* remove each of the old references */
+ final int prevDefSize = prevDefinedKeySets.size();
+ for (int i = 0; i < prevDefSize; i++) {
+ decrementKeySetLPw(prevDefinedKeySets.valueAt(i));
+ }
+ pkg.keySetData.removeAllUpgradeKeySets();
+
+ /* switch to the just-added */
+ pkg.keySetData.setAliases(newKeySetAliases);
+ return;
+ }
+
/**
- * Fetches the KeySet corresponding to the given stable identifier.
- *
- * Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't
- * identify a {@link KeySet}.
+ * This informs the system that the given package has defined a KeySet
+ * alias in its manifest to be an upgradeKeySet. This must be called
+ * after all of the defined KeySets have been added.
*/
- public KeySetHandle getKeySetByIdLPr(long id) {
- return mKeySets.get(id);
+ public void addUpgradeKeySetsToPackageLPw(String packageName,
+ ArraySet<String> upgradeAliases) {
+ PackageSetting pkg = mPackages.get(packageName);
+ final int uaSize = upgradeAliases.size();
+ for (int i = 0; i < uaSize; i++) {
+ pkg.keySetData.addUpgradeKeySet(upgradeAliases.valueAt(i));
+ }
+ return;
}
/**
- * Fetches the {@link KeySetHandle} that a given package refers to by the
- * provided alias. Returns null if the package is unknown or does not have a
+ * Fetched the {@link KeySetHandle} that a given package refers to by the
+ * provided alias. Returns null if the package is unknown or does not have a
* KeySet corresponding to that alias.
*/
public KeySetHandle getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
PackageSetting p = mPackages.get(packageName);
if (p == null || p.keySetData == null) {
- return null;
+ return null;
}
Long keySetId = p.keySetData.getAliases().get(alias);
if (keySetId == null) {
@@ -244,8 +289,10 @@ public class KeySetManagerService {
return null;
}
ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>();
- for (long pkId : mKeySetMapping.get(id)) {
- mPubKeys.add(mPublicKeys.get(pkId));
+ ArraySet<Long> pkIds = mKeySetMapping.get(id);
+ final int pkSize = pkIds.size();
+ for (int i = 0; i < pkSize; i++) {
+ mPubKeys.add(mPublicKeys.get(pkIds.valueAt(i)).getKey());
}
return mPubKeys;
}
@@ -255,7 +302,7 @@ public class KeySetManagerService {
* package.
*
* @throws IllegalArgumentException if the package has no keyset data.
- * @throws NullPointerException if the package is unknown.
+ * @throws NullPointerException if the packgae is unknown.
*/
public KeySetHandle getSigningKeySetByPackageNameLPr(String packageName) {
PackageSetting p = mPackages.get(packageName);
@@ -269,99 +316,100 @@ public class KeySetManagerService {
}
/**
- * Fetches all the known {@link KeySetHandle KeySets} that may upgrade the given
- * package.
- *
- * @throws IllegalArgumentException if the package has no keyset data.
- * @throws NullPointerException if the package is unknown.
- */
- public ArraySet<KeySetHandle> getUpgradeKeySetsByPackageNameLPr(String packageName) {
- ArraySet<KeySetHandle> upgradeKeySets = new ArraySet<KeySetHandle>();
- PackageSetting p = mPackages.get(packageName);
- if (p == null) {
- throw new NullPointerException("Unknown package");
- }
- if (p.keySetData == null) {
- throw new IllegalArgumentException("Package has no keySet data");
- }
- if (p.keySetData.isUsingUpgradeKeySets()) {
- for (long l : p.keySetData.getUpgradeKeySets()) {
- upgradeKeySets.add(mKeySets.get(l));
- }
- }
- return upgradeKeySets;
- }
-
- /**
* Creates a new KeySet corresponding to the given keys.
*
* If the {@link PublicKey PublicKeys} aren't known to the system, this
- * adds them. Otherwise, they're deduped.
+ * adds them. Otherwise, they're deduped and the reference count
+ * incremented.
*
* If the KeySet isn't known to the system, this adds that and creates the
- * mapping to the PublicKeys. If it is known, then it's deduped.
- *
- * If the KeySet isn't known to the system, this adds it to all appropriate
- * signingKeySets
+ * mapping to the PublicKeys. If it is known, then it's deduped and the
+ * reference count is incremented.
*
* Throws if the provided set is {@code null}.
*/
private KeySetHandle addKeySetLPw(ArraySet<PublicKey> keys) {
- if (keys == null) {
- throw new NullPointerException("Provided keys cannot be null");
+ if (keys == null || keys.size() == 0) {
+ throw new IllegalArgumentException("Cannot add an empty set of keys!");
}
- // add each of the keys in the provided set
+
+ /* add each of the keys in the provided set */
ArraySet<Long> addedKeyIds = new ArraySet<Long>(keys.size());
- for (PublicKey k : keys) {
- long id = addPublicKeyLPw(k);
+ final int kSize = keys.size();
+ for (int i = 0; i < kSize; i++) {
+ long id = addPublicKeyLPw(keys.valueAt(i));
addedKeyIds.add(id);
}
- // check to see if the resulting keyset is new
+ /* check to see if the resulting keyset is new */
long existingKeySetId = getIdFromKeyIdsLPr(addedKeyIds);
if (existingKeySetId != KEYSET_NOT_FOUND) {
- return mKeySets.get(existingKeySetId);
+
+ /* public keys were incremented, but we aren't adding a new keyset: undo */
+ for (int i = 0; i < kSize; i++) {
+ decrementPublicKeyLPw(addedKeyIds.valueAt(i));
+ }
+ KeySetHandle ks = mKeySets.get(existingKeySetId);
+ ks.incrRefCountLPw();
+ return ks;
}
- // create the KeySet object
- KeySetHandle ks = new KeySetHandle();
- // get the first unoccupied slot in mKeySets
+ // get the next keyset id
long id = getFreeKeySetIDLPw();
- // add the KeySet object to it
+
+ // create the KeySet object and add to mKeySets and mapping
+ KeySetHandle ks = new KeySetHandle(id);
mKeySets.put(id, ks);
- // add the stable key ids to the mapping
mKeySetMapping.put(id, addedKeyIds);
- // add this KeySet id to all packages which are signed by it
- for (String pkgName : mPackages.keySet()) {
- PackageSetting p = mPackages.get(pkgName);
- if (p.keySetData != null) {
- long pProperSigning = p.keySetData.getProperSigningKeySet();
- if (pProperSigning != PackageKeySetData.KEYSET_UNASSIGNED) {
- ArraySet<Long> pSigningKeys = mKeySetMapping.get(pProperSigning);
- if (pSigningKeys.containsAll(addedKeyIds)) {
- p.keySetData.addSigningKeySet(id);
- }
- }
+ return ks;
+ }
+
+ /*
+ * Decrements the reference to KeySet represented by the given id. If this
+ * drops to zero, then also decrement the reference to each public key it
+ * contains and remove the KeySet.
+ */
+ private void decrementKeySetLPw(long id) {
+ KeySetHandle ks = mKeySets.get(id);
+ if (ks.decrRefCountLPw() <= 0) {
+ ArraySet<Long> pubKeys = mKeySetMapping.get(id);
+ final int pkSize = pubKeys.size();
+ for (int i = 0; i < pkSize; i++) {
+ decrementPublicKeyLPw(pubKeys.valueAt(i));
}
+ mKeySets.delete(id);
+ mKeySetMapping.delete(id);
}
- // go home
- return ks;
+ return;
+ }
+
+ /*
+ * Decrements the reference to PublicKey represented by the given id. If
+ * this drops to zero, then remove it.
+ */
+ private void decrementPublicKeyLPw(long id) {
+ PublicKeyHandle pk = mPublicKeys.get(id);
+ if (pk.decrRefCountLPw() <= 0) {
+ mPublicKeys.delete(id);
+ }
+ return;
}
/**
* Adds the given PublicKey to the system, deduping as it goes.
*/
private long addPublicKeyLPw(PublicKey key) {
- // check if the public key is new
- long existingKeyId = getIdForPublicKeyLPr(key);
- if (existingKeyId != PUBLIC_KEY_NOT_FOUND) {
- return existingKeyId;
- }
- // if it's new find the first unoccupied slot in the public keys
- long id = getFreePublicKeyIdLPw();
- // add the public key to it
- mPublicKeys.put(id, key);
- // return the stable identifier
+ long id = getIdForPublicKeyLPr(key);
+ if (id != PUBLIC_KEY_NOT_FOUND) {
+
+ /* We already know about this key, increment its ref count and ret */
+ mPublicKeys.get(id).incrRefCountLPw();
+ return id;
+ }
+
+ /* if it's new find the first unoccupied slot in the public keys */
+ id = getFreePublicKeyIdLPw();
+ mPublicKeys.put(id, new PublicKeyHandle(id, key));
return id;
}
@@ -386,7 +434,7 @@ public class KeySetManagerService {
private long getIdForPublicKeyLPr(PublicKey k) {
String encodedPublicKey = new String(k.getEncoded());
for (int publicKeyIndex = 0; publicKeyIndex < mPublicKeys.size(); publicKeyIndex++) {
- PublicKey value = mPublicKeys.valueAt(publicKeyIndex);
+ PublicKey value = mPublicKeys.valueAt(publicKeyIndex).getKey();
String encodedExistingKey = new String(value.getEncoded());
if (encodedPublicKey.equals(encodedExistingKey)) {
return mPublicKeys.keyAt(publicKeyIndex);
@@ -411,89 +459,42 @@ public class KeySetManagerService {
return lastIssuedKeyId;
}
+ /*
+ * This package is being removed from the system, so we need to
+ * remove its keyset and public key references, then remove its
+ * keyset data.
+ */
public void removeAppKeySetDataLPw(String packageName) {
- // Get the package's known keys and KeySets
- ArraySet<Long> deletableKeySets = getOriginalKeySetsByPackageNameLPr(packageName);
- ArraySet<Long> deletableKeys = new ArraySet<Long>();
- ArraySet<Long> knownKeys = null;
- for (Long ks : deletableKeySets) {
- knownKeys = mKeySetMapping.get(ks);
- if (knownKeys != null) {
- deletableKeys.addAll(knownKeys);
- }
- }
-
- // Now remove the keys and KeySets on which any other package relies
- for (String pkgName : mPackages.keySet()) {
- if (pkgName.equals(packageName)) {
- continue;
- }
- ArraySet<Long> knownKeySets = getOriginalKeySetsByPackageNameLPr(pkgName);
- deletableKeySets.removeAll(knownKeySets);
- knownKeys = new ArraySet<Long>();
- for (Long ks : knownKeySets) {
- knownKeys = mKeySetMapping.get(ks);
- if (knownKeys != null) {
- deletableKeys.removeAll(knownKeys);
- }
- }
- }
-
- // The remaining keys and KeySets are not relied on by any other
- // application and so can be safely deleted.
- for (Long ks : deletableKeySets) {
- mKeySets.delete(ks);
- mKeySetMapping.delete(ks);
- }
- for (Long keyId : deletableKeys) {
- mPublicKeys.delete(keyId);
- }
- // Now remove the deleted KeySets from each package's signingKeySets
- for (String pkgName : mPackages.keySet()) {
- PackageSetting p = mPackages.get(pkgName);
- for (Long ks : deletableKeySets) {
- p.keySetData.removeSigningKeySet(ks);
- }
+ /* remove refs from common keysets and public keys */
+ PackageSetting pkg = mPackages.get(packageName);
+ long signingKeySetId = pkg.keySetData.getProperSigningKeySet();
+ decrementKeySetLPw(signingKeySetId);
+ ArrayMap<String, Long> definedKeySets = pkg.keySetData.getAliases();
+ for (int i = 0; i < definedKeySets.size(); i++) {
+ decrementKeySetLPw(definedKeySets.valueAt(i));
}
- // Finally, remove all KeySets from the original package
- PackageSetting p = mPackages.get(packageName);
- clearPackageKeySetDataLPw(p);
- }
- private void clearPackageKeySetDataLPw(PackageSetting p) {
- p.keySetData.removeAllSigningKeySets();
- p.keySetData.removeAllUpgradeKeySets();
- p.keySetData.removeAllDefinedKeySets();
+ /* remove from package */
+ clearPackageKeySetDataLPw(pkg);
return;
}
- private ArraySet<Long> getOriginalKeySetsByPackageNameLPr(String packageName) {
- PackageSetting p = mPackages.get(packageName);
- if (p == null) {
- throw new NullPointerException("Unknown package");
- }
- if (p.keySetData == null) {
- throw new IllegalArgumentException("Package has no keySet data");
- }
- ArraySet<Long> knownKeySets = new ArraySet<Long>();
- knownKeySets.add(p.keySetData.getProperSigningKeySet());
- if (p.keySetData.isUsingDefinedKeySets()) {
- for (long ks : p.keySetData.getDefinedKeySets()) {
- knownKeySets.add(ks);
- }
- }
- return knownKeySets;
+ private void clearPackageKeySetDataLPw(PackageSetting pkg) {
+ pkg.keySetData.setProperSigningKeySet(PackageKeySetData.KEYSET_UNASSIGNED);
+ pkg.keySetData.removeAllDefinedKeySets();
+ pkg.keySetData.removeAllUpgradeKeySets();
+ return;
}
public String encodePublicKey(PublicKey k) throws IOException {
- return new String(Base64.encode(k.getEncoded(), 0));
+ return new String(Base64.encode(k.getEncoded(), Base64.NO_WRAP));
}
public void dumpLPr(PrintWriter pw, String packageName,
PackageManagerService.DumpState dumpState) {
boolean printedHeader = false;
- for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) {
+ for (ArrayMap.Entry<String, PackageSetting> e : mPackages.entrySet()) {
String keySetPackage = e.getKey();
if (packageName != null && !packageName.equals(keySetPackage)) {
continue;
@@ -508,7 +509,7 @@ public class KeySetManagerService {
pw.print(" ["); pw.print(keySetPackage); pw.println("]");
if (pkg.keySetData != null) {
boolean printedLabel = false;
- for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
+ for (ArrayMap.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
if (!printedLabel) {
pw.print(" KeySets Aliases: ");
printedLabel = true;
@@ -524,36 +525,26 @@ public class KeySetManagerService {
}
printedLabel = false;
if (pkg.keySetData.isUsingDefinedKeySets()) {
- for (long keySetId : pkg.keySetData.getDefinedKeySets()) {
+ ArrayMap<String, Long> definedKeySets = pkg.keySetData.getAliases();
+ final int dksSize = definedKeySets.size();
+ for (int i = 0; i < dksSize; i++) {
if (!printedLabel) {
pw.print(" Defined KeySets: ");
printedLabel = true;
} else {
pw.print(", ");
}
- pw.print(Long.toString(keySetId));
- }
- }
- if (printedLabel) {
- pw.println("");
- }
- printedLabel = false;
- final long[] signingKeySets = pkg.keySetData.getSigningKeySets();
- if (signingKeySets != null) {
- for (long keySetId : signingKeySets) {
- if (!printedLabel) {
- pw.print(" Signing KeySets: ");
- printedLabel = true;
- } else {
- pw.print(", ");
- }
- pw.print(Long.toString(keySetId));
+ pw.print(Long.toString(definedKeySets.valueAt(i)));
}
}
if (printedLabel) {
pw.println("");
}
printedLabel = false;
+ final long signingKeySet = pkg.keySetData.getProperSigningKeySet();
+ pw.print(" Signing KeySets: ");
+ pw.print(Long.toString(signingKeySet));
+ pw.println("");
if (pkg.keySetData.isUsingUpgradeKeySets()) {
for (long keySetId : pkg.keySetData.getUpgradeKeySets()) {
if (!printedLabel) {
@@ -590,8 +581,8 @@ public class KeySetManagerService {
serializer.startTag(null, "keys");
for (int pKeyIndex = 0; pKeyIndex < mPublicKeys.size(); pKeyIndex++) {
long id = mPublicKeys.keyAt(pKeyIndex);
- PublicKey key = mPublicKeys.valueAt(pKeyIndex);
- String encodedKey = encodePublicKey(key);
+ PublicKeyHandle pkh = mPublicKeys.valueAt(pKeyIndex);
+ String encodedKey = encodePublicKey(pkh.getKey());
serializer.startTag(null, "public-key");
serializer.attribute(null, "identifier", Long.toString(id));
serializer.attribute(null, "value", encodedKey);
@@ -617,17 +608,17 @@ public class KeySetManagerService {
serializer.endTag(null, "keysets");
}
- void readKeySetsLPw(XmlPullParser parser)
+ void readKeySetsLPw(XmlPullParser parser, ArrayMap<Long, Integer> keySetRefCounts)
throws XmlPullParserException, IOException {
int type;
long currentKeySetId = 0;
int outerDepth = parser.getDepth();
- String recordedVersion = parser.getAttributeValue(null, "version");
- if (recordedVersion == null || Integer.parseInt(recordedVersion) != CURRENT_VERSION) {
+ String recordedVersionStr = parser.getAttributeValue(null, "version");
+ if (recordedVersionStr == null) {
+ // The keyset information comes from pre-versioned devices, and
+ // is inaccurate, don't collect any of it.
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- // Our version is different than the one which generated the old keyset data.
- // We don't want any of the old data, but we must advance the parser
continue;
}
// The KeySet information read previously from packages.xml is invalid.
@@ -637,6 +628,7 @@ public class KeySetManagerService {
}
return;
}
+ int recordedVersion = Integer.parseInt(recordedVersionStr);
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -653,6 +645,8 @@ public class KeySetManagerService {
lastIssuedKeySetId = Long.parseLong(parser.getAttributeValue(null, "value"));
}
}
+
+ addRefCountsFromSavedPackagesLPw(keySetRefCounts);
}
void readKeysLPw(XmlPullParser parser)
@@ -683,29 +677,49 @@ public class KeySetManagerService {
}
final String tagName = parser.getName();
if (tagName.equals("keyset")) {
- currentKeySetId = readIdentifierLPw(parser);
- mKeySets.put(currentKeySetId, new KeySetHandle());
+ String encodedID = parser.getAttributeValue(null, "identifier");
+ currentKeySetId = Long.parseLong(encodedID);
+ int refCount = 0;
+ mKeySets.put(currentKeySetId, new KeySetHandle(currentKeySetId, refCount));
mKeySetMapping.put(currentKeySetId, new ArraySet<Long>());
} else if (tagName.equals("key-id")) {
- long id = readIdentifierLPw(parser);
+ String encodedID = parser.getAttributeValue(null, "identifier");
+ long id = Long.parseLong(encodedID);
mKeySetMapping.get(currentKeySetId).add(id);
}
}
}
- long readIdentifierLPw(XmlPullParser parser)
- throws XmlPullParserException {
- return Long.parseLong(parser.getAttributeValue(null, "identifier"));
- }
-
void readPublicKeyLPw(XmlPullParser parser)
throws XmlPullParserException {
String encodedID = parser.getAttributeValue(null, "identifier");
long identifier = Long.parseLong(encodedID);
+ int refCount = 0;
String encodedPublicKey = parser.getAttributeValue(null, "value");
PublicKey pub = PackageParser.parsePublicKey(encodedPublicKey);
if (pub != null) {
- mPublicKeys.put(identifier, pub);
+ PublicKeyHandle pkh = new PublicKeyHandle(identifier, refCount, pub);
+ mPublicKeys.put(identifier, pkh);
+ }
+ }
+
+ /*
+ * Set each KeySet ref count. Also increment all public keys in each keyset.
+ */
+ private void addRefCountsFromSavedPackagesLPw(ArrayMap<Long, Integer> keySetRefCounts) {
+ final int numRefCounts = keySetRefCounts.size();
+ for (int i = 0; i < numRefCounts; i++) {
+ KeySetHandle ks = mKeySets.get(keySetRefCounts.keyAt(i));
+ ks.setRefCountLPw(keySetRefCounts.valueAt(i));
+ }
+
+ final int numKeySets = mKeySets.size();
+ for (int i = 0; i < numKeySets; i++) {
+ ArraySet<Long> pubKeys = mKeySetMapping.valueAt(i);
+ final int pkSize = pubKeys.size();
+ for (int j = 0; j < pkSize; j++) {
+ mPublicKeys.get(pubKeys.valueAt(j)).incrRefCountLPw();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
new file mode 100644
index 0000000..a42e4e7
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm;
+
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageParser;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import dalvik.system.DexFile;
+import dalvik.system.StaleDexCacheError;
+
+import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
+import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
+
+/**
+ * Helper class for running dexopt command on packages.
+ */
+final class PackageDexOptimizer {
+ private static final String TAG = "PackageManager.DexOptimizer";
+ static final String OAT_DIR_NAME = "oat";
+ // TODO b/19550105 Remove error codes and use exceptions
+ static final int DEX_OPT_SKIPPED = 0;
+ static final int DEX_OPT_PERFORMED = 1;
+ static final int DEX_OPT_DEFERRED = 2;
+ static final int DEX_OPT_FAILED = -1;
+
+ private final PackageManagerService mPackageManagerService;
+ private ArraySet<PackageParser.Package> mDeferredDexOpt;
+
+ PackageDexOptimizer(PackageManagerService packageManagerService) {
+ this.mPackageManagerService = packageManagerService;
+ }
+
+ /**
+ * Performs dexopt on all code paths and libraries of the specified package for specified
+ * instruction sets.
+ *
+ * <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on
+ * {@link PackageManagerService#mInstallLock}.
+ */
+ int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
+ boolean forceDex, boolean defer, boolean inclDependencies) {
+ ArraySet<String> done;
+ if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
+ done = new ArraySet<String>();
+ done.add(pkg.packageName);
+ } else {
+ done = null;
+ }
+ synchronized (mPackageManagerService.mInstallLock) {
+ return performDexOptLI(pkg, instructionSets, forceDex, defer, done);
+ }
+ }
+
+ private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
+ boolean forceDex, boolean defer, ArraySet<String> done) {
+ final String[] instructionSets = targetInstructionSets != null ?
+ targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
+
+ if (done != null) {
+ done.add(pkg.packageName);
+ if (pkg.usesLibraries != null) {
+ performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer, done);
+ }
+ if (pkg.usesOptionalLibraries != null) {
+ performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, forceDex, defer,
+ done);
+ }
+ }
+
+ if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
+ return DEX_OPT_SKIPPED;
+ }
+
+ final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
+ final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+
+ final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
+ boolean performedDexOpt = false;
+ // There are three basic cases here:
+ // 1.) we need to dexopt, either because we are forced or it is needed
+ // 2.) we are deferring a needed dexopt
+ // 3.) we are skipping an unneeded dexopt
+ final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+ for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+ if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
+ continue;
+ }
+
+ for (String path : paths) {
+ try {
+ final int dexoptNeeded;
+ if (forceDex) {
+ dexoptNeeded = DexFile.DEX2OAT_NEEDED;
+ } else {
+ dexoptNeeded = DexFile.getDexOptNeeded(path,
+ pkg.packageName, dexCodeInstructionSet, defer);
+ }
+
+ if (!forceDex && defer && dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ // We're deciding to defer a needed dexopt. Don't bother dexopting for other
+ // paths and instruction sets. We'll deal with them all together when we process
+ // our list of deferred dexopts.
+ addPackageForDeferredDexopt(pkg);
+ return DEX_OPT_DEFERRED;
+ }
+
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ final String dexoptType;
+ String oatDir = null;
+ if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) {
+ dexoptType = "dex2oat";
+ oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
+ } else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) {
+ dexoptType = "patchoat";
+ } else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) {
+ dexoptType = "self patchoat";
+ } else {
+ throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
+ }
+ Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
+ + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
+ + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
+ + " oatDir = " + oatDir);
+ final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+ final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid,
+ !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet,
+ dexoptNeeded, vmSafeMode, debuggable, oatDir);
+ if (ret < 0) {
+ return DEX_OPT_FAILED;
+ }
+ performedDexOpt = true;
+ }
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "Apk not found for dexopt: " + path);
+ return DEX_OPT_FAILED;
+ } catch (IOException e) {
+ Slog.w(TAG, "IOException reading apk: " + path, e);
+ return DEX_OPT_FAILED;
+ } catch (StaleDexCacheError e) {
+ Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
+ return DEX_OPT_FAILED;
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception when doing dexopt : ", e);
+ return DEX_OPT_FAILED;
+ }
+ }
+
+ // At this point we haven't failed dexopt and we haven't deferred dexopt. We must
+ // either have either succeeded dexopt, or have had getDexOptNeeded tell us
+ // it isn't required. We therefore mark that this package doesn't need dexopt unless
+ // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
+ // it.
+ pkg.mDexOptPerformed.add(dexCodeInstructionSet);
+ }
+
+ // If we've gotten here, we're sure that no error occurred and that we haven't
+ // deferred dex-opt. We've either dex-opted one more paths or instruction sets or
+ // we've skipped all of them because they are up to date. In both cases this
+ // package doesn't need dexopt any longer.
+ return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
+ }
+
+ /**
+ * Creates oat dir for the specified package. In certain cases oat directory
+ * <strong>cannot</strong> be created:
+ * <ul>
+ * <li>{@code pkg} is a system app, which is not updated.</li>
+ * <li>Package location is not a directory, i.e. monolithic install.</li>
+ * </ul>
+ *
+ * @return Absolute path to the oat directory or null, if oat directory
+ * cannot be created.
+ */
+ @Nullable
+ private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
+ throws IOException {
+ if ((pkg.isSystemApp() && !pkg.isUpdatedSystemApp()) || pkg.isForwardLocked()
+ || pkg.applicationInfo.isExternalAsec()) {
+ return null;
+ }
+ File codePath = new File(pkg.codePath);
+ if (codePath.isDirectory()) {
+ File oatDir = getOatDir(codePath);
+ mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(),
+ dexInstructionSet);
+ return oatDir.getAbsolutePath();
+ }
+ return null;
+ }
+
+ static File getOatDir(File codePath) {
+ return new File(codePath, OAT_DIR_NAME);
+ }
+
+ private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets,
+ boolean forceDex, boolean defer, ArraySet<String> done) {
+ for (String libName : libs) {
+ PackageParser.Package libPkg = mPackageManagerService.findSharedNonSystemLibrary(
+ libName);
+ if (libPkg != null && !done.contains(libName)) {
+ performDexOptLI(libPkg, instructionSets, forceDex, defer, done);
+ }
+ }
+ }
+
+ /**
+ * Clears set of deferred dexopt packages.
+ * @return content of dexopt set if it was not empty
+ */
+ public ArraySet<PackageParser.Package> clearDeferredDexOptPackages() {
+ ArraySet<PackageParser.Package> result = mDeferredDexOpt;
+ mDeferredDexOpt = null;
+ return result;
+ }
+
+ public void addPackageForDeferredDexopt(PackageParser.Package pkg) {
+ if (mDeferredDexOpt == null) {
+ mDeferredDexOpt = new ArraySet<>();
+ }
+ mDeferredDexOpt.add(pkg);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2150e9a..a406175 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -30,17 +30,24 @@ import static com.android.internal.util.XmlUtils.writeUriAttribute;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.Manifest;
import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PackageDeleteObserver;
import android.app.PackageInstallObserver;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageInstallerCallback;
import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
@@ -53,7 +60,6 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -64,6 +70,8 @@ import android.os.RemoteException;
import android.os.SELinux;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
@@ -76,15 +84,17 @@ import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.Xml;
+import libcore.io.IoUtils;
+
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageHelper;
import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
import com.google.android.collect.Sets;
-import libcore.io.IoUtils;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -132,6 +142,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
private static final String ATTR_ORIGINATING_URI = "originatingUri";
private static final String ATTR_REFERRER_URI = "referrerUri";
private static final String ATTR_ABI_OVERRIDE = "abiOverride";
+ private static final String ATTR_VOLUME_UUID = "volumeUuid";
/** Automatically destroy sessions older than this */
private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
@@ -142,9 +153,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
private final Context mContext;
private final PackageManagerService mPm;
- private final AppOpsManager mAppOps;
- private final File mStagingDir;
+ private AppOpsManager mAppOps;
+ private StorageManager mStorage;
+
private final HandlerThread mInstallThread;
private final Handler mInstallHandler;
@@ -187,12 +199,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
};
- public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
+ public PackageInstallerService(Context context, PackageManagerService pm) {
mContext = context;
mPm = pm;
- mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-
- mStagingDir = stagingDir;
mInstallThread = new HandlerThread(TAG);
mInstallThread.start();
@@ -209,8 +218,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
synchronized (mSessions) {
readSessionsLocked();
+ final File internalStagingDir = buildInternalStagingDir();
final ArraySet<File> unclaimedStages = Sets.newArraySet(
- mStagingDir.listFiles(sStageFilter));
+ internalStagingDir.listFiles(sStageFilter));
final ArraySet<File> unclaimedIcons = Sets.newArraySet(
mSessionsDir.listFiles());
@@ -225,9 +235,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
for (File stage : unclaimedStages) {
Slog.w(TAG, "Deleting orphan stage " + stage);
if (stage.isDirectory()) {
- FileUtils.deleteContents(stage);
+ mPm.mInstaller.rmPackageDir(stage.getAbsolutePath());
+ } else {
+ stage.delete();
}
- stage.delete();
}
// Clean up orphaned icons
@@ -238,6 +249,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
}
+ public void systemReady() {
+ mAppOps = mContext.getSystemService(AppOpsManager.class);
+ mStorage = mContext.getSystemService(StorageManager.class);
+ }
+
public void onSecureContainersAvailable() {
synchronized (mSessions) {
final ArraySet<String> unclaimed = new ArraySet<>();
@@ -275,13 +291,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
@Deprecated
- public File allocateInternalStageDirLegacy() throws IOException {
+ public File allocateStageDirLegacy(String volumeUuid) throws IOException {
synchronized (mSessions) {
try {
final int sessionId = allocateSessionIdLocked();
mLegacySessions.put(sessionId, true);
- final File stageDir = buildInternalStageDir(sessionId);
- prepareInternalStageDir(stageDir);
+ final File stageDir = buildStageDir(volumeUuid, sessionId);
+ prepareStageDir(stageDir);
return stageDir;
} catch (IllegalStateException e) {
throw new IOException(e);
@@ -322,11 +338,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
Slog.w(TAG, "Abandoning old session first created at "
+ session.createdMillis);
valid = false;
- } else if (session.stageDir != null
- && !session.stageDir.exists()) {
- Slog.w(TAG, "Abandoning internal session with missing stage "
- + session.stageDir);
- valid = false;
} else {
valid = true;
}
@@ -378,6 +389,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI);
params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
+ params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
final File appIconFile = buildAppIconFile(sessionId);
if (appIconFile.exists()) {
@@ -448,6 +460,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
+ writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
// Persist app icon if changed since last written
final File appIconFile = buildAppIconFile(session.sessionId);
@@ -516,6 +529,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}
+ // Only system components can circumvent runtime permissions when installing.
+ if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException("You need the "
+ + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
+ }
+
// Defensively resize giant app icons
if (params.appIcon != null) {
final ActivityManager am = (ActivityManager) mContext.getSystemService(
@@ -528,28 +550,40 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
}
- if (params.mode == SessionParams.MODE_FULL_INSTALL
- || params.mode == SessionParams.MODE_INHERIT_EXISTING) {
+ switch (params.mode) {
+ case SessionParams.MODE_FULL_INSTALL:
+ case SessionParams.MODE_INHERIT_EXISTING:
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid install mode: " + params.mode);
+ }
+
+ // If caller requested explicit location, sanity check it, otherwise
+ // resolve the best internal or adopted location.
+ if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
+ if (!PackageHelper.fitsOnInternal(mContext, params.sizeBytes)) {
+ throw new IOException("No suitable internal storage available");
+ }
+
+ } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
+ if (!PackageHelper.fitsOnExternal(mContext, params.sizeBytes)) {
+ throw new IOException("No suitable external storage available");
+ }
+
+ } else {
+ // For now, installs to adopted media are treated as internal from
+ // an install flag point-of-view.
+ params.setInstallFlagsInternal();
+
// Resolve best location for install, based on combination of
// requested install flags, delta size, and manifest settings.
final long ident = Binder.clearCallingIdentity();
try {
- final int resolved = PackageHelper.resolveInstallLocation(mContext,
- params.appPackageName, params.installLocation, params.sizeBytes,
- params.installFlags);
-
- if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) {
- params.setInstallFlagsInternal();
- } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
- params.setInstallFlagsExternal();
- } else {
- throw new IOException("No storage with enough free space; res=" + resolved);
- }
+ params.volumeUuid = PackageHelper.resolveInstallVolume(mContext,
+ params.appPackageName, params.installLocation, params.sizeBytes);
} finally {
Binder.restoreCallingIdentity(ident);
}
- } else {
- throw new IllegalArgumentException("Invalid install mode: " + params.mode);
}
final int sessionId;
@@ -574,7 +608,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
File stageDir = null;
String stageCid = null;
if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
- stageDir = buildInternalStageDir(sessionId);
+ stageDir = buildStageDir(params.volumeUuid, sessionId);
} else {
stageCid = buildExternalStageCid(sessionId);
}
@@ -673,11 +707,30 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
throw new IllegalStateException("Failed to allocate session ID");
}
- private File buildInternalStageDir(int sessionId) {
- return new File(mStagingDir, "vmdl" + sessionId + ".tmp");
+ private File buildInternalStagingDir() {
+ return new File(Environment.getDataDirectory(), "app");
}
- static void prepareInternalStageDir(File stageDir) throws IOException {
+ private File buildStagingDir(String volumeUuid) throws FileNotFoundException {
+ if (volumeUuid == null) {
+ return buildInternalStagingDir();
+ } else {
+ final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
+ if (vol != null && vol.type == VolumeInfo.TYPE_PRIVATE
+ && vol.isMountedWritable()) {
+ return new File(vol.path, "app");
+ } else {
+ throw new FileNotFoundException("Failed to find volume for UUID " + volumeUuid);
+ }
+ }
+ }
+
+ private File buildStageDir(String volumeUuid, int sessionId) throws FileNotFoundException {
+ final File stagingDir = buildStagingDir(volumeUuid);
+ return new File(stagingDir, "vmdl" + sessionId + ".tmp");
+ }
+
+ static void prepareStageDir(File stageDir) throws IOException {
if (stageDir.exists()) {
throw new IOException("Session dir already exists: " + stageDir);
}
@@ -749,16 +802,34 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
@Override
- public void uninstall(String packageName, int flags, IntentSender statusReceiver, int userId) {
- mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, true, "uninstall");
+ public void uninstall(String packageName, String callerPackageName, int flags,
+ IntentSender statusReceiver, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
+ if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
+ mAppOps.checkPackage(callingUid, callerPackageName);
+ }
+
+ // Check whether the caller is device owner
+ DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ boolean isDeviceOwner = (dpm != null) && dpm.isDeviceOwnerApp(callerPackageName);
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
- statusReceiver, packageName);
+ statusReceiver, packageName, isDeviceOwner, userId);
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
mPm.deletePackage(packageName, adapter.getBinder(), userId, flags);
-
+ } else if (isDeviceOwner) {
+ // Allow the DeviceOwner to silently delete packages
+ // Need to clear the calling identity to get DELETE_PACKAGES permission
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mPm.deletePackage(packageName, adapter.getBinder(), userId, flags);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
} else {
// Take a short detour to confirm with user
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
@@ -814,12 +885,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
private final Context mContext;
private final IntentSender mTarget;
private final String mPackageName;
+ private final Notification mNotification;
public PackageDeleteObserverAdapter(Context context, IntentSender target,
- String packageName) {
+ String packageName, boolean showNotification, int userId) {
mContext = context;
mTarget = target;
mPackageName = packageName;
+ if (showNotification) {
+ mNotification = buildSuccessNotification(mContext,
+ mContext.getResources().getString(R.string.package_deleted_device_owner),
+ packageName,
+ userId);
+ } else {
+ mNotification = null;
+ }
}
@Override
@@ -837,6 +917,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
@Override
public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
+ if (PackageManager.DELETE_SUCCEEDED == returnCode && mNotification != null) {
+ NotificationManager notificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(basePackageName, 0, mNotification);
+ }
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
@@ -855,11 +940,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
private final Context mContext;
private final IntentSender mTarget;
private final int mSessionId;
+ private final boolean mShowNotification;
+ private final int mUserId;
- public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId) {
+ public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId,
+ boolean showNotification, int userId) {
mContext = context;
mTarget = target;
mSessionId = sessionId;
+ mShowNotification = showNotification;
+ mUserId = userId;
}
@Override
@@ -878,6 +968,17 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
+ if (PackageManager.INSTALL_SUCCEEDED == returnCode && mShowNotification) {
+ Notification notification = buildSuccessNotification(mContext,
+ mContext.getResources().getString(R.string.package_installed_device_owner),
+ basePackageName,
+ mUserId);
+ if (notification != null) {
+ NotificationManager notificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(basePackageName, 0, notification);
+ }
+ }
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
@@ -899,6 +1000,40 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
}
+ /**
+ * Build a notification for package installation / deletion by device owners that is shown if
+ * the operation succeeds.
+ */
+ private static Notification buildSuccessNotification(Context context, String contentText,
+ String basePackageName, int userId) {
+ PackageInfo packageInfo = null;
+ try {
+ packageInfo = AppGlobals.getPackageManager().getPackageInfo(
+ basePackageName, 0, userId);
+ } catch (RemoteException ignored) {
+ }
+ if (packageInfo == null || packageInfo.applicationInfo == null) {
+ Slog.w(TAG, "Notification not built for package: " + basePackageName);
+ return null;
+ }
+ PackageManager pm = context.getPackageManager();
+ Bitmap packageIcon = ImageUtils.buildScaledBitmap(
+ packageInfo.applicationInfo.loadIcon(pm),
+ context.getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_width),
+ context.getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_height));
+ CharSequence packageLabel = packageInfo.applicationInfo.loadLabel(pm);
+ return new Notification.Builder(context)
+ .setSmallIcon(R.drawable.ic_check_circle_24px)
+ .setColor(context.getResources().getColor(
+ R.color.system_notification_accent_color))
+ .setContentTitle(packageLabel)
+ .setContentText(contentText)
+ .setLargeIcon(packageIcon)
+ .build();
+ }
+
private static class Callbacks extends Handler {
private static final int MSG_SESSION_CREATED = 1;
private static final int MSG_SESSION_BADGING_CHANGED = 2;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index cc1b3ad..89ca00e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -25,8 +25,9 @@ import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
-import static com.android.server.pm.PackageInstallerService.prepareInternalStageDir;
+import static com.android.server.pm.PackageInstallerService.prepareStageDir;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -61,6 +62,9 @@ import android.util.ExceptionUtils;
import android.util.MathUtils;
import android.util.Slog;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.content.PackageHelper;
@@ -69,9 +73,6 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
-import libcore.io.IoUtils;
-import libcore.io.Libcore;
-
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -92,6 +93,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private final Context mContext;
private final PackageManagerService mPm;
private final Handler mHandler;
+ private final boolean mIsInstallerDeviceOwner;
final int sessionId;
final int userId;
@@ -208,8 +210,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mPrepared = prepared;
mSealed = sealed;
+ // Device owners are allowed to silently install packages, so the permission check is
+ // waived if the installer is the device owner.
+ DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerApp(installerPackageName);
if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
- == PackageManager.PERMISSION_GRANTED) || (installerUid == Process.ROOT_UID)) {
+ == PackageManager.PERMISSION_GRANTED)
+ || (installerUid == Process.ROOT_UID)
+ || mIsInstallerDeviceOwner) {
mPermissionsAccepted = true;
} else {
mPermissionsAccepted = false;
@@ -362,7 +371,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final long deltaBytes = lengthBytes - stat.st_size;
// Only need to free up space when writing to internal stage
if (stageDir != null && deltaBytes > 0) {
- mPm.freeStorage(deltaBytes);
+ mPm.freeStorage(params.volumeUuid, deltaBytes);
}
Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
}
@@ -440,7 +449,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mActiveCount.incrementAndGet();
final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
- statusReceiver, sessionId);
+ statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
@@ -892,7 +901,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
synchronized (mLock) {
if (!mPrepared) {
if (stageDir != null) {
- prepareInternalStageDir(stageDir);
+ prepareStageDir(stageDir);
} else if (stageCid != null) {
prepareExternalStageCid(stageCid, params.sizeBytes);
diff --git a/services/core/java/com/android/server/pm/PackageKeySetData.java b/services/core/java/com/android/server/pm/PackageKeySetData.java
index 8f12c03..a9126c0 100644
--- a/services/core/java/com/android/server/pm/PackageKeySetData.java
+++ b/services/core/java/com/android/server/pm/PackageKeySetData.java
@@ -27,12 +27,8 @@ public class PackageKeySetData {
/* KeySet containing all signing keys - superset of the others */
private long mProperSigningKeySet;
- private long[] mSigningKeySets;
-
private long[] mUpgradeKeySets;
- private long[] mDefinedKeySets;
-
private final ArrayMap<String, Long> mKeySetAliases = new ArrayMap<String, Long>();
PackageKeySetData() {
@@ -41,23 +37,12 @@ public class PackageKeySetData {
PackageKeySetData(PackageKeySetData original) {
mProperSigningKeySet = original.mProperSigningKeySet;
- mSigningKeySets = ArrayUtils.cloneOrNull(original.mSigningKeySets);
mUpgradeKeySets = ArrayUtils.cloneOrNull(original.mUpgradeKeySets);
- mDefinedKeySets = ArrayUtils.cloneOrNull(original.mDefinedKeySets);
mKeySetAliases.putAll(original.mKeySetAliases);
}
protected void setProperSigningKeySet(long ks) {
- if (ks == mProperSigningKeySet) {
-
- /* nothing to change */
- return;
- }
-
- /* otherwise, our current signing keysets are likely invalid */
- removeAllSigningKeySets();
mProperSigningKeySet = ks;
- addSigningKeySet(ks);
return;
}
@@ -65,15 +50,10 @@ public class PackageKeySetData {
return mProperSigningKeySet;
}
- protected void addSigningKeySet(long ks) {
- mSigningKeySets = ArrayUtils.appendLong(mSigningKeySets, ks);
- }
-
- protected void removeSigningKeySet(long ks) {
- mSigningKeySets = ArrayUtils.removeLong(mSigningKeySets, ks);
- }
-
protected void addUpgradeKeySet(String alias) {
+ if (alias == null) {
+ return;
+ }
/* must have previously been defined */
Long ks = mKeySetAliases.get(alias);
@@ -89,19 +69,9 @@ public class PackageKeySetData {
* Used only when restoring keyset data from persistent storage. Must
* correspond to a defined-keyset.
*/
- protected void addUpgradeKeySetById(long ks) {
- mSigningKeySets = ArrayUtils.appendLong(mSigningKeySets, ks);
- }
-
- protected void addDefinedKeySet(long ks, String alias) {
- mDefinedKeySets = ArrayUtils.appendLong(mDefinedKeySets, ks);
- mKeySetAliases.put(alias, ks);
- }
- protected void removeAllSigningKeySets() {
- mProperSigningKeySet = KEYSET_UNASSIGNED;
- mSigningKeySets = null;
- return;
+ protected void addUpgradeKeySetById(long ks) {
+ mUpgradeKeySets = ArrayUtils.appendLong(mUpgradeKeySets, ks);
}
protected void removeAllUpgradeKeySets() {
@@ -109,36 +79,44 @@ public class PackageKeySetData {
return;
}
- protected void removeAllDefinedKeySets() {
- mDefinedKeySets = null;
- mKeySetAliases.clear();
- return;
+ protected long[] getUpgradeKeySets() {
+ return mUpgradeKeySets;
}
- protected boolean packageIsSignedBy(long ks) {
- return ArrayUtils.contains(mSigningKeySets, ks);
+ protected ArrayMap<String, Long> getAliases() {
+ return mKeySetAliases;
}
- protected long[] getSigningKeySets() {
- return mSigningKeySets;
- }
+ /*
+ * Replace defined keysets with new ones.
+ */
+ protected void setAliases(ArrayMap<String, Long> newAliases) {
- protected long[] getUpgradeKeySets() {
- return mUpgradeKeySets;
+ /* remove old aliases */
+ removeAllDefinedKeySets();
+
+ /* add new ones */
+ final int newAliasSize = newAliases.size();
+ for (int i = 0; i < newAliasSize; i++) {
+ mKeySetAliases.put(newAliases.keyAt(i), newAliases.valueAt(i));;
+ }
}
- protected long[] getDefinedKeySets() {
- return mDefinedKeySets;
+ protected void addDefinedKeySet(long ks, String alias) {
+ mKeySetAliases.put(alias, ks);
}
- protected ArrayMap<String, Long> getAliases() {
- return mKeySetAliases;
+ protected void removeAllDefinedKeySets() {
+ final int aliasSize = mKeySetAliases.size();
+ for (int i = 0; i < aliasSize; i++) {
+ mKeySetAliases.removeAt(i);
+ }
}
protected boolean isUsingDefinedKeySets() {
- /* should never be the case that mDefinedKeySets.length == 0 */
- return (mDefinedKeySets != null && mDefinedKeySets.length > 0);
+ /* should never be the case that mUpgradeKeySets.length == 0 */
+ return (mKeySetAliases.size() > 0);
}
protected boolean isUsingUpgradeKeySets() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6498dcc..bd22524 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -43,7 +43,16 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIB
import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK;
+import static android.content.pm.PackageManager.INSTALL_INTERNAL;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
+import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR;
+import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING;
+import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
import static android.content.pm.PackageParser.isApkFile;
import static android.os.Process.PACKAGE_INFO_GID;
import static android.os.Process.SYSTEM_UID;
@@ -54,31 +63,13 @@ import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO
import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
import static com.android.internal.util.ArrayUtils.appendInt;
-import static com.android.internal.util.ArrayUtils.removeInt;
-
-import android.util.ArrayMap;
-
-import com.android.internal.R;
-import com.android.internal.app.IMediaContainerService;
-import com.android.internal.app.ResolverActivity;
-import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
-import com.android.internal.os.IParcelFileDescriptorFactory;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.EventLogTags;
-import com.android.server.IntentResolver;
-import com.android.server.LocalServices;
-import com.android.server.ServiceThread;
-import com.android.server.SystemConfig;
-import com.android.server.Watchdog;
-import com.android.server.pm.Settings.DatabaseVersion;
-import com.android.server.storage.DeviceStorageMonitorInternal;
-
-import org.xmlpull.v1.XmlSerializer;
+import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
+import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
+import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
+import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
+import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
@@ -108,6 +99,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageCleanItem;
@@ -116,10 +108,10 @@ import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
+import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser;
import android.content.pm.PackageStats;
import android.content.pm.PackageUserState;
import android.content.pm.ParceledListSlice;
@@ -139,11 +131,9 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Environment;
import android.os.Environment.UserEnvironment;
-import android.os.storage.IMountService;
-import android.os.storage.StorageManager;
-import android.os.Debug;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
@@ -152,6 +142,7 @@ import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SELinux;
import android.os.ServiceManager;
@@ -159,6 +150,10 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.IMountService;
+import android.os.storage.StorageEventListener;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
import android.security.KeyStore;
import android.security.SystemKeyStore;
import android.system.ErrnoException;
@@ -166,6 +161,7 @@ import android.system.Os;
import android.system.StructStat;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DisplayMetrics;
@@ -177,14 +173,48 @@ import android.util.PrintStreamPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.Xml;
import android.view.Display;
+import dalvik.system.DexFile;
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+
+import com.android.internal.R;
+import com.android.internal.app.IMediaContainerService;
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.content.PackageHelper;
+import com.android.internal.os.IParcelFileDescriptorFactory;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.server.EventLogTags;
+import com.android.server.FgThread;
+import com.android.server.IntentResolver;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
+import com.android.server.Watchdog;
+import com.android.server.pm.Settings.DatabaseVersion;
+import com.android.server.storage.DeviceStorageMonitorInternal;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
@@ -210,15 +240,9 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import dalvik.system.DexFile;
-import dalvik.system.StaleDexCacheError;
-import dalvik.system.VMRuntime;
-
-import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
-
/**
* Keep track of all those .apks everywhere.
*
@@ -236,6 +260,7 @@ public class PackageManagerService extends IPackageManager.Stub {
static final boolean DEBUG_SETTINGS = false;
static final boolean DEBUG_PREFERRED = false;
static final boolean DEBUG_UPGRADE = false;
+ private static final boolean DEBUG_BACKUP = true;
private static final boolean DEBUG_INSTALL = false;
private static final boolean DEBUG_REMOVE = false;
private static final boolean DEBUG_BROADCASTS = false;
@@ -247,6 +272,8 @@ public class PackageManagerService extends IPackageManager.Stub {
private static final boolean DEBUG_DEXOPT = false;
private static final boolean DEBUG_ABI_SELECTION = false;
+ static final boolean RUNTIME_PERMISSIONS_ENABLED = true;
+
private static final int RADIO_UID = Process.PHONE_UID;
private static final int LOG_UID = Process.LOG_UID;
private static final int NFC_UID = Process.NFC_UID;
@@ -320,16 +347,29 @@ public class PackageManagerService extends IPackageManager.Stub {
DEFAULT_CONTAINER_PACKAGE,
"com.android.defcontainer.DefaultContainerService");
+ private static final String KILL_APP_REASON_GIDS_CHANGED =
+ "permission grant or revoke changed gids";
+
+ private static final String KILL_APP_REASON_PERMISSIONS_REVOKED =
+ "permissions revoked";
+
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
- private static String sPreferredInstructionSet;
+ /** Permission grant: not grant the permission. */
+ private static final int GRANT_DENIED = 1;
- final ServiceThread mHandlerThread;
+ /** Permission grant: grant the permission as an install permission. */
+ private static final int GRANT_INSTALL = 2;
+
+ /** Permission grant: grant the permission as a runtime one. */
+ private static final int GRANT_RUNTIME = 3;
- private static final String IDMAP_PREFIX = "/data/resource-cache/";
- private static final String IDMAP_SUFFIX = "@idmap";
+ /** Permission grant: grant as runtime a permission that was granted as an install time one. */
+ private static final int GRANT_UPGRADE = 4;
+
+ final ServiceThread mHandlerThread;
final PackageHandler mHandler;
@@ -467,7 +507,10 @@ public class PackageManagerService extends IPackageManager.Stub {
final PackageInstallerService mInstallerService;
- ArraySet<PackageParser.Package> mDeferredDexOpt = null;
+ private final PackageDexOptimizer mPackageDexOptimizer;
+
+ private AtomicInteger mNextMoveId = new AtomicInteger();
+ private final MoveCallbacks mMoveCallbacks;
// Cache of users who need badging.
SparseBooleanArray mUserNeedsBadging = new SparseBooleanArray();
@@ -488,6 +531,218 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean mResolverReplaced = false;
+ private final ComponentName mIntentFilterVerifierComponent;
+ private int mIntentFilterVerificationToken = 0;
+
+ final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
+ = new SparseArray<IntentFilterVerificationState>();
+
+ private interface IntentFilterVerifier<T extends IntentFilter> {
+ boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId,
+ T filter, String packageName);
+ void startVerifications(int userId);
+ void receiveVerificationResponse(int verificationId);
+ }
+
+ private class IntentVerifierProxy implements IntentFilterVerifier<ActivityIntentInfo> {
+ private Context mContext;
+ private ComponentName mIntentFilterVerifierComponent;
+ private ArrayList<Integer> mCurrentIntentFilterVerifications = new ArrayList<Integer>();
+
+ public IntentVerifierProxy(Context context, ComponentName verifierComponent) {
+ mContext = context;
+ mIntentFilterVerifierComponent = verifierComponent;
+ }
+
+ private String getDefaultScheme() {
+ // TODO: replace SCHEME_HTTP with SCHEME_HTTPS
+ return IntentFilter.SCHEME_HTTP;
+ }
+
+ @Override
+ public void startVerifications(int userId) {
+ // Launch verifications requests
+ int count = mCurrentIntentFilterVerifications.size();
+ for (int n=0; n<count; n++) {
+ int verificationId = mCurrentIntentFilterVerifications.get(n);
+ final IntentFilterVerificationState ivs =
+ mIntentFilterVerificationStates.get(verificationId);
+
+ String packageName = ivs.getPackageName();
+
+ ArrayList<PackageParser.ActivityIntentInfo> filters = ivs.getFilters();
+ final int filterCount = filters.size();
+ ArraySet<String> domainsSet = new ArraySet<>();
+ for (int m=0; m<filterCount; m++) {
+ PackageParser.ActivityIntentInfo filter = filters.get(m);
+ domainsSet.addAll(filter.getHostsList());
+ }
+ ArrayList<String> domainsList = new ArrayList<>(domainsSet);
+ synchronized (mPackages) {
+ if (mSettings.createIntentFilterVerificationIfNeededLPw(
+ packageName, domainsList) != null) {
+ scheduleWriteSettingsLocked();
+ }
+ }
+ sendVerificationRequest(userId, verificationId, ivs);
+ }
+ mCurrentIntentFilterVerifications.clear();
+ }
+
+ private void sendVerificationRequest(int userId, int verificationId,
+ IntentFilterVerificationState ivs) {
+
+ Intent verificationIntent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
+ verificationIntent.putExtra(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
+ verificationId);
+ verificationIntent.putExtra(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
+ getDefaultScheme());
+ verificationIntent.putExtra(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
+ ivs.getHostsString());
+ verificationIntent.putExtra(
+ PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
+ ivs.getPackageName());
+ verificationIntent.setComponent(mIntentFilterVerifierComponent);
+ verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ UserHandle user = new UserHandle(userId);
+ mContext.sendBroadcastAsUser(verificationIntent, user);
+ Slog.d(TAG, "Sending IntenFilter verification broadcast");
+ }
+
+ public void receiveVerificationResponse(int verificationId) {
+ IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
+
+ final boolean verified = ivs.isVerified();
+
+ ArrayList<PackageParser.ActivityIntentInfo> filters = ivs.getFilters();
+ final int count = filters.size();
+ for (int n=0; n<count; n++) {
+ PackageParser.ActivityIntentInfo filter = filters.get(n);
+ filter.setVerified(verified);
+
+ Slog.d(TAG, "IntentFilter " + filter.toString() + " verified with result:"
+ + verified + " and hosts:" + ivs.getHostsString());
+ }
+
+ mIntentFilterVerificationStates.remove(verificationId);
+
+ final String packageName = ivs.getPackageName();
+ IntentFilterVerificationInfo ivi = null;
+
+ synchronized (mPackages) {
+ ivi = mSettings.getIntentFilterVerificationLPr(packageName);
+ }
+ if (ivi == null) {
+ Slog.w(TAG, "IntentFilterVerificationInfo not found for verificationId:"
+ + verificationId + " packageName:" + packageName);
+ return;
+ }
+ Slog.d(TAG, "Updating IntentFilterVerificationInfo for verificationId:"
+ + verificationId);
+
+ synchronized (mPackages) {
+ if (verified) {
+ ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS);
+ } else {
+ ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK);
+ }
+ scheduleWriteSettingsLocked();
+
+ final int userId = ivs.getUserId();
+ if (userId != UserHandle.USER_ALL) {
+ final int userStatus =
+ mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
+
+ int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ boolean needUpdate = false;
+
+ // We cannot override the STATUS_ALWAYS / STATUS_NEVER states if they have
+ // already been set by the User thru the Disambiguation dialog
+ switch (userStatus) {
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ if (verified) {
+ updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+ } else {
+ updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+ }
+ needUpdate = true;
+ break;
+
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+ if (verified) {
+ updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+ needUpdate = true;
+ }
+ break;
+
+ default:
+ // Nothing to do
+ }
+
+ if (needUpdate) {
+ mSettings.updateIntentFilterVerificationStatusLPw(
+ packageName, updatedStatus, userId);
+ scheduleWritePackageRestrictionsLocked(userId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId,
+ ActivityIntentInfo filter, String packageName) {
+ if (!(filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
+ filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) {
+ Slog.d(TAG, "IntentFilter does not contain HTTP nor HTTPS data scheme");
+ return false;
+ }
+ IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
+ if (ivs == null) {
+ ivs = createDomainVerificationState(verifierId, userId, verificationId,
+ packageName);
+ }
+ if (!hasValidDomains(filter)) {
+ return false;
+ }
+ ivs.addFilter(filter);
+ return true;
+ }
+
+ private IntentFilterVerificationState createDomainVerificationState(int verifierId,
+ int userId, int verificationId, String packageName) {
+ IntentFilterVerificationState ivs = new IntentFilterVerificationState(
+ verifierId, userId, packageName);
+ ivs.setPendingState();
+ synchronized (mPackages) {
+ mIntentFilterVerificationStates.append(verificationId, ivs);
+ mCurrentIntentFilterVerifications.add(verificationId);
+ }
+ return ivs;
+ }
+ }
+
+ private static boolean hasValidDomains(ActivityIntentInfo filter) {
+ return hasValidDomains(filter, true);
+ }
+
+ private static boolean hasValidDomains(ActivityIntentInfo filter, boolean logging) {
+ boolean hasHTTPorHTTPS = filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
+ filter.hasDataScheme(IntentFilter.SCHEME_HTTPS);
+ if (!hasHTTPorHTTPS) {
+ if (logging) {
+ Slog.d(TAG, "IntentFilter does not contain any HTTP or HTTPS data scheme");
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private IntentFilterVerifier mIntentFilterVerifier;
+
// Set of pending broadcasts for aggregating enable/disable of components.
static class PendingPackageBroadcasts {
// for each user id, a map of <package name -> components within that package>
@@ -574,6 +829,8 @@ public class PackageManagerService extends IPackageManager.Stub {
static final int WRITE_PACKAGE_RESTRICTIONS = 14;
static final int PACKAGE_VERIFIED = 15;
static final int CHECK_PENDING_VERIFICATION = 16;
+ static final int START_INTENT_FILTER_VERIFICATIONS = 17;
+ static final int INTENT_FILTER_VERIFIED = 18;
static final int WRITE_SETTINGS_DELAY = 10*1000; // 10 seconds
@@ -614,6 +871,9 @@ public class PackageManagerService extends IPackageManager.Stub {
final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<PostInstallData>();
int mNextInstallToken = 1; // nonzero; will be wrapped back to 1 when ++ overflows
+ // backup/restore of preferred activity state
+ private static final String TAG_PREFERRED_BACKUP = "pa";
+
private final String mRequiredVerifierPackage;
private final PackageUsage mPackageUsage = new PackageUsage();
@@ -999,6 +1259,15 @@ public class PackageManagerService extends IPackageManager.Stub {
res.removedInfo.sendBroadcast(false, true, false);
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, res.uid);
+
+ // Now that we successfully installed the package, grant runtime
+ // permissions if requested before broadcasting the install.
+ if ((args.installFlags
+ & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0) {
+ grantRequestedRuntimePermissions(res.pkg,
+ args.user.getIdentifier());
+ }
+
// Determine the set of users who are adding this
// package for the first time vs. those who are seeing
// an update.
@@ -1051,7 +1320,7 @@ public class PackageManagerService extends IPackageManager.Stub {
res.pkg.applicationInfo.packageName, null, updateUsers);
// treat asec-hosted packages like removable media on upgrade
- if (isForwardLocked(res.pkg) || isExternal(res.pkg)) {
+ if (res.pkg.isForwardLocked() || isExternal(res.pkg)) {
if (DEBUG_INSTALL) {
Slog.i(TAG, "upgrading pkg " + res.pkg
+ " is ASEC-hosted -> AVAILABLE");
@@ -1215,6 +1484,103 @@ public class PackageManagerService extends IPackageManager.Stub {
break;
}
+ case START_INTENT_FILTER_VERIFICATIONS: {
+ int userId = msg.arg1;
+ int verifierUid = msg.arg2;
+ PackageParser.Package pkg = (PackageParser.Package)msg.obj;
+
+ verifyIntentFiltersIfNeeded(userId, verifierUid, pkg);
+ break;
+ }
+ case INTENT_FILTER_VERIFIED: {
+ final int verificationId = msg.arg1;
+
+ final IntentFilterVerificationState state = mIntentFilterVerificationStates.get(
+ verificationId);
+ if (state == null) {
+ Slog.w(TAG, "Invalid IntentFilter verification token "
+ + verificationId + " received");
+ break;
+ }
+
+ final int userId = state.getUserId();
+
+ Slog.d(TAG, "Processing IntentFilter verification with token:"
+ + verificationId + " and userId:" + userId);
+
+ final IntentFilterVerificationResponse response =
+ (IntentFilterVerificationResponse) msg.obj;
+
+ state.setVerifierResponse(response.callerUid, response.code);
+
+ Slog.d(TAG, "IntentFilter verification with token:" + verificationId
+ + " and userId:" + userId
+ + " is settings verifier response with response code:"
+ + response.code);
+
+ if (response.code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
+ Slog.d(TAG, "Domains failing verification: "
+ + response.getFailedDomainsString());
+ }
+
+ if (state.isVerificationComplete()) {
+ mIntentFilterVerifier.receiveVerificationResponse(verificationId);
+ } else {
+ Slog.d(TAG, "IntentFilter verification with token:" + verificationId
+ + " was not said to be complete");
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ private StorageEventListener mStorageListener = new StorageEventListener() {
+ @Override
+ public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ if (vol.state == VolumeInfo.STATE_MOUNTED) {
+ // TODO: ensure that private directories exist for all active users
+ // TODO: remove user data whose serial number doesn't match
+ loadPrivatePackages(vol);
+ } else if (vol.state == VolumeInfo.STATE_EJECTING) {
+ unloadPrivatePackages(vol);
+ }
+ }
+
+ if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isPrimary()) {
+ if (vol.state == VolumeInfo.STATE_MOUNTED) {
+ updateExternalMediaStatus(true, false);
+ } else if (vol.state == VolumeInfo.STATE_EJECTING) {
+ updateExternalMediaStatus(false, false);
+ }
+ }
+ }
+ };
+
+ private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int userId) {
+ if (userId >= UserHandle.USER_OWNER) {
+ grantRequestedRuntimePermissionsForUser(pkg, userId);
+ } else if (userId == UserHandle.USER_ALL) {
+ for (int someUserId : UserManagerService.getInstance().getUserIds()) {
+ grantRequestedRuntimePermissionsForUser(pkg, someUserId);
+ }
+ }
+ }
+
+ private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId) {
+ SettingBase sb = (SettingBase) pkg.mExtras;
+ if (sb == null) {
+ return;
+ }
+
+ PermissionsState permissionsState = sb.getPermissionsState();
+
+ for (String permission : pkg.requestedPermissions) {
+ BasePermission bp = mSettings.mPermissions.get(permission);
+ if (bp != null && bp.isRuntime()) {
+ permissionsState.grantRuntimePermission(bp, userId);
}
}
}
@@ -1248,7 +1614,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- public static final PackageManagerService main(Context context, Installer installer,
+ public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
@@ -1298,19 +1664,19 @@ public class PackageManagerService extends IPackageManager.Stub {
mOnlyCore = onlyCore;
mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
mMetrics = new DisplayMetrics();
- mSettings = new Settings(context);
+ mSettings = new Settings(mPackages);
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
- ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+ ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
- ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+ ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
- ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+ ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
- ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+ ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
- ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+ ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
- ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+ ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
// TODO: add a property to control this?
long dexOptLRUThresholdInMinutes;
@@ -1339,6 +1705,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
mInstaller = installer;
+ mPackageDexOptimizer = new PackageDexOptimizer(this);
+ mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
getDefaultDisplayMetrics(context, mMetrics);
@@ -1378,7 +1746,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mSettings.mPermissions.put(perm.name, bp);
}
if (perm.gids != null) {
- bp.gids = appendInts(bp.gids, perm.gids);
+ bp.setGids(perm.gids, perm.perUser);
}
}
@@ -1439,9 +1807,10 @@ public class PackageManagerService extends IPackageManager.Stub {
Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");
}
- final List<String> allInstructionSets = getAllInstructionSets();
+ final List<String> allInstructionSets = InstructionSets.getAllInstructionSets();
final String[] dexCodeInstructionSets =
- getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));
+ getDexCodeInstructionSets(
+ allInstructionSets.toArray(new String[allInstructionSets.size()]));
/**
* Ensure all external libraries have had dexopt run on them.
@@ -1459,18 +1828,10 @@ public class PackageManagerService extends IPackageManager.Stub {
}
try {
- byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,
- dexCodeInstructionSet,
- false);
- if (dexoptRequired != DexFile.UP_TO_DATE) {
+ int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
alreadyDexOpted.add(lib);
-
- // The list of "shared libraries" we have at this point is
- if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
- mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- } else {
- mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- }
+ mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Library not found: " + lib);
@@ -1516,13 +1877,9 @@ public class PackageManagerService extends IPackageManager.Stub {
continue;
}
try {
- byte dexoptRequired = DexFile.isDexOptNeededInternal(path, null,
- dexCodeInstructionSet,
- false);
- if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
- mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- } else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) {
- mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
+ int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Jar not found: " + path);
@@ -1621,7 +1978,7 @@ public class PackageManagerService extends IPackageManager.Stub {
psit.remove();
logCriticalInfo(Log.WARN, "System package " + ps.name
+ " no longer exists; wiping its data");
- removeDataDirsLI(ps.name);
+ removeDataDirsLI(null, ps.name);
} else {
final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
@@ -1666,7 +2023,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (deletedPkg == null) {
msg = "Updated system package " + deletedAppName
+ " no longer exists; wiping its data";
- removeDataDirsLI(deletedAppName);
+ removeDataDirsLI(null, deletedAppName);
} else {
msg = "Updated system app + " + deletedAppName
+ " no longer present; removing system privileges for "
@@ -1758,7 +2115,14 @@ public class PackageManagerService extends IPackageManager.Stub {
+ mSettings.mInternalSdkPlatform + " to " + mSdkVersion
+ "; regranting permissions for internal storage");
mSettings.mInternalSdkPlatform = mSdkVersion;
-
+
+ // For now runtime permissions are toggled via a system property.
+ if (!RUNTIME_PERMISSIONS_ENABLED) {
+ // Remove the runtime permissions state if the feature
+ // was disabled by flipping the system property.
+ mSettings.deleteRuntimePermissionsFiles();
+ }
+
updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
| (regrantPermissions
? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL)
@@ -1775,8 +2139,9 @@ public class PackageManagerService extends IPackageManager.Stub {
mIsUpgrade = !Build.FINGERPRINT.equals(mSettings.mFingerprint);
if (mIsUpgrade && !onlyCore) {
Slog.i(TAG, "Build fingerprint changed; clearing code caches");
- for (String pkgName : mSettings.mPackages.keySet()) {
- deleteCodeCacheDirsLI(pkgName);
+ for (int i = 0; i < mSettings.mPackages.size(); i++) {
+ final PackageSetting ps = mSettings.mPackages.valueAt(i);
+ deleteCodeCacheDirsLI(ps.volumeUuid, ps.name);
}
mSettings.mFingerprint = Build.FINGERPRINT;
}
@@ -1790,13 +2155,19 @@ public class PackageManagerService extends IPackageManager.Stub {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
-
mRequiredVerifierPackage = getRequiredVerifierLPr();
+
+ mInstallerService = new PackageInstallerService(context, this);
+
+ mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
+ mIntentFilterVerifier = new IntentVerifierProxy(mContext,
+ mIntentFilterVerifierComponent);
+
+ primeDomainVerificationsLPw(false);
+
} // synchronized (mPackages)
} // synchronized (mInstallLock)
- mInstallerService = new PackageInstallerService(context, this, mAppInstallDir);
-
// Now after opening every single application zip, make sure they
// are all flushed. Not really needed, but keeps things nice and
// tidy.
@@ -1835,14 +2206,8 @@ public class PackageManagerService extends IPackageManager.Stub {
final String packageName = info.activityInfo.packageName;
- final PackageSetting ps = mSettings.mPackages.get(packageName);
- if (ps == null) {
- continue;
- }
-
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
- if (!gp.grantedPermissions
- .contains(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)) {
+ if (checkPermission(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+ packageName, UserHandle.USER_OWNER) != PackageManager.PERMISSION_GRANTED) {
continue;
}
@@ -1856,6 +2221,90 @@ public class PackageManagerService extends IPackageManager.Stub {
return requiredVerifier;
}
+ private ComponentName getIntentFilterVerifierComponentNameLPr() {
+ final Intent verification = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
+ final List<ResolveInfo> receivers = queryIntentReceivers(verification, PACKAGE_MIME_TYPE,
+ PackageManager.GET_DISABLED_COMPONENTS, 0 /* userId */);
+
+ ComponentName verifierComponentName = null;
+
+ int priority = -1000;
+ final int N = receivers.size();
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo info = receivers.get(i);
+
+ if (info.activityInfo == null) {
+ continue;
+ }
+
+ final String packageName = info.activityInfo.packageName;
+
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ continue;
+ }
+
+ if (checkPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+ packageName, UserHandle.USER_OWNER) != PackageManager.PERMISSION_GRANTED) {
+ continue;
+ }
+
+ // Select the IntentFilterVerifier with the highest priority
+ if (priority < info.priority) {
+ priority = info.priority;
+ verifierComponentName = new ComponentName(packageName, info.activityInfo.name);
+ Slog.d(TAG, "Selecting IntentFilterVerifier: " + verifierComponentName +
+ " with priority: " + info.priority);
+ }
+ }
+
+ return verifierComponentName;
+ }
+
+ private void primeDomainVerificationsLPw(boolean logging) {
+ Slog.d(TAG, "Start priming domain verification");
+ boolean updated = false;
+ ArrayList<String> allHosts = new ArrayList<>();
+ for (PackageParser.Package pkg : mPackages.values()) {
+ final String packageName = pkg.packageName;
+ if (!hasDomainURLs(pkg)) {
+ if (logging) {
+ Slog.d(TAG, "No priming domain verifications for " +
+ "package with no domain URLs: " + packageName);
+ }
+ continue;
+ }
+ for (PackageParser.Activity a : pkg.activities) {
+ for (ActivityIntentInfo filter : a.intents) {
+ if (hasValidDomains(filter, false)) {
+ allHosts.addAll(filter.getHostsList());
+ }
+ }
+ }
+ if (allHosts.size() > 0) {
+ allHosts.add("*");
+ }
+ IntentFilterVerificationInfo ivi =
+ mSettings.createIntentFilterVerificationIfNeededLPw(packageName, allHosts);
+ if (ivi != null) {
+ // We will always log this
+ Slog.d(TAG, "Priming domain verifications for package: " + packageName);
+ ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS);
+ updated = true;
+ }
+ else {
+ if (logging) {
+ Slog.d(TAG, "No priming domain verifications for package: " + packageName);
+ }
+ }
+ allHosts.clear();
+ }
+ if (updated) {
+ scheduleWriteSettingsLocked();
+ }
+ Slog.d(TAG, "End priming domain verification");
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -1872,12 +2321,13 @@ public class PackageManagerService extends IPackageManager.Stub {
void cleanupInstallFailedPackage(PackageSetting ps) {
logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + ps.name);
- removeDataDirsLI(ps.name);
+ removeDataDirsLI(ps.volumeUuid, ps.name);
if (ps.codePath != null) {
if (ps.codePath.isDirectory()) {
- FileUtils.deleteContents(ps.codePath);
+ mInstaller.rmPackageDir(ps.codePath.getAbsolutePath());
+ } else {
+ ps.codePath.delete();
}
- ps.codePath.delete();
}
if (ps.resourcePath != null && !ps.resourcePath.equals(ps.codePath)) {
if (ps.resourcePath.isDirectory()) {
@@ -1898,27 +2348,21 @@ public class PackageManagerService extends IPackageManager.Stub {
return cur;
}
- static int[] removeInts(int[] cur, int[] rem) {
- if (rem == null) return cur;
- if (cur == null) return cur;
- final int N = rem.length;
- for (int i=0; i<N; i++) {
- cur = removeInt(cur, rem[i]);
- }
- return cur;
- }
-
PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
final PackageSetting ps = (PackageSetting) p.mExtras;
if (ps == null) {
return null;
}
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+
+ final PermissionsState permissionsState = ps.getPermissionsState();
+
+ final int[] gids = permissionsState.computeGids(userId);
+ final Set<String> permissions = permissionsState.getPermissions(userId);
final PackageUserState state = ps.readUserState(userId);
- return PackageParser.generatePackageInfo(p, gp.gids, flags,
- ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions,
- state, userId);
+
+ return PackageParser.generatePackageInfo(p, gids, flags,
+ ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId);
}
@Override
@@ -1989,6 +2433,7 @@ public class PackageManagerService extends IPackageManager.Stub {
public int getPackageUid(String packageName, int userId) {
if (!sUserManager.exists(userId)) return -1;
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get package uid");
+
// reader
synchronized (mPackages) {
PackageParser.Package p = mPackages.get(packageName);
@@ -2005,22 +2450,30 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
- public int[] getPackageGids(String packageName) {
+ public int[] getPackageGids(String packageName, int userId) throws RemoteException {
+ if (!sUserManager.exists(userId)) {
+ return null;
+ }
+
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false,
+ "getPackageGids");
+
// reader
synchronized (mPackages) {
PackageParser.Package p = mPackages.get(packageName);
- if (DEBUG_PACKAGE_INFO)
+ if (DEBUG_PACKAGE_INFO) {
Log.v(TAG, "getPackageGids" + packageName + ": " + p);
+ }
if (p != null) {
- final PackageSetting ps = (PackageSetting)p.mExtras;
- return ps.getGids();
+ PackageSetting ps = (PackageSetting) p.mExtras;
+ return ps.getPermissionsState().computeGids(userId);
}
}
- // stupid thing to indicate an error.
- return new int[0];
+
+ return null;
}
- static final PermissionInfo generatePermissionInfo(
+ static PermissionInfo generatePermissionInfo(
BasePermission bp, int flags) {
if (bp.perm != null) {
return PackageParser.generatePermissionInfo(bp.perm, flags);
@@ -2125,8 +2578,9 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg = new PackageParser.Package(packageName);
pkg.applicationInfo.packageName = packageName;
pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
- pkg.applicationInfo.dataDir =
- getDataPathForPackage(packageName, 0).getPath();
+ pkg.applicationInfo.privateFlags = ps.pkgPrivateFlags;
+ pkg.applicationInfo.dataDir = PackageManager.getDataDirForUser(ps.volumeUuid,
+ packageName, userId).getAbsolutePath();
pkg.applicationInfo.primaryCpuAbi = ps.primaryCpuAbiString;
pkg.applicationInfo.secondaryCpuAbi = ps.secondaryCpuAbiString;
}
@@ -2162,9 +2616,9 @@ public class PackageManagerService extends IPackageManager.Stub {
return null;
}
-
@Override
- public void freeStorageAndNotify(final long freeStorageSize, final IPackageDataObserver observer) {
+ public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize,
+ final IPackageDataObserver observer) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CLEAR_APP_CACHE, null);
// Queue up an async operation since clearing cache may take a little while.
@@ -2173,7 +2627,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mHandler.removeCallbacks(this);
int retCode = -1;
synchronized (mInstallLock) {
- retCode = mInstaller.freeCache(freeStorageSize);
+ retCode = mInstaller.freeCache(volumeUuid, freeStorageSize);
if (retCode < 0) {
Slog.w(TAG, "Couldn't clear application caches");
}
@@ -2190,7 +2644,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
- public void freeStorage(final long freeStorageSize, final IntentSender pi) {
+ public void freeStorage(final String volumeUuid, final long freeStorageSize,
+ final IntentSender pi) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CLEAR_APP_CACHE, null);
// Queue up an async operation since clearing cache may take a little while.
@@ -2199,7 +2654,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mHandler.removeCallbacks(this);
int retCode = -1;
synchronized (mInstallLock) {
- retCode = mInstaller.freeCache(freeStorageSize);
+ retCode = mInstaller.freeCache(volumeUuid, freeStorageSize);
if (retCode < 0) {
Slog.w(TAG, "Couldn't clear application caches");
}
@@ -2218,9 +2673,9 @@ public class PackageManagerService extends IPackageManager.Stub {
});
}
- void freeStorage(long freeStorageSize) throws IOException {
+ void freeStorage(String volumeUuid, long freeStorageSize) throws IOException {
synchronized (mInstallLock) {
- if (mInstaller.freeCache(freeStorageSize) < 0) {
+ if (mInstaller.freeCache(volumeUuid, freeStorageSize) < 0) {
throw new IOException("Failed to free enough space");
}
}
@@ -2335,6 +2790,19 @@ public class PackageManagerService extends IPackageManager.Stub {
return null;
}
+ /**
+ * @hide
+ */
+ PackageParser.Package findSharedNonSystemLibrary(String libName) {
+ synchronized (mPackages) {
+ PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
+ if (lib != null && lib.apk != null) {
+ return mPackages.get(lib.apk);
+ }
+ }
+ return null;
+ }
+
@Override
public FeatureInfo[] getSystemAvailableFeatures() {
Collection<FeatureInfo> featSet;
@@ -2370,30 +2838,37 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
- public int checkPermission(String permName, String pkgName) {
+ public int checkPermission(String permName, String pkgName, int userId) {
+ if (!sUserManager.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
synchronized (mPackages) {
- PackageParser.Package p = mPackages.get(pkgName);
+ final PackageParser.Package p = mPackages.get(pkgName);
if (p != null && p.mExtras != null) {
- PackageSetting ps = (PackageSetting)p.mExtras;
- if (ps.sharedUser != null) {
- if (ps.sharedUser.grantedPermissions.contains(permName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- } else if (ps.grantedPermissions.contains(permName)) {
+ final PackageSetting ps = (PackageSetting) p.mExtras;
+ if (ps.getPermissionsState().hasPermission(permName, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
+
return PackageManager.PERMISSION_DENIED;
}
@Override
public int checkUidPermission(String permName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+
+ if (!sUserManager.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
- GrantedPermissions gp = (GrantedPermissions)obj;
- if (gp.grantedPermissions.contains(permName)) {
+ final SettingBase ps = (SettingBase) obj;
+ if (ps.getPermissionsState().hasPermission(permName, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
@@ -2403,6 +2878,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
}
+
return PackageManager.PERMISSION_DENIED;
}
@@ -2609,121 +3085,132 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- private static void checkGrantRevokePermissions(PackageParser.Package pkg, BasePermission bp) {
+ private static void enforceDeclaredAsUsedAndRuntimePermission(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) {
+ if (!bp.isRuntime()) {
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");
- }
- }
}
@Override
- public void grantPermission(String packageName, String permissionName) {
+ public boolean grantPermission(String packageName, String name, int userId) {
+ if (!RUNTIME_PERMISSIONS_ENABLED) {
+ return false;
+ }
+
+ if (!sUserManager.exists(userId)) {
+ return false;
+ }
+
mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null);
+ android.Manifest.permission.GRANT_REVOKE_PERMISSIONS,
+ "grantPermission");
+
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+ "grantPermission");
+
+ boolean gidsChanged = false;
+ final SettingBase sb;
+
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- final BasePermission bp = mSettings.mPermissions.get(permissionName);
+
+ final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + permissionName);
+ throw new IllegalArgumentException("Unknown permission: " + name);
}
- checkGrantRevokePermissions(pkg, bp);
+ enforceDeclaredAsUsedAndRuntimePermission(pkg, bp);
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null) {
- return;
+ sb = (SettingBase) pkg.mExtras;
+ if (sb == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
- final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps;
- if (gp.grantedPermissions.add(permissionName)) {
- if (ps.haveGids) {
- gp.gids = appendInts(gp.gids, bp.gids);
+
+ final PermissionsState permissionsState = sb.getPermissionsState();
+
+ final int result = permissionsState.grantRuntimePermission(bp, userId);
+ switch (result) {
+ case PermissionsState.PERMISSION_OPERATION_FAILURE: {
+ return false;
}
- mSettings.writeLPr();
+
+ case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
+ gidsChanged = true;
+ } break;
}
+
+ // Not critical if that is lost - app has to request again.
+ mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+ }
+
+ if (gidsChanged) {
+ killSettingPackagesForUser(sb, userId, KILL_APP_REASON_GIDS_CHANGED);
}
+
+ return true;
}
@Override
- public void revokePermission(String packageName, String permissionName) {
- int changedAppId = -1;
+ public boolean revokePermission(String packageName, String name, int userId) {
+ if (!RUNTIME_PERMISSIONS_ENABLED) {
+ return false;
+ }
+
+ if (!sUserManager.exists(userId)) {
+ return false;
+ }
+
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.GRANT_REVOKE_PERMISSIONS,
+ "revokePermission");
+
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+ "revokePermission");
+
+ final SettingBase sb;
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- if (pkg.applicationInfo.uid != Binder.getCallingUid()) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null);
- }
- final BasePermission bp = mSettings.mPermissions.get(permissionName);
+
+ final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + permissionName);
+ throw new IllegalArgumentException("Unknown permission: " + name);
}
- checkGrantRevokePermissions(pkg, bp);
+ enforceDeclaredAsUsedAndRuntimePermission(pkg, bp);
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null) {
- return;
- }
- 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;
+ sb = (SettingBase) pkg.mExtras;
+ if (sb == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
- }
- 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);
- }
+ final PermissionsState permissionsState = sb.getPermissionsState();
+
+ if (permissionsState.revokeRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ return false;
}
+
+ // Critical, after this call all should never have the permission.
+ mSettings.writeRuntimePermissionsForUserLPr(userId, true);
}
+
+ killSettingPackagesForUser(sb, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+
+ return true;
}
@Override
@@ -2783,6 +3270,46 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private void killSettingPackagesForUser(SettingBase sb, int userId, String reason) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (sb instanceof SharedUserSetting) {
+ SharedUserSetting sus = (SharedUserSetting) sb;
+ final int packageCount = sus.packages.size();
+ for (int i = 0; i < packageCount; i++) {
+ PackageSetting susPs = sus.packages.valueAt(i);
+ if (userId == UserHandle.USER_ALL) {
+ killApplication(susPs.pkg.packageName, susPs.appId, reason);
+ } else {
+ final int uid = UserHandle.getUid(userId, susPs.appId);
+ killUid(uid, reason);
+ }
+ }
+ } else if (sb instanceof PackageSetting) {
+ PackageSetting ps = (PackageSetting) sb;
+ if (userId == UserHandle.USER_ALL) {
+ killApplication(ps.pkg.packageName, ps.appId, reason);
+ } else {
+ final int uid = UserHandle.getUid(userId, ps.appId);
+ killUid(uid, reason);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private static void killUid(int uid, String reason) {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ if (am != null) {
+ try {
+ am.killUid(uid, reason);
+ } catch (RemoteException e) {
+ /* ignore - same process */
+ }
+ }
+ }
+
/**
* Compares two sets of signatures. Returns:
* <br />
@@ -2967,7 +3494,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
// reader
synchronized (mPackages) {
- final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, false);
+ final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, 0, false);
if (suid == null) {
return -1;
}
@@ -2991,6 +3518,21 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
+ public int getPrivateFlagsForUid(int uid) {
+ synchronized (mPackages) {
+ Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
+ if (obj instanceof SharedUserSetting) {
+ final SharedUserSetting sus = (SharedUserSetting) obj;
+ return sus.pkgPrivateFlags;
+ } else if (obj instanceof PackageSetting) {
+ final PackageSetting ps = (PackageSetting) obj;
+ return ps.pkgPrivateFlags;
+ }
+ }
+ return 0;
+ }
+
+ @Override
public boolean isUidPrivileged(int uid) {
uid = UserHandle.getAppId(uid);
// reader
@@ -3382,7 +3924,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (resolveInfo != null) {
List<ResolveInfo> result = new ArrayList<ResolveInfo>(1);
result.add(resolveInfo);
- return result;
+ return filterIfNotPrimaryUser(result, userId);
}
// Check for cross profile results.
resolveInfo = queryCrossProfileIntents(
@@ -3395,17 +3937,121 @@ public class PackageManagerService extends IPackageManager.Stub {
result.add(resolveInfo);
Collections.sort(result, mResolvePrioritySorter);
}
+ result = filterIfNotPrimaryUser(result, userId);
+ if (result.size() > 1 && hasWebURI(intent)) {
+ return filterCandidatesWithDomainPreferedActivitiesLPr(result);
+ }
return result;
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
- return mActivities.queryIntentForPackage(intent, resolvedType, flags,
- pkg.activities, userId);
+ return filterIfNotPrimaryUser(
+ mActivities.queryIntentForPackage(
+ intent, resolvedType, flags, pkg.activities, userId),
+ userId);
}
return new ArrayList<ResolveInfo>();
}
}
+ /**
+ * Filter out activities with primaryUserOnly flag set, when current user is not the owner.
+ *
+ * @return filtered list
+ */
+ private List<ResolveInfo> filterIfNotPrimaryUser(List<ResolveInfo> resolveInfos, int userId) {
+ if (userId == UserHandle.USER_OWNER) {
+ return resolveInfos;
+ }
+ for (int i = resolveInfos.size() - 1; i >= 0; i--) {
+ ResolveInfo info = resolveInfos.get(i);
+ if ((info.activityInfo.flags & ActivityInfo.FLAG_PRIMARY_USER_ONLY) != 0) {
+ resolveInfos.remove(i);
+ }
+ }
+ return resolveInfos;
+ }
+
+ private static boolean hasWebURI(Intent intent) {
+ if (intent.getData() == null) {
+ return false;
+ }
+ final String scheme = intent.getScheme();
+ if (TextUtils.isEmpty(scheme)) {
+ return false;
+ }
+ return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS);
+ }
+
+ private List<ResolveInfo> filterCandidatesWithDomainPreferedActivitiesLPr(
+ List<ResolveInfo> candidates) {
+ if (DEBUG_PREFERRED) {
+ Slog.v("TAG", "Filtering results with prefered activities. Candidates count: " +
+ candidates.size());
+ }
+
+ final int userId = UserHandle.getCallingUserId();
+ ArrayList<ResolveInfo> result = new ArrayList<ResolveInfo>();
+ ArrayList<ResolveInfo> undefinedList = new ArrayList<ResolveInfo>();
+ ArrayList<ResolveInfo> neverList = new ArrayList<ResolveInfo>();
+ ArrayList<ResolveInfo> matchAllList = new ArrayList<ResolveInfo>();
+
+ synchronized (mPackages) {
+ final int count = candidates.size();
+ // First, try to use the domain prefered App
+ for (int n=0; n<count; n++) {
+ ResolveInfo info = candidates.get(n);
+ String packageName = info.activityInfo.packageName;
+ PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ // Try to get the status from User settings first
+ int status = getDomainVerificationStatusLPr(ps, userId);
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+ result.add(info);
+ } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+ neverList.add(info);
+ } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ undefinedList.add(info);
+ }
+ // Add to the special match all list (Browser use case)
+ if (info.handleAllWebDataURI) {
+ matchAllList.add(info);
+ }
+ }
+ }
+ // If there is nothing selected, add all candidates and remove the ones that the User
+ // has explicitely put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state and
+ // also remove any Browser Apps ones.
+ // If there is still none after this pass, add all undefined one and Browser Apps and
+ // let the User decide with the Disambiguation dialog if there are several ones.
+ if (result.size() == 0) {
+ result.addAll(candidates);
+ }
+ result.removeAll(neverList);
+ result.removeAll(matchAllList);
+ if (result.size() == 0) {
+ result.addAll(undefinedList);
+ result.addAll(matchAllList);
+ }
+ }
+ if (DEBUG_PREFERRED) {
+ Slog.v("TAG", "Filtered results with prefered activities. New candidates count: " +
+ result.size());
+ }
+ return result;
+ }
+
+ private int getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
+ int status = ps.getDomainVerificationStatusForUser(userId);
+ // if none available, get the master status
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ if (ps.getIntentFilterVerificationInfo() != null) {
+ status = ps.getIntentFilterVerificationInfo().getStatus();
+ }
+ }
+ return status;
+ }
+
private ResolveInfo querySkipCurrentProfileIntents(
List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
int flags, int sourceUserId) {
@@ -3828,9 +4474,10 @@ public class PackageManagerService extends IPackageManager.Stub {
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;
+ final PermissionsState permissionsState = ps.getPermissionsState();
for (int i=0; i<permissions.length; i++) {
- if (gp.grantedPermissions.contains(permissions[i])) {
+ final String permission = permissions[i];
+ if (permissionsState.hasPermission(permission, userId)) {
tmp[i] = true;
numMatch++;
} else {
@@ -4164,9 +4811,10 @@ public class PackageManagerService extends IPackageManager.Stub {
e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
if (file.isDirectory()) {
- FileUtils.deleteContents(file);
+ mInstaller.rmPackageDir(file.getAbsolutePath());
+ } else {
+ file.delete();
}
- file.delete();
}
}
}
@@ -4289,9 +4937,9 @@ public class PackageManagerService extends IPackageManager.Stub {
// If new package is not located in "/system/priv-app" (e.g. due to an OTA),
// it needs to drop FLAG_PRIVILEGED.
if (locationIsPrivileged(scanFile)) {
- updatedPkg.pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED;
+ updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
} else {
- updatedPkg.pkgFlags &= ~ApplicationInfo.FLAG_PRIVILEGED;
+ updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
if (ps != null && !ps.codePath.equals(scanFile)) {
@@ -4311,6 +4959,8 @@ public class PackageManagerService extends IPackageManager.Stub {
+ " to " + scanFile);
updatedPkg.codePath = scanFile;
updatedPkg.codePathString = scanFile.toString();
+ updatedPkg.resourcePath = scanFile;
+ updatedPkg.resourcePathString = scanFile.toString();
}
updatedPkg.pkg = pkg;
throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, null);
@@ -4353,7 +5003,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// An updated privileged app will not have the PARSE_IS_PRIVILEGED
// flag set initially
- if ((updatedPkg.pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+ if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
}
@@ -4436,6 +5086,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
// Set application objects path explicitly.
+ pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
pkg.applicationInfo.setCodePath(pkg.codePath);
pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
@@ -4578,8 +5229,7 @@ public class PackageManagerService extends IPackageManager.Stub {
final ArraySet<PackageParser.Package> pkgs;
synchronized (mPackages) {
- pkgs = mDeferredDexOpt;
- mDeferredDexOpt = null;
+ pkgs = mPackageDexOptimizer.clearDeferredDexOptPackages();
}
if (pkgs != null) {
@@ -4613,7 +5263,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Give priority to system apps.
for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
PackageParser.Package pkg = it.next();
- if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {
+ if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Adding system app " + sortedPkgs.size() + ": " + pkg.packageName);
}
@@ -4624,7 +5274,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Give priority to updated system apps.
for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) {
PackageParser.Package pkg = it.next();
- if (isUpdatedSystemApp(pkg)) {
+ if (pkg.isUpdatedSystemApp()) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Adding updated system app " + sortedPkgs.size() + ": " + pkg.packageName);
}
@@ -4735,8 +5385,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
PackageParser.Package p = pkg;
synchronized (mInstallLock) {
- performDexOptLI(p, null /* instruction sets */, false /* force dex */,
- false /* defer */, true /* include dependencies */);
+ mPackageDexOptimizer.performDexOpt(p, null /* instruction sets */,
+ false /* force dex */, false /* defer */, true /* include dependencies */);
}
}
@@ -4745,14 +5395,6 @@ public class PackageManagerService extends IPackageManager.Stub {
return performDexOpt(packageName, instructionSet, false);
}
- private static String getPrimaryInstructionSet(ApplicationInfo info) {
- if (info.primaryCpuAbi == null) {
- return getPreferredInstructionSet();
- }
-
- return VMRuntime.getInstructionSet(info.primaryCpuAbi);
- }
-
public boolean performDexOpt(String packageName, String instructionSet, boolean backgroundDexopt) {
boolean dexopt = mLazyDexOpt || backgroundDexopt;
boolean updateUsage = !backgroundDexopt; // Don't update usage if this is just a backgroundDexopt
@@ -4785,8 +5427,9 @@ public class PackageManagerService extends IPackageManager.Stub {
synchronized (mInstallLock) {
final String[] instructionSets = new String[] { targetInstructionSet };
- return performDexOptLI(p, instructionSets, false /* force dex */, false /* defer */,
- true /* include dependencies */) == DEX_OPT_PERFORMED;
+ int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
+ false /* forceDex */, false /* defer */, true /* inclDependencies */);
+ return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
}
}
@@ -4813,226 +5456,6 @@ public class PackageManagerService extends IPackageManager.Stub {
mPackageUsage.write(true);
}
- private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets,
- boolean forceDex, boolean defer, ArraySet<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, instructionSets, forceDex, defer, done);
- }
- }
- }
-
- static final int DEX_OPT_SKIPPED = 0;
- static final int DEX_OPT_PERFORMED = 1;
- static final int DEX_OPT_DEFERRED = 2;
- static final int DEX_OPT_FAILED = -1;
-
- private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
- boolean forceDex, boolean defer, ArraySet<String> done) {
- final String[] instructionSets = targetInstructionSets != null ?
- targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
-
- if (done != null) {
- done.add(pkg.packageName);
- if (pkg.usesLibraries != null) {
- performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer, done);
- }
- if (pkg.usesOptionalLibraries != null) {
- performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, forceDex, defer, done);
- }
- }
-
- if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
- return DEX_OPT_SKIPPED;
- }
-
- final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
-
- final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
- boolean performedDexOpt = false;
- // There are three basic cases here:
- // 1.) we need to dexopt, either because we are forced or it is needed
- // 2.) we are defering a needed dexopt
- // 3.) we are skipping an unneeded dexopt
- final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
- for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
- continue;
- }
-
- for (String path : paths) {
- try {
- // This will return DEXOPT_NEEDED if we either cannot find any odex file for this
- // patckage or the one we find does not match the image checksum (i.e. it was
- // compiled against an old image). It will return PATCHOAT_NEEDED if we can find a
- // odex file and it matches the checksum of the image but not its base address,
- // meaning we need to move it.
- final byte isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
- pkg.packageName, dexCodeInstructionSet, defer);
- if (forceDex || (!defer && isDexOptNeeded == DexFile.DEXOPT_NEEDED)) {
- Log.i(TAG, "Running dexopt on: " + path + " pkg="
- + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
- + " vmSafeMode=" + vmSafeMode);
- final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
- pkg.packageName, dexCodeInstructionSet, vmSafeMode);
-
- if (ret < 0) {
- // Don't bother running dexopt again if we failed, it will probably
- // just result in an error again. Also, don't bother dexopting for other
- // paths & ISAs.
- return DEX_OPT_FAILED;
- }
-
- performedDexOpt = true;
- } else if (!defer && isDexOptNeeded == DexFile.PATCHOAT_NEEDED) {
- Log.i(TAG, "Running patchoat on: " + pkg.applicationInfo.packageName);
- final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final int ret = mInstaller.patchoat(path, sharedGid, !isForwardLocked(pkg),
- pkg.packageName, dexCodeInstructionSet);
-
- if (ret < 0) {
- // Don't bother running patchoat again if we failed, it will probably
- // just result in an error again. Also, don't bother dexopting for other
- // paths & ISAs.
- return DEX_OPT_FAILED;
- }
-
- performedDexOpt = true;
- }
-
- // We're deciding to defer a needed dexopt. Don't bother dexopting for other
- // paths and instruction sets. We'll deal with them all together when we process
- // our list of deferred dexopts.
- if (defer && isDexOptNeeded != DexFile.UP_TO_DATE) {
- if (mDeferredDexOpt == null) {
- mDeferredDexOpt = new ArraySet<PackageParser.Package>();
- }
- mDeferredDexOpt.add(pkg);
- return DEX_OPT_DEFERRED;
- }
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "Apk not found for dexopt: " + path);
- return DEX_OPT_FAILED;
- } catch (IOException e) {
- Slog.w(TAG, "IOException reading apk: " + path, e);
- return DEX_OPT_FAILED;
- } catch (StaleDexCacheError e) {
- Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
- return DEX_OPT_FAILED;
- } catch (Exception e) {
- Slog.w(TAG, "Exception when doing dexopt : ", e);
- return DEX_OPT_FAILED;
- }
- }
-
- // At this point we haven't failed dexopt and we haven't deferred dexopt. We must
- // either have either succeeded dexopt, or have had isDexOptNeededInternal tell us
- // it isn't required. We therefore mark that this package doesn't need dexopt unless
- // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
- // it.
- pkg.mDexOptPerformed.add(dexCodeInstructionSet);
- }
-
- // If we've gotten here, we're sure that no error occurred and that we haven't
- // deferred dex-opt. We've either dex-opted one more paths or instruction sets or
- // we've skipped all of them because they are up to date. In both cases this
- // package doesn't need dexopt any longer.
- return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
- }
-
- private static String[] getAppDexInstructionSets(ApplicationInfo info) {
- if (info.primaryCpuAbi != null) {
- if (info.secondaryCpuAbi != null) {
- return new String[] {
- VMRuntime.getInstructionSet(info.primaryCpuAbi),
- VMRuntime.getInstructionSet(info.secondaryCpuAbi) };
- } else {
- return new String[] {
- VMRuntime.getInstructionSet(info.primaryCpuAbi) };
- }
- }
-
- return new String[] { getPreferredInstructionSet() };
- }
-
- private static String[] getAppDexInstructionSets(PackageSetting ps) {
- if (ps.primaryCpuAbiString != null) {
- if (ps.secondaryCpuAbiString != null) {
- return new String[] {
- VMRuntime.getInstructionSet(ps.primaryCpuAbiString),
- VMRuntime.getInstructionSet(ps.secondaryCpuAbiString) };
- } else {
- return new String[] {
- VMRuntime.getInstructionSet(ps.primaryCpuAbiString) };
- }
- }
-
- return new String[] { getPreferredInstructionSet() };
- }
-
- private static String getPreferredInstructionSet() {
- if (sPreferredInstructionSet == null) {
- sPreferredInstructionSet = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
- }
-
- return sPreferredInstructionSet;
- }
-
- private static List<String> getAllInstructionSets() {
- final String[] allAbis = Build.SUPPORTED_ABIS;
- final List<String> allInstructionSets = new ArrayList<String>(allAbis.length);
-
- for (String abi : allAbis) {
- final String instructionSet = VMRuntime.getInstructionSet(abi);
- if (!allInstructionSets.contains(instructionSet)) {
- allInstructionSets.add(instructionSet);
- }
- }
-
- return allInstructionSets;
- }
-
- /**
- * Returns the instruction set that should be used to compile dex code. In the presence of
- * a native bridge this might be different than the one shared libraries use.
- */
- private static String getDexCodeInstructionSet(String sharedLibraryIsa) {
- String dexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + sharedLibraryIsa);
- return (dexCodeIsa.isEmpty() ? sharedLibraryIsa : dexCodeIsa);
- }
-
- private static String[] getDexCodeInstructionSets(String[] instructionSets) {
- ArraySet<String> dexCodeInstructionSets = new ArraySet<String>(instructionSets.length);
- for (String instructionSet : instructionSets) {
- dexCodeInstructionSets.add(getDexCodeInstructionSet(instructionSet));
- }
- return dexCodeInstructionSets.toArray(new String[dexCodeInstructionSets.size()]);
- }
-
- /**
- * Returns deduplicated list of supported instructions for dex code.
- */
- public static String[] getAllDexCodeInstructionSets() {
- String[] supportedInstructionSets = new String[Build.SUPPORTED_ABIS.length];
- for (int i = 0; i < supportedInstructionSets.length; i++) {
- String abi = Build.SUPPORTED_ABIS[i];
- supportedInstructionSets[i] = VMRuntime.getInstructionSet(abi);
- }
- return getDexCodeInstructionSets(supportedInstructionSets);
- }
-
@Override
public void forceDexOpt(String packageName) {
enforceSystemOrRoot("forceDexOpt");
@@ -5048,25 +5471,14 @@ public class PackageManagerService extends IPackageManager.Stub {
synchronized (mInstallLock) {
final String[] instructionSets = new String[] {
getPrimaryInstructionSet(pkg.applicationInfo) };
- final int res = performDexOptLI(pkg, instructionSets, true, false, true);
- if (res != DEX_OPT_PERFORMED) {
+ final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets,
+ true /*forceDex*/, false /* defer */, true /* inclDependencies */);
+ if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
throw new IllegalStateException("Failed to dexopt: " + res);
}
}
}
- private int performDexOptLI(PackageParser.Package pkg, String[] instructionSets,
- boolean forceDex, boolean defer, boolean inclDependencies) {
- ArraySet<String> done;
- if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
- done = new ArraySet<String>();
- done.add(pkg.packageName);
- } else {
- done = null;
- }
- return performDexOptLI(pkg, instructionSets, 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
@@ -5082,33 +5494,15 @@ public class PackageManagerService extends IPackageManager.Stub {
return true;
}
- File getDataPathForUser(int userId) {
- return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId);
- }
-
- private File getDataPathForPackage(String packageName, int userId) {
- /*
- * Until we fully support multiple users, return the directory we
- * previously would have. The PackageManagerTests will need to be
- * revised when this is changed back..
- */
- if (userId == 0) {
- return new File(mAppDataDir, packageName);
- } else {
- return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId
- + File.separator + packageName);
- }
- }
-
- private int createDataDirsLI(String packageName, int uid, String seinfo) {
+ private int createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo) {
int[] users = sUserManager.getUserIds();
- int res = mInstaller.install(packageName, uid, uid, seinfo);
+ int res = mInstaller.install(volumeUuid, packageName, uid, uid, seinfo);
if (res < 0) {
return res;
}
for (int user : users) {
if (user != 0) {
- res = mInstaller.createUserData(packageName,
+ res = mInstaller.createUserData(volumeUuid, packageName,
UserHandle.getUid(user, uid), user, seinfo);
if (res < 0) {
return res;
@@ -5118,11 +5512,11 @@ public class PackageManagerService extends IPackageManager.Stub {
return res;
}
- private int removeDataDirsLI(String packageName) {
+ private int removeDataDirsLI(String volumeUuid, String packageName) {
int[] users = sUserManager.getUserIds();
int res = 0;
for (int user : users) {
- int resInner = mInstaller.remove(packageName, user);
+ int resInner = mInstaller.remove(volumeUuid, packageName, user);
if (resInner < 0) {
res = resInner;
}
@@ -5131,11 +5525,11 @@ public class PackageManagerService extends IPackageManager.Stub {
return res;
}
- private int deleteCodeCacheDirsLI(String packageName) {
+ private int deleteCodeCacheDirsLI(String volumeUuid, String packageName) {
int[] users = sUserManager.getUserIds();
int res = 0;
for (int user : users) {
- int resInner = mInstaller.deleteCodeCacheFiles(packageName, user);
+ int resInner = mInstaller.deleteCodeCacheFiles(volumeUuid, packageName, user);
if (resInner < 0) {
res = resInner;
}
@@ -5270,7 +5664,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return res;
} finally {
if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
- removeDataDirsLI(pkg.packageName);
+ removeDataDirsLI(pkg.volumeUuid, pkg.packageName);
}
}
}
@@ -5293,7 +5687,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if ((parseFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
- pkg.applicationInfo.flags |= ApplicationInfo.FLAG_PRIVILEGED;
+ pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
if (mCustomResolverComponentName != null &&
@@ -5389,7 +5783,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// writer
synchronized (mPackages) {
if (pkg.mSharedUserId != null) {
- suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, true);
+ suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, true);
if (suid == null) {
throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
"Creating application package " + pkg.packageName
@@ -5463,7 +5857,8 @@ public class PackageManagerService extends IPackageManager.Stub {
destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,
pkg.applicationInfo.primaryCpuAbi,
pkg.applicationInfo.secondaryCpuAbi,
- pkg.applicationInfo.flags, user, false);
+ pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags,
+ user, false);
if (pkgSetting == null) {
throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
"Creating application package " + pkg.packageName + " failed");
@@ -5618,7 +6013,8 @@ public class PackageManagerService extends IPackageManager.Stub {
} else {
// This is a normal package, need to make its data directory.
- dataPath = getDataPathForPackage(pkg.packageName, 0);
+ dataPath = PackageManager.getDataDirForUser(pkg.volumeUuid, pkg.packageName,
+ UserHandle.USER_OWNER);
boolean uidError = false;
if (dataPath.exists()) {
@@ -5638,8 +6034,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// This is probably because the system was stopped while
// installd was in the middle of messing with its libs
// directory. Ask installd to fix that.
- int ret = mInstaller.fixUid(pkgName, pkg.applicationInfo.uid,
- pkg.applicationInfo.uid);
+ int ret = mInstaller.fixUid(pkg.volumeUuid, pkgName,
+ pkg.applicationInfo.uid, pkg.applicationInfo.uid);
if (ret >= 0) {
recovered = true;
String msg = "Package " + pkg.packageName
@@ -5652,7 +6048,7 @@ public class PackageManagerService extends IPackageManager.Stub {
|| (scanFlags&SCAN_BOOTING) != 0)) {
// If this is a system app, we can at least delete its
// current data so the application will still work.
- int ret = removeDataDirsLI(pkgName);
+ int ret = removeDataDirsLI(pkg.volumeUuid, pkgName);
if (ret >= 0) {
// TODO: Kill the processes first
// Old data gone!
@@ -5666,8 +6062,8 @@ public class PackageManagerService extends IPackageManager.Stub {
recovered = true;
// And now re-install the app.
- ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
- pkg.applicationInfo.seinfo);
+ ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+ pkg.applicationInfo.seinfo);
if (ret == -1) {
// Ack should not happen!
msg = prefix + pkg.packageName
@@ -5710,8 +6106,8 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.dataDir = dataPath.getPath();
if (mShouldRestoreconData) {
Slog.i(TAG, "SELinux relabeling of " + pkg.packageName + " issued.");
- mInstaller.restoreconData(pkg.packageName, pkg.applicationInfo.seinfo,
- pkg.applicationInfo.uid);
+ mInstaller.restoreconData(pkg.volumeUuid, pkg.packageName,
+ pkg.applicationInfo.seinfo, pkg.applicationInfo.uid);
}
} else {
if (DEBUG_PACKAGE_SCANNING) {
@@ -5719,8 +6115,8 @@ public class PackageManagerService extends IPackageManager.Stub {
Log.v(TAG, "Want this data dir: " + dataPath);
}
//invoke installer to do the actual installation
- int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
- pkg.applicationInfo.seinfo);
+ int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+ pkg.applicationInfo.seinfo);
if (ret < 0) {
// Error from installer
throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
@@ -5741,7 +6137,7 @@ public class PackageManagerService extends IPackageManager.Stub {
final String path = scanFile.getPath();
final String codePath = pkg.applicationInfo.getCodePath();
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
- if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {
+ if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) {
setBundledAppAbisAndRoots(pkg, pkgSetting);
// If we haven't found any native libraries for the app, check if it has
@@ -5774,7 +6170,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// pass once we've determined ABI below.
setNativeLibraryPaths(pkg);
- final boolean isAsec = isForwardLocked(pkg) || isExternal(pkg);
+ final boolean isAsec = pkg.isForwardLocked() || isExternal(pkg);
final String nativeLibraryRootStr = pkg.applicationInfo.nativeLibraryRootDir;
final boolean useIsaSpecificSubdirs = pkg.applicationInfo.nativeLibraryRootRequiresIsa;
@@ -5897,7 +6293,8 @@ public class PackageManagerService extends IPackageManager.Stub {
!VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) {
final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
for (int userId : userIds) {
- if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) {
+ if (mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName,
+ nativeLibPath, userId) < 0) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Failed linking native library dir (user=" + userId + ")");
}
@@ -5950,12 +6347,12 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if ((scanFlags & SCAN_NO_DEX) == 0) {
- if (performDexOptLI(pkg, null /* instruction sets */, forceDex,
- (scanFlags & SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) {
+ int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instruction sets */,
+ forceDex, (scanFlags & SCAN_DEFER_DEX) != 0, false /* inclDependencies */);
+ if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI");
}
}
-
if (mFactoryTest && pkg.requestedPermissions.contains(
android.Manifest.permission.FACTORY_TEST)) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
@@ -5971,7 +6368,7 @@ public class PackageManagerService extends IPackageManager.Stub {
for (int i=0; i<pkg.libraryNames.size(); i++) {
String name = pkg.libraryNames.get(i);
boolean allowed = false;
- if (isUpdatedSystemApp(pkg)) {
+ if (pkg.isUpdatedSystemApp()) {
// 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-
@@ -6025,8 +6422,10 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((scanFlags & SCAN_NO_DEX) == 0) {
for (int i = 0; i < clientLibPkgs.size(); i++) {
PackageParser.Package clientPkg = clientLibPkgs.get(i);
- if (performDexOptLI(clientPkg, null /* instruction sets */, forceDex,
- (scanFlags & SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) {
+ int result = mPackageDexOptimizer.performDexOpt(clientPkg,
+ null /* instruction sets */, forceDex,
+ (scanFlags & SCAN_DEFER_DEX) != 0, false);
+ if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
throw new PackageManagerException(INSTALL_FAILED_DEXOPT,
"scanPackageLI failed to dexopt clientLibPkgs");
}
@@ -6089,21 +6488,11 @@ public class PackageManagerService extends IPackageManager.Stub {
// Add the package's KeySets to the global KeySetManagerService
KeySetManagerService ksms = mSettings.mKeySetManagerService;
try {
- // Old KeySetData no longer valid.
- ksms.removeAppKeySetDataLPw(pkg.packageName);
ksms.addSigningKeySetToPackageLPw(pkg.packageName, pkg.mSigningKeys);
if (pkg.mKeySetMapping != null) {
- for (Map.Entry<String, ArraySet<PublicKey>> entry :
- pkg.mKeySetMapping.entrySet()) {
- if (entry.getValue() != null) {
- ksms.addDefinedKeySetToPackageLPw(pkg.packageName,
- entry.getValue(), entry.getKey());
- }
- }
+ ksms.addDefinedKeySetsToPackageLPw(pkg.packageName, pkg.mKeySetMapping);
if (pkg.mUpgradeKeySets != null) {
- for (String upgradeAlias : pkg.mUpgradeKeySets) {
- ksms.addUpgradeKeySetToPackageLPw(pkg.packageName, upgradeAlias);
- }
+ ksms.addUpgradeKeySetsToPackageLPw(pkg.packageName, pkg.mUpgradeKeySets);
}
}
} catch (NullPointerException e) {
@@ -6270,86 +6659,94 @@ public class PackageManagerService extends IPackageManager.Stub {
r = null;
for (i=0; i<N; i++) {
PackageParser.Permission p = pkg.permissions.get(i);
+
+ // Now that permission groups have a special meaning, we ignore permission
+ // groups for legacy apps to prevent unexpected behavior. In particular,
+ // permissions for one app being granted to someone just becuase they happen
+ // to be in a group defined by another app (before this had no implications).
+ if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+ p.group = mPermissionGroups.get(p.info.group);
+ // Warn for a permission in an unknown group.
+ if (p.info.group != null && p.group == null) {
+ Slog.w(TAG, "Permission " + p.info.name + " from package "
+ + p.info.packageName + " in an unknown group " + p.info.group);
+ }
+ }
+
ArrayMap<String, BasePermission> permissionMap =
p.tree ? mSettings.mPermissionTrees
- : mSettings.mPermissions;
- p.group = mPermissionGroups.get(p.info.group);
- if (p.info.group == null || p.group != null) {
- BasePermission bp = permissionMap.get(p.info.name);
-
- // Allow system apps to redefine non-system permissions
- if (bp != null && !Objects.equals(bp.sourcePackage, p.info.packageName)) {
- final boolean currentOwnerIsSystem = (bp.perm != null
- && isSystemApp(bp.perm.owner));
- if (isSystemApp(p.owner)) {
- if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
- // It's a built-in permission and no owner, take ownership now
- bp.packageSetting = pkgSetting;
- bp.perm = p;
- bp.uid = pkg.applicationInfo.uid;
- bp.sourcePackage = p.info.packageName;
- } else if (!currentOwnerIsSystem) {
- String msg = "New decl " + p.owner + " of permission "
- + p.info.name + " is system; overriding " + bp.sourcePackage;
- reportSettingsProblem(Log.WARN, msg);
- bp = null;
- }
+ : mSettings.mPermissions;
+ BasePermission bp = permissionMap.get(p.info.name);
+
+ // Allow system apps to redefine non-system permissions
+ if (bp != null && !Objects.equals(bp.sourcePackage, p.info.packageName)) {
+ final boolean currentOwnerIsSystem = (bp.perm != null
+ && isSystemApp(bp.perm.owner));
+ if (isSystemApp(p.owner)) {
+ if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
+ // It's a built-in permission and no owner, take ownership now
+ bp.packageSetting = pkgSetting;
+ bp.perm = p;
+ bp.uid = pkg.applicationInfo.uid;
+ bp.sourcePackage = p.info.packageName;
+ } else if (!currentOwnerIsSystem) {
+ String msg = "New decl " + p.owner + " of permission "
+ + p.info.name + " is system; overriding " + bp.sourcePackage;
+ reportSettingsProblem(Log.WARN, msg);
+ bp = null;
}
}
+ }
- if (bp == null) {
- bp = new BasePermission(p.info.name, p.info.packageName,
- BasePermission.TYPE_NORMAL);
- permissionMap.put(p.info.name, bp);
- }
-
- if (bp.perm == null) {
- if (bp.sourcePackage == null
- || bp.sourcePackage.equals(p.info.packageName)) {
- BasePermission tree = findPermissionTreeLP(p.info.name);
- if (tree == null
- || tree.sourcePackage.equals(p.info.packageName)) {
- bp.packageSetting = pkgSetting;
- bp.perm = p;
- bp.uid = pkg.applicationInfo.uid;
- bp.sourcePackage = p.info.packageName;
- if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- r.append(p.info.name);
+ if (bp == null) {
+ bp = new BasePermission(p.info.name, p.info.packageName,
+ BasePermission.TYPE_NORMAL);
+ permissionMap.put(p.info.name, bp);
+ }
+
+ if (bp.perm == null) {
+ if (bp.sourcePackage == null
+ || bp.sourcePackage.equals(p.info.packageName)) {
+ BasePermission tree = findPermissionTreeLP(p.info.name);
+ if (tree == null
+ || tree.sourcePackage.equals(p.info.packageName)) {
+ bp.packageSetting = pkgSetting;
+ bp.perm = p;
+ bp.uid = pkg.applicationInfo.uid;
+ bp.sourcePackage = p.info.packageName;
+ if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
}
- } else {
- Slog.w(TAG, "Permission " + p.info.name + " from package "
- + p.info.packageName + " ignored: base tree "
- + tree.name + " is from package "
- + tree.sourcePackage);
+ r.append(p.info.name);
}
} else {
Slog.w(TAG, "Permission " + p.info.name + " from package "
- + p.info.packageName + " ignored: original from "
- + bp.sourcePackage);
+ + p.info.packageName + " ignored: base tree "
+ + tree.name + " is from package "
+ + tree.sourcePackage);
}
- } else if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- r.append("DUP:");
- r.append(p.info.name);
+ } else {
+ Slog.w(TAG, "Permission " + p.info.name + " from package "
+ + p.info.packageName + " ignored: original from "
+ + bp.sourcePackage);
}
- if (bp.perm == p) {
- bp.protectionLevel = p.info.protectionLevel;
+ } else if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
}
- } else {
- Slog.w(TAG, "Permission " + p.info.name + " from package "
- + p.info.packageName + " ignored: no group "
- + p.group);
+ r.append("DUP:");
+ r.append(p.info.name);
+ }
+ if (bp.perm == p) {
+ bp.protectionLevel = p.info.protectionLevel;
}
}
+
if (r != null) {
if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permissions: " + r);
}
@@ -6495,14 +6892,15 @@ public class PackageManagerService extends IPackageManager.Stub {
ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
Slog.i(TAG, "Adjusting ABI for : " + ps.name + " to " + adjustedAbi);
- if (performDexOptLI(ps.pkg, null /* instruction sets */, forceDexOpt,
- deferDexOpt, true) == DEX_OPT_FAILED) {
+ int result = mPackageDexOptimizer.performDexOpt(ps.pkg,
+ null /* instruction sets */, forceDexOpt, deferDexOpt, true);
+ if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
ps.primaryCpuAbiString = null;
ps.pkg.applicationInfo.primaryCpuAbi = null;
return;
} else {
mInstaller.rmdex(ps.codePathString,
- getDexCodeInstructionSet(getPreferredInstructionSet()));
+ getDexCodeInstructionSet(getPreferredInstructionSet()));
}
}
}
@@ -6574,8 +6972,8 @@ public class PackageManagerService extends IPackageManager.Stub {
final ApplicationInfo info = pkg.applicationInfo;
final String codePath = pkg.codePath;
final File codeFile = new File(codePath);
- final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info);
- final boolean asecApp = isForwardLocked(info) || isExternal(info);
+ final boolean bundledApp = info.isSystemApp() && !info.isUpdatedSystemApp();
+ final boolean asecApp = info.isForwardLocked() || isExternal(info);
info.nativeLibraryRootDir = null;
info.nativeLibraryRootRequiresIsa = false;
@@ -7056,36 +7454,49 @@ public class PackageManagerService extends IPackageManager.Stub {
private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
String packageOfInterest) {
+ // IMPORTANT: There are two types of permissions: install and runtime.
+ // Install time permissions are granted when the app is installed to
+ // all device users and users added in the future. Runtime permissions
+ // are granted at runtime explicitly to specific users. Normal and signature
+ // protected permissions are install time permissions. Dangerous permissions
+ // are install permissions if the app's target SDK is Lollipop MR1 or older,
+ // otherwise they are runtime permissions. This function does not manage
+ // runtime permissions except for the case an app targeting Lollipop MR1
+ // being upgraded to target a newer SDK, in which case dangerous permissions
+ // are transformed from install time to runtime ones.
+
final PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
return;
}
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
- ArraySet<String> origPermissions = gp.grantedPermissions;
- boolean changedPermission = false;
+
+ PermissionsState permissionsState = ps.getPermissionsState();
+ PermissionsState origPermissions = permissionsState;
+
+ final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+
+ int[] upgradeUserIds = PermissionsState.USERS_NONE;
+ int[] changedRuntimePermissionUserIds = PermissionsState.USERS_NONE;
+
+ boolean changedInstallPermission = false;
if (replace) {
- ps.permissionsFixed = false;
- if (gp == ps) {
- origPermissions = new ArraySet<String>(gp.grantedPermissions);
- gp.grantedPermissions.clear();
- gp.gids = mGlobalGids;
+ ps.installPermissionsFixed = false;
+ if (!ps.isSharedUser()) {
+ origPermissions = new PermissionsState(permissionsState);
+ permissionsState.reset();
}
}
- if (gp.gids == null) {
- gp.gids = mGlobalGids;
- }
+ permissionsState.setGlobalGids(mGlobalGids);
final int N = pkg.requestedPermissions.size();
for (int i=0; i<N; i++) {
final String name = pkg.requestedPermissions.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);
- }
+ Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
}
if (bp == null || bp.packageSetting == null) {
@@ -7097,10 +7508,11 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final String perm = bp.name;
- boolean allowed;
boolean allowedSig = false;
- if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
- // Keep track of app op permissions.
+ int grant = GRANT_DENIED;
+
+ // Keep track of app op permissions.
+ if ((bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
ArraySet<String> pkgs = mAppOpPermissionPackages.get(bp.name);
if (pkgs == null) {
pkgs = new ArraySet<>();
@@ -7108,65 +7520,126 @@ public class PackageManagerService extends IPackageManager.Stub {
}
pkgs.add(pkg.packageName);
}
+
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;
- }
- } else {
- allowed = false;
+ switch (level) {
+ case PermissionInfo.PROTECTION_NORMAL: {
+ // For all apps normal permissions are install time ones.
+ grant = GRANT_INSTALL;
+ } break;
+
+ case PermissionInfo.PROTECTION_DANGEROUS: {
+ if (!RUNTIME_PERMISSIONS_ENABLED
+ || pkg.applicationInfo.targetSdkVersion
+ <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ // For legacy apps dangerous permissions are install time ones.
+ grant = GRANT_INSTALL;
+ } else if (ps.isSystem()) {
+ final int[] updatedUserIds = ps.getPermissionsUpdatedForUserIds();
+ if (origPermissions.hasInstallPermission(bp.name)) {
+ // If a system app had an install permission, then the app was
+ // upgraded and we grant the permissions as runtime to all users.
+ grant = GRANT_UPGRADE;
+ upgradeUserIds = currentUserIds;
+ } else if (!Arrays.equals(updatedUserIds, currentUserIds)) {
+ // If users changed since the last permissions update for a
+ // system app, we grant the permission as runtime to the new users.
+ grant = GRANT_UPGRADE;
+ upgradeUserIds = currentUserIds;
+ for (int userId : updatedUserIds) {
+ upgradeUserIds = ArrayUtils.removeInt(upgradeUserIds, userId);
+ }
+ } else {
+ // Otherwise, we grant the permission as runtime if the app
+ // already had it, i.e. we preserve runtime permissions.
+ grant = GRANT_RUNTIME;
+ }
+ } else if (origPermissions.hasInstallPermission(bp.name)) {
+ // For legacy apps that became modern, install becomes runtime.
+ grant = GRANT_UPGRADE;
+ upgradeUserIds = currentUserIds;
+ } else if (replace) {
+ // For upgraded modern apps keep runtime permissions unchanged.
+ grant = GRANT_RUNTIME;
+ }
+ } break;
+
+ case PermissionInfo.PROTECTION_SIGNATURE: {
+ // For all apps signature permissions are install time ones.
+ allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
+ if (allowedSig) {
+ grant = GRANT_INSTALL;
+ }
+ } break;
}
+
if (DEBUG_INSTALL) {
- if (gp != ps) {
- Log.i(TAG, "Package " + pkg.packageName + " granting " + perm);
- }
+ Log.i(TAG, "Package " + pkg.packageName + " granting " + perm);
}
- if (allowed) {
- if (!isSystemApp(ps) && ps.permissionsFixed) {
+
+ if (grant != GRANT_DENIED) {
+ if (!isSystemApp(ps) && ps.installPermissionsFixed) {
// If this is an existing, non-system package, then
// we can't add any new permissions to it.
- if (!allowedSig && !gp.grantedPermissions.contains(perm)) {
+ if (!allowedSig && !origPermissions.hasInstallPermission(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 (!isNewPlatformPermissionForPackage(perm, pkg)) {
+ grant = GRANT_DENIED;
+ }
}
}
- 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 {
- if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
- Slog.w(TAG, "Not granting permission " + perm
- + " to package " + pkg.packageName
- + " because it was previously installed without");
- }
+
+ switch (grant) {
+ case GRANT_INSTALL: {
+ // Grant an install permission.
+ if (permissionsState.grantInstallPermission(bp) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ changedInstallPermission = true;
+ }
+ } break;
+
+ case GRANT_RUNTIME: {
+ // Grant previously granted runtime permissions.
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ if (origPermissions.hasRuntimePermission(bp.name, userId)) {
+ if (permissionsState.grantRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // If we cannot put the permission as it was, we have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
+ }
+ }
+ } break;
+
+ case GRANT_UPGRADE: {
+ // Grant runtime permissions for a previously held install permission.
+ permissionsState.revokeInstallPermission(bp);
+ for (int userId : upgradeUserIds) {
+ if (permissionsState.grantRuntimePermission(bp, userId) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ // If we granted the permission, we have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
+ }
+ } break;
+
+ default: {
+ if (packageOfInterest == null
+ || packageOfInterest.equals(pkg.packageName)) {
+ Slog.w(TAG, "Not granting permission " + perm
+ + " to package " + pkg.packageName
+ + " because it was previously installed without");
+ }
+ } break;
}
} else {
- if (gp.grantedPermissions.remove(perm)) {
- changedPermission = true;
- gp.gids = removeInts(gp.gids, bp.gids);
+ if (permissionsState.revokeInstallPermission(bp) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ changedInstallPermission = true;
Slog.i(TAG, "Un-granting permission " + perm
+ " from package " + pkg.packageName
+ " (protectionLevel=" + bp.protectionLevel
@@ -7186,14 +7659,22 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- if ((changedPermission || replace) && !ps.permissionsFixed &&
+ if ((changedInstallPermission || replace) && !ps.installPermissionsFixed &&
!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.
- ps.permissionsFixed = true;
+ ps.installPermissionsFixed = true;
+ }
+
+ ps.setPermissionsUpdatedForUserIds(currentUserIds);
+
+ // Persist the runtime permissions state for users with changes.
+ if (RUNTIME_PERMISSIONS_ENABLED) {
+ for (int userId : changedRuntimePermissionUserIds) {
+ mSettings.writeRuntimePermissionsForUserLPr(userId, true);
+ }
}
- ps.haveGids = true;
}
private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
@@ -7214,7 +7695,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
- BasePermission bp, ArraySet<String> origPermissions) {
+ BasePermission bp, PermissionsState origPermissions) {
boolean allowed;
allowed = (compareSignatures(
bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
@@ -7226,13 +7707,10 @@ public class PackageManagerService extends IPackageManager.Stub {
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)) {
+ if (pkg.isUpdatedSystemApp()) {
final PackageSetting sysPs = mSettings
.getDisabledSystemPkgLPr(pkg.packageName);
- final GrantedPermissions origGp = sysPs.sharedUser != null
- ? sysPs.sharedUser : sysPs;
-
- if (origGp.grantedPermissions.contains(perm)) {
+ if (sysPs.getPermissionsState().hasInstallPermission(perm)) {
// If the original was granted this permission, we take
// that grant decision as read and propagate it to the
// update.
@@ -7266,7 +7744,7 @@ public class PackageManagerService extends IPackageManager.Stub {
& PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
// For development permissions, a development permission
// is granted only if it was already granted.
- allowed = origPermissions.contains(perm);
+ allowed = origPermissions.hasInstallPermission(perm);
}
return allowed;
}
@@ -7314,7 +7792,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
public final void addActivity(PackageParser.Activity a, String type) {
- final boolean systemApp = isSystemApp(a.info.applicationInfo);
+ final boolean systemApp = a.info.applicationInfo.isSystemApp();
mActivities.put(a.getComponentName(), a);
if (DEBUG_SHOW_INFO)
Log.v(
@@ -7428,6 +7906,9 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) {
res.filter = info;
}
+ if (info != null) {
+ res.handleAllWebDataURI = info.handleAllWebDataURI();
+ }
res.priority = info.getPriority();
res.preferredOrder = activity.owner.mPreferredOrder;
//System.out.println("Result: " + res.activityInfo.className +
@@ -7441,7 +7922,7 @@ public class PackageManagerService extends IPackageManager.Stub {
} else {
res.icon = info.icon;
}
- res.system = isSystemApp(res.activityInfo.applicationInfo);
+ res.system = res.activityInfo.applicationInfo.isSystemApp();
return res;
}
@@ -7650,14 +8131,12 @@ public class PackageManagerService extends IPackageManager.Stub {
}
res.priority = info.getPriority();
res.preferredOrder = service.owner.mPreferredOrder;
- //System.out.println("Result: " + res.activityInfo.className +
- // " = " + res.priority);
res.match = match;
res.isDefault = info.hasDefault;
res.labelRes = info.labelRes;
res.nonLocalizedLabel = info.nonLocalizedLabel;
res.icon = info.icon;
- res.system = isSystemApp(res.serviceInfo.applicationInfo);
+ res.system = res.serviceInfo.applicationInfo.isSystemApp();
return res;
}
@@ -7880,7 +8359,7 @@ public class PackageManagerService extends IPackageManager.Stub {
res.labelRes = info.labelRes;
res.nonLocalizedLabel = info.nonLocalizedLabel;
res.icon = info.icon;
- res.system = isSystemApp(res.providerInfo.applicationInfo);
+ res.system = res.providerInfo.applicationInfo.isSystemApp();
return res;
}
@@ -8072,8 +8551,8 @@ public class PackageManagerService extends IPackageManager.Stub {
public void installPackage(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams,
String packageAbiOverride) {
- installPackageAsUser(originPath, observer, installFlags, installerPackageName, verificationParams,
- packageAbiOverride, UserHandle.getCallingUserId());
+ installPackageAsUser(originPath, observer, installFlags, installerPackageName,
+ verificationParams, packageAbiOverride, UserHandle.getCallingUserId());
}
@Override
@@ -8113,6 +8592,15 @@ public class PackageManagerService extends IPackageManager.Stub {
user = new UserHandle(userId);
}
+ // Only system components can circumvent runtime permissions when installing.
+ if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException("You need the "
+ + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
+ }
+
verificationParams.setInstallerUid(callingUid);
final File originFile = new File(originPath);
@@ -8120,7 +8608,7 @@ public class PackageManagerService extends IPackageManager.Stub {
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(origin, observer, installFlags,
- installerPackageName, verificationParams, user, packageAbiOverride);
+ installerPackageName, null, verificationParams, user, packageAbiOverride);
mHandler.sendMessage(msg);
}
@@ -8139,7 +8627,7 @@ public class PackageManagerService extends IPackageManager.Stub {
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(origin, observer, params.installFlags,
- installerPackageName, verifParams, user, params.abiOverride);
+ installerPackageName, params.volumeUuid, verifParams, user, params.abiOverride);
mHandler.sendMessage(msg);
}
@@ -8270,7 +8758,6 @@ public class PackageManagerService extends IPackageManager.Stub {
long callingId = Binder.clearCallingIdentity();
try {
boolean sendAdded = false;
- Bundle extras = new Bundle(1);
// writer
synchronized (mPackages) {
@@ -8528,6 +9015,81 @@ public class PackageManagerService extends IPackageManager.Stub {
android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 1;
}
+ @Override
+ public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains)
+ throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+ "Only intentfilter verification agents can verify applications");
+
+ final Message msg = mHandler.obtainMessage(INTENT_FILTER_VERIFIED);
+ final IntentFilterVerificationResponse response = new IntentFilterVerificationResponse(
+ Binder.getCallingUid(), verificationCode, failedDomains);
+ msg.arg1 = id;
+ msg.obj = response;
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public int getIntentVerificationStatus(String packageName, int userId) {
+ synchronized (mPackages) {
+ return mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
+ }
+ }
+
+ @Override
+ public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
+ boolean result = false;
+ synchronized (mPackages) {
+ result = mSettings.updateIntentFilterVerificationStatusLPw(packageName, status, userId);
+ }
+ scheduleWritePackageRestrictionsLocked(userId);
+ return result;
+ }
+
+ @Override
+ public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
+ synchronized (mPackages) {
+ return mSettings.getIntentFilterVerificationsLPr(packageName);
+ }
+ }
+
+ @Override
+ public List<IntentFilter> getAllIntentFilters(String packageName) {
+ if (TextUtils.isEmpty(packageName)) {
+ return Collections.<IntentFilter>emptyList();
+ }
+ synchronized (mPackages) {
+ PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg == null || pkg.activities == null) {
+ return Collections.<IntentFilter>emptyList();
+ }
+ final int count = pkg.activities.size();
+ ArrayList<IntentFilter> result = new ArrayList<>();
+ for (int n=0; n<count; n++) {
+ PackageParser.Activity activity = pkg.activities.get(n);
+ if (activity.intents != null || activity.intents.size() > 0) {
+ result.addAll(activity.intents);
+ }
+ }
+ return result;
+ }
+ }
+
+ @Override
+ public boolean setDefaultBrowserPackageName(String packageName, int userId) {
+ synchronized (mPackages) {
+ return mSettings.setDefaultBrowserPackageNameLPr(packageName, userId);
+ }
+ }
+
+ @Override
+ public String getDefaultBrowserPackageName(int userId) {
+ synchronized (mPackages) {
+ return mSettings.getDefaultBrowserPackageNameLPw(userId);
+ }
+ }
+
/**
* Get the "allow unknown sources" setting.
*
@@ -8902,19 +9464,21 @@ public class PackageManagerService extends IPackageManager.Stub {
final IPackageInstallObserver2 observer;
int installFlags;
final String installerPackageName;
+ final String volumeUuid;
final VerificationParams verificationParams;
private InstallArgs mArgs;
private int mRet;
final String packageAbiOverride;
InstallParams(OriginInfo origin, IPackageInstallObserver2 observer, int installFlags,
- String installerPackageName, VerificationParams verificationParams, UserHandle user,
- String packageAbiOverride) {
+ String installerPackageName, String volumeUuid,
+ VerificationParams verificationParams, UserHandle user, String packageAbiOverride) {
super(user);
this.origin = origin;
this.observer = observer;
this.installFlags = installFlags;
this.installerPackageName = installerPackageName;
+ this.volumeUuid = volumeUuid;
this.verificationParams = verificationParams;
this.packageAbiOverride = packageAbiOverride;
}
@@ -9039,7 +9603,7 @@ public class PackageManagerService extends IPackageManager.Stub {
final long sizeBytes = mContainerService.calculateInstalledSize(
origin.resolvedPath, isForwardLocked(), packageAbiOverride);
- if (mInstaller.freeCache(sizeBytes + lowThreshold) >= 0) {
+ if (mInstaller.freeCache(null, sizeBytes + lowThreshold) >= 0) {
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath,
installFlags, packageAbiOverride);
}
@@ -9268,7 +9832,7 @@ public class PackageManagerService extends IPackageManager.Stub {
* @param installFlags package installation flags
* @return true if should be installed on external storage
*/
- private static boolean installOnSd(int installFlags) {
+ private static boolean installOnExternalAsec(int installFlags) {
if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
return false;
}
@@ -9289,7 +9853,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
private InstallArgs createInstallArgs(InstallParams params) {
- if (installOnSd(params.installFlags) || params.isForwardLocked()) {
+ if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
return new AsecInstallArgs(params);
} else {
return new FileInstallArgs(params);
@@ -9303,7 +9867,7 @@ public class PackageManagerService extends IPackageManager.Stub {
private InstallArgs createInstallArgsForExisting(int installFlags, String codePath,
String resourcePath, String nativeLibraryRoot, String[] instructionSets) {
final boolean isInAsec;
- if (installOnSd(installFlags)) {
+ if (installOnExternalAsec(installFlags)) {
/* Apps on SD card are always in ASEC containers. */
isInAsec = true;
} else if (installForwardLocked(installFlags)
@@ -9319,7 +9883,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (isInAsec) {
return new AsecInstallArgs(codePath, instructionSets,
- installOnSd(installFlags), installForwardLocked(installFlags));
+ installOnExternalAsec(installFlags), installForwardLocked(installFlags));
} else {
return new FileInstallArgs(codePath, resourcePath, nativeLibraryRoot,
instructionSets);
@@ -9334,6 +9898,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Always refers to PackageManager flags only
final int installFlags;
final String installerPackageName;
+ final String volumeUuid;
final ManifestDigest manifestDigest;
final UserHandle user;
final String abiOverride;
@@ -9344,12 +9909,13 @@ public class PackageManagerService extends IPackageManager.Stub {
/* nullable */ String[] instructionSets;
InstallArgs(OriginInfo origin, IPackageInstallObserver2 observer, int installFlags,
- String installerPackageName, ManifestDigest manifestDigest, UserHandle user,
- String[] instructionSets, String abiOverride) {
+ String installerPackageName, String volumeUuid, ManifestDigest manifestDigest,
+ UserHandle user, String[] instructionSets, String abiOverride) {
this.origin = origin;
this.installFlags = installFlags;
this.observer = observer;
this.installerPackageName = installerPackageName;
+ this.volumeUuid = volumeUuid;
this.manifestDigest = manifestDigest;
this.user = user;
this.instructionSets = instructionSets;
@@ -9401,7 +9967,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
}
- protected boolean isExternal() {
+ protected boolean isExternalAsec() {
return (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
}
@@ -9410,6 +9976,25 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private void removeDexFiles(List<String> allCodePaths, String[] instructionSets) {
+ if (!allCodePaths.isEmpty()) {
+ if (instructionSets == null) {
+ throw new IllegalStateException("instructionSet == null");
+ }
+ String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+ for (String codePath : allCodePaths) {
+ for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+ int retCode = mInstaller.rmdex(codePath, dexCodeInstructionSet);
+ if (retCode < 0) {
+ Slog.w(TAG, "Couldn't remove dex file for package: "
+ + " at location " + codePath + ", retcode=" + retCode);
+ // we don't consider this to be a failure of the core package deletion
+ }
+ }
+ }
+ }
+ }
+
/**
* Logic to handle installation of non-ASEC applications, including copying
* and renaming logic.
@@ -9429,8 +10014,8 @@ public class PackageManagerService extends IPackageManager.Stub {
/** New install */
FileInstallArgs(InstallParams params) {
super(params.origin, params.observer, params.installFlags,
- params.installerPackageName, params.getManifestDigest(), params.getUser(),
- null /* instruction sets */, params.packageAbiOverride);
+ params.installerPackageName, params.volumeUuid, params.getManifestDigest(),
+ params.getUser(), null /* instruction sets */, params.packageAbiOverride);
if (isFwdLocked()) {
throw new IllegalArgumentException("Forward locking only supported in ASEC");
}
@@ -9439,7 +10024,7 @@ public class PackageManagerService extends IPackageManager.Stub {
/** Existing install */
FileInstallArgs(String codePath, String resourcePath, String legacyNativeLibraryPath,
String[] instructionSets) {
- super(OriginInfo.fromNothing(), null, 0, null, null, null, instructionSets, null);
+ super(OriginInfo.fromNothing(), null, 0, null, null, null, null, instructionSets, null);
this.codeFile = (codePath != null) ? new File(codePath) : null;
this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
this.legacyNativeLibraryPath = (legacyNativeLibraryPath != null) ?
@@ -9463,7 +10048,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
try {
- final File tempDir = mInstallerService.allocateInternalStageDirLegacy();
+ final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid);
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
@@ -9524,8 +10109,9 @@ public class PackageManagerService extends IPackageManager.Stub {
cleanUp();
return false;
} else {
+ final File targetDir = codeFile.getParentFile();
final File beforeCodeFile = codeFile;
- final File afterCodeFile = getNextCodePath(pkg.packageName);
+ final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);
Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
try {
@@ -9552,6 +10138,7 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.splitCodePaths);
// Reflect the rename in app info
+ pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
pkg.applicationInfo.setCodePath(pkg.codePath);
pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
@@ -9591,9 +10178,10 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if (codeFile.isDirectory()) {
- FileUtils.deleteContents(codeFile);
+ mInstaller.rmPackageDir(codeFile.getAbsolutePath());
+ } else {
+ codeFile.delete();
}
- codeFile.delete();
if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) {
resourceFile.delete();
@@ -9622,23 +10210,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
cleanUp();
-
- if (!allCodePaths.isEmpty()) {
- if (instructionSets == null) {
- throw new IllegalStateException("instructionSet == null");
- }
- String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
- for (String codePath : allCodePaths) {
- for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- int retCode = mInstaller.rmdex(codePath, dexCodeInstructionSet);
- if (retCode < 0) {
- Slog.w(TAG, "Couldn't remove dex file for package: "
- + " at location " + codePath + ", retcode=" + retCode);
- // we don't consider this to be a failure of the core package deletion
- }
- }
- }
- }
+ removeDexFiles(allCodePaths, instructionSets);
}
boolean doPostDeleteLI(boolean delete) {
@@ -9690,16 +10262,15 @@ public class PackageManagerService extends IPackageManager.Stub {
/** New install */
AsecInstallArgs(InstallParams params) {
super(params.origin, params.observer, params.installFlags,
- params.installerPackageName, params.getManifestDigest(),
- params.getUser(), null /* instruction sets */,
- params.packageAbiOverride);
+ params.installerPackageName, params.volumeUuid, params.getManifestDigest(),
+ params.getUser(), null /* instruction sets */, params.packageAbiOverride);
}
/** Existing install */
AsecInstallArgs(String fullCodePath, String[] instructionSets,
boolean isExternal, boolean isForwardLocked) {
super(OriginInfo.fromNothing(), null, (isExternal ? INSTALL_EXTERNAL : 0)
- | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
+ | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, null,
instructionSets, null);
// Hackily pretend we're still looking at a full code path
if (!fullCodePath.endsWith(RES_FILE_NAME)) {
@@ -9716,7 +10287,7 @@ public class PackageManagerService extends IPackageManager.Stub {
AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) {
super(OriginInfo.fromNothing(), null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0)
- | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
+ | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, null,
instructionSets, null);
this.cid = cid;
setMountPath(PackageHelper.getSdDir(cid));
@@ -9731,7 +10302,7 @@ public class PackageManagerService extends IPackageManager.Stub {
abiOverride);
final File target;
- if (isExternal()) {
+ if (isExternalAsec()) {
target = new UserEnvironment(UserHandle.USER_OWNER).getExternalStorageDirectory();
} else {
target = Environment.getDataDirectory();
@@ -9760,7 +10331,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final String newMountPath = imcs.copyPackageToContainer(
- origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternal(),
+ origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternalAsec(),
isFwdLocked(), deriveAbiOverride(abiOverride, null /* settings */));
if (newMountPath != null) {
@@ -9858,6 +10429,7 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.splitCodePaths);
// Reflect the rename in app info
+ pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
pkg.applicationInfo.setCodePath(pkg.codePath);
pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
@@ -9943,31 +10515,10 @@ public class PackageManagerService extends IPackageManager.Stub {
private void cleanUpResourcesLI(List<String> allCodePaths) {
cleanUp();
-
- if (!allCodePaths.isEmpty()) {
- if (instructionSets == null) {
- throw new IllegalStateException("instructionSet == null");
- }
- String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
- for (String codePath : allCodePaths) {
- for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- int retCode = mInstaller.rmdex(codePath, dexCodeInstructionSet);
- if (retCode < 0) {
- Slog.w(TAG, "Couldn't remove dex file for package: "
- + " at location " + codePath + ", retcode=" + retCode);
- // we don't consider this to be a failure of the core package deletion
- }
- }
- }
- }
+ removeDexFiles(allCodePaths, instructionSets);
}
- boolean matchContainer(String app) {
- if (cid.startsWith(app)) {
- return true;
- }
- return false;
- }
+
String getPackageName() {
return getAsecPackageName(cid);
@@ -10062,32 +10613,16 @@ public class PackageManagerService extends IPackageManager.Stub {
return prefix + idxStr;
}
- private File getNextCodePath(String packageName) {
+ private File getNextCodePath(File targetDir, String packageName) {
int suffix = 1;
File result;
do {
- result = new File(mAppInstallDir, packageName + "-" + suffix);
+ result = new File(targetDir, packageName + "-" + suffix);
suffix++;
} while (result.exists());
return result;
}
- // Utility method used to ignore ADD/REMOVE events
- // by directory observer.
- private static boolean ignoreCodePath(String fullPathStr) {
- String apkName = deriveCodePathName(fullPathStr);
- int idx = apkName.lastIndexOf(INSTALL_PACKAGE_SUFFIX);
- if (idx != -1 && ((idx+1) < apkName.length())) {
- // Make sure the package ends with a numeral
- String version = apkName.substring(idx+1);
- try {
- Integer.parseInt(version);
- return true;
- } catch (NumberFormatException e) {}
- }
- return false;
- }
-
// Utility method that returns the relative package path with respect
// to the installation directory. Like say for /data/data/com.test-1.apk
// string com.test-1 is returned.
@@ -10146,14 +10681,15 @@ public class PackageManagerService extends IPackageManager.Stub {
/*
* Install a non-existing package.
*/
- private void installNewPackageLI(PackageParser.Package pkg,
- int parseFlags, int scanFlags, UserHandle user,
- String installerPackageName, PackageInstalledInfo res) {
+ private void installNewPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags,
+ UserHandle user, String installerPackageName, String volumeUuid,
+ PackageInstalledInfo res) {
// 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();
+ final boolean dataDirExists = PackageManager.getDataDirForUser(volumeUuid, pkgName,
+ UserHandle.USER_OWNER).exists();
synchronized(mPackages) {
if (mSettings.mRenamedPackages.containsKey(pkgName)) {
// A package with the same name is already installed, though
@@ -10177,7 +10713,7 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,
System.currentTimeMillis(), user);
- updateSettingsLI(newPackage, installerPackageName, null, null, res);
+ updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user);
// delete the partially installed application. the data directory will have to be
// restored if it was already existing
if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -10209,9 +10745,9 @@ public class PackageManagerService extends IPackageManager.Stub {
return false;
}
- private void replacePackageLI(PackageParser.Package pkg,
- int parseFlags, int scanFlags, UserHandle user,
- String installerPackageName, PackageInstalledInfo res) {
+ private void replacePackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags,
+ UserHandle user, String installerPackageName, String volumeUuid,
+ PackageInstalledInfo res) {
PackageParser.Package oldPackage;
String pkgName = pkg.packageName;
int[] allUsers;
@@ -10250,17 +10786,17 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean sysPkg = (isSystemApp(oldPackage));
if (sysPkg) {
replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanFlags,
- user, allUsers, perUserInstalled, installerPackageName, res);
+ user, allUsers, perUserInstalled, installerPackageName, volumeUuid, res);
} else {
replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanFlags,
- user, allUsers, perUserInstalled, installerPackageName, res);
+ user, allUsers, perUserInstalled, installerPackageName, volumeUuid, res);
}
}
private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage,
PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user,
- int[] allUsers, boolean[] perUserInstalled,
- String installerPackageName, PackageInstalledInfo res) {
+ int[] allUsers, boolean[] perUserInstalled, String installerPackageName,
+ String volumeUuid, PackageInstalledInfo res) {
String pkgName = deletedPackage.packageName;
boolean deletedPkg = true;
boolean updatedSettings = false;
@@ -10285,7 +10821,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// If deleted package lived in a container, give users a chance to
// relinquish resources before killing.
- if (isForwardLocked(deletedPackage) || isExternal(deletedPackage)) {
+ if (deletedPackage.isForwardLocked() || isExternal(deletedPackage)) {
if (DEBUG_INSTALL) {
Slog.i(TAG, "upgrading pkg " + deletedPackage + " is ASEC-hosted -> UNAVAILABLE");
}
@@ -10295,11 +10831,12 @@ public class PackageManagerService extends IPackageManager.Stub {
sendResourcesChangedBroadcast(false, true, pkgList, uidArray, null);
}
- deleteCodeCacheDirsLI(pkgName);
+ deleteCodeCacheDirsLI(pkg.volumeUuid, pkgName);
try {
final PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags,
scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user);
- updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
+ updateSettingsLI(newPackage, installerPackageName, volumeUuid, allUsers,
+ perUserInstalled, res, user);
updatedSettings = true;
} catch (PackageManagerException e) {
res.setError("Package couldn't be installed in " + pkg.codePath, e);
@@ -10324,10 +10861,10 @@ public class PackageManagerService extends IPackageManager.Stub {
if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage);
File restoreFile = new File(deletedPackage.codePath);
// Parse old package
- boolean oldOnSd = isExternal(deletedPackage);
+ boolean oldExternal = isExternal(deletedPackage);
int oldParseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY |
- (isForwardLocked(deletedPackage) ? PackageParser.PARSE_FORWARD_LOCK : 0) |
- (oldOnSd ? PackageParser.PARSE_ON_SDCARD : 0);
+ (deletedPackage.isForwardLocked() ? PackageParser.PARSE_FORWARD_LOCK : 0) |
+ (oldExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
int oldScanFlags = SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME;
try {
scanPackageLI(restoreFile, oldParseFlags, oldScanFlags, origUpdateTime, null);
@@ -10351,14 +10888,15 @@ public class PackageManagerService extends IPackageManager.Stub {
private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user,
- int[] allUsers, boolean[] perUserInstalled,
- String installerPackageName, PackageInstalledInfo res) {
+ int[] allUsers, boolean[] perUserInstalled, String installerPackageName,
+ String volumeUuid, PackageInstalledInfo res) {
if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
+ ", old=" + deletedPackage);
boolean disabledSystem = false;
boolean updatedSettings = false;
parseFlags |= PackageParser.PARSE_IS_SYSTEM;
- if ((deletedPackage.applicationInfo.flags&ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+ if ((deletedPackage.applicationInfo.privateFlags&ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+ != 0) {
parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
String packageName = deletedPackage.packageName;
@@ -10405,7 +10943,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
// Successfully disabled the old package. Now proceed with re-installation
- deleteCodeCacheDirsLI(packageName);
+ deleteCodeCacheDirsLI(pkg.volumeUuid, packageName);
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
@@ -10428,7 +10966,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
- updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
+ updateSettingsLI(newPackage, installerPackageName, volumeUuid, allUsers,
+ perUserInstalled, res, user);
updatedSettings = true;
}
@@ -10463,8 +11002,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
- int[] allUsers, boolean[] perUserInstalled,
- PackageInstalledInfo res) {
+ String volumeUuid, int[] allUsers, boolean[] perUserInstalled, PackageInstalledInfo res,
+ UserHandle user) {
String pkgName = newPackage.packageName;
synchronized (mPackages) {
//write settings. the installStatus will be incomplete at this stage.
@@ -10483,13 +11022,13 @@ public class PackageManagerService extends IPackageManager.Stub {
// 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) {
+ PackageSetting ps = mSettings.mPackages.get(pkgName);
+ if (ps != null) {
+ 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);
+ }
if (res.origUsers != null) {
for (int userHandle : res.origUsers) {
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
@@ -10509,6 +11048,13 @@ public class PackageManagerService extends IPackageManager.Stub {
// upcoming call to mSettings.writeLPr().
}
}
+ // It's implied that when a user requests installation, they want the app to be
+ // installed and enabled.
+ int userId = user.getIdentifier();
+ if (userId != UserHandle.USER_ALL) {
+ ps.setInstalled(true, userId);
+ ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
+ }
}
res.name = pkgName;
res.uid = newPackage.applicationInfo.uid;
@@ -10523,12 +11069,14 @@ public class PackageManagerService extends IPackageManager.Stub {
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
final int installFlags = args.installFlags;
- String installerPackageName = args.installerPackageName;
- File tmpPackageFile = new File(args.getCodePath());
- boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
- boolean onSd = ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0);
+ final String installerPackageName = args.installerPackageName;
+ final String volumeUuid = args.volumeUuid;
+ final File tmpPackageFile = new File(args.getCodePath());
+ final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
+ final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0)
+ || (args.volumeUuid != null));
boolean replace = false;
- final int scanFlags = SCAN_NEW_INSTALL | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE;
+ int scanFlags = SCAN_NEW_INSTALL | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE;
// Result object to be returned
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
@@ -10536,7 +11084,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Retrieve PackageSettings and parse package
final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
- | (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
+ | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setDisplayMetrics(mMetrics);
@@ -10688,24 +11236,40 @@ public class PackageManagerService extends IPackageManager.Stub {
}
- if (systemApp && onSd) {
+ if (systemApp && onExternal) {
// Disable updates to system apps on sdcard
res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
"Cannot install updates to system apps on sdcard");
return;
}
+ // If app directory is not writable, dexopt will be called after the rename
+ if (!forwardLocked && !pkg.applicationInfo.isExternalAsec()) {
+ // Enable SCAN_NO_DEX flag to skip dexopt at a later stage
+ scanFlags |= SCAN_NO_DEX;
+ // Run dexopt before old package gets removed, to minimize time when app is unavailable
+ int result = mPackageDexOptimizer
+ .performDexOpt(pkg, null /* instruction sets */, true /* forceDex */,
+ false /* defer */, false /* inclDependencies */);
+ if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
+ res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath);
+ return;
+ }
+ }
+
if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
return;
}
+ startIntentFilterVerifications(args.user.getIdentifier(), pkg);
+
if (replace) {
replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
- installerPackageName, res);
+ installerPackageName, volumeUuid, res);
} else {
installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
- args.user, installerPackageName, res);
+ args.user, installerPackageName, volumeUuid, res);
}
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(pkgName);
@@ -10715,16 +11279,103 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- private static boolean isForwardLocked(PackageParser.Package pkg) {
- return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0;
+ private void startIntentFilterVerifications(int userId, PackageParser.Package pkg) {
+ if (mIntentFilterVerifierComponent == null) {
+ Slog.d(TAG, "No IntentFilter verification will not be done as "
+ + "there is no IntentFilterVerifier available!");
+ return;
+ }
+
+ final int verifierUid = getPackageUid(
+ mIntentFilterVerifierComponent.getPackageName(),
+ (userId == UserHandle.USER_ALL) ? UserHandle.USER_OWNER : userId);
+
+ mHandler.removeMessages(START_INTENT_FILTER_VERIFICATIONS);
+ final Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS);
+ msg.obj = pkg;
+ msg.arg1 = userId;
+ msg.arg2 = verifierUid;
+
+ mHandler.sendMessage(msg);
}
- private static boolean isForwardLocked(ApplicationInfo info) {
- return (info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0;
+ private void verifyIntentFiltersIfNeeded(int userId, int verifierUid,
+ PackageParser.Package pkg) {
+ int size = pkg.activities.size();
+ if (size == 0) {
+ Slog.d(TAG, "No activity, so no need to verify any IntentFilter!");
+ return;
+ }
+
+ final boolean hasDomainURLs = hasDomainURLs(pkg);
+ if (!hasDomainURLs) {
+ Slog.d(TAG, "No domain URLs, so no need to verify any IntentFilter!");
+ return;
+ }
+
+ Slog.d(TAG, "Checking for userId:" + userId + " if any IntentFilter from the " + size
+ + " Activities needs verification ...");
+
+ final int verificationId = mIntentFilterVerificationToken++;
+ int count = 0;
+ final String packageName = pkg.packageName;
+ ArrayList<String> allHosts = new ArrayList<>();
+
+ synchronized (mPackages) {
+ for (PackageParser.Activity a : pkg.activities) {
+ for (ActivityIntentInfo filter : a.intents) {
+ boolean needsFilterVerification = filter.needsVerification();
+ if (needsFilterVerification && needsNetworkVerificationLPr(filter)) {
+ Slog.d(TAG, "Verification needed for IntentFilter:" + filter.toString());
+ mIntentFilterVerifier.addOneIntentFilterVerification(
+ verifierUid, userId, verificationId, filter, packageName);
+ count++;
+ } else if (!needsFilterVerification) {
+ Slog.d(TAG, "No verification needed for IntentFilter:"
+ + filter.toString());
+ if (hasValidDomains(filter)) {
+ allHosts.addAll(filter.getHostsList());
+ }
+ } else {
+ Slog.d(TAG, "Verification already done for IntentFilter:"
+ + filter.toString());
+ }
+ }
+ }
+ }
+
+ if (count > 0) {
+ mIntentFilterVerifier.startVerifications(userId);
+ Slog.d(TAG, "Started " + count + " IntentFilter verification"
+ + (count > 1 ? "s" : "") + " for userId:" + userId + "!");
+ } else {
+ Slog.d(TAG, "No need to start any IntentFilter verification!");
+ if (allHosts.size() > 0 && mSettings.createIntentFilterVerificationIfNeededLPw(
+ packageName, allHosts) != null) {
+ scheduleWriteSettingsLocked();
+ }
+ }
}
- private boolean isForwardLocked(PackageSetting ps) {
- return (ps.pkgFlags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0;
+ private boolean needsNetworkVerificationLPr(ActivityIntentInfo filter) {
+ final ComponentName cn = filter.activity.getComponentName();
+ final String packageName = cn.getPackageName();
+
+ IntentFilterVerificationInfo ivi = mSettings.getIntentFilterVerificationLPr(
+ packageName);
+ if (ivi == null) {
+ return true;
+ }
+ int status = ivi.getStatus();
+ switch (status) {
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+ return true;
+
+ default:
+ // Nothing to do
+ return false;
+ }
}
private static boolean isMultiArch(PackageSetting ps) {
@@ -10752,11 +11403,11 @@ public class PackageManagerService extends IPackageManager.Stub {
}
private static boolean isPrivilegedApp(PackageParser.Package pkg) {
- return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0;
+ return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
- private static boolean isSystemApp(ApplicationInfo info) {
- return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ private static boolean hasDomainURLs(PackageParser.Package pkg) {
+ return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
}
private static boolean isSystemApp(PackageSetting ps) {
@@ -10767,20 +11418,14 @@ public class PackageManagerService extends IPackageManager.Stub {
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;
- }
-
- private static boolean isUpdatedSystemApp(ApplicationInfo info) {
- return (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- }
-
private int packageFlagsToInstallFlags(PackageSetting ps) {
int installFlags = 0;
- if (isExternal(ps)) {
+ if (isExternal(ps) && TextUtils.isEmpty(ps.volumeUuid)) {
+ // This existing package was an external ASEC install when we have
+ // the external flag without a UUID
installFlags |= PackageManager.INSTALL_EXTERNAL;
}
- if (isForwardLocked(ps)) {
+ if (ps.isForwardLocked()) {
installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
}
return installFlags;
@@ -11033,7 +11678,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
- removeDataDirsLI(packageName);
+ removeDataDirsLI(ps.volumeUuid, packageName);
schedulePackageCleaning(packageName, UserHandle.USER_ALL, true);
}
// writer
@@ -11044,14 +11689,33 @@ public class PackageManagerService extends IPackageManager.Stub {
mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName);
outInfo.removedAppId = mSettings.removePackageLPw(packageName);
}
- if (deletedPs != null) {
- updatePermissionsLPw(deletedPs.name, null, 0);
- if (deletedPs.sharedUser != null) {
- // remove permissions associated with package
- mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids);
+ updatePermissionsLPw(deletedPs.name, null, 0);
+ if (deletedPs.sharedUser != null) {
+ // Remove permissions associated with package. Since runtime
+ // permissions are per user we have to kill the removed package
+ // or packages running under the shared user of the removed
+ // package if revoking the permissions requested only by the removed
+ // package is successful and this causes a change in gids.
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ final int userIdToKill = mSettings.updateSharedUserPermsLPw(deletedPs,
+ userId);
+ if (userIdToKill == UserHandle.USER_ALL
+ || userIdToKill >= UserHandle.USER_OWNER) {
+ // If gids changed for this user, kill all affected packages.
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // This has to happen with no lock held.
+ killSettingPackagesForUser(deletedPs, userIdToKill,
+ KILL_APP_REASON_GIDS_CHANGED);
+ }
+ });
+ break;
+ }
}
}
clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL);
+ clearIntentFilterVerificationsLPw(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
@@ -11280,8 +11944,8 @@ public class PackageManagerService extends IPackageManager.Stub {
true, //notLaunched
false, //hidden
null, null, null,
- false // blockUninstall
- );
+ false, // blockUninstall
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
if (!isSystemApp(ps)) {
if (ps.isAnyInstalled(sUserManager.getUserIds())) {
// Other user still have this package installed, so all
@@ -11290,7 +11954,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
removeUser = user.getIdentifier();
appId = ps.appId;
- mSettings.writePackageRestrictionsLPr(removeUser);
+ scheduleWritePackageRestrictionsLocked(removeUser);
} else {
// We need to set it back to 'installed' so the uninstall
// broadcasts will be sent correctly.
@@ -11305,7 +11969,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
removeUser = user.getIdentifier();
appId = ps.appId;
- mSettings.writePackageRestrictionsLPr(removeUser);
+ scheduleWritePackageRestrictionsLocked(removeUser);
}
}
}
@@ -11319,9 +11983,14 @@ public class PackageManagerService extends IPackageManager.Stub {
outInfo.removedAppId = appId;
outInfo.removedUsers = new int[] {removeUser};
}
- mInstaller.clearUserData(packageName, removeUser);
+ mInstaller.clearUserData(ps.volumeUuid, packageName, removeUser);
removeKeystoreDataIfNeeded(removeUser, appId);
schedulePackageCleaning(packageName, removeUser, false);
+ synchronized (mPackages) {
+ if (clearPackagePreferredActivitiesLPw(packageName, removeUser)) {
+ scheduleWritePackageRestrictionsLocked(removeUser);
+ }
+ }
return true;
}
@@ -11483,7 +12152,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Always delete data directories for package, even if we found no other
// record of app. This helps users recover from UID mismatches without
// resorting to a full data wipe.
- int retCode = mInstaller.clearUserData(packageName, userId);
+ int retCode = mInstaller.clearUserData(pkg.volumeUuid, packageName, userId);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove cache files for package: " + packageName);
return false;
@@ -11504,7 +12173,8 @@ public class PackageManagerService extends IPackageManager.Stub {
if (pkg != null && pkg.applicationInfo.primaryCpuAbi != null &&
!VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) {
final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
- if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) {
+ if (mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName,
+ nativeLibPath, userId) < 0) {
Slog.w(TAG, "Failed linking native library dir");
return false;
}
@@ -11580,7 +12250,7 @@ public class PackageManagerService extends IPackageManager.Stub {
Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
return false;
}
- int retCode = mInstaller.deleteCacheFiles(packageName, userId);
+ int retCode = mInstaller.deleteCacheFiles(p.volumeUuid, packageName, userId);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove cache files for package: "
+ packageName + " u" + userId);
@@ -11634,7 +12304,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (ps != null) {
libDirRoot = ps.legacyNativeLibraryPathString;
}
- if (p != null && (isExternal(p) || isForwardLocked(p))) {
+ if (p != null && (isExternal(p) || p.isForwardLocked())) {
String secureContainerId = cidFromCodePath(p.applicationInfo.getBaseCodePath());
if (secureContainerId != null) {
asecPath = PackageHelper.getSdFilesystem(secureContainerId);
@@ -11648,7 +12318,7 @@ public class PackageManagerService extends IPackageManager.Stub {
Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
return false;
}
- if (isForwardLocked(p)) {
+ if (p.isForwardLocked()) {
publicSrcDir = applicationInfo.getBaseResourcePath();
}
}
@@ -11658,8 +12328,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// TODO(multiArch): Extend getSizeInfo to look at *all* instruction sets, not
// just the primary.
String[] dexCodeInstructionSets = getDexCodeInstructionSets(getAppDexInstructionSets(ps));
- int res = mInstaller.getSizeInfo(packageName, userHandle, p.baseCodePath, libDirRoot,
- publicSrcDir, asecPath, dexCodeInstructionSets, pStats);
+ int res = mInstaller.getSizeInfo(p.volumeUuid, packageName, userHandle, p.baseCodePath,
+ libDirRoot, publicSrcDir, asecPath, dexCodeInstructionSets, pStats);
if (res < 0) {
return false;
}
@@ -11904,6 +12574,19 @@ public class PackageManagerService extends IPackageManager.Stub {
return changed;
}
+ /** This method takes a specific user id as well as UserHandle.USER_ALL. */
+ void clearIntentFilterVerificationsLPw(String packageName, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ mSettings.removeIntentFilterVerificationLPw(packageName, sUserManager.getUserIds());
+ for (int oneUserId : sUserManager.getUserIds()) {
+ scheduleWritePackageRestrictionsLocked(oneUserId);
+ }
+ } else {
+ mSettings.removeIntentFilterVerificationLPw(packageName, userId);
+ scheduleWritePackageRestrictionsLocked(userId);
+ }
+ }
+
@Override
public void resetPreferredActivities(int userId) {
/* TODO: Actually use userId. Why is it being passed in? */
@@ -12013,13 +12696,90 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ /**
+ * Non-Binder method, support for the backup/restore mechanism: write the
+ * full set of preferred activities in its canonical XML format. Returns true
+ * on success; false otherwise.
+ */
+ @Override
+ public byte[] getPreferredActivityBackup(int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call getPreferredActivityBackup()");
+ }
+
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ try {
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(dataStream, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_PREFERRED_BACKUP);
+
+ synchronized (mPackages) {
+ mSettings.writePreferredActivitiesLPr(serializer, userId, true);
+ }
+
+ serializer.endTag(null, TAG_PREFERRED_BACKUP);
+ serializer.endDocument();
+ serializer.flush();
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Unable to write preferred activities for backup", e);
+ }
+ return null;
+ }
+
+ return dataStream.toByteArray();
+ }
+
+ @Override
+ public void restorePreferredActivities(byte[] backup, int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call restorePreferredActivities()");
+ }
+
+ try {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new ByteArrayInputStream(backup), null);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ // oops didn't find a start tag?!
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Didn't find start tag during restore");
+ }
+ return;
+ }
+
+ // this is supposed to be TAG_PREFERRED_BACKUP
+ if (!TAG_PREFERRED_BACKUP.equals(parser.getName())) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Found unexpected tag " + parser.getName());
+ }
+ return;
+ }
+
+ // skip interfering stuff, then we're aligned with the backing implementation
+ while ((type = parser.next()) == XmlPullParser.TEXT) { }
+ synchronized (mPackages) {
+ mSettings.readPreferredActivitiesLPw(parser, userId);
+ }
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
+ }
+ }
+ }
+
@Override
public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage,
- int ownerUserId, int sourceUserId, int targetUserId, int flags) {
+ int sourceUserId, int targetUserId, int flags) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
int callingUid = Binder.getCallingUid();
- enforceOwnerRights(ownerPackage, ownerUserId, callingUid);
+ enforceOwnerRights(ownerPackage, callingUid);
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
if (intentFilter.countActions() == 0) {
Slog.w(TAG, "Cannot set a crossProfile intent filter with no filter actions");
@@ -12027,7 +12787,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
synchronized (mPackages) {
CrossProfileIntentFilter newFilter = new CrossProfileIntentFilter(intentFilter,
- ownerPackage, UserHandle.getUserId(callingUid), targetUserId, flags);
+ ownerPackage, targetUserId, flags);
CrossProfileIntentResolver resolver =
mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
ArrayList<CrossProfileIntentFilter> existing = resolver.findFilters(intentFilter);
@@ -12046,22 +12806,19 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
- public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage,
- int ownerUserId) {
+ public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
int callingUid = Binder.getCallingUid();
- enforceOwnerRights(ownerPackage, ownerUserId, callingUid);
+ enforceOwnerRights(ownerPackage, callingUid);
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
- int callingUserId = UserHandle.getUserId(callingUid);
synchronized (mPackages) {
CrossProfileIntentResolver resolver =
mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
ArraySet<CrossProfileIntentFilter> set =
new ArraySet<CrossProfileIntentFilter>(resolver.filterSet());
for (CrossProfileIntentFilter filter : set) {
- if (filter.getOwnerPackage().equals(ownerPackage)
- && filter.getOwnerUserId() == callingUserId) {
+ if (filter.getOwnerPackage().equals(ownerPackage)) {
resolver.removeFilter(filter);
}
}
@@ -12070,17 +12827,12 @@ public class PackageManagerService extends IPackageManager.Stub {
}
// Enforcing that callingUid is owning pkg on userId
- private void enforceOwnerRights(String pkg, int userId, int callingUid) {
+ private void enforceOwnerRights(String pkg, int callingUid) {
// The system owns everything.
if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
return;
}
int callingUserId = UserHandle.getUserId(callingUid);
- if (callingUserId != userId) {
- throw new SecurityException("calling uid " + callingUid
- + " pretends to own " + pkg + " on user " + userId + " but belongs to user "
- + callingUserId);
- }
PackageInfo pi = getPackageInfo(pkg, 0, callingUserId);
if (pi == null) {
throw new IllegalArgumentException("Unknown package " + pkg + " on user "
@@ -12221,7 +12973,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return;
}
}
- mSettings.writePackageRestrictionsLPr(userId);
+ scheduleWritePackageRestrictionsLocked(userId);
components = mPendingBroadcasts.get(userId, packageName);
final boolean newPackage = components == null;
if (newPackage) {
@@ -12382,6 +13134,12 @@ public class PackageManagerService extends IPackageManager.Stub {
}
mPostSystemReadyMessages = null;
}
+
+ // Watch for external volumes that come and go over time
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ storage.registerListener(mStorageListener);
+
+ mInstallerService.systemReady();
}
@Override
@@ -12422,6 +13180,8 @@ public class PackageManagerService extends IPackageManager.Stub {
public static final int DUMP_KEYSETS = 1 << 11;
public static final int DUMP_VERSION = 1 << 12;
public static final int DUMP_INSTALLS = 1 << 13;
+ public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 14;
+ public static final int DUMP_DOMAIN_PREFERRED = 1 << 15;
public static final int OPTION_SHOW_FILTERS = 1 << 0;
@@ -12527,6 +13287,8 @@ public class PackageManagerService extends IPackageManager.Stub {
pw.println(" write: write current settings now");
pw.println(" <package.name>: info about given package");
pw.println(" installs: details about install sessions");
+ pw.println(" d[omain-preferred-apps]: print domains preferred apps");
+ pw.println(" i[ntent-filter-verifiers]|ifv: print intent filter verifier info");
return;
} else if ("--checkin".equals(opt)) {
checkin = true;
@@ -12563,6 +13325,8 @@ public class PackageManagerService extends IPackageManager.Stub {
fullPreferred = true;
opti++;
}
+ } else if ("d".equals(cmd) || "domain-preferred-apps".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_DOMAIN_PREFERRED);
} else if ("p".equals(cmd) || "packages".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_PACKAGES);
} else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
@@ -12573,6 +13337,9 @@ public class PackageManagerService extends IPackageManager.Stub {
dumpState.setDump(DumpState.DUMP_MESSAGES);
} else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERIFIERS);
+ } else if ("i".equals(cmd) || "ifv".equals(cmd)
+ || "intent-filter-verifiers".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_INTENT_FILTER_VERIFIERS);
} else if ("version".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_VERSION);
} else if ("k".equals(cmd) || "keysets".equals(cmd)) {
@@ -12628,6 +13395,29 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ if (dumpState.isDumping(DumpState.DUMP_INTENT_FILTER_VERIFIERS) &&
+ packageName == null) {
+ if (mIntentFilterVerifierComponent != null) {
+ String verifierPackageName = mIntentFilterVerifierComponent.getPackageName();
+ if (!checkin) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Intent Filter Verifier:");
+ pw.print(" Using: ");
+ pw.print(verifierPackageName);
+ pw.print(" (uid=");
+ pw.print(getPackageUid(verifierPackageName, 0));
+ pw.println(")");
+ } else if (verifierPackageName != null) {
+ pw.print("ifv,"); pw.print(verifierPackageName);
+ pw.print(","); pw.println(getPackageUid(verifierPackageName, 0));
+ }
+ } else {
+ pw.println();
+ pw.println("No Intent Filter Verifier available!");
+ }
+ }
+
if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) {
boolean printedHeader = false;
final Iterator<String> it = mSharedLibraries.keySet().iterator();
@@ -12747,6 +13537,65 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
+ pw.println();
+ int count = mSettings.mPackages.size();
+ if (count == 0) {
+ pw.println("No domain preferred apps!");
+ pw.println();
+ } else {
+ final String prefix = " ";
+ Collection<PackageSetting> allPackageSettings = mSettings.mPackages.values();
+ if (allPackageSettings.size() == 0) {
+ pw.println("No domain preferred apps!");
+ pw.println();
+ } else {
+ pw.println("Domain preferred apps status:");
+ pw.println();
+ count = 0;
+ for (PackageSetting ps : allPackageSettings) {
+ IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
+ if (ivi == null || ivi.getPackageName() == null) continue;
+ pw.println(prefix + "Package Name: " + ivi.getPackageName());
+ pw.println(prefix + "Domains: " + ivi.getDomainsString());
+ pw.println(prefix + "Status: " + ivi.getStatusString());
+ pw.println();
+ count++;
+ }
+ if (count == 0) {
+ pw.println(prefix + "No domain preferred app status!");
+ pw.println();
+ }
+ for (int userId : sUserManager.getUserIds()) {
+ pw.println("Domain preferred apps for User " + userId + ":");
+ pw.println();
+ count = 0;
+ for (PackageSetting ps : allPackageSettings) {
+ IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
+ if (ivi == null || ivi.getPackageName() == null) {
+ continue;
+ }
+ final int status = ps.getDomainVerificationStatusForUser(userId);
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ continue;
+ }
+ pw.println(prefix + "Package Name: " + ivi.getPackageName());
+ pw.println(prefix + "Domains: " + ivi.getDomainsString());
+ String statusStr = IntentFilterVerificationInfo.
+ getStatusStringFromValue(status);
+ pw.println(prefix + "Status: " + statusStr);
+ pw.println();
+ count++;
+ }
+ if (count == 0) {
+ pw.println(prefix + "No domain preferred apps!");
+ pw.println();
+ }
+ }
+ }
+ }
+ }
+
if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
mSettings.dumpPermissionsLPr(pw, packageName, dumpState);
if (packageName == null) {
@@ -12988,7 +13837,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final AsecInstallArgs args = new AsecInstallArgs(cid,
- getAppDexInstructionSets(ps), isForwardLocked(ps));
+ getAppDexInstructionSets(ps), ps.isForwardLocked());
// The package status is changed only if the code path
// matches between settings and the container id.
if (ps.codePathString != null
@@ -13029,13 +13878,32 @@ public class PackageManagerService extends IPackageManager.Stub {
}
private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
+ ArrayList<ApplicationInfo> infos, IIntentReceiver finishedReceiver) {
+ final int size = infos.size();
+ final String[] packageNames = new String[size];
+ final int[] packageUids = new int[size];
+ for (int i = 0; i < size; i++) {
+ final ApplicationInfo info = infos.get(i);
+ packageNames[i] = info.packageName;
+ packageUids[i] = info.uid;
+ }
+ sendResourcesChangedBroadcast(mediaStatus, replacing, packageNames, packageUids,
+ finishedReceiver);
+ }
+
+ private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) {
- int size = pkgList.size();
+ sendResourcesChangedBroadcast(mediaStatus, replacing,
+ pkgList.toArray(new String[pkgList.size()]), uidArr, finishedReceiver);
+ }
+
+ private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
+ String[] pkgList, int uidArr[], IIntentReceiver finishedReceiver) {
+ int size = pkgList.length;
if (size > 0) {
// Send broadcasts here
Bundle extras = new Bundle();
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList
- .toArray(new String[size]));
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
if (uidArr != null) {
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr);
}
@@ -13078,8 +13946,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
// Parse package
int parseFlags = mDefParseFlags;
- if (args.isExternal()) {
- parseFlags |= PackageParser.PARSE_ON_SDCARD;
+ if (args.isExternalAsec()) {
+ parseFlags |= PackageParser.PARSE_EXTERNAL_STORAGE;
}
if (args.isFwdLocked()) {
parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
@@ -13226,75 +14094,164 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- /** Binder call */
+ private void loadPrivatePackages(VolumeInfo vol) {
+ final ArrayList<ApplicationInfo> loaded = new ArrayList<>();
+ final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE;
+ synchronized (mPackages) {
+ final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
+ for (PackageSetting ps : packages) {
+ synchronized (mInstallLock) {
+ final PackageParser.Package pkg;
+ try {
+ pkg = scanPackageLI(ps.codePath, parseFlags, 0, 0, null);
+ loaded.add(pkg.applicationInfo);
+ } catch (PackageManagerException e) {
+ Slog.w(TAG, "Failed to scan " + ps.codePath + ": " + e.getMessage());
+ }
+ }
+ }
+
+ // TODO: regrant any permissions that changed based since original install
+
+ mSettings.writeLPr();
+ }
+
+ Slog.d(TAG, "Loaded packages " + loaded);
+ sendResourcesChangedBroadcast(true, false, loaded, null);
+ }
+
+ private void unloadPrivatePackages(VolumeInfo vol) {
+ final ArrayList<ApplicationInfo> unloaded = new ArrayList<>();
+ synchronized (mPackages) {
+ final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
+ for (PackageSetting ps : packages) {
+ if (ps.pkg == null) continue;
+ synchronized (mInstallLock) {
+ final ApplicationInfo info = ps.pkg.applicationInfo;
+ final PackageRemovedInfo outInfo = new PackageRemovedInfo();
+ if (deletePackageLI(ps.name, null, false, null, null,
+ PackageManager.DELETE_KEEP_DATA, outInfo, false)) {
+ unloaded.add(info);
+ } else {
+ Slog.w(TAG, "Failed to unload " + ps.codePath);
+ }
+ }
+ }
+
+ mSettings.writeLPr();
+ }
+
+ Slog.d(TAG, "Unloaded packages " + unloaded);
+ sendResourcesChangedBroadcast(false, false, unloaded, null);
+ }
+
@Override
- public void movePackage(final String packageName, final IPackageMoveObserver observer,
- final int flags) {
+ public int movePackage(final String packageName, final String volumeUuid) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null);
- UserHandle user = new UserHandle(UserHandle.getCallingUserId());
- int returnCode = PackageManager.MOVE_SUCCEEDED;
- int currInstallFlags = 0;
- int newInstallFlags = 0;
- File codeFile = null;
- String installerPackageName = null;
- String packageAbiOverride = null;
+ final int moveId = mNextMoveId.getAndIncrement();
+ try {
+ movePackageInternal(packageName, volumeUuid, moveId);
+ } catch (PackageManagerException e) {
+ Slog.d(TAG, "Failed to move " + packageName, e);
+ mMoveCallbacks.notifyStatusChanged(moveId, PackageManager.MOVE_FAILED_INTERNAL_ERROR);
+ }
+ return moveId;
+ }
+
+ private void movePackageInternal(final String packageName, final String volumeUuid,
+ final int moveId) throws PackageManagerException {
+ final UserHandle user = new UserHandle(UserHandle.getCallingUserId());
+ final PackageManager pm = mContext.getPackageManager();
+
+ final boolean currentAsec;
+ final String currentVolumeUuid;
+ final File codeFile;
+ final String installerPackageName;
+ final String packageAbiOverride;
+ final int appId;
+ final String seinfo;
// reader
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (pkg == null || ps == null) {
- returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
- } else {
- // Disable moving fwd locked apps and system packages
- if (pkg.applicationInfo != null && isSystemApp(pkg)) {
- Slog.w(TAG, "Cannot move system application");
- returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
- } else if (pkg.mOperationPending) {
- Slog.w(TAG, "Attempt to move package which has pending operations");
- returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING;
- } else {
- // Find install location first
- if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0
- && (flags & PackageManager.MOVE_INTERNAL) != 0) {
- Slog.w(TAG, "Ambigous flags specified for move location.");
- returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION;
- } else {
- newInstallFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0
- ? PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL;
- currInstallFlags = isExternal(pkg)
- ? PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL;
-
- if (newInstallFlags == currInstallFlags) {
- Slog.w(TAG, "No move required. Trying to move to same location");
- returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION;
- } else {
- if (isForwardLocked(pkg)) {
- currInstallFlags |= PackageManager.INSTALL_FORWARD_LOCK;
- newInstallFlags |= PackageManager.INSTALL_FORWARD_LOCK;
- }
- }
- }
- if (returnCode == PackageManager.MOVE_SUCCEEDED) {
- pkg.mOperationPending = true;
- }
- }
+ throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
+ }
- codeFile = new File(pkg.codePath);
- installerPackageName = ps.installerPackageName;
- packageAbiOverride = ps.cpuAbiOverrideString;
+ if (pkg.applicationInfo.isSystemApp()) {
+ throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE,
+ "Cannot move system application");
+ } else if (pkg.mOperationPending) {
+ throw new PackageManagerException(MOVE_FAILED_OPERATION_PENDING,
+ "Attempt to move package which has pending operations");
}
+
+ // TODO: yell if already in desired location
+
+ mMoveCallbacks.notifyStarted(moveId,
+ String.valueOf(pm.getApplicationLabel(pkg.applicationInfo)));
+
+ pkg.mOperationPending = true;
+
+ currentAsec = pkg.applicationInfo.isForwardLocked()
+ || pkg.applicationInfo.isExternalAsec();
+ currentVolumeUuid = ps.volumeUuid;
+ codeFile = new File(pkg.codePath);
+ installerPackageName = ps.installerPackageName;
+ packageAbiOverride = ps.cpuAbiOverrideString;
+ appId = UserHandle.getAppId(pkg.applicationInfo.uid);
+ seinfo = pkg.applicationInfo.seinfo;
}
- if (returnCode != PackageManager.MOVE_SUCCEEDED) {
- try {
- observer.packageMoved(packageName, returnCode);
- } catch (RemoteException ignored) {
+ int installFlags;
+ final boolean moveData;
+
+ if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
+ installFlags = INSTALL_INTERNAL;
+ moveData = !currentAsec;
+ } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
+ installFlags = INSTALL_EXTERNAL;
+ moveData = false;
+ } else {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final VolumeInfo volume = storage.findVolumeByUuid(volumeUuid);
+ if (volume == null || volume.getType() != VolumeInfo.TYPE_PRIVATE
+ || !volume.isMountedWritable()) {
+ throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
+ "Move location not mounted private volume");
}
- return;
+
+ Preconditions.checkState(!currentAsec);
+
+ installFlags = INSTALL_INTERNAL;
+ moveData = true;
}
+ Slog.d(TAG, "Moving " + packageName + " from " + currentVolumeUuid + " to " + volumeUuid);
+ mMoveCallbacks.notifyStatusChanged(moveId, 10, -1);
+
+ if (moveData) {
+ synchronized (mInstallLock) {
+ // TODO: split this into separate copy and delete operations
+ if (mInstaller.moveUserDataDirs(currentVolumeUuid, volumeUuid, packageName, appId,
+ seinfo) != 0) {
+ synchronized (mPackages) {
+ final PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg != null) {
+ pkg.mOperationPending = false;
+ }
+ }
+
+ throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
+ "Failed to move private data");
+ }
+ }
+ }
+
+ mMoveCallbacks.notifyStatusChanged(moveId, 50);
+
final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() {
@Override
public void onUserActionRequired(Intent intent) throws RemoteException {
@@ -13320,13 +14277,16 @@ public class PackageManagerService extends IPackageManager.Stub {
final int status = PackageManager.installStatusToPublicStatus(returnCode);
switch (status) {
case PackageInstaller.STATUS_SUCCESS:
- observer.packageMoved(packageName, PackageManager.MOVE_SUCCEEDED);
+ mMoveCallbacks.notifyStatusChanged(moveId,
+ PackageManager.MOVE_SUCCEEDED);
break;
case PackageInstaller.STATUS_FAILURE_STORAGE:
- observer.packageMoved(packageName, PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE);
+ mMoveCallbacks.notifyStatusChanged(moveId,
+ PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE);
break;
default:
- observer.packageMoved(packageName, PackageManager.MOVE_FAILED_INTERNAL_ERROR);
+ mMoveCallbacks.notifyStatusChanged(moveId,
+ PackageManager.MOVE_FAILED_INTERNAL_ERROR);
break;
}
}
@@ -13334,16 +14294,49 @@ public class PackageManagerService extends IPackageManager.Stub {
// Treat a move like reinstalling an existing app, which ensures that we
// process everythign uniformly, like unpacking native libraries.
- newInstallFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+ installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
final Message msg = mHandler.obtainMessage(INIT_COPY);
final OriginInfo origin = OriginInfo.fromExistingFile(codeFile);
- msg.obj = new InstallParams(origin, installObserver, newInstallFlags,
- installerPackageName, null, user, packageAbiOverride);
+ msg.obj = new InstallParams(origin, installObserver, installFlags,
+ installerPackageName, volumeUuid, null, user, packageAbiOverride);
mHandler.sendMessage(msg);
}
@Override
+ public int movePrimaryStorage(String volumeUuid) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null);
+
+ final int moveId = mNextMoveId.getAndIncrement();
+
+ // TODO: ask mountservice to take down both, connect over to DCS to
+ // migrate, and then bring up new storage
+
+ return moveId;
+ }
+
+ @Override
+ public int getMoveStatus(int moveId) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+ return mMoveCallbacks.mLastStatus.get(moveId);
+ }
+
+ @Override
+ public void registerMoveCallback(IPackageMoveObserver callback) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+ mMoveCallbacks.register(callback);
+ }
+
+ @Override
+ public void unregisterMoveCallback(IPackageMoveObserver callback) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+ mMoveCallbacks.unregister(callback);
+ }
+
+ @Override
public boolean setInstallLocation(int loc) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
null);
@@ -13375,7 +14368,15 @@ public class PackageManagerService extends IPackageManager.Stub {
// Technically, we shouldn't be doing this with the package lock
// held. However, this is very rare, and there is already so much
// other disk I/O going on, that we'll let it slide for now.
- mInstaller.removeUserDataDirs(userHandle);
+ final StorageManager storage = StorageManager.from(mContext);
+ final List<VolumeInfo> vols = storage.getVolumes();
+ for (VolumeInfo vol : vols) {
+ if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
+ final String volumeUuid = vol.getFsUuid();
+ Slog.d(TAG, "Removing user data on volume " + volumeUuid);
+ mInstaller.removeUserDataDirs(volumeUuid, userHandle);
+ }
+ }
}
mUserNeedsBadging.delete(userHandle);
removeUnusedPackagesLILPw(userManager, userHandle);
@@ -13435,6 +14436,11 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ void newUserCreatedLILPw(int userHandle) {
+ // Adding a user requires updating runtime permissions for system apps.
+ updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL);
+ }
+
@Override
public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
mContext.enforceCallingOrSelfPermission(
@@ -13653,4 +14659,82 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
}
+
+ private static class MoveCallbacks extends Handler {
+ private static final int MSG_STARTED = 1;
+ private static final int MSG_STATUS_CHANGED = 2;
+
+ private final RemoteCallbackList<IPackageMoveObserver>
+ mCallbacks = new RemoteCallbackList<>();
+
+ private final SparseIntArray mLastStatus = new SparseIntArray();
+
+ public MoveCallbacks(Looper looper) {
+ super(looper);
+ }
+
+ public void register(IPackageMoveObserver callback) {
+ mCallbacks.register(callback);
+ }
+
+ public void unregister(IPackageMoveObserver callback) {
+ mCallbacks.unregister(callback);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ final IPackageMoveObserver callback = mCallbacks.getBroadcastItem(i);
+ try {
+ invokeCallback(callback, msg.what, args);
+ } catch (RemoteException ignored) {
+ }
+ }
+ mCallbacks.finishBroadcast();
+ args.recycle();
+ }
+
+ private void invokeCallback(IPackageMoveObserver callback, int what, SomeArgs args)
+ throws RemoteException {
+ switch (what) {
+ case MSG_STARTED: {
+ callback.onStarted(args.argi1, (String) args.arg2);
+ break;
+ }
+ case MSG_STATUS_CHANGED: {
+ callback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3);
+ break;
+ }
+ }
+ }
+
+ private void notifyStarted(int moveId, String title) {
+ Slog.v(TAG, "Move " + moveId + " started with title " + title);
+
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = moveId;
+ args.arg2 = title;
+ obtainMessage(MSG_STARTED, args).sendToTarget();
+ }
+
+ private void notifyStatusChanged(int moveId, int status) {
+ notifyStatusChanged(moveId, status, -1);
+ }
+
+ private void notifyStatusChanged(int moveId, int status, long estMillis) {
+ Slog.v(TAG, "Move " + moveId + " status " + status);
+
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = moveId;
+ args.argi2 = status;
+ args.arg3 = estMillis;
+ obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget();
+
+ synchronized (mLastStatus) {
+ mLastStatus.put(moveId, status);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 696aa34..e7c0ef7 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -32,10 +32,10 @@ final class PackageSetting extends PackageSettingBase {
PackageSetting(String name, String realName, File codePath, File resourcePath,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
- int pVersionCode, int pkgFlags) {
+ int pVersionCode, int pkgFlags, int privateFlags) {
super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
- pVersionCode, pkgFlags);
+ pVersionCode, pkgFlags, privateFlags);
}
/**
@@ -57,11 +57,25 @@ final class PackageSetting extends PackageSettingBase {
+ " " + name + "/" + appId + "}";
}
- public int[] getGids() {
- return sharedUser != null ? sharedUser.gids : gids;
+ public PermissionsState getPermissionsState() {
+ return (sharedUser != null)
+ ? sharedUser.getPermissionsState()
+ : super.getPermissionsState();
}
public boolean isPrivileged() {
- return (pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0;
+ return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
+ }
+
+ public boolean isForwardLocked() {
+ return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
+ }
+
+ public boolean isSystem() {
+ return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ public boolean isSharedUser() {
+ return sharedUser != null;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 1dcadb4..5429517 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -20,7 +20,11 @@ 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_ENABLED;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageUserState;
+import android.os.storage.VolumeInfo;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -29,7 +33,7 @@ import java.io.File;
/**
* Settings base class for pending and resolved classes.
*/
-class PackageSettingBase extends GrantedPermissions {
+abstract class PackageSettingBase extends SettingBase {
/**
* Indicates the state of installation. Used by PackageManager to figure out
* incomplete installations. Say a package is being installed (the state is
@@ -92,8 +96,7 @@ class PackageSettingBase extends GrantedPermissions {
PackageSignatures signatures = new PackageSignatures();
- boolean permissionsFixed;
- boolean haveGids;
+ boolean installPermissionsFixed;
PackageKeySetData keySetData = new PackageKeySetData();
@@ -107,13 +110,18 @@ class PackageSettingBase extends GrantedPermissions {
PackageSettingBase origPackage;
- /* package name of the app that installed this package */
+ /** Package name of the app that installed this package */
String installerPackageName;
+ /** UUID of {@link VolumeInfo} hosting this app */
+ String volumeUuid;
+
+ IntentFilterVerificationInfo verificationInfo;
+
PackageSettingBase(String name, String realName, File codePath, File resourcePath,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
- int pVersionCode, int pkgFlags) {
- super(pkgFlags);
+ int pVersionCode, int pkgFlags, int pkgPrivateFlags) {
+ super(pkgFlags, pkgPrivateFlags);
this.name = name;
this.realName = realName;
init(codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString,
@@ -146,8 +154,7 @@ class PackageSettingBase extends GrantedPermissions {
signatures = new PackageSignatures(base.signatures);
- permissionsFixed = base.permissionsFixed;
- haveGids = base.haveGids;
+ installPermissionsFixed = base.installPermissionsFixed;
userState.clear();
for (int i=0; i<base.userState.size(); i++) {
userState.put(base.userState.keyAt(i),
@@ -158,9 +165,9 @@ class PackageSettingBase extends GrantedPermissions {
origPackage = base.origPackage;
installerPackageName = base.installerPackageName;
+ volumeUuid = base.volumeUuid;
keySetData = new PackageKeySetData(base.keySetData);
-
}
void init(File codePath, File resourcePath, String legacyNativeLibraryPathString,
@@ -181,10 +188,18 @@ class PackageSettingBase extends GrantedPermissions {
installerPackageName = packageName;
}
- String getInstallerPackageName() {
+ public String getInstallerPackageName() {
return installerPackageName;
}
+ public void setVolumeUuid(String volumeUuid) {
+ this.volumeUuid = volumeUuid;
+ }
+
+ public String getVolumeUuid() {
+ return volumeUuid;
+ }
+
public void setInstallStatus(int newStatus) {
installStatus = newStatus;
}
@@ -201,9 +216,8 @@ class PackageSettingBase extends GrantedPermissions {
* Make a shallow copy of this package settings.
*/
public void copyFrom(PackageSettingBase base) {
- grantedPermissions = base.grantedPermissions;
- gids = base.gids;
-
+ setPermissionsUpdatedForUserIds(base.getPermissionsUpdatedForUserIds());
+ mPermissionsState.copyFrom(base.mPermissionsState);
primaryCpuAbiString = base.primaryCpuAbiString;
secondaryCpuAbiString = base.secondaryCpuAbiString;
cpuAbiOverrideString = base.cpuAbiOverrideString;
@@ -211,14 +225,14 @@ class PackageSettingBase extends GrantedPermissions {
firstInstallTime = base.firstInstallTime;
lastUpdateTime = base.lastUpdateTime;
signatures = base.signatures;
- permissionsFixed = base.permissionsFixed;
- haveGids = base.haveGids;
+ installPermissionsFixed = base.installPermissionsFixed;
userState.clear();
for (int i=0; i<base.userState.size(); i++) {
userState.put(base.userState.keyAt(i), base.userState.valueAt(i));
}
installStatus = base.installStatus;
keySetData = base.keySetData;
+ verificationInfo = base.verificationInfo;
}
private PackageUserState modifyUserState(int userId) {
@@ -322,7 +336,7 @@ class PackageSettingBase extends GrantedPermissions {
void setUserState(int userId, int enabled, boolean installed, boolean stopped,
boolean notLaunched, boolean hidden,
String lastDisableAppCaller, ArraySet<String> enabledComponents,
- ArraySet<String> disabledComponents, boolean blockUninstall) {
+ ArraySet<String> disabledComponents, boolean blockUninstall, int domainVerifState) {
PackageUserState state = modifyUserState(userId);
state.enabled = enabled;
state.installed = installed;
@@ -333,6 +347,7 @@ class PackageSettingBase extends GrantedPermissions {
state.enabledComponents = enabledComponents;
state.disabledComponents = disabledComponents;
state.blockUninstall = blockUninstall;
+ state.domainVerificationStatus = domainVerifState;
}
ArraySet<String> getEnabledComponents(int userId) {
@@ -420,4 +435,25 @@ class PackageSettingBase extends GrantedPermissions {
void removeUser(int userId) {
userState.delete(userId);
}
+
+ IntentFilterVerificationInfo getIntentFilterVerificationInfo() {
+ return verificationInfo;
+ }
+
+ void setIntentFilterVerificationInfo(IntentFilterVerificationInfo info) {
+ verificationInfo = info;
+ }
+
+ int getDomainVerificationStatusForUser(int userId) {
+ return readUserState(userId).domainVerificationStatus;
+ }
+
+ void setDomainVerificationStatusForUser(int status, int userId) {
+ modifyUserState(userId).domainVerificationStatus = status;
+ }
+
+ void clearDomainVerificationStatusForUser(int userId) {
+ modifyUserState(userId).domainVerificationStatus =
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PendingPackage.java b/services/core/java/com/android/server/pm/PendingPackage.java
index 5d30e76..bb0dba1 100644
--- a/services/core/java/com/android/server/pm/PendingPackage.java
+++ b/services/core/java/com/android/server/pm/PendingPackage.java
@@ -24,10 +24,10 @@ final class PendingPackage extends PackageSettingBase {
PendingPackage(String name, String realName, File codePath, File resourcePath,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString, int sharedId,
- int pVersionCode, int pkgFlags) {
+ int pVersionCode, int pkgFlags, int pkgPrivateFlags) {
super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
- pVersionCode, pkgFlags);
+ pVersionCode, pkgFlags, pkgPrivateFlags);
this.sharedId = sharedId;
}
}
diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java
new file mode 100644
index 0000000..3749957
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PermissionsState.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * This class encapsulates the permissions for a package or a shared user.
+ * <p>
+ * There are two types of permissions: install (granted at installation)
+ * and runtime (granted at runtime). Install permissions are granted to
+ * all device users while runtime permissions are granted explicitly to
+ * specific users.
+ * </p>
+ * <p>
+ * The permissions are kept on a per device user basis. For example, an
+ * application may have some runtime permissions granted under the device
+ * owner but not granted under the secondary user.
+ * <p>
+ * This class is also responsible for keeping track of the Linux gids per
+ * user for a package or a shared user. The gids are computed as a set of
+ * the gids for all granted permissions' gids on a per user basis.
+ * </p>
+ */
+public final class PermissionsState {
+
+ /** The permission operation succeeded and no gids changed. */
+ public static final int PERMISSION_OPERATION_SUCCESS = 1;
+
+ /** The permission operation succeeded and gids changed. */
+ public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 2;
+
+ /** The permission operation failed. */
+ public static final int PERMISSION_OPERATION_FAILURE = 3;
+
+ public static final int[] USERS_ALL = {UserHandle.USER_ALL};
+
+ public static final int[] USERS_NONE = {};
+
+ private static final int[] NO_GIDS = {};
+
+ private static final int FLAG_INSTALL_PERMISSIONS = 1 << 0;
+ private static final int FLAG_RUNTIME_PERMISSIONS = 1 << 1;
+
+ private ArrayMap<String, PermissionData> mPermissions;
+
+ private int[] mGlobalGids = NO_GIDS;
+
+ public PermissionsState() {
+ /* do nothing */
+ }
+
+ public PermissionsState(PermissionsState prototype) {
+ copyFrom(prototype);
+ }
+
+ /**
+ * Sets the global gids, applicable to all users.
+ *
+ * @param globalGids The global gids.
+ */
+ public void setGlobalGids(int[] globalGids) {
+ if (!ArrayUtils.isEmpty(globalGids)) {
+ mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
+ }
+ }
+
+ /**
+ * Initialized this instance from another one.
+ *
+ * @param other The other instance.
+ */
+ public void copyFrom(PermissionsState other) {
+ if (other == this) {
+ return;
+ }
+ if (mPermissions != null) {
+ if (other.mPermissions == null) {
+ mPermissions = null;
+ } else {
+ mPermissions.clear();
+ }
+ }
+ if (other.mPermissions != null) {
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ }
+ final int permissionCount = other.mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ String name = other.mPermissions.keyAt(i);
+ PermissionData permissionData = other.mPermissions.valueAt(i);
+ mPermissions.put(name, new PermissionData(permissionData));
+ }
+ }
+
+ mGlobalGids = NO_GIDS;
+ if (other.mGlobalGids != NO_GIDS) {
+ mGlobalGids = Arrays.copyOf(other.mGlobalGids,
+ other.mGlobalGids.length);
+ }
+ }
+
+ /**
+ * Grant an install permission.
+ *
+ * @param permission The permission to grant.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int grantInstallPermission(BasePermission permission) {
+ return grantPermission(permission, UserHandle.USER_ALL);
+ }
+
+ /**
+ * Revoke an install permission.
+ *
+ * @param permission The permission to revoke.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int revokeInstallPermission(BasePermission permission) {
+ return revokePermission(permission, UserHandle.USER_ALL);
+ }
+
+ /**
+ * Grant a runtime permission.
+ *
+ * @param permission The permission to grant.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int grantRuntimePermission(BasePermission permission, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+ return grantPermission(permission, userId);
+ }
+
+ /**
+ * Revoke a runtime permission for a given device user.
+ *
+ * @param permission The permission to revoke.
+ * @param userId The device user id.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int revokeRuntimePermission(BasePermission permission, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+ return revokePermission(permission, userId);
+ }
+
+ /**
+ * Gets whether this state has a given permission, regardless if
+ * it is install time or runtime one.
+ *
+ * @param name The permission name.
+ * @return Whether this state has the permission.
+ */
+ public boolean hasPermission(String name) {
+ return mPermissions != null && mPermissions.get(name) != null;
+ }
+
+ /**
+ * Gets whether this state has a given runtime permission for a
+ * given device user id.
+ *
+ * @param name The permission name.
+ * @param userId The device user id.
+ * @return Whether this state has the permission.
+ */
+ public boolean hasRuntimePermission(String name, int userId) {
+ return !hasInstallPermission(name) && hasPermission(name, userId);
+ }
+
+ /**
+ * Gets whether this state has a given install permission.
+ *
+ * @param name The permission name.
+ * @return Whether this state has the permission.
+ */
+ public boolean hasInstallPermission(String name) {
+ return hasPermission(name, UserHandle.USER_ALL);
+ }
+
+ /**
+ * Revokes a permission for all users regardless if it is an install or
+ * a runtime permission.
+ *
+ * @param permission The permission to revoke.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int revokePermission(BasePermission permission) {
+ if (!hasPermission(permission.name)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ int result = PERMISSION_OPERATION_SUCCESS;
+
+ PermissionData permissionData = mPermissions.get(permission.name);
+ for (int userId : permissionData.getUserIds()) {
+ if (revokePermission(permission, userId)
+ == PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+ result = PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+ break;
+ }
+ }
+
+ mPermissions.remove(permission.name);
+
+ return result;
+ }
+
+ /**
+ * Gets whether the state has a given permission for the specified
+ * user, regardless if this is an install or a runtime permission.
+ *
+ * @param name The permission name.
+ * @param userId The device user id.
+ * @return Whether the user has the permission.
+ */
+ public boolean hasPermission(String name, int userId) {
+ enforceValidUserId(userId);
+
+ if (mPermissions == null) {
+ return false;
+ }
+
+ PermissionData permissionData = mPermissions.get(name);
+ return permissionData != null && permissionData.hasUserId(userId);
+ }
+
+ /**
+ * Gets all permissions regardless if they are install or runtime.
+ *
+ * @return The permissions or an empty set.
+ */
+ public Set<String> getPermissions() {
+ if (mPermissions != null) {
+ return mPermissions.keySet();
+ }
+
+ return Collections.emptySet();
+ }
+
+ /**
+ * Gets all permissions for a given device user id regardless if they
+ * are install time or runtime permissions.
+ *
+ * @param userId The device user id.
+ * @return The permissions or an empty set.
+ */
+ public Set<String> getPermissions(int userId) {
+ return getPermissionsInternal(FLAG_INSTALL_PERMISSIONS | FLAG_RUNTIME_PERMISSIONS, userId);
+ }
+
+ /**
+ * Gets all runtime permissions.
+ *
+ * @return The permissions or an empty set.
+ */
+ public Set<String> getRuntimePermissions(int userId) {
+ return getPermissionsInternal(FLAG_RUNTIME_PERMISSIONS, userId);
+ }
+
+ /**
+ * Gets all install permissions.
+ *
+ * @return The permissions or an empty set.
+ */
+ public Set<String> getInstallPermissions() {
+ return getPermissionsInternal(FLAG_INSTALL_PERMISSIONS, UserHandle.USER_ALL);
+ }
+
+ /**
+ * Compute the Linux gids for a given device user from the permissions
+ * granted to this user. Note that these are computed to avoid additional
+ * state as they are rarely accessed.
+ *
+ * @param userId The device user id.
+ * @return The gids for the device user.
+ */
+ public int[] computeGids(int userId) {
+ enforceValidUserId(userId);
+
+ int[] gids = mGlobalGids;
+
+ if (mPermissions != null) {
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ String permission = mPermissions.keyAt(i);
+ if (!hasPermission(permission, userId)) {
+ continue;
+ }
+ PermissionData permissionData = mPermissions.valueAt(i);
+ final int[] permGids = permissionData.computeGids(userId);
+ if (permGids != NO_GIDS) {
+ gids = appendInts(gids, permGids);
+ }
+ }
+ }
+
+ return gids;
+ }
+
+ /**
+ * Compute the Linux gids for all device users from the permissions
+ * granted to these users.
+ *
+ * @return The gids for all device users.
+ */
+ public int[] computeGids() {
+ int[] gids = mGlobalGids;
+
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ final int[] userGids = computeGids(userId);
+ gids = appendInts(gids, userGids);
+ }
+
+ return gids;
+ }
+
+ /**
+ * Resets the internal state of this object.
+ */
+ public void reset() {
+ mGlobalGids = NO_GIDS;
+ mPermissions = null;
+ }
+
+ private Set<String> getPermissionsInternal(int flags, int userId) {
+ enforceValidUserId(userId);
+
+ if (mPermissions == null) {
+ return Collections.emptySet();
+ }
+
+ if (userId == UserHandle.USER_ALL) {
+ flags = FLAG_INSTALL_PERMISSIONS;
+ }
+
+ Set<String> permissions = new ArraySet<>();
+
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ String permission = mPermissions.keyAt(i);
+
+ if ((flags & FLAG_INSTALL_PERMISSIONS) != 0) {
+ if (hasInstallPermission(permission)) {
+ permissions.add(permission);
+ }
+ }
+ if ((flags & FLAG_RUNTIME_PERMISSIONS) != 0) {
+ if (hasRuntimePermission(permission, userId)) {
+ permissions.add(permission);
+ }
+ }
+ }
+
+ return permissions;
+ }
+
+ private int grantPermission(BasePermission permission, int userId) {
+ if (hasPermission(permission.name, userId)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
+ final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
+
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ }
+
+ PermissionData permissionData = mPermissions.get(permission.name);
+ if (permissionData == null) {
+ permissionData = new PermissionData(permission);
+ mPermissions.put(permission.name, permissionData);
+ }
+
+ if (!permissionData.addUserId(userId)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ if (hasGids) {
+ final int[] newGids = computeGids(userId);
+ if (oldGids.length != newGids.length) {
+ return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+ }
+ }
+
+ return PERMISSION_OPERATION_SUCCESS;
+ }
+
+ private int revokePermission(BasePermission permission, int userId) {
+ if (!hasPermission(permission.name, userId)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
+ final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
+
+ PermissionData permissionData = mPermissions.get(permission.name);
+
+ if (!permissionData.removeUserId(userId)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ if (permissionData.getUserIds() == USERS_NONE) {
+ mPermissions.remove(permission.name);
+ }
+
+ if (mPermissions.isEmpty()) {
+ mPermissions = null;
+ }
+
+ if (hasGids) {
+ final int[] newGids = computeGids(userId);
+ if (oldGids.length != newGids.length) {
+ return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+ }
+ }
+
+ return PERMISSION_OPERATION_SUCCESS;
+ }
+
+ private static int[] appendInts(int[] current, int[] added) {
+ if (current != null && added != null) {
+ for (int guid : added) {
+ current = ArrayUtils.appendInt(current, guid);
+ }
+ }
+ return current;
+ }
+
+ private static void enforceValidUserId(int userId) {
+ if (userId != UserHandle.USER_ALL && userId < 0) {
+ throw new IllegalArgumentException("Invalid userId:" + userId);
+ }
+ }
+
+ private static final class PermissionData {
+ private final BasePermission mPerm;
+ private int[] mUserIds = USERS_NONE;
+
+ public PermissionData(BasePermission perm) {
+ mPerm = perm;
+ }
+
+ public PermissionData(PermissionData other) {
+ this(other.mPerm);
+
+ if (other.mUserIds == USERS_ALL || other.mUserIds == USERS_NONE) {
+ mUserIds = other.mUserIds;
+ } else {
+ mUserIds = Arrays.copyOf(other.mUserIds, other.mUserIds.length);
+ }
+ }
+
+ public int[] computeGids(int userId) {
+ return mPerm.computeGids(userId);
+ }
+
+ public int[] getUserIds() {
+ return mUserIds;
+ }
+
+ public boolean hasUserId(int userId) {
+ if (mUserIds == USERS_ALL) {
+ return true;
+ }
+
+ if (userId != UserHandle.USER_ALL) {
+ return ArrayUtils.contains(mUserIds, userId);
+ }
+
+ return false;
+ }
+
+ public boolean addUserId(int userId) {
+ if (hasUserId(userId)) {
+ return false;
+ }
+
+ if (userId == UserHandle.USER_ALL) {
+ mUserIds = USERS_ALL;
+ return true;
+ }
+
+ mUserIds = ArrayUtils.appendInt(mUserIds, userId);
+
+ return true;
+ }
+
+ public boolean removeUserId(int userId) {
+ if (!hasUserId(userId)) {
+ return false;
+ }
+
+ if (mUserIds == USERS_ALL) {
+ mUserIds = UserManagerService.getInstance().getUserIds();
+ }
+
+ mUserIds = ArrayUtils.removeInt(mUserIds, userId);
+
+ if (mUserIds.length == 0) {
+ mUserIds = USERS_NONE;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java
index 9c8a9bd..ef29cb3 100644
--- a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java
+++ b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java
@@ -16,8 +16,6 @@
package com.android.server.pm;
-import java.io.PrintWriter;
-
import com.android.server.IntentResolver;
public class PersistentPreferredIntentResolver
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 81302b9..c75a1d3 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -16,15 +16,12 @@
package com.android.server.pm;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
import android.content.pm.Signature;
import android.os.Environment;
import android.util.Slog;
import android.util.Xml;
-import com.android.internal.util.XmlUtils;
-
import libcore.io.IoUtils;
import java.io.File;
@@ -35,27 +32,36 @@ import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
- * Centralized access to SELinux MMAC (middleware MAC) implementation.
+ * Centralized access to SELinux MMAC (middleware MAC) implementation. This
+ * class is responsible for loading the appropriate mac_permissions.xml file
+ * as well as providing an interface for assigning seinfo values to apks.
+ *
* {@hide}
*/
public final class SELinuxMMAC {
- private static final String TAG = "SELinuxMMAC";
+ static final String TAG = "SELinuxMMAC";
private static final boolean DEBUG_POLICY = false;
private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
+ private static final boolean DEBUG_POLICY_ORDER = DEBUG_POLICY || false;
- // Signature seinfo values read from policy.
- private static HashMap<Signature, Policy> sSigSeinfo = new HashMap<Signature, Policy>();
-
- // Default seinfo read from policy.
- private static String sDefaultSeinfo = null;
+ // All policy stanzas read from mac_permissions.xml. This is also the lock
+ // to synchronize access during policy load and access attempts.
+ private static List<Policy> sPolicies = new ArrayList<>();
// Data policy override version file.
private static final String DATA_VERSION_FILE =
@@ -94,285 +100,272 @@ public final class SELinuxMMAC {
private static final String SEAPP_HASH_FILE =
Environment.getDataDirectory().toString() + "/system/seapp_hash";
-
- // Signature policy stanzas
- static class Policy {
- private String seinfo;
- private final HashMap<String, String> pkgMap;
-
- Policy() {
- seinfo = null;
- pkgMap = new HashMap<String, String>();
- }
-
- void putSeinfo(String seinfoValue) {
- seinfo = seinfoValue;
- }
-
- void putPkg(String pkg, String seinfoValue) {
- pkgMap.put(pkg, seinfoValue);
- }
-
- // Valid policy stanza means there exists a global
- // seinfo value or at least one package policy.
- boolean isValid() {
- return (seinfo != null) || (!pkgMap.isEmpty());
- }
-
- String checkPolicy(String pkgName) {
- // Check for package name seinfo value first.
- String seinfoValue = pkgMap.get(pkgName);
- if (seinfoValue != null) {
- return seinfoValue;
- }
-
- // Return the global seinfo value.
- return seinfo;
- }
- }
-
- private static void flushInstallPolicy() {
- sSigSeinfo.clear();
- sDefaultSeinfo = null;
- }
-
+ /**
+ * Load the mac_permissions.xml file containing all seinfo assignments used to
+ * label apps. The loaded mac_permissions.xml file is determined by the
+ * MAC_PERMISSIONS class variable which is set at class load time which itself
+ * is based on the USE_OVERRIDE_POLICY class variable. For further guidance on
+ * the proper structure of a mac_permissions.xml file consult the source code
+ * located at external/sepolicy/mac_permissions.xml.
+ *
+ * @return boolean indicating if policy was correctly loaded. A value of false
+ * typically indicates a structural problem with the xml or incorrectly
+ * constructed policy stanzas. A value of true means that all stanzas
+ * were loaded successfully; no partial loading is possible.
+ */
public static boolean readInstallPolicy() {
- // Temp structures to hold the rules while we parse the xml file.
- // We add all the rules together once we know there's no structural problems.
- HashMap<Signature, Policy> sigSeinfo = new HashMap<Signature, Policy>();
- String defaultSeinfo = null;
+ // Temp structure to hold the rules while we parse the xml file
+ List<Policy> policies = new ArrayList<>();
FileReader policyFile = null;
+ XmlPullParser parser = Xml.newPullParser();
try {
policyFile = new FileReader(MAC_PERMISSIONS);
Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS);
- XmlPullParser parser = Xml.newPullParser();
parser.setInput(policyFile);
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, null, "policy");
- XmlUtils.beginDocument(parser, "policy");
- while (true) {
- XmlUtils.nextElement(parser);
- if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
- break;
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
}
- String tagName = parser.getName();
- if ("signer".equals(tagName)) {
- String cert = parser.getAttributeValue(null, "signature");
- if (cert == null) {
- Slog.w(TAG, "<signer> without signature at "
- + parser.getPositionDescription());
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- Signature signature;
- try {
- signature = new Signature(cert);
- } catch (IllegalArgumentException e) {
- Slog.w(TAG, "<signer> with bad signature at "
- + parser.getPositionDescription(), e);
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- Policy policy = readPolicyTags(parser);
- if (policy.isValid()) {
- sigSeinfo.put(signature, policy);
- }
- } else if ("default".equals(tagName)) {
- // Value is null if default tag is absent or seinfo tag is malformed.
- defaultSeinfo = readSeinfoTag(parser);
- if (DEBUG_POLICY_INSTALL)
- Slog.i(TAG, "<default> tag assigned seinfo=" + defaultSeinfo);
-
- } else {
- XmlUtils.skipCurrentTag(parser);
+ switch (parser.getName()) {
+ case "signer":
+ policies.add(readSignerOrThrow(parser));
+ break;
+ case "default":
+ policies.add(readDefaultOrThrow(parser));
+ break;
+ default:
+ skip(parser);
}
}
- } catch (XmlPullParserException xpe) {
- Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, xpe);
+ } catch (IllegalStateException | IllegalArgumentException |
+ XmlPullParserException ex) {
+ StringBuilder sb = new StringBuilder("Exception @");
+ sb.append(parser.getPositionDescription());
+ sb.append(" while parsing ");
+ sb.append(MAC_PERMISSIONS);
+ sb.append(":");
+ sb.append(ex);
+ Slog.w(TAG, sb.toString());
return false;
} catch (IOException ioe) {
- Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, ioe);
+ Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS, ioe);
return false;
} finally {
IoUtils.closeQuietly(policyFile);
}
- flushInstallPolicy();
- sSigSeinfo = sigSeinfo;
- sDefaultSeinfo = defaultSeinfo;
+ // Now sort the policy stanzas
+ PolicyComparator policySort = new PolicyComparator();
+ Collections.sort(policies, policySort);
+ if (policySort.foundDuplicate()) {
+ Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS);
+ return false;
+ }
+
+ synchronized (sPolicies) {
+ sPolicies = policies;
+
+ if (DEBUG_POLICY_ORDER) {
+ for (Policy policy : sPolicies) {
+ Slog.d(TAG, "Policy: " + policy.toString());
+ }
+ }
+ }
return true;
}
- private static Policy readPolicyTags(XmlPullParser parser) throws
- IOException, XmlPullParserException {
+ /**
+ * Loop over a signer tag looking for seinfo, package and cert tags. A {@link Policy}
+ * instance will be created and returned in the process. During the pass all other
+ * tag elements will be skipped.
+ *
+ * @param parser an XmlPullParser object representing a signer element.
+ * @return the constructed {@link Policy} instance
+ * @throws IOException
+ * @throws XmlPullParserException
+ * @throws IllegalArgumentException if any of the validation checks fail while
+ * parsing tag values.
+ * @throws IllegalStateException if any of the invariants fail when constructing
+ * the {@link Policy} instance.
+ */
+ private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException,
+ XmlPullParserException {
+
+ parser.require(XmlPullParser.START_TAG, null, "signer");
+ Policy.PolicyBuilder pb = new Policy.PolicyBuilder();
+
+ // Check for a cert attached to the signer tag. We allow a signature
+ // to appear as an attribute as well as those attached to cert tags.
+ String cert = parser.getAttributeValue(null, "signature");
+ if (cert != null) {
+ pb.addSignature(cert);
+ }
- int type;
- int outerDepth = parser.getDepth();
- Policy policy = new Policy();
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG
- || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG
- || type == XmlPullParser.TEXT) {
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String tagName = parser.getName();
if ("seinfo".equals(tagName)) {
- String seinfo = parseSeinfo(parser);
- if (seinfo != null) {
- policy.putSeinfo(seinfo);
- }
- XmlUtils.skipCurrentTag(parser);
+ String seinfo = parser.getAttributeValue(null, "value");
+ pb.setGlobalSeinfoOrThrow(seinfo);
+ readSeinfo(parser);
} else if ("package".equals(tagName)) {
- String pkg = parser.getAttributeValue(null, "name");
- if (!validatePackageName(pkg)) {
- Slog.w(TAG, "<package> without valid name at "
- + parser.getPositionDescription());
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
-
- String seinfo = readSeinfoTag(parser);
- if (seinfo != null) {
- policy.putPkg(pkg, seinfo);
- }
+ readPackageOrThrow(parser, pb);
+ } else if ("cert".equals(tagName)) {
+ String sig = parser.getAttributeValue(null, "signature");
+ pb.addSignature(sig);
+ readCert(parser);
} else {
- XmlUtils.skipCurrentTag(parser);
+ skip(parser);
}
}
- return policy;
+
+ return pb.build();
}
- private static String readSeinfoTag(XmlPullParser parser) throws
- IOException, XmlPullParserException {
+ /**
+ * Loop over a default element looking for seinfo child tags. A {@link Policy}
+ * instance will be created and returned in the process. All other tags encountered
+ * will be skipped.
+ *
+ * @param parser an XmlPullParser object representing a default element.
+ * @return the constructed {@link Policy} instance
+ * @throws IOException
+ * @throws XmlPullParserException
+ * @throws IllegalArgumentException if any of the validation checks fail while
+ * parsing tag values.
+ * @throws IllegalStateException if any of the invariants fail when constructing
+ * the {@link Policy} instance.
+ */
+ private static Policy readDefaultOrThrow(XmlPullParser parser) throws IOException,
+ XmlPullParserException {
- int type;
- int outerDepth = parser.getDepth();
- String seinfo = null;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG
- || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG
- || type == XmlPullParser.TEXT) {
+ parser.require(XmlPullParser.START_TAG, null, "default");
+ Policy.PolicyBuilder pb = new Policy.PolicyBuilder();
+ pb.setAsDefaultPolicy();
+
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String tagName = parser.getName();
if ("seinfo".equals(tagName)) {
- seinfo = parseSeinfo(parser);
+ String seinfo = parser.getAttributeValue(null, "value");
+ pb.setGlobalSeinfoOrThrow(seinfo);
+ readSeinfo(parser);
+ } else {
+ skip(parser);
}
- XmlUtils.skipCurrentTag(parser);
}
- return seinfo;
- }
- private static String parseSeinfo(XmlPullParser parser) {
-
- String seinfoValue = parser.getAttributeValue(null, "value");
- if (!validateValue(seinfoValue)) {
- Slog.w(TAG, "<seinfo> without valid value at "
- + parser.getPositionDescription());
- seinfoValue = null;
- }
- return seinfoValue;
+ return pb.build();
}
/**
- * General validation routine for package names.
- * Returns a boolean indicating if the passed string
- * is a valid android package name.
+ * Loop over a package element looking for seinfo child tags. If found return the
+ * value attribute of the seinfo tag, otherwise return null. All other tags encountered
+ * will be skipped.
+ *
+ * @param parser an XmlPullParser object representing a package element.
+ * @param pb a Policy.PolicyBuilder instance to build
+ * @throws IOException
+ * @throws XmlPullParserException
+ * @throws IllegalArgumentException if any of the validation checks fail while
+ * parsing tag values.
+ * @throws IllegalStateException if there is a duplicate seinfo tag for the current
+ * package tag.
*/
- private static boolean validatePackageName(String name) {
- if (name == null)
- return false;
+ private static void readPackageOrThrow(XmlPullParser parser, Policy.PolicyBuilder pb) throws
+ IOException, XmlPullParserException {
+ parser.require(XmlPullParser.START_TAG, null, "package");
+ String pkgName = parser.getAttributeValue(null, "name");
- final int N = name.length();
- boolean hasSep = false;
- boolean front = true;
- for (int i=0; i<N; i++) {
- final char c = name.charAt(i);
- if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
- front = false;
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
- if (!front) {
- if ((c >= '0' && c <= '9') || c == '_') {
- continue;
- }
- }
- if (c == '.') {
- hasSep = true;
- front = true;
- continue;
+
+ String tagName = parser.getName();
+ if ("seinfo".equals(tagName)) {
+ String seinfo = parser.getAttributeValue(null, "value");
+ pb.addInnerPackageMapOrThrow(pkgName, seinfo);
+ readSeinfo(parser);
+ } else {
+ skip(parser);
}
- return false;
}
- return hasSep;
}
- /**
- * General validation routine for tag values.
- * Returns a boolean indicating if the passed string
- * contains only letters or underscores.
- */
- private static boolean validateValue(String name) {
- if (name == null)
- return false;
+ private static void readCert(XmlPullParser parser) throws IOException,
+ XmlPullParserException {
+ parser.require(XmlPullParser.START_TAG, null, "cert");
+ parser.nextTag();
+ }
- final int N = name.length();
- if (N == 0)
- return false;
+ private static void readSeinfo(XmlPullParser parser) throws IOException,
+ XmlPullParserException {
+ parser.require(XmlPullParser.START_TAG, null, "seinfo");
+ parser.nextTag();
+ }
- for (int i = 0; i < N; i++) {
- final char c = name.charAt(i);
- if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) {
- return false;
+ private static void skip(XmlPullParser p) throws IOException, XmlPullParserException {
+ if (p.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException();
+ }
+ int depth = 1;
+ while (depth != 0) {
+ switch (p.next()) {
+ case XmlPullParser.END_TAG:
+ depth--;
+ break;
+ case XmlPullParser.START_TAG:
+ depth++;
+ break;
}
}
- return true;
}
/**
- * Labels a package based on an seinfo tag from install policy.
- * The label is attached to the ApplicationInfo instance of the package.
+ * Applies a security label to a package based on an seinfo tag taken from a matched
+ * policy. All signature based policy stanzas are consulted first and, if no match
+ * is found, the default policy stanza is then consulted. The security label is
+ * attached to the ApplicationInfo instance of the package in the event that a matching
+ * policy was found.
+ *
* @param pkg object representing the package to be labeled.
- * @return boolean which determines whether a non null seinfo label
- * was assigned to the package. A null value simply meaning that
- * no policy matched.
+ * @return boolean which determines whether a non null seinfo label was assigned
+ * to the package. A null value simply represents that no policy matched.
*/
public static boolean assignSeinfoValue(PackageParser.Package pkg) {
-
- // We just want one of the signatures to match.
- for (Signature s : pkg.mSignatures) {
- if (s == null)
- continue;
-
- Policy policy = sSigSeinfo.get(s);
- if (policy != null) {
- String seinfo = policy.checkPolicy(pkg.packageName);
+ synchronized (sPolicies) {
+ for (Policy policy : sPolicies) {
+ String seinfo = policy.getMatchedSeinfo(pkg);
if (seinfo != null) {
pkg.applicationInfo.seinfo = seinfo;
- if (DEBUG_POLICY_INSTALL)
- Slog.i(TAG, "package (" + pkg.packageName +
- ") labeled with seinfo=" + seinfo);
-
+ if (DEBUG_POLICY_INSTALL) {
+ Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
+ "seinfo=" + seinfo);
+ }
return true;
}
}
}
- // If we have a default seinfo value then great, otherwise
- // we set a null object and that is what we started with.
- pkg.applicationInfo.seinfo = sDefaultSeinfo;
- if (DEBUG_POLICY_INSTALL)
- Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo="
- + (sDefaultSeinfo == null ? "null" : sDefaultSeinfo));
-
- return (sDefaultSeinfo != null);
+ if (DEBUG_POLICY_INSTALL) {
+ Slog.i(TAG, "package (" + pkg.packageName + ") doesn't match any policy; " +
+ "seinfo will remain null");
+ }
+ return false;
}
/**
@@ -477,3 +470,438 @@ public final class SELinuxMMAC {
return false;
}
}
+
+/**
+ * Holds valid policy representations of individual stanzas from a mac_permissions.xml
+ * file. Each instance can further be used to assign seinfo values to apks using the
+ * {@link Policy#getMatchedSeinfo} method. To create an instance of this use the
+ * {@link PolicyBuilder} pattern class, where each instance is validated against a set
+ * of invariants before being built and returned. Each instance can be guaranteed to
+ * hold one valid policy stanza as outlined in the external/sepolicy/mac_permissions.xml
+ * file.
+ * <p>
+ * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
+ * signer based Policy instance with only inner package name refinements.
+ * </p>
+ * <pre>
+ * {@code
+ * Policy policy = new Policy.PolicyBuilder()
+ * .addSignature("308204a8...")
+ * .addSignature("483538c8...")
+ * .addInnerPackageMapOrThrow("com.foo.", "bar")
+ * .addInnerPackageMapOrThrow("com.foo.other", "bar")
+ * .build();
+ * }
+ * </pre>
+ * <p>
+ * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
+ * signer based Policy instance with only a global seinfo tag.
+ * </p>
+ * <pre>
+ * {@code
+ * Policy policy = new Policy.PolicyBuilder()
+ * .addSignature("308204a8...")
+ * .addSignature("483538c8...")
+ * .setGlobalSeinfoOrThrow("paltform")
+ * .build();
+ * }
+ * </pre>
+ * <p>
+ * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
+ * default based Policy instance.
+ * </p>
+ * <pre>
+ * {@code
+ * Policy policy = new Policy.PolicyBuilder()
+ * .setAsDefaultPolicy()
+ * .setGlobalSeinfoOrThrow("default")
+ * .build();
+ * }
+ * </pre>
+ */
+final class Policy {
+
+ private final String mSeinfo;
+ private final boolean mDefaultStanza;
+ private final Set<Signature> mCerts;
+ private final Map<String, String> mPkgMap;
+
+ // Use the PolicyBuilder pattern to instantiate
+ private Policy(PolicyBuilder builder) {
+ mSeinfo = builder.mSeinfo;
+ mDefaultStanza = builder.mDefaultStanza;
+ mCerts = Collections.unmodifiableSet(builder.mCerts);
+ mPkgMap = Collections.unmodifiableMap(builder.mPkgMap);
+ }
+
+ /**
+ * Return all the certs stored with this policy stanza.
+ *
+ * @return A set of Signature objects representing all the certs stored
+ * with the policy.
+ */
+ public Set<Signature> getSignatures() {
+ return mCerts;
+ }
+
+ /**
+ * Return whether this policy object represents a default stanza.
+ *
+ * @return A boolean indicating if this object represents a default policy stanza.
+ */
+ public boolean isDefaultStanza() {
+ return mDefaultStanza;
+ }
+
+ /**
+ * Return whether this policy object contains package name mapping refinements.
+ *
+ * @return A boolean indicating if this object has inner package name mappings.
+ */
+ public boolean hasInnerPackages() {
+ return !mPkgMap.isEmpty();
+ }
+
+ /**
+ * Return the mapping of all package name refinements.
+ *
+ * @return A Map object whose keys are the package names and whose values are
+ * the seinfo assignments.
+ */
+ public Map<String, String> getInnerPackages() {
+ return mPkgMap;
+ }
+
+ /**
+ * Return whether the policy object has a global seinfo tag attached.
+ *
+ * @return A boolean indicating if this stanza has a global seinfo tag.
+ */
+ public boolean hasGlobalSeinfo() {
+ return mSeinfo != null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (mDefaultStanza) {
+ sb.append("defaultStanza=true ");
+ }
+
+ for (Signature cert : mCerts) {
+ sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... ");
+ }
+
+ if (mSeinfo != null) {
+ sb.append("seinfo=" + mSeinfo);
+ }
+
+ for (String name : mPkgMap.keySet()) {
+ sb.append(" " + name + "=" + mPkgMap.get(name));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * <p>
+ * Determine the seinfo value to assign to an apk. The appropriate seinfo value
+ * is determined using the following steps:
+ * </p>
+ * <ul>
+ * <li> If this Policy instance is defined as a default stanza:
+ * <ul><li>Return the global seinfo value</li></ul>
+ * </li>
+ * <li> If this Policy instance is defined as a signer stanza:
+ * <ul>
+ * <li> All certs used to sign the apk and all certs stored with this policy
+ * instance are tested for set equality. If this fails then null is returned.
+ * </li>
+ * <li> If all certs match then an appropriate inner package stanza is
+ * searched based on package name alone. If matched, the stored seinfo
+ * value for that mapping is returned.
+ * </li>
+ * <li> If all certs matched and no inner package stanza matches then return
+ * the global seinfo value. The returned value can be null in this case.
+ * </li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <p>
+ * In all cases, a return value of null should be interpreted as the apk failing
+ * to match this Policy instance; i.e. failing this policy stanza.
+ * </p>
+ * @param pkg the apk to check given as a PackageParser.Package object
+ * @return A string representing the seinfo matched during policy lookup.
+ * A value of null can also be returned if no match occured.
+ */
+ public String getMatchedSeinfo(PackageParser.Package pkg) {
+ if (!mDefaultStanza) {
+ // Check for exact signature matches across all certs.
+ Signature[] certs = mCerts.toArray(new Signature[0]);
+ if (!Signature.areExactMatch(certs, pkg.mSignatures)) {
+ return null;
+ }
+
+ // Check for inner package name matches given that the
+ // signature checks already passed.
+ String seinfoValue = mPkgMap.get(pkg.packageName);
+ if (seinfoValue != null) {
+ return seinfoValue;
+ }
+ }
+
+ // Return the global seinfo value (even if it's null).
+ return mSeinfo;
+ }
+
+ /**
+ * A nested builder class to create {@link Policy} instances. A {@link Policy}
+ * class instance represents one valid policy stanza found in a mac_permissions.xml
+ * file. A valid policy stanza is defined to be either a signer or default stanza
+ * which obeys the rules outlined in external/sepolicy/mac_permissions.xml. The
+ * {@link #build} method ensures a set of invariants are upheld enforcing the correct
+ * stanza structure before returning a valid Policy object.
+ */
+ public static final class PolicyBuilder {
+
+ private String mSeinfo;
+ private boolean mDefaultStanza;
+ private final Set<Signature> mCerts;
+ private final Map<String, String> mPkgMap;
+
+ public PolicyBuilder() {
+ mCerts = new HashSet<Signature>(2);
+ mPkgMap = new HashMap<String, String>(2);
+ }
+
+ /**
+ * Sets this stanza as a default stanza. All policy stanzas are assumed to
+ * be signer stanzas unless this method is explicitly called. Default stanzas
+ * are treated differently with respect to allowable child tags, ordering and
+ * when and how policy decisions are enforced.
+ *
+ * @return The reference to this PolicyBuilder.
+ */
+ public PolicyBuilder setAsDefaultPolicy() {
+ mDefaultStanza = true;
+ return this;
+ }
+
+ /**
+ * Adds a signature to the set of certs used for validation checks. The purpose
+ * being that all contained certs will need to be matched against all certs
+ * contained with an apk.
+ *
+ * @param cert the signature to add given as a String.
+ * @return The reference to this PolicyBuilder.
+ * @throws IllegalArgumentException if the cert value fails validation;
+ * null or is an invalid hex-encoded ASCII string.
+ */
+ public PolicyBuilder addSignature(String cert) {
+ if (cert == null) {
+ String err = "Invalid signature value " + cert;
+ throw new IllegalArgumentException(err);
+ }
+
+ mCerts.add(new Signature(cert));
+ return this;
+ }
+
+ /**
+ * Set the global seinfo tag for this policy stanza. The global seinfo tag
+ * represents the seinfo element that is used in one of two ways depending on
+ * its context. When attached to a signer tag the global seinfo represents an
+ * assignment when there isn't a further inner package refinement in policy.
+ * When used with a default tag, it represents the only allowable assignment
+ * value.
+ *
+ * @param seinfo the seinfo value given as a String.
+ * @return The reference to this PolicyBuilder.
+ * @throws IllegalArgumentException if the seinfo value fails validation;
+ * null, zero length or contains non-valid characters [^a-zA-Z_\._0-9].
+ * @throws IllegalStateException if an seinfo value has already been found
+ */
+ public PolicyBuilder setGlobalSeinfoOrThrow(String seinfo) {
+ if (!validateValue(seinfo)) {
+ String err = "Invalid seinfo value " + seinfo;
+ throw new IllegalArgumentException(err);
+ }
+
+ if (mSeinfo != null && !mSeinfo.equals(seinfo)) {
+ String err = "Duplicate seinfo tag found";
+ throw new IllegalStateException(err);
+ }
+
+ mSeinfo = seinfo;
+ return this;
+ }
+
+ /**
+ * Create a package name to seinfo value mapping. Each mapping represents
+ * the seinfo value that will be assigned to the described package name.
+ * These localized mappings allow the global seinfo to be overriden. This
+ * mapping provides no value when used in conjunction with a default stanza;
+ * enforced through the {@link #build} method.
+ *
+ * @param pkgName the android package name given to the app
+ * @param seinfo the seinfo value that will be assigned to the passed pkgName
+ * @return The reference to this PolicyBuilder.
+ * @throws IllegalArgumentException if the seinfo value fails validation;
+ * null, zero length or contains non-valid characters [^a-zA-Z_\.0-9].
+ * Or, if the package name isn't a valid android package name.
+ * @throws IllegalStateException if trying to reset a package mapping with a
+ * different seinfo value.
+ */
+ public PolicyBuilder addInnerPackageMapOrThrow(String pkgName, String seinfo) {
+ if (!validateValue(pkgName)) {
+ String err = "Invalid package name " + pkgName;
+ throw new IllegalArgumentException(err);
+ }
+ if (!validateValue(seinfo)) {
+ String err = "Invalid seinfo value " + seinfo;
+ throw new IllegalArgumentException(err);
+ }
+
+ String pkgValue = mPkgMap.get(pkgName);
+ if (pkgValue != null && !pkgValue.equals(seinfo)) {
+ String err = "Conflicting seinfo value found";
+ throw new IllegalStateException(err);
+ }
+
+ mPkgMap.put(pkgName, seinfo);
+ return this;
+ }
+
+ /**
+ * General validation routine for the attribute strings of an element. Checks
+ * if the string is non-null, positive length and only contains [a-zA-Z_\.0-9].
+ *
+ * @param name the string to validate.
+ * @return boolean indicating if the string was valid.
+ */
+ private boolean validateValue(String name) {
+ if (name == null)
+ return false;
+
+ // Want to match on [0-9a-zA-Z_.]
+ if (!name.matches("\\A[\\.\\w]+\\z")) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * <p>
+ * Create a {@link Policy} instance based on the current configuration. This
+ * method checks for certain policy invariants used to enforce certain guarantees
+ * about the expected structure of a policy stanza.
+ * Those invariants are:
+ * </p>
+ * <ul>
+ * <li> If a default stanza
+ * <ul>
+ * <li> an attached global seinfo tag must be present </li>
+ * <li> no signatures and no package names can be present </li>
+ * </ul>
+ * </li>
+ * <li> If a signer stanza
+ * <ul>
+ * <li> at least one cert must be found </li>
+ * <li> either a global seinfo value is present OR at least one
+ * inner package mapping must be present BUT not both. </li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @return an instance of {@link Policy} with the options set from this builder
+ * @throws IllegalStateException if an invariant is violated.
+ */
+ public Policy build() {
+ Policy p = new Policy(this);
+
+ if (p.mDefaultStanza) {
+ if (p.mSeinfo == null) {
+ String err = "Missing global seinfo tag with default stanza.";
+ throw new IllegalStateException(err);
+ }
+ if (p.mCerts.size() != 0) {
+ String err = "Certs not allowed with default stanza.";
+ throw new IllegalStateException(err);
+ }
+ if (!p.mPkgMap.isEmpty()) {
+ String err = "Inner package mappings not allowed with default stanza.";
+ throw new IllegalStateException(err);
+ }
+ } else {
+ if (p.mCerts.size() == 0) {
+ String err = "Missing certs with signer tag. Expecting at least one.";
+ throw new IllegalStateException(err);
+ }
+ if (!(p.mSeinfo == null ^ p.mPkgMap.isEmpty())) {
+ String err = "Only seinfo tag XOR package tags are allowed within " +
+ "a signer stanza.";
+ throw new IllegalStateException(err);
+ }
+ }
+
+ return p;
+ }
+ }
+}
+
+/**
+ * Comparision imposing an ordering on Policy objects. It is understood that Policy
+ * objects can only take one of three forms and ordered according to the following
+ * set of rules most specific to least.
+ * <ul>
+ * <li> signer stanzas with inner package mappings </li>
+ * <li> signer stanzas with global seinfo tags </li>
+ * <li> default stanza </li>
+ * </ul>
+ * This comparison also checks for duplicate entries on the input selectors. Any
+ * found duplicates will be flagged and can be checked with {@link #foundDuplicate}.
+ */
+
+final class PolicyComparator implements Comparator<Policy> {
+
+ private boolean duplicateFound = false;
+
+ public boolean foundDuplicate() {
+ return duplicateFound;
+ }
+
+ @Override
+ public int compare(Policy p1, Policy p2) {
+
+ // Give precedence to signature stanzas over default stanzas
+ if (p1.isDefaultStanza() != p2.isDefaultStanza()) {
+ return p1.isDefaultStanza() ? 1 : -1;
+ }
+
+ // Give precedence to stanzas with inner package mappings
+ if (p1.hasInnerPackages() != p2.hasInnerPackages()) {
+ return p1.hasInnerPackages() ? -1 : 1;
+ }
+
+ // Check for duplicate entries
+ if (p1.getSignatures().equals(p2.getSignatures())) {
+ // Checks if default stanza or a signer w/o inner package names
+ if (p1.hasGlobalSeinfo()) {
+ duplicateFound = true;
+ Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
+ }
+
+ // Look for common inner package name mappings
+ final Map<String, String> p1Packages = p1.getInnerPackages();
+ final Map<String, String> p2Packages = p2.getInnerPackages();
+ if (!Collections.disjoint(p1Packages.keySet(), p2Packages.keySet())) {
+ duplicateFound = true;
+ Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
new file mode 100644
index 0000000..0c7f79d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.ApplicationInfo;
+
+import java.util.Arrays;
+
+abstract class SettingBase {
+ int pkgFlags;
+ int pkgPrivateFlags;
+
+ protected final PermissionsState mPermissionsState;
+ private int[] mPermissionsUpdatedForUserIds = PermissionsState.USERS_NONE;
+
+ SettingBase(int pkgFlags, int pkgPrivateFlags) {
+ setFlags(pkgFlags);
+ setPrivateFlags(pkgPrivateFlags);
+ mPermissionsState = new PermissionsState();
+ }
+
+ SettingBase(SettingBase base) {
+ pkgFlags = base.pkgFlags;
+ pkgPrivateFlags = base.pkgPrivateFlags;
+ mPermissionsState = new PermissionsState(base.mPermissionsState);
+ setPermissionsUpdatedForUserIds(base.mPermissionsUpdatedForUserIds);
+ }
+
+ public PermissionsState getPermissionsState() {
+ return mPermissionsState;
+ }
+
+ public int[] getPermissionsUpdatedForUserIds() {
+ return mPermissionsUpdatedForUserIds;
+ }
+
+ public void setPermissionsUpdatedForUserIds(int[] userIds) {
+ if (Arrays.equals(mPermissionsUpdatedForUserIds, userIds)) {
+ return;
+ }
+
+ if (userIds == PermissionsState.USERS_NONE || userIds == PermissionsState.USERS_ALL) {
+ mPermissionsUpdatedForUserIds = userIds;
+ } else {
+ mPermissionsUpdatedForUserIds = Arrays.copyOf(userIds, userIds.length);
+ }
+ }
+
+ void setFlags(int pkgFlags) {
+ this.pkgFlags = pkgFlags
+ & (ApplicationInfo.FLAG_SYSTEM
+ | ApplicationInfo.FLAG_EXTERNAL_STORAGE);
+ }
+
+ void setPrivateFlags(int pkgPrivateFlags) {
+ this.pkgPrivateFlags = pkgPrivateFlags
+ & (ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
+ | ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 524f638..252c16a 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -22,28 +22,46 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
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 static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.PACKAGE_INFO_GID;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Message;
import android.os.PatternMatcher;
import android.os.Process;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.VolumeInfo;
+import android.util.AtomicFile;
+import android.text.TextUtils;
import android.util.LogPrinter;
+import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.PackageManagerService.DumpState;
+import java.io.FileNotFoundException;
import java.util.Collection;
import org.xmlpull.v1.XmlPullParser;
@@ -51,7 +69,6 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
@@ -79,6 +96,7 @@ import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@@ -134,6 +152,8 @@ final class Settings {
private static final boolean DEBUG_STOPPED = false;
private static final boolean DEBUG_MU = false;
+ private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml";
+
private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage";
private static final String ATTR_ENFORCEMENT = "enforcement";
@@ -142,10 +162,16 @@ final class Settings {
private static final String TAG_ENABLED_COMPONENTS = "enabled-components";
private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions";
private static final String TAG_PACKAGE = "pkg";
+ private static final String TAG_SHARED_USER = "shared-user";
+ private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions";
+ private static final String TAG_PERMISSIONS = "perms";
private static final String TAG_PERSISTENT_PREFERRED_ACTIVITIES =
"persistent-preferred-activities";
static final String TAG_CROSS_PROFILE_INTENT_FILTERS =
"crossProfile-intent-filters";
+ public static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
+ public static final String TAG_DEFAULT_APPS= "default-apps";
+ public static final String TAG_DEFAULT_BROWSER= "default-browser";
private static final String ATTR_NAME = "name";
private static final String ATTR_USER = "user";
@@ -160,6 +186,12 @@ final class Settings {
private static final String ATTR_HIDDEN = "hidden";
private static final String ATTR_INSTALLED = "inst";
private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall";
+ private static final String ATTR_DOMAIN_VERIFICATON_STATE = "domainVerificationStatus";
+ private static final String ATTR_PACKAGE_NAME= "packageName";
+
+ private final Object mLock;
+
+ private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
private final File mSettingsFilename;
private final File mBackupSettingsFilename;
@@ -175,6 +207,8 @@ final class Settings {
private static int mFirstAvailableUid = 0;
+ // TODO: store SDK versions and fingerprint for each volume UUID
+
// These are the last platform API version we were using for
// the apps installed on internal and external storage. It is
// used to grant newer permissions one time during a system upgrade.
@@ -223,6 +257,8 @@ final class Settings {
// For reading/writing settings file.
private final ArrayList<Signature> mPastSignatures =
new ArrayList<Signature>();
+ private final ArrayMap<Long, Integer> mKeySetRefs =
+ new ArrayMap<Long, Integer>();
// Mapping from permission names to info about them.
final ArrayMap<String, BasePermission> mPermissions =
@@ -241,7 +277,10 @@ final class Settings {
// names. The packages appear everwhere else under their original
// names.
final ArrayMap<String, String> mRenamedPackages = new ArrayMap<String, String>();
-
+
+ // For every user, it is used to find the package name of the default Browser App.
+ final SparseArray<String> mDefaultBrowserApp = new SparseArray<String>();
+
final StringBuilder mReadMessages = new StringBuilder();
/**
@@ -257,11 +296,15 @@ final class Settings {
public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages);
- Settings(Context context) {
- this(context, Environment.getDataDirectory());
+ Settings(Object lock) {
+ this(Environment.getDataDirectory(), lock);
}
- Settings(Context context, File dataDir) {
+ Settings(File dataDir, Object lock) {
+ mLock = lock;
+
+ mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
+
mSystemDir = new File(dataDir, "system");
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
@@ -281,11 +324,11 @@ final class Settings {
PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage,
String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
String legacyNativeLibraryPathString, String primaryCpuAbi, String secondaryCpuAbi,
- int pkgFlags, UserHandle user, boolean add) {
+ int pkgFlags, int pkgPrivateFlags, UserHandle user, boolean add) {
final String name = pkg.packageName;
PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath,
resourcePath, legacyNativeLibraryPathString, primaryCpuAbi, secondaryCpuAbi,
- pkg.mVersionCode, pkgFlags, user, add, true /* allowInstall */);
+ pkg.mVersionCode, pkgFlags, pkgPrivateFlags, user, add, true /* allowInstall */);
return p;
}
@@ -302,22 +345,21 @@ final class Settings {
}
}
- void setInstallerPackageName(String pkgName,
- String installerPkgName) {
+ void setInstallerPackageName(String pkgName, String installerPkgName) {
PackageSetting p = mPackages.get(pkgName);
- if(p != null) {
+ if (p != null) {
p.setInstallerPackageName(installerPkgName);
}
}
SharedUserSetting getSharedUserLPw(String name,
- int pkgFlags, boolean create) {
+ int pkgFlags, int pkgPrivateFlags, boolean create) {
SharedUserSetting s = mSharedUsers.get(name);
if (s == null) {
if (!create) {
return null;
}
- s = new SharedUserSetting(name, pkgFlags);
+ s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
s.userId = newUserIdLPw(s);
Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId);
// < 0 means we couldn't assign a userid; fall out and return
@@ -373,7 +415,7 @@ final class Settings {
PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath,
p.legacyNativeLibraryPathString, p.primaryCpuAbiString,
p.secondaryCpuAbiString, p.secondaryCpuAbiString,
- p.appId, p.versionCode, p.pkgFlags);
+ p.appId, p.versionCode, p.pkgFlags, p.pkgPrivateFlags);
mDisabledSysPackages.remove(name);
return ret;
}
@@ -388,7 +430,7 @@ final class Settings {
PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath,
String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString,
- String cpuAbiOverrideString, int uid, int vc, int pkgFlags) {
+ String cpuAbiOverrideString, int uid, int vc, int pkgFlags, int pkgPrivateFlags) {
PackageSetting p = mPackages.get(name);
if (p != null) {
if (p.appId == uid) {
@@ -400,7 +442,7 @@ final class Settings {
}
p = new PackageSetting(name, realName, codePath, resourcePath,
legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
- cpuAbiOverrideString, vc, pkgFlags);
+ cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags);
p.appId = uid;
if (addUserIdLPw(uid, p, name)) {
mPackages.put(name, p);
@@ -409,7 +451,7 @@ final class Settings {
return null;
}
- SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {
+ SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
if (s.userId == uid) {
@@ -419,7 +461,7 @@ final class Settings {
"Adding duplicate shared user, keeping first: " + name);
return null;
}
- s = new SharedUserSetting(name, pkgFlags);
+ s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
s.userId = uid;
if (addUserIdLPw(uid, s, name)) {
mSharedUsers.put(name, s);
@@ -460,7 +502,7 @@ final class Settings {
bp.pendingInfo.packageName = newPkg;
}
bp.uid = 0;
- bp.gids = null;
+ bp.setGids(null, false);
}
}
}
@@ -468,9 +510,9 @@ final class Settings {
private PackageSetting getPackageLPw(String name, PackageSetting origPackage,
String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
- String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString,
- int vc, int pkgFlags, UserHandle installUser, boolean add,
- boolean allowInstall) {
+ String legacyNativeLibraryPathString, String primaryCpuAbiString,
+ String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags,
+ UserHandle installUser, boolean add, boolean allowInstall) {
PackageSetting p = mPackages.get(name);
UserManagerService userManager = UserManagerService.getInstance();
if (p != null) {
@@ -511,9 +553,8 @@ final class Settings {
// If what we are scanning is a system (and possibly privileged) package,
// then make it so, regardless of whether it was previously installed only
// in the data partition.
- final int sysPrivFlags = pkgFlags
- & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PRIVILEGED);
- p.pkgFlags |= sysPrivFlags;
+ p.pkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
+ p.pkgPrivateFlags |= pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
}
if (p == null) {
@@ -521,7 +562,7 @@ final class Settings {
// We are consuming the data from an existing package.
p = new PackageSetting(origPackage.name, name, codePath, resourcePath,
legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
- null /* cpuAbiOverrideString */, vc, pkgFlags);
+ null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags);
if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
+ name + " is adopting original package " + origPackage.name);
// Note that we will retain the new package's signature so
@@ -539,7 +580,7 @@ final class Settings {
} else {
p = new PackageSetting(name, realName, codePath, resourcePath,
legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
- null /* cpuAbiOverrideString */, vc, pkgFlags);
+ null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags);
p.setTimeStamp(codePath.lastModified());
p.sharedUser = sharedUser;
// If this is not a system app, it starts out stopped.
@@ -569,8 +610,8 @@ final class Settings {
true, // notLaunched
false, // hidden
null, null, null,
- false // blockUninstall
- );
+ false, // blockUninstall
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
writePackageRestrictionsLPr(user.id);
}
}
@@ -590,7 +631,7 @@ final class Settings {
}
p.appId = dis.appId;
// Clone permissions
- p.grantedPermissions = new ArraySet<String>(dis.grantedPermissions);
+ p.getPermissionsState().copyFrom(dis.getPermissionsState());
// Clone component info
List<UserInfo> users = getAllUsers();
if (users != null) {
@@ -653,19 +694,26 @@ final class Settings {
p.pkg = pkg;
// pkg.mSetEnabled = p.getEnabled(userId);
// pkg.mSetStopped = p.getStopped(userId);
+ final String volumeUuid = pkg.applicationInfo.volumeUuid;
final String codePath = pkg.applicationInfo.getCodePath();
final String resourcePath = pkg.applicationInfo.getResourcePath();
final String legacyNativeLibraryPath = pkg.applicationInfo.nativeLibraryRootDir;
+ // Update volume if needed
+ if (!Objects.equals(volumeUuid, p.volumeUuid)) {
+ Slog.w(PackageManagerService.TAG, "Volume for " + p.pkg.packageName +
+ " changing from " + p.volumeUuid + " to " + volumeUuid);
+ p.volumeUuid = volumeUuid;
+ }
// Update code path if needed
if (!Objects.equals(codePath, p.codePathString)) {
- Slog.w(PackageManagerService.TAG, "Code path for pkg : " + p.pkg.packageName +
+ Slog.w(PackageManagerService.TAG, "Code path for " + p.pkg.packageName +
" changing from " + p.codePathString + " to " + codePath);
p.codePath = new File(codePath);
p.codePathString = codePath;
}
//Update resource path if needed
if (!Objects.equals(resourcePath, p.resourcePathString)) {
- Slog.w(PackageManagerService.TAG, "Resource path for pkg : " + p.pkg.packageName +
+ Slog.w(PackageManagerService.TAG, "Resource path for " + p.pkg.packageName +
" changing from " + p.resourcePathString + " to " + resourcePath);
p.resourcePath = new File(resourcePath);
p.resourcePathString = resourcePath;
@@ -733,45 +781,60 @@ final class Settings {
* not in use by other permissions of packages in the
* shared user setting.
*/
- void updateSharedUserPermsLPw(PackageSetting deletedPs, int[] globalGids) {
+ int updateSharedUserPermsLPw(PackageSetting deletedPs, int userId) {
if ((deletedPs == null) || (deletedPs.pkg == null)) {
Slog.i(PackageManagerService.TAG,
"Trying to update info for null package. Just ignoring");
- return;
+ return UserHandle.USER_NULL;
}
+
// No sharedUserId
if (deletedPs.sharedUser == null) {
- return;
+ return UserHandle.USER_NULL;
}
+
SharedUserSetting sus = deletedPs.sharedUser;
+
// Update permissions
for (String eachPerm : deletedPs.pkg.requestedPermissions) {
- boolean used = false;
- if (!sus.grantedPermissions.contains(eachPerm)) {
+ BasePermission bp = mPermissions.get(eachPerm);
+ if (bp == null) {
continue;
}
- for (PackageSetting pkg:sus.packages) {
- if (pkg.pkg != null &&
- !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) &&
- pkg.pkg.requestedPermissions.contains(eachPerm)) {
+
+ // If no user has the permission, nothing to remove.
+ if (!sus.getPermissionsState().hasPermission(bp.name, userId)) {
+ continue;
+ }
+
+ boolean used = false;
+
+ // Check if another package in the shared user needs the permission.
+ for (PackageSetting pkg : sus.packages) {
+ if (pkg.pkg != null
+ && !pkg.pkg.packageName.equals(deletedPs.pkg.packageName)
+ && pkg.pkg.requestedPermissions.contains(eachPerm)) {
used = true;
break;
}
}
+
if (!used) {
- // can safely delete this permission from list
- sus.grantedPermissions.remove(eachPerm);
- }
- }
- // Update gids
- int newGids[] = globalGids;
- for (String eachPerm : sus.grantedPermissions) {
- BasePermission bp = mPermissions.get(eachPerm);
- if (bp != null) {
- newGids = PackageManagerService.appendInts(newGids, bp.gids);
+ // Try to revoke as an install permission which is for all users.
+ if (sus.getPermissionsState().revokeInstallPermission(bp) ==
+ PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+ return UserHandle.USER_ALL;
+ }
+
+ // Try to revoke as an install permission which is per user.
+ if (sus.getPermissionsState().revokeRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+ return userId;
+ }
}
}
- sus.gids = newGids;
+
+ return UserHandle.USER_NULL;
}
int removePackageLPw(String name) {
@@ -829,7 +892,7 @@ final class Settings {
if (mOtherUserIds.get(uid) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate shared id: " + uid
- + " name=" + name);
+ + " name=" + name);
return false;
}
mOtherUserIds.put(uid, obj);
@@ -895,8 +958,159 @@ final class Settings {
return cpir;
}
+ /**
+ * The following functions suppose that you have a lock for managing access to the
+ * mIntentFiltersVerifications map.
+ */
+
+ /* package protected */
+ IntentFilterVerificationInfo getIntentFilterVerificationLPr(String packageName) {
+ PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return null;
+ }
+ return ps.getIntentFilterVerificationInfo();
+ }
+
+ /* package protected */
+ IntentFilterVerificationInfo createIntentFilterVerificationIfNeededLPw(String packageName,
+ ArrayList<String> domains) {
+ PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return null;
+ }
+ IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
+ if (ivi == null) {
+ ivi = new IntentFilterVerificationInfo(packageName, domains);
+ ps.setIntentFilterVerificationInfo(ivi);
+ }
+ return ivi;
+ }
+
+ int getIntentFilterVerificationStatusLPr(String packageName, int userId) {
+ PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+ int status = ps.getDomainVerificationStatusForUser(userId);
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ if (ps.getIntentFilterVerificationInfo() != null) {
+ status = ps.getIntentFilterVerificationInfo().getStatus();
+ }
+ }
+ return status;
+ }
+
+ boolean updateIntentFilterVerificationStatusLPw(String packageName, int status, int userId) {
+ // Update the status for the current package
+ PackageSetting current = mPackages.get(packageName);
+ if (current == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return false;
+ }
+ current.setDomainVerificationStatusForUser(status, userId);
+
+ if (current.getIntentFilterVerificationInfo() == null) {
+ Slog.w(PackageManagerService.TAG,
+ "No IntentFilterVerificationInfo known for name: " + packageName);
+ return false;
+ }
+
+ // Then, if we set a ALWAYS status, then put NEVER status for Apps whose IntentFilter
+ // domains overlap the domains of the current package
+ ArraySet<String> currentDomains = current.getIntentFilterVerificationInfo().getDomainsSet();
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+ for (PackageSetting ps : mPackages.values()) {
+ if (ps == null || ps.pkg.packageName.equals(packageName)) continue;
+ IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
+ if (ivi == null) {
+ continue;
+ }
+ ArraySet<String> set = ivi.getDomainsSet();
+ set.retainAll(currentDomains);
+ if (set.size() > 0) {
+ ps.setDomainVerificationStatusForUser(
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, userId);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Used for Settings App and PackageManagerService dump. Should be read only.
+ */
+ List<IntentFilterVerificationInfo> getIntentFilterVerificationsLPr(
+ String packageName) {
+ if (packageName == null) {
+ return Collections.<IntentFilterVerificationInfo>emptyList();
+ }
+ ArrayList<IntentFilterVerificationInfo> result = new ArrayList<>();
+ for (PackageSetting ps : mPackages.values()) {
+ IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
+ if (ivi == null || TextUtils.isEmpty(ivi.getPackageName()) ||
+ !ivi.getPackageName().equalsIgnoreCase(packageName)) {
+ continue;
+ }
+ result.add(ivi);
+ }
+ return result;
+ }
+
+ void removeIntentFilterVerificationLPw(String packageName, int userId) {
+ PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
+ return;
+ }
+ ps.clearDomainVerificationStatusForUser(userId);
+ }
+
+ void removeIntentFilterVerificationLPw(String packageName, int[] userIds) {
+ for (int userId : userIds) {
+ removeIntentFilterVerificationLPw(packageName, userId);
+ }
+ }
+
+ boolean setDefaultBrowserPackageNameLPr(String packageName, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ return false;
+ }
+ mDefaultBrowserApp.put(userId, packageName);
+ writePackageRestrictionsLPr(userId);
+ return true;
+ }
+
+ String getDefaultBrowserPackageNameLPw(int userId) {
+ return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.get(userId);
+ }
+
private File getUserPackagesStateFile(int userId) {
- return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml");
+ // TODO: Implement a cleaner solution when adding tests.
+ // This instead of Environment.getUserSystemDirectory(userId) to support testing.
+ File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
+ return new File(userDir, "package-restrictions.xml");
+ }
+
+ private File getUserRuntimePermissionsFile(int userId) {
+ // TODO: Implement a cleaner solution when adding tests.
+ // This instead of Environment.getUserSystemDirectory(userId) to support testing.
+ File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
+ return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
+ }
+
+ boolean isFirstRuntimePermissionsBoot() {
+ return !getUserRuntimePermissionsFile(UserHandle.USER_OWNER).exists();
+ }
+
+ void deleteRuntimePermissionsFiles() {
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ File file = getUserRuntimePermissionsFile(userId);
+ file.delete();
+ }
}
private File getUserPackagesStateBackupFile(int userId) {
@@ -913,15 +1127,9 @@ final class Settings {
}
}
- void readAllUsersPackageRestrictionsLPr() {
- List<UserInfo> users = getAllUsers();
- if (users == null) {
- readPackageRestrictionsLPr(0);
- return;
- }
-
- for (UserInfo user : users) {
- readPackageRestrictionsLPr(user.id);
+ void writeAllRuntimePermissionsLPr() {
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
}
}
@@ -959,7 +1167,13 @@ final class Settings {
mExternalDatabaseVersion = CURRENT_DATABASE_VERSION;
}
- private void readPreferredActivitiesLPw(XmlPullParser parser, int userId)
+ /**
+ * Applies the preferred activity state described by the given XML. This code
+ * also supports the restore-from-backup code path.
+ *
+ * @see PreferredActivityBackupHelper
+ */
+ void readPreferredActivitiesLPw(XmlPullParser parser, int userId)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
int type;
@@ -1032,6 +1246,35 @@ final class Settings {
}
}
+ private void readDomainVerificationLPw(XmlPullParser parser, PackageSettingBase packageSetting)
+ throws XmlPullParserException, IOException {
+ IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
+ packageSetting.setIntentFilterVerificationInfo(ivi);
+ Log.d(TAG, "Read domain verification for package:" + ivi.getPackageName());
+ }
+
+ private void readDefaultAppsLPw(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_DEFAULT_BROWSER)) {
+ String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+ mDefaultBrowserApp.put(userId, packageName);
+ } else {
+ String msg = "Unknown element under " + TAG_DEFAULT_APPS + ": " +
+ parser.getName();
+ PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
void readPackageRestrictionsLPr(int userId) {
if (DEBUG_MU) {
Log.i(TAG, "Reading package restrictions for user=" + userId);
@@ -1076,8 +1319,8 @@ final class Settings {
false, // notLaunched
false, // hidden
null, null, null,
- false // blockUninstall
- );
+ false, // blockUninstall
+ INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
}
return;
}
@@ -1146,6 +1389,12 @@ final class Settings {
final boolean blockUninstall = blockUninstallStr == null
? false : Boolean.parseBoolean(blockUninstallStr);
+ final String verifStateStr =
+ parser.getAttributeValue(null, ATTR_DOMAIN_VERIFICATON_STATE);
+ final int verifState = (verifStateStr == null) ?
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED :
+ Integer.parseInt(verifStateStr);
+
ArraySet<String> enabledComponents = null;
ArraySet<String> disabledComponents = null;
@@ -1166,13 +1415,16 @@ final class Settings {
}
ps.setUserState(userId, enabled, installed, stopped, notLaunched, hidden,
- enabledCaller, enabledComponents, disabledComponents, blockUninstall);
+ enabledCaller, enabledComponents, disabledComponents, blockUninstall,
+ verifState);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
} else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
readPersistentPreferredActivitiesLPw(parser, userId);
} else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {
readCrossProfileIntentFiltersLPw(parser, userId);
+ } else if (tagName.equals(TAG_DEFAULT_APPS)) {
+ readDefaultAppsLPw(parser, userId);
} else {
Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
+ parser.getName());
@@ -1224,6 +1476,11 @@ final class Settings {
return components;
}
+ /**
+ * Record the state of preferred activity configuration into XML. This is used both
+ * for recording packages.xml internally and for supporting backup/restore of the
+ * preferred activity configuration.
+ */
void writePreferredActivitiesLPr(XmlSerializer serializer, int userId, boolean full)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, "preferred-activities");
@@ -1266,6 +1523,30 @@ final class Settings {
serializer.endTag(null, TAG_CROSS_PROFILE_INTENT_FILTERS);
}
+ void writeDomainVerificationsLPr(XmlSerializer serializer,
+ IntentFilterVerificationInfo verificationInfo)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ if (verificationInfo != null && verificationInfo.getPackageName() != null) {
+ serializer.startTag(null, TAG_DOMAIN_VERIFICATION);
+ verificationInfo.writeToXml(serializer);
+ Log.d(TAG, "Wrote domain verification for package: "
+ + verificationInfo.getPackageName());
+ serializer.endTag(null, TAG_DOMAIN_VERIFICATION);
+ }
+ }
+
+ void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ serializer.startTag(null, TAG_DEFAULT_APPS);
+ String packageName = mDefaultBrowserApp.get(userId);
+ if (!TextUtils.isEmpty(packageName)) {
+ serializer.startTag(null, TAG_DEFAULT_BROWSER);
+ serializer.attribute(null, ATTR_PACKAGE_NAME, packageName);
+ serializer.endTag(null, TAG_DEFAULT_BROWSER);
+ }
+ serializer.endTag(null, TAG_DEFAULT_APPS);
+ }
+
void writePackageRestrictionsLPr(int userId) {
if (DEBUG_MU) {
Log.i(TAG, "Writing package restrictions for user=" + userId);
@@ -1313,7 +1594,9 @@ final class Settings {
&& ustate.enabledComponents.size() > 0)
|| (ustate.disabledComponents != null
&& ustate.disabledComponents.size() > 0)
- || ustate.blockUninstall) {
+ || ustate.blockUninstall
+ || (ustate.domainVerificationStatus !=
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED)) {
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_NAME, pkg.name);
if (DEBUG_MU) Log.i(TAG, " pkg=" + pkg.name + ", state=" + ustate.enabled);
@@ -1341,6 +1624,11 @@ final class Settings {
ustate.lastDisableAppCaller);
}
}
+ if (ustate.domainVerificationStatus !=
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ serializer.attribute(null, ATTR_DOMAIN_VERIFICATON_STATE,
+ Integer.toString(ustate.domainVerificationStatus));
+ }
if (ustate.enabledComponents != null
&& ustate.enabledComponents.size() > 0) {
serializer.startTag(null, TAG_ENABLED_COMPONENTS);
@@ -1361,15 +1649,15 @@ final class Settings {
}
serializer.endTag(null, TAG_DISABLED_COMPONENTS);
}
+
serializer.endTag(null, TAG_PACKAGE);
}
}
writePreferredActivitiesLPr(serializer, userId, true);
-
writePersistentPreferredActivitiesLPr(serializer, userId);
-
writeCrossProfileIntentFiltersLPr(serializer, userId);
+ writeDefaultAppsLPr(serializer, userId);
serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
@@ -1404,6 +1692,58 @@ final class Settings {
}
}
+ void readInstallPermissionsLPr(XmlPullParser parser,
+ PermissionsState permissionsState) throws IOException, XmlPullParserException {
+ 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)) {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+
+ BasePermission bp = mPermissions.get(name);
+ if (bp == null) {
+ Slog.w(PackageManagerService.TAG, "Unknown permission: " + name);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+
+ if (permissionsState.grantInstallPermission(bp) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ Slog.w(PackageManagerService.TAG, "Permission already added: " + name);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ } else {
+ Slog.w(PackageManagerService.TAG, "Unknown element under <permissions>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ void writePermissionsLPr(XmlSerializer serializer, Set<String> permissions)
+ throws IOException {
+ if (permissions.isEmpty()) {
+ return;
+ }
+
+ serializer.startTag(null, TAG_PERMISSIONS);
+
+ for (String permission : permissions) {
+ serializer.startTag(null, TAG_ITEM);
+ serializer.attribute(null, ATTR_NAME, permission);
+ serializer.endTag(null, TAG_ITEM);
+ }
+
+ serializer.endTag(null, TAG_PERMISSIONS);
+ }
+
// Note: assumed "stopped" field is already cleared in all packages.
// Legacy reader, used to read in the old file format after an upgrade. Not used after that.
void readStoppedLPw() {
@@ -1595,13 +1935,7 @@ final class Settings {
serializer.attribute(null, "userId",
Integer.toString(usr.userId));
usr.signatures.writeXml(serializer, "sigs", mPastSignatures);
- serializer.startTag(null, "perms");
- for (String name : usr.grantedPermissions) {
- serializer.startTag(null, TAG_ITEM);
- serializer.attribute(null, ATTR_NAME, name);
- serializer.endTag(null, TAG_ITEM);
- }
- serializer.endTag(null, "perms");
+ writePermissionsLPr(serializer, usr.getPermissionsState().getInstallPermissions());
serializer.endTag(null, "shared-user");
}
@@ -1615,7 +1949,7 @@ final class Settings {
serializer.endTag(null, "cleaning-package");
}
}
-
+
if (mRenamedPackages.size() > 0) {
for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
serializer.startTag(null, "renamed-package");
@@ -1624,7 +1958,7 @@ final class Settings {
serializer.endTag(null, "renamed-package");
}
}
-
+
mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
serializer.endTag(null, "packages");
@@ -1663,7 +1997,7 @@ final class Settings {
final ApplicationInfo ai = pkg.pkg.applicationInfo;
final String dataPath = ai.dataDir;
final boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
- final int[] gids = pkg.getGids();
+ final int[] gids = pkg.getPermissionsState().computeGids();
// Avoid any application that has a space in its path.
if (dataPath.indexOf(" ") >= 0)
@@ -1682,6 +2016,7 @@ final class Settings {
//
// DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS
// FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES:
+ // system/core/logd/LogStatistics.cpp
// system/core/run-as/run-as.c
// system/core/sdcard/sdcard.c
// external/libselinux/src/android.c:package_info_init()
@@ -1718,6 +2053,8 @@ final class Settings {
}
writeAllUsersPackageRestrictionsLPr();
+
+ writeAllRuntimePermissionsLPr();
return;
} catch(XmlPullParserException e) {
@@ -1770,26 +2107,12 @@ final class Settings {
} else {
serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId));
}
- serializer.startTag(null, "perms");
+
+ // If this is a shared user, the permissions will be written there.
if (pkg.sharedUser == null) {
- // If this is a shared user, the permissions will
- // be written there. We still need to write an
- // empty permissions list so permissionsFixed will
- // be set.
- for (final String name : pkg.grantedPermissions) {
- BasePermission bp = mPermissions.get(name);
- if (bp != null) {
- // We only need to write signature or system permissions but
- // this wont
- // match the semantics of grantedPermissions. So write all
- // permissions.
- serializer.startTag(null, TAG_ITEM);
- serializer.attribute(null, ATTR_NAME, name);
- serializer.endTag(null, TAG_ITEM);
- }
- }
+ writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions());
}
- serializer.endTag(null, "perms");
+
serializer.endTag(null, "updated-package");
}
@@ -1818,7 +2141,8 @@ final class Settings {
serializer.attribute(null, "cpuAbiOverride", pkg.cpuAbiOverrideString);
}
- serializer.attribute(null, "flags", Integer.toString(pkg.pkgFlags));
+ serializer.attribute(null, "publicFlags", Integer.toString(pkg.pkgFlags));
+ serializer.attribute(null, "privateFlags", Integer.toString(pkg.pkgPrivateFlags));
serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp));
serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime));
serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime));
@@ -1837,50 +2161,33 @@ final class Settings {
if (pkg.installerPackageName != null) {
serializer.attribute(null, "installer", pkg.installerPackageName);
}
+ if (pkg.volumeUuid != null) {
+ serializer.attribute(null, "volumeUuid", pkg.volumeUuid);
+ }
pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
if ((pkg.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
- serializer.startTag(null, "perms");
- if (pkg.sharedUser == null) {
- // If this is a shared user, the permissions will
- // be written there. We still need to write an
- // empty permissions list so permissionsFixed will
- // be set.
- for (final String name : pkg.grantedPermissions) {
- serializer.startTag(null, TAG_ITEM);
- serializer.attribute(null, ATTR_NAME, name);
- serializer.endTag(null, TAG_ITEM);
- }
- }
- serializer.endTag(null, "perms");
+ writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions());
}
- writeSigningKeySetsLPr(serializer, pkg.keySetData);
+ writeSigningKeySetLPr(serializer, pkg.keySetData);
writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
writeKeySetAliasesLPr(serializer, pkg.keySetData);
+ writeDomainVerificationsLPr(serializer, pkg.verificationInfo);
serializer.endTag(null, "package");
}
- void writeSigningKeySetsLPr(XmlSerializer serializer,
+ void writeSigningKeySetLPr(XmlSerializer serializer,
PackageKeySetData data) throws IOException {
- if (data.getSigningKeySets() != null) {
- // Keep track of the original signing-keyset.
- // Must be recorded first, since it will be read first and wipe the
- // current signing-keysets for the package when set.
- long properSigningKeySet = data.getProperSigningKeySet();
- serializer.startTag(null, "proper-signing-keyset");
- serializer.attribute(null, "identifier", Long.toString(properSigningKeySet));
- serializer.endTag(null, "proper-signing-keyset");
- for (long id : data.getSigningKeySets()) {
- serializer.startTag(null, "signing-keyset");
- serializer.attribute(null, "identifier", Long.toString(id));
- serializer.endTag(null, "signing-keyset");
- }
- }
+ serializer.startTag(null, "proper-signing-keyset");
+ serializer.attribute(null, "identifier",
+ Long.toString(data.getProperSigningKeySet()));
+ serializer.endTag(null, "proper-signing-keyset");
}
void writeUpgradeKeySetsLPr(XmlSerializer serializer,
PackageKeySetData data) throws IOException {
+ long properSigning = data.getProperSigningKeySet();
if (data.isUsingUpgradeKeySets()) {
for (long id : data.getUpgradeKeySets()) {
serializer.startTag(null, "upgrade-keyset");
@@ -1972,6 +2279,7 @@ final class Settings {
mPendingPackages.clear();
mPastSignatures.clear();
+ mKeySetRefs.clear();
try {
if (str == null) {
@@ -2033,6 +2341,8 @@ final class Settings {
// TODO: check whether this is okay! as it is very
// similar to how preferred-activities are treated
readCrossProfileIntentFiltersLPw(parser, 0);
+ } else if (tagName.equals(TAG_DEFAULT_BROWSER)) {
+ readDefaultAppsLPw(parser, 0);
} else if (tagName.equals("updated-package")) {
readDisabledSysPackageLPw(parser);
} else if (tagName.equals("cleaning-package")) {
@@ -2098,7 +2408,7 @@ final class Settings {
final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT);
mReadExternalStorageEnforced = "1".equals(enforcement);
} else if (tagName.equals("keyset-settings")) {
- mKeySetManagerService.readKeySetsLPw(parser);
+ mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs);
} else {
Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
+ parser.getName());
@@ -2120,6 +2430,7 @@ final class Settings {
}
final int N = mPendingPackages.size();
+
for (int i = 0; i < N; i++) {
final PendingPackage pp = mPendingPackages.get(i);
Object idObj = getUserIdLPr(pp.sharedId);
@@ -2127,8 +2438,8 @@ final class Settings {
PackageSetting p = getPackageLPw(pp.name, null, pp.realName,
(SharedUserSetting) idObj, pp.codePath, pp.resourcePath,
pp.legacyNativeLibraryPathString, pp.primaryCpuAbiString,
- pp.secondaryCpuAbiString, pp.versionCode, pp.pkgFlags, null,
- true /* add */, false /* allowInstall */);
+ pp.secondaryCpuAbiString, pp.versionCode, pp.pkgFlags, pp.pkgPrivateFlags,
+ null, true /* add */, false /* allowInstall */);
if (p == null) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unable to create application package for " + pp.name);
@@ -2160,9 +2471,11 @@ final class Settings {
} else {
if (users == null) {
readPackageRestrictionsLPr(0);
+ mRuntimePermissionsPersistence.readStateForUserSyncLPr(UserHandle.USER_OWNER);
} else {
for (UserInfo user : users) {
readPackageRestrictionsLPr(user.id);
+ mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
}
}
}
@@ -2536,7 +2849,7 @@ final class Settings {
final String ptype = parser.getAttributeValue(null, "type");
if (name != null && sourcePackage != null) {
final boolean dynamic = "dynamic".equals(ptype);
- final BasePermission bp = new BasePermission(name, sourcePackage,
+ final BasePermission bp = new BasePermission(name.intern(), sourcePackage,
dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL);
bp.protectionLevel = readInt(parser, null, "protection",
PermissionInfo.PROTECTION_NORMAL);
@@ -2596,14 +2909,15 @@ final class Settings {
}
int pkgFlags = 0;
+ int pkgPrivateFlags = 0;
pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
final File codePathFile = new File(codePathStr);
if (PackageManagerService.locationIsPrivileged(codePathFile)) {
- pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED;
+ pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
PackageSetting ps = new PackageSetting(name, realName, codePathFile,
new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr,
- secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags);
+ secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags);
String timeStampStr = parser.getAttributeValue(null, "ft");
if (timeStampStr != null) {
try {
@@ -2641,6 +2955,7 @@ final class Settings {
String sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
ps.appId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
}
+
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -2649,9 +2964,8 @@ final class Settings {
continue;
}
- String tagName = parser.getName();
- if (tagName.equals("perms")) {
- readGrantedPermissionsLPw(parser, ps.grantedPermissions);
+ if (parser.getName().equals(TAG_PERMISSIONS)) {
+ readInstallPermissionsLPr(parser, ps.getPermissionsState());
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <updated-package>: " + parser.getName());
@@ -2659,9 +2973,26 @@ final class Settings {
}
}
+ // We keep track for which users we granted permissions to be able
+ // to grant runtime permissions to system apps for newly appeared
+ // users or newly appeared system apps. If we supported runtime
+ // permissions during the previous boot, then we already granted
+ // permissions for all device users. In such a case we set the users
+ // for which we granted permissions to avoid clobbering of runtime
+ // permissions we granted to system apps but the user revoked later.
+ if (!isFirstRuntimePermissionsBoot()) {
+ final int[] userIds = UserManagerService.getInstance().getUserIds();
+ ps.setPermissionsUpdatedForUserIds(userIds);
+ }
+
mDisabledSysPackages.put(name, ps);
}
+ private static int PRE_M_APP_INFO_FLAG_HIDDEN = 1<<27;
+ private static int PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE = 1<<28;
+ private static int PRE_M_APP_INFO_FLAG_FORWARD_LOCK = 1<<29;
+ private static int PRE_M_APP_INFO_FLAG_PRIVILEGED = 1<<30;
+
private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
String name = null;
String realName = null;
@@ -2676,8 +3007,10 @@ final class Settings {
String cpuAbiOverrideString = null;
String systemStr = null;
String installerPackageName = null;
+ String volumeUuid = null;
String uidError = null;
int pkgFlags = 0;
+ int pkgPrivateFlags = 0;
long timeStamp = 0;
long firstInstallTime = 0;
long lastUpdateTime = 0;
@@ -2703,7 +3036,7 @@ final class Settings {
if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
primaryCpuAbiString = legacyCpuAbiString;
}
-;
+
version = parser.getAttributeValue(null, "version");
if (version != null) {
try {
@@ -2712,23 +3045,56 @@ final class Settings {
}
}
installerPackageName = parser.getAttributeValue(null, "installer");
+ volumeUuid = parser.getAttributeValue(null, "volumeUuid");
- systemStr = parser.getAttributeValue(null, "flags");
+ systemStr = parser.getAttributeValue(null, "publicFlags");
if (systemStr != null) {
try {
pkgFlags = Integer.parseInt(systemStr);
} catch (NumberFormatException e) {
}
+ systemStr = parser.getAttributeValue(null, "privateFlags");
+ if (systemStr != null) {
+ try {
+ pkgPrivateFlags = Integer.parseInt(systemStr);
+ } catch (NumberFormatException e) {
+ }
+ }
} else {
- // For backward compatibility
- systemStr = parser.getAttributeValue(null, "system");
+ // Pre-M -- both public and private flags were stored in one "flags" field.
+ systemStr = parser.getAttributeValue(null, "flags");
if (systemStr != null) {
- pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM
- : 0;
+ try {
+ pkgFlags = Integer.parseInt(systemStr);
+ } catch (NumberFormatException e) {
+ }
+ if ((pkgFlags & PRE_M_APP_INFO_FLAG_HIDDEN) != 0) {
+ pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN;
+ }
+ if ((pkgFlags & PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE) != 0) {
+ pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
+ }
+ if ((pkgFlags & PRE_M_APP_INFO_FLAG_FORWARD_LOCK) != 0) {
+ pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK;
+ }
+ if ((pkgFlags & PRE_M_APP_INFO_FLAG_PRIVILEGED) != 0) {
+ pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+ }
+ pkgFlags &= ~(PRE_M_APP_INFO_FLAG_HIDDEN
+ | PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE
+ | PRE_M_APP_INFO_FLAG_FORWARD_LOCK
+ | PRE_M_APP_INFO_FLAG_PRIVILEGED);
} else {
- // Old settings that don't specify system... just treat
- // them as system, good enough.
- pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
+ // For backward compatibility
+ systemStr = parser.getAttributeValue(null, "system");
+ if (systemStr != null) {
+ pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM
+ : 0;
+ } else {
+ // Old settings that don't specify system... just treat
+ // them as system, good enough.
+ pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
+ }
}
}
String timeStampStr = parser.getAttributeValue(null, "ft");
@@ -2781,7 +3147,8 @@ final class Settings {
} else if (userId > 0) {
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,
- secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags);
+ secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,
+ pkgPrivateFlags);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
+ userId + " pkg=" + packageSetting);
@@ -2800,7 +3167,7 @@ final class Settings {
packageSetting = new PendingPackage(name.intern(), realName, new File(
codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
- userId, versionCode, pkgFlags);
+ userId, versionCode, pkgFlags, pkgPrivateFlags);
packageSetting.setTimeStamp(timeStamp);
packageSetting.firstInstallTime = firstInstallTime;
packageSetting.lastUpdateTime = lastUpdateTime;
@@ -2827,6 +3194,7 @@ final class Settings {
if (packageSetting != null) {
packageSetting.uidError = "true".equals(uidError);
packageSetting.installerPackageName = installerPackageName;
+ packageSetting.volumeUuid = volumeUuid;
packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
packageSetting.primaryCpuAbiString = primaryCpuAbiString;
packageSetting.secondaryCpuAbiString = secondaryCpuAbiString;
@@ -2861,7 +3229,6 @@ final class Settings {
packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_COMPLETE;
}
}
-
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -2878,22 +3245,36 @@ final class Settings {
readEnabledComponentsLPw(packageSetting, parser, 0);
} else if (tagName.equals("sigs")) {
packageSetting.signatures.readXml(parser, mPastSignatures);
- } else if (tagName.equals("perms")) {
- readGrantedPermissionsLPw(parser, packageSetting.grantedPermissions);
- packageSetting.permissionsFixed = true;
+ } else if (tagName.equals(TAG_PERMISSIONS)) {
+ readInstallPermissionsLPr(parser,
+ packageSetting.getPermissionsState());
+ packageSetting.installPermissionsFixed = true;
} else if (tagName.equals("proper-signing-keyset")) {
long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
+ Integer refCt = mKeySetRefs.get(id);
+ if (refCt != null) {
+ mKeySetRefs.put(id, refCt + 1);
+ } else {
+ mKeySetRefs.put(id, 1);
+ }
packageSetting.keySetData.setProperSigningKeySet(id);
} else if (tagName.equals("signing-keyset")) {
- long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
- packageSetting.keySetData.addSigningKeySet(id);
+ // from v1 of keysetmanagerservice - no longer used
} else if (tagName.equals("upgrade-keyset")) {
long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
packageSetting.keySetData.addUpgradeKeySetById(id);
} else if (tagName.equals("defined-keyset")) {
long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
String alias = parser.getAttributeValue(null, "alias");
+ Integer refCt = mKeySetRefs.get(id);
+ if (refCt != null) {
+ mKeySetRefs.put(id, refCt + 1);
+ } else {
+ mKeySetRefs.put(id, 1);
+ }
packageSetting.keySetData.addDefinedKeySet(id, alias);
+ } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
+ readDomainVerificationLPw(parser, packageSetting);
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <package>: " + parser.getName());
@@ -2901,7 +3282,17 @@ final class Settings {
}
}
-
+ // We keep track for which users we granted permissions to be able
+ // to grant runtime permissions to system apps for newly appeared
+ // users or newly appeared system apps. If we supported runtime
+ // permissions during the previous boot, then we already granted
+ // permissions for all device users. In such a case we set the users
+ // for which we granted permissions to avoid clobbering of runtime
+ // permissions we granted to system apps but the user revoked later.
+ if (!isFirstRuntimePermissionsBoot()) {
+ final int[] userIds = UserManagerService.getInstance().getUserIds();
+ packageSetting.setPermissionsUpdatedForUserIds(userIds);
+ }
} else {
XmlUtils.skipCurrentTag(parser);
}
@@ -2967,6 +3358,7 @@ final class Settings {
String name = null;
String idStr = null;
int pkgFlags = 0;
+ int pkgPrivateFlags = 0;
SharedUserSetting su = null;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
@@ -2985,7 +3377,8 @@ final class Settings {
+ " has bad userId " + idStr + " at "
+ parser.getPositionDescription());
} else {
- if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags)) == null) {
+ if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags))
+ == null) {
PackageManagerService
.reportSettingsProblem(Log.ERROR, "Occurred while parsing settings at "
+ parser.getPositionDescription());
@@ -2996,7 +3389,6 @@ final class Settings {
"Error in package manager settings: package " + name + " has bad userId "
+ idStr + " at " + parser.getPositionDescription());
}
- ;
if (su != null) {
int outerDepth = parser.getDepth();
@@ -3011,7 +3403,7 @@ final class Settings {
if (tagName.equals("sigs")) {
su.signatures.readXml(parser, mPastSignatures);
} else if (tagName.equals("perms")) {
- readGrantedPermissionsLPw(parser, su.grantedPermissions);
+ readInstallPermissionsLPr(parser, su.getPermissionsState());
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <shared-user>: " + parser.getName());
@@ -3019,35 +3411,18 @@ final class Settings {
}
}
- } else {
- XmlUtils.skipCurrentTag(parser);
- }
- }
-
- private void readGrantedPermissionsLPw(XmlPullParser parser, ArraySet<String> outPerms)
- throws IOException, XmlPullParserException {
- 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)) {
- String name = parser.getAttributeValue(null, ATTR_NAME);
- if (name != null) {
- outPerms.add(name.intern());
- } else {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Error in package manager settings: <perms> has" + " no name at "
- + parser.getPositionDescription());
- }
- } else {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Unknown element under <perms>: " + parser.getName());
+ // We keep track for which users we granted permissions to be able
+ // to grant runtime permissions to system apps for newly appeared
+ // users or newly appeared system apps. If we supported runtime
+ // permissions during the previous boot, then we already granted
+ // permissions for all device users. In such a case we set the users
+ // for which we granted permissions to avoid clobbering of runtime
+ // permissions we granted to system apps but the user revoked later.
+ if (!isFirstRuntimePermissionsBoot()) {
+ final int[] userIds = UserManagerService.getInstance().getUserIds();
+ su.setPermissionsUpdatedForUserIds(userIds);
}
+ } else {
XmlUtils.skipCurrentTag(parser);
}
}
@@ -3064,7 +3439,7 @@ final class Settings {
// Only system apps are initially installed.
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,
+ installer.createUserData(ps.volumeUuid, ps.name,
UserHandle.getUid(userHandle, ps.appId), userHandle,
ps.pkg.applicationInfo.seinfo);
}
@@ -3083,6 +3458,8 @@ final class Settings {
file = getUserPackagesStateBackupFile(userId);
file.delete();
removeCrossProfileIntentFiltersLPw(userId);
+
+ mRuntimePermissionsPersistence.onUserRemoved(userId);
}
void removeCrossProfileIntentFiltersLPw(int userId) {
@@ -3262,7 +3639,7 @@ final class Settings {
return false;
}
- private List<UserInfo> getAllUsers() {
+ List<UserInfo> getAllUsers() {
long id = Binder.clearCallingIdentity();
try {
return UserManagerService.getInstance().getUsers(false);
@@ -3274,7 +3651,23 @@ final class Settings {
return null;
}
- static final void printFlags(PrintWriter pw, int val, Object[] spec) {
+ /**
+ * Return all {@link PackageSetting} that are actively installed on the
+ * given {@link VolumeInfo#fsUuid}.
+ */
+ List<PackageSetting> getVolumePackagesLPr(String volumeUuid) {
+ Preconditions.checkNotNull(volumeUuid);
+ ArrayList<PackageSetting> res = new ArrayList<>();
+ for (int i = 0; i < mPackages.size(); i++) {
+ final PackageSetting setting = mPackages.valueAt(i);
+ if (Objects.equals(volumeUuid, setting.volumeUuid)) {
+ res.add(setting);
+ }
+ }
+ return res;
+ }
+
+ static void printFlags(PrintWriter pw, int val, Object[] spec) {
pw.print("[ ");
for (int i=0; i<spec.length; i+=2) {
int mask = (Integer)spec[i];
@@ -3302,9 +3695,12 @@ final class Settings {
ApplicationInfo.FLAG_RESTORE_ANY_VERSION, "RESTORE_ANY_VERSION",
ApplicationInfo.FLAG_EXTERNAL_STORAGE, "EXTERNAL_STORAGE",
ApplicationInfo.FLAG_LARGE_HEAP, "LARGE_HEAP",
- ApplicationInfo.FLAG_PRIVILEGED, "PRIVILEGED",
- ApplicationInfo.FLAG_FORWARD_LOCK, "FORWARD_LOCK",
- ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
+ };
+
+ static final Object[] PRIVATE_FLAG_DUMP_SPEC = new Object[] {
+ ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, "PRIVILEGED",
+ ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK, "FORWARD_LOCK",
+ ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
};
void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag, PackageSetting ps,
@@ -3368,8 +3764,8 @@ final class Settings {
pw.println(ps.name);
}
- pw.print(prefix); pw.print(" userId="); pw.print(ps.appId);
- pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
+ pw.print(prefix); pw.print(" userId="); pw.println(ps.appId);
+
if (ps.sharedUser != null) {
pw.print(prefix); pw.print(" sharedUser="); pw.println(ps.sharedUser);
}
@@ -3391,6 +3787,8 @@ final class Settings {
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(" priavateFlags="); printFlags(pw,
+ ps.pkg.applicationInfo.privateFlags, PRIVATE_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");
@@ -3475,12 +3873,22 @@ final class Settings {
pw.print(prefix); pw.print(" installerPackageName=");
pw.println(ps.installerPackageName);
}
+ if (ps.volumeUuid != null) {
+ pw.print(prefix); pw.print(" volumeUuid=");
+ pw.println(ps.volumeUuid);
+ }
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(prefix); pw.print(" installPermissionsFixed=");
+ pw.print(ps.installPermissionsFixed);
pw.print(" installStatus="); pw.println(ps.installStatus);
pw.print(prefix); pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
pw.println();
+
+ if (ps.sharedUser == null) {
+ PermissionsState permissionsState = ps.getPermissionsState();
+ dumpInstallPermissionsLPr(pw, prefix + " ", permissionsState);
+ }
+
for (UserInfo user : users) {
pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": ");
pw.print(" installed=");
@@ -3498,6 +3906,14 @@ final class Settings {
pw.print(prefix); pw.print(" lastDisabledCaller: ");
pw.println(lastDisabledAppCaller);
}
+
+ if (ps.sharedUser == null) {
+ PermissionsState permissionsState = ps.getPermissionsState();
+ dumpGidsLPr(pw, prefix + " ", permissionsState.computeGids(user.id));
+ dumpRuntimePermissionsLPr(pw, prefix + " ", permissionsState
+ .getRuntimePermissions(user.id));
+ }
+
ArraySet<String> cmp = ps.getDisabledComponents(user.id);
if (cmp != null && cmp.size() > 0) {
pw.print(prefix); pw.println(" disabledComponents:");
@@ -3513,12 +3929,6 @@ final class Settings {
}
}
}
- 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, boolean checkin) {
@@ -3604,7 +4014,8 @@ final class Settings {
pw.println("):");
pw.print(" sourcePackage="); pw.println(p.sourcePackage);
pw.print(" uid="); pw.print(p.uid);
- pw.print(" gids="); pw.print(PackageManagerService.arrayToString(p.gids));
+ pw.print(" gids="); pw.print(Arrays.toString(
+ p.computeGids(UserHandle.USER_OWNER)));
pw.print(" type="); pw.print(p.type);
pw.print(" prot=");
pw.println(PermissionInfo.protectionToString(p.protectionLevel));
@@ -3640,14 +4051,21 @@ final class Settings {
pw.print("] (");
pw.print(Integer.toHexString(System.identityHashCode(su)));
pw.println("):");
- pw.print(" userId=");
- pw.print(su.userId);
- pw.print(" gids=");
- pw.println(PackageManagerService.arrayToString(su.gids));
- pw.println(" grantedPermissions:");
- for (String s : su.grantedPermissions) {
- pw.print(" ");
- pw.println(s);
+
+ String prefix = " ";
+ pw.print(prefix); pw.print("userId="); pw.println(su.userId);
+
+ PermissionsState permissionsState = su.getPermissionsState();
+ dumpInstallPermissionsLPr(pw, prefix, permissionsState);
+
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ final int[] gids = permissionsState.computeGids(userId);
+ Set<String> permissions = permissionsState.getRuntimePermissions(userId);
+ if (!ArrayUtils.isEmpty(gids) || !permissions.isEmpty()) {
+ pw.print(prefix); pw.print("User "); pw.print(userId); pw.println(": ");
+ dumpGidsLPr(pw, prefix + " ", gids);
+ dumpRuntimePermissionsLPr(pw, prefix + " ", permissions);
+ }
}
} else {
pw.print("suid,"); pw.print(su.userId); pw.print(","); pw.println(su.name);
@@ -3682,4 +4100,326 @@ final class Settings {
pw.print("]");
}
}
+
+ void dumpGidsLPr(PrintWriter pw, String prefix, int[] gids) {
+ if (!ArrayUtils.isEmpty(gids)) {
+ pw.print(prefix);
+ pw.print("gids="); pw.println(
+ PackageManagerService.arrayToString(gids));
+ }
+ }
+
+ void dumpRuntimePermissionsLPr(PrintWriter pw, String prefix, Set<String> permissions) {
+ if (!permissions.isEmpty()) {
+ pw.print(prefix); pw.println("runtime permissions:");
+ for (String permission : permissions) {
+ pw.print(prefix); pw.print(" "); pw.println(permission);
+ }
+ }
+ }
+
+ void dumpInstallPermissionsLPr(PrintWriter pw, String prefix,
+ PermissionsState permissionsState) {
+ Set<String> permissions = permissionsState.getInstallPermissions();
+ if (!permissions.isEmpty()) {
+ pw.print(prefix); pw.println("install permissions:");
+ for (String permission : permissions) {
+ pw.print(prefix); pw.print(" "); pw.println(permission);
+ }
+ }
+ }
+
+ public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {
+ if (sync) {
+ mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);
+ } else {
+ mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
+ }
+ }
+
+ private final class RuntimePermissionPersistence {
+ private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200;
+
+ private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000;
+
+ private final Handler mHandler = new MyHandler();
+
+ private final Object mLock;
+
+ @GuardedBy("mLock")
+ private SparseBooleanArray mWriteScheduled = new SparseBooleanArray();
+
+ @GuardedBy("mLock")
+ private SparseLongArray mLastNotWrittenMutationTimesMillis = new SparseLongArray();
+
+ public RuntimePermissionPersistence(Object lock) {
+ mLock = lock;
+ }
+
+ public void writePermissionsForUserSyncLPr(int userId) {
+ if (!PackageManagerService.RUNTIME_PERMISSIONS_ENABLED) {
+ return;
+ }
+
+ mHandler.removeMessages(userId);
+ writePermissionsSync(userId);
+ }
+
+ public void writePermissionsForUserAsyncLPr(int userId) {
+ if (!PackageManagerService.RUNTIME_PERMISSIONS_ENABLED) {
+ return;
+ }
+
+ final long currentTimeMillis = SystemClock.uptimeMillis();
+
+ if (mWriteScheduled.get(userId)) {
+ mHandler.removeMessages(userId);
+
+ // If enough time passed, write without holding off anymore.
+ final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis
+ .get(userId);
+ final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis
+ - lastNotWrittenMutationTimeMillis;
+ if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) {
+ mHandler.obtainMessage(userId).sendToTarget();
+ return;
+ }
+
+ // Hold off a bit more as settings are frequently changing.
+ final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis
+ + MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0);
+ final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS,
+ maxDelayMillis);
+
+ Message message = mHandler.obtainMessage(userId);
+ mHandler.sendMessageDelayed(message, writeDelayMillis);
+ } else {
+ mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis);
+ Message message = mHandler.obtainMessage(userId);
+ mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
+ mWriteScheduled.put(userId, true);
+ }
+ }
+
+ private void writePermissionsSync(int userId) {
+ AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId));
+
+ ArrayMap<String, Set<String>> permissionsForPackage = new ArrayMap<>();
+ ArrayMap<String, Set<String>> permissionsForSharedUser = new ArrayMap<>();
+
+ synchronized (mLock) {
+ mWriteScheduled.delete(userId);
+
+ final int packageCount = mPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ String packageName = mPackages.keyAt(i);
+ PackageSetting packageSetting = mPackages.valueAt(i);
+ if (packageSetting.sharedUser == null) {
+ PermissionsState permissionsState = packageSetting.getPermissionsState();
+ Set<String> permissions = permissionsState.getRuntimePermissions(userId);
+ if (!permissions.isEmpty()) {
+ permissionsForPackage.put(packageName, permissions);
+ }
+ }
+ }
+
+ final int sharedUserCount = mSharedUsers.size();
+ for (int i = 0; i < sharedUserCount; i++) {
+ String sharedUserName = mSharedUsers.keyAt(i);
+ SharedUserSetting sharedUser = mSharedUsers.valueAt(i);
+ PermissionsState permissionsState = sharedUser.getPermissionsState();
+ Set<String> permissions = permissionsState.getRuntimePermissions(userId);
+ if (!permissions.isEmpty()) {
+ permissionsForSharedUser.put(sharedUserName, permissions);
+ }
+ }
+ }
+
+ FileOutputStream out = null;
+ try {
+ out = destination.startWrite();
+
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.setFeature(
+ "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
+
+ final int packageCount = permissionsForPackage.size();
+ for (int i = 0; i < packageCount; i++) {
+ String packageName = permissionsForPackage.keyAt(i);
+ Set<String> permissions = permissionsForPackage.valueAt(i);
+ serializer.startTag(null, TAG_PACKAGE);
+ serializer.attribute(null, ATTR_NAME, packageName);
+ writePermissions(serializer, permissions);
+ serializer.endTag(null, TAG_PACKAGE);
+ }
+
+ final int sharedUserCount = permissionsForSharedUser.size();
+ for (int i = 0; i < sharedUserCount; i++) {
+ String packageName = permissionsForSharedUser.keyAt(i);
+ Set<String> permissions = permissionsForSharedUser.valueAt(i);
+ serializer.startTag(null, TAG_SHARED_USER);
+ serializer.attribute(null, ATTR_NAME, packageName);
+ writePermissions(serializer, permissions);
+ serializer.endTag(null, TAG_SHARED_USER);
+ }
+
+ serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
+ serializer.endDocument();
+ destination.finishWrite(out);
+
+ // Any error while writing is fatal.
+ } catch (Throwable t) {
+ Slog.wtf(PackageManagerService.TAG,
+ "Failed to write settings, restoring backup", t);
+ destination.failWrite(out);
+ throw new IllegalStateException("Failed to write runtime permissions,"
+ + " restoring backup", t);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private void onUserRemoved(int userId) {
+ // Make sure we do not
+ mHandler.removeMessages(userId);
+
+ for (SettingBase sb : mPackages.values()) {
+ revokeRuntimePermissions(sb, userId);
+ }
+
+ for (SettingBase sb : mSharedUsers.values()) {
+ revokeRuntimePermissions(sb, userId);
+ }
+ }
+
+ private void revokeRuntimePermissions(SettingBase sb, int userId) {
+ PermissionsState permissionsState = sb.getPermissionsState();
+ for (String permission : permissionsState.getRuntimePermissions(userId)) {
+ BasePermission bp = mPermissions.get(permission);
+ if (bp != null) {
+ permissionsState.revokeRuntimePermission(bp, userId);
+ }
+ }
+ }
+
+ public void readStateForUserSyncLPr(int userId) {
+ File permissionsFile = getUserRuntimePermissionsFile(userId);
+ if (!permissionsFile.exists()) {
+ return;
+ }
+
+ FileInputStream in;
+ try {
+ in = new FileInputStream(permissionsFile);
+ } catch (FileNotFoundException fnfe) {
+ Slog.i(PackageManagerService.TAG, "No permissions state");
+ return;
+ }
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseRuntimePermissionsLPr(parser, userId);
+
+ } catch (XmlPullParserException | IOException e) {
+ throw new IllegalStateException("Failed parsing permissions file: "
+ + permissionsFile , e);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ private void parseRuntimePermissionsLPr(XmlPullParser parser, int userId)
+ throws IOException, XmlPullParserException {
+ final 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;
+ }
+
+ switch (parser.getName()) {
+ case TAG_PACKAGE: {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ PackageSetting ps = mPackages.get(name);
+ if (ps == null) {
+ Slog.w(PackageManagerService.TAG, "Unknown package:" + name);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ parsePermissionsLPr(parser, ps.getPermissionsState(), userId);
+ } break;
+
+ case TAG_SHARED_USER: {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ SharedUserSetting sus = mSharedUsers.get(name);
+ if (sus == null) {
+ Slog.w(PackageManagerService.TAG, "Unknown shared user:" + name);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ parsePermissionsLPr(parser, sus.getPermissionsState(), userId);
+ } break;
+ }
+ }
+ }
+
+ private void parsePermissionsLPr(XmlPullParser parser, PermissionsState permissionsState,
+ int userId) throws IOException, XmlPullParserException {
+ final 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;
+ }
+
+ switch (parser.getName()) {
+ case TAG_ITEM: {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ BasePermission bp = mPermissions.get(name);
+ if (bp == null) {
+ Slog.w(PackageManagerService.TAG, "Unknown permission:" + name);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+
+ if (permissionsState.grantRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ Slog.w(PackageManagerService.TAG, "Duplicate permission:" + name);
+ }
+ } break;
+ }
+ }
+ }
+
+ private void writePermissions(XmlSerializer serializer, Set<String> permissions)
+ throws IOException {
+ for (String permission : permissions) {
+ serializer.startTag(null, TAG_ITEM);
+ serializer.attribute(null, ATTR_NAME, permission);
+ serializer.endTag(null, TAG_ITEM);
+ }
+ }
+
+ private final class MyHandler extends Handler {
+ public MyHandler() {
+ super(BackgroundThread.getHandler().getLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ final int userId = message.what;
+ Runnable callback = (Runnable) message.obj;
+ writePermissionsSync(userId);
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index 2b406f7..06e020a 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -21,21 +21,23 @@ import android.util.ArraySet;
/**
* Settings data for a particular shared user ID we know about.
*/
-final class SharedUserSetting extends GrantedPermissions {
+final class SharedUserSetting extends SettingBase {
final String name;
int userId;
// flags that are associated with this uid, regardless of any package flags
int uidFlags;
+ int uidPrivateFlags;
final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>();
final PackageSignatures signatures = new PackageSignatures();
- SharedUserSetting(String _name, int _pkgFlags) {
- super(_pkgFlags);
+ SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) {
+ super(_pkgFlags, _pkgPrivateFlags);
uidFlags = _pkgFlags;
+ uidPrivateFlags = _pkgPrivateFlags;
name = _name;
}
@@ -55,12 +57,20 @@ final class SharedUserSetting extends GrantedPermissions {
}
setFlags(aggregatedFlags);
}
+ if ((this.pkgPrivateFlags & packageSetting.pkgPrivateFlags) != 0) {
+ int aggregatedPrivateFlags = uidPrivateFlags;
+ for (PackageSetting ps : packages) {
+ aggregatedPrivateFlags |= ps.pkgPrivateFlags;
+ }
+ setPrivateFlags(aggregatedPrivateFlags);
+ }
}
}
void addPackage(PackageSetting packageSetting) {
if (packages.add(packageSetting)) {
setFlags(this.pkgFlags | packageSetting.pkgFlags);
+ setPrivateFlags(this.pkgPrivateFlags | packageSetting.pkgPrivateFlags);
}
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d484b8f..e79a206 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,8 +16,6 @@
package com.android.server.pm;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
@@ -30,14 +28,15 @@ 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.Debug;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IUserManager;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -51,9 +50,11 @@ import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -73,6 +74,8 @@ import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
+import libcore.io.IoUtils;
+
public class UserManagerService extends IUserManager.Stub {
private static final String LOG_TAG = "UserManagerService";
@@ -109,10 +112,13 @@ public class UserManagerService extends IUserManager.Stub {
private static final String ATTR_TYPE_STRING = "s";
private static final String ATTR_TYPE_BOOLEAN = "b";
private static final String ATTR_TYPE_INTEGER = "i";
+ private static final String ATTR_TYPE_BUNDLE = "B";
+ private static final String ATTR_TYPE_BUNDLE_ARRAY = "BA";
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 USER_PHOTO_FILENAME_TMP = USER_PHOTO_FILENAME + ".tmp";
private static final String RESTRICTIONS_FILE_PREFIX = "res_";
private static final String XML_SUFFIX = ".xml";
@@ -134,6 +140,8 @@ public class UserManagerService extends IUserManager.Stub {
// BACKOFF_INC_INTERVAL times.
private static final int[] BACKOFF_TIMES = { 0, 30*1000, 60*1000, 5*60*1000, 30*60*1000 };
+ static final int WRITE_USER_MSG = 1;
+ static final int WRITE_USER_DELAY = 2*1000; // 2 seconds
private final Context mContext;
private final PackageManagerService mPm;
@@ -210,7 +218,7 @@ public class UserManagerService extends IUserManager.Stub {
mPm = pm;
mInstallLock = installLock;
mPackagesLock = packagesLock;
- mHandler = new Handler();
+ mHandler = new MainHandler();
synchronized (mInstallLock) {
synchronized (mPackagesLock) {
mUsersDir = new File(dataDir, USER_INFO_DIR);
@@ -319,16 +327,20 @@ public class UserManagerService extends IUserManager.Stub {
public UserInfo getProfileParent(int userHandle) {
checkManageUsersPermission("get the profile parent");
synchronized (mPackagesLock) {
- UserInfo profile = getUserInfoLocked(userHandle);
- if (profile == null) {
- return null;
- }
- int parentUserId = profile.profileGroupId;
- if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
- return null;
- } else {
- return getUserInfoLocked(parentUserId);
- }
+ return getProfileParentLocked(userHandle);
+ }
+ }
+
+ private UserInfo getProfileParentLocked(int userHandle) {
+ UserInfo profile = getUserInfoLocked(userHandle);
+ if (profile == null) {
+ return null;
+ }
+ int parentUserId = profile.profileGroupId;
+ if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+ return null;
+ } else {
+ return getUserInfoLocked(parentUserId);
}
}
@@ -433,7 +445,8 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
- public Bitmap getUserIcon(int userId) {
+ public ParcelFileDescriptor getUserIcon(int userId) {
+ String iconPath;
synchronized (mPackagesLock) {
UserInfo info = mUsers.get(userId);
if (info == null || info.partial) {
@@ -448,8 +461,16 @@ public class UserManagerService extends IUserManager.Stub {
if (info.iconPath == null) {
return null;
}
- return BitmapFactory.decodeFile(info.iconPath);
+ iconPath = info.iconPath;
+ }
+
+ try {
+ return ParcelFileDescriptor.open(
+ new File(iconPath), ParcelFileDescriptor.MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, "Couldn't find icon file", e);
}
+ return null;
}
public void makeInitialized(int userId) {
@@ -461,7 +482,7 @@ public class UserManagerService extends IUserManager.Stub {
}
if ((info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
info.flags |= UserInfo.FLAG_INITIALIZED;
- writeUserLocked(info);
+ scheduleWriteUserLocked(info);
}
}
}
@@ -529,7 +550,7 @@ public class UserManagerService extends IUserManager.Stub {
} finally {
Binder.restoreCallingIdentity(token);
}
- writeUserLocked(mUsers.get(userId));
+ scheduleWriteUserLocked(mUsers.get(userId));
}
}
@@ -572,6 +593,7 @@ public class UserManagerService extends IUserManager.Stub {
try {
File dir = new File(mUsersDir, Integer.toString(info.id));
File file = new File(dir, USER_PHOTO_FILENAME);
+ File tmp = new File(dir, USER_PHOTO_FILENAME_TMP);
if (!dir.exists()) {
dir.mkdir();
FileUtils.setPermissions(
@@ -580,7 +602,8 @@ public class UserManagerService extends IUserManager.Stub {
-1, -1);
}
FileOutputStream os;
- if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, os = new FileOutputStream(file))) {
+ if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, os = new FileOutputStream(tmp))
+ && tmp.renameTo(file)) {
info.iconPath = file.getAbsolutePath();
}
try {
@@ -588,6 +611,7 @@ public class UserManagerService extends IUserManager.Stub {
} catch (IOException ioe) {
// What the ... !
}
+ tmp.delete();
} catch (FileNotFoundException e) {
Slog.w(LOG_TAG, "Error setting photo for user ", e);
}
@@ -695,7 +719,7 @@ public class UserManagerService extends IUserManager.Stub {
UserInfo user = mUsers.get(UserHandle.USER_OWNER);
if ("Primary".equals(user.name)) {
user.name = mContext.getResources().getString(com.android.internal.R.string.owner_name);
- writeUserLocked(user);
+ scheduleWriteUserLocked(user);
}
userVersion = 1;
}
@@ -705,7 +729,7 @@ public class UserManagerService extends IUserManager.Stub {
UserInfo user = mUsers.get(UserHandle.USER_OWNER);
if ((user.flags & UserInfo.FLAG_INITIALIZED) == 0) {
user.flags |= UserInfo.FLAG_INITIALIZED;
- writeUserLocked(user);
+ scheduleWriteUserLocked(user);
}
userVersion = 2;
}
@@ -748,6 +772,13 @@ public class UserManagerService extends IUserManager.Stub {
writeUserLocked(primary);
}
+ private void scheduleWriteUserLocked(UserInfo userInfo) {
+ if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
+ Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userInfo);
+ mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
+ }
+ }
+
/*
* Writes the user file in this format:
*
@@ -897,6 +928,8 @@ public class UserManagerService extends IUserManager.Stub {
writeBoolean(serializer, restrictions, UserManager.DISALLOW_CREATE_WINDOWS);
writeBoolean(serializer, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
writeBoolean(serializer, restrictions, UserManager.DISALLOW_OUTGOING_BEAM);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_WALLPAPER);
+ writeBoolean(serializer, restrictions, UserManager.DISALLOW_SAFE_BOOT);
serializer.endTag(null, TAG_RESTRICTIONS);
}
@@ -951,12 +984,6 @@ public class UserManagerService extends IUserManager.Stub {
lastAttemptTime = readLongAttribute(parser, ATTR_LAST_RETRY_MS, 0L);
profileGroupId = readIntAttribute(parser, ATTR_PROFILE_GROUP_ID,
UserInfo.NO_PROFILE_GROUP_ID);
- if (profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
- // This attribute was added and renamed during development of L.
- // TODO Remove upgrade path by 1st May 2014
- profileGroupId = readIntAttribute(parser, "relatedGroupId",
- UserInfo.NO_PROFILE_GROUP_ID);
- }
String valueString = parser.getAttributeValue(null, ATTR_PARTIAL);
if ("true".equals(valueString)) {
partial = true;
@@ -1049,6 +1076,8 @@ public class UserManagerService extends IUserManager.Stub {
readBoolean(parser, restrictions, UserManager.DISALLOW_CREATE_WINDOWS);
readBoolean(parser, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
readBoolean(parser, restrictions, UserManager.DISALLOW_OUTGOING_BEAM);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_WALLPAPER);
+ readBoolean(parser, restrictions, UserManager.DISALLOW_SAFE_BOOT);
}
private void readBoolean(XmlPullParser parser, Bundle restrictions,
@@ -1191,17 +1220,17 @@ public class UserManagerService extends IUserManager.Stub {
if (parent != null) {
if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.profileGroupId = parent.id;
- writeUserLocked(parent);
+ scheduleWriteUserLocked(parent);
}
userInfo.profileGroupId = parent.profileGroupId;
}
- writeUserLocked(userInfo);
mPm.createNewUserLILPw(userId, userPath);
userInfo.partial = false;
- writeUserLocked(userInfo);
+ scheduleWriteUserLocked(userInfo);
updateUserIdsLocked();
Bundle restrictions = new Bundle();
mUserRestrictions.append(userId, restrictions);
+ mPm.newUserCreatedLILPw(userId);
}
}
if (userInfo != null) {
@@ -1523,12 +1552,12 @@ public class UserManagerService extends IUserManager.Stub {
}
if (passwordToHash(pin, pinState.salt).equals(pinState.pinHash)) {
pinState.failedAttempts = 0;
- writeUserLocked(mUsers.get(userId));
+ scheduleWriteUserLocked(mUsers.get(userId));
return UserManager.PIN_VERIFICATION_SUCCESS;
} else {
pinState.failedAttempts++;
pinState.lastAttemptTime = System.currentTimeMillis();
- writeUserLocked(mUsers.get(userId));
+ scheduleWriteUserLocked(mUsers.get(userId));
return waitTime;
}
}
@@ -1592,7 +1621,8 @@ public class UserManagerService extends IUserManager.Stub {
try {
for (ApplicationInfo appInfo : apps) {
if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0
- && (appInfo.flags & ApplicationInfo.FLAG_HIDDEN) != 0) {
+ && (appInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN)
+ != 0) {
mPm.setApplicationHiddenSettingAsUser(appInfo.packageName, false,
userHandle);
}
@@ -1653,124 +1683,171 @@ public class UserManagerService extends IUserManager.Stub {
private Bundle readApplicationRestrictionsLocked(String packageName,
int userId) {
+ AtomicFile restrictionsFile =
+ new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
+ packageToRestrictionsFileName(packageName)));
+ return readApplicationRestrictionsLocked(restrictionsFile);
+ }
+
+ @VisibleForTesting
+ static Bundle readApplicationRestrictionsLocked(AtomicFile restrictionsFile) {
final Bundle restrictions = new Bundle();
- final ArrayList<String> values = new ArrayList<String>();
+ final ArrayList<String> values = new ArrayList<>();
+ if (!restrictionsFile.getBaseFile().exists()) {
+ return restrictions;
+ }
FileInputStream fis = null;
try {
- AtomicFile restrictionsFile =
- new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
- packageToRestrictionsFileName(packageName)));
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) {
+ XmlUtils.nextElement(parser);
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
Slog.e(LOG_TAG, "Unable to read restrictions file "
+ restrictionsFile.getBaseFile());
return restrictions;
}
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ readEntry(restrictions, values, parser);
+ }
+ } catch (IOException|XmlPullParserException e) {
+ Log.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ 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) {
- values.clear();
- 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 {
- String value = parser.nextText().trim();
- if (ATTR_TYPE_BOOLEAN.equals(valType)) {
- restrictions.putBoolean(key, Boolean.parseBoolean(value));
- } else if (ATTR_TYPE_INTEGER.equals(valType)) {
- restrictions.putInt(key, Integer.parseInt(value));
- } else {
- restrictions.putString(key, value);
- }
+ private static void readEntry(Bundle restrictions, ArrayList<String> values,
+ XmlPullParser parser) throws XmlPullParserException, IOException {
+ int type = parser.getEventType();
+ 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) {
+ values.clear();
+ 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--;
}
}
- }
- } catch (IOException ioe) {
- } catch (XmlPullParserException pe) {
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
+ String [] valueStrings = new String[values.size()];
+ values.toArray(valueStrings);
+ restrictions.putStringArray(key, valueStrings);
+ } else if (ATTR_TYPE_BUNDLE.equals(valType)) {
+ restrictions.putBundle(key, readBundleEntry(parser, values));
+ } else if (ATTR_TYPE_BUNDLE_ARRAY.equals(valType)) {
+ final int outerDepth = parser.getDepth();
+ ArrayList<Bundle> bundleList = new ArrayList<>();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ Bundle childBundle = readBundleEntry(parser, values);
+ bundleList.add(childBundle);
+ }
+ restrictions.putParcelableArray(key,
+ bundleList.toArray(new Bundle[bundleList.size()]));
+ } else {
+ String value = parser.nextText().trim();
+ if (ATTR_TYPE_BOOLEAN.equals(valType)) {
+ restrictions.putBoolean(key, Boolean.parseBoolean(value));
+ } else if (ATTR_TYPE_INTEGER.equals(valType)) {
+ restrictions.putInt(key, Integer.parseInt(value));
+ } else {
+ restrictions.putString(key, value);
}
}
}
- return restrictions;
+ }
+
+ private static Bundle readBundleEntry(XmlPullParser parser, ArrayList<String> values)
+ throws IOException, XmlPullParserException {
+ Bundle childBundle = new Bundle();
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ readEntry(childBundle, values, parser);
+ }
+ return childBundle;
}
private void writeApplicationRestrictionsLocked(String packageName,
Bundle restrictions, int userId) {
- FileOutputStream fos = null;
AtomicFile restrictionsFile = new AtomicFile(
new File(Environment.getUserSystemDirectory(userId),
packageToRestrictionsFileName(packageName)));
+ writeApplicationRestrictionsLocked(restrictions, restrictionsFile);
+ }
+
+ @VisibleForTesting
+ static void writeApplicationRestrictionsLocked(Bundle restrictions,
+ AtomicFile restrictionsFile) {
+ FileOutputStream fos = null;
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 instanceof Integer) {
- serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER);
- 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);
- }
-
+ writeBundle(restrictions, serializer);
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");
+ Slog.e(LOG_TAG, "Error writing application restrictions list", e);
+ }
+ }
+
+ private static void writeBundle(Bundle restrictions, XmlSerializer serializer)
+ throws IOException {
+ 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 instanceof Integer) {
+ serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER);
+ 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 if (value instanceof Bundle) {
+ serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
+ writeBundle((Bundle) value, serializer);
+ } else if (value instanceof Parcelable[]) {
+ serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE_ARRAY);
+ Parcelable[] array = (Parcelable[]) value;
+ for (Parcelable parcelable : array) {
+ if (!(parcelable instanceof Bundle)) {
+ throw new IllegalArgumentException("bundle-array can only hold Bundles");
+ }
+ serializer.startTag(null, TAG_ENTRY);
+ serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
+ writeBundle((Bundle) parcelable, serializer);
+ serializer.endTag(null, TAG_ENTRY);
+ }
+ } 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);
}
}
@@ -1786,13 +1863,35 @@ public class UserManagerService extends IUserManager.Stub {
public int getUserHandle(int userSerialNumber) {
synchronized (mPackagesLock) {
for (int userId : mUserIds) {
- if (getUserInfoLocked(userId).serialNumber == userSerialNumber) return userId;
+ UserInfo info = getUserInfoLocked(userId);
+ if (info != null && info.serialNumber == userSerialNumber) return userId;
}
// Not found
return -1;
}
}
+ @Override
+ public long getUserCreationTime(int userHandle) {
+ int callingUserId = UserHandle.getCallingUserId();
+ UserInfo userInfo = null;
+ synchronized (mPackagesLock) {
+ if (callingUserId == userHandle) {
+ userInfo = getUserInfoLocked(userHandle);
+ } else {
+ UserInfo parent = getProfileParentLocked(userHandle);
+ if (parent != null && parent.id == callingUserId) {
+ userInfo = getUserInfoLocked(userHandle);
+ }
+ }
+ }
+ if (userInfo == null) {
+ throw new SecurityException("userHandle can only be the calling user or a managed "
+ + "profile associated with this user");
+ }
+ return userInfo.creationTime;
+ }
+
/**
* Caches the list of user ids in an array, adjusting the array size when necessary.
*/
@@ -1827,7 +1926,7 @@ public class UserManagerService extends IUserManager.Stub {
}
if (now > EPOCH_PLUS_30_YEARS) {
user.lastLoggedInTime = now;
- writeUserLocked(user);
+ scheduleWriteUserLocked(user);
}
}
}
@@ -1904,4 +2003,22 @@ public class UserManagerService extends IUserManager.Stub {
}
}
}
+
+ final class MainHandler extends Handler {
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case WRITE_USER_MSG:
+ removeMessages(WRITE_USER_MSG, msg.obj);
+ synchronized (mPackagesLock) {
+ int userId = ((UserInfo) msg.obj).id;
+ UserInfo userInfo = mUsers.get(userId);
+ if (userInfo != null) {
+ writeUserLocked(userInfo);
+ }
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java
new file mode 100644
index 0000000..e972ec7
--- /dev/null
+++ b/services/core/java/com/android/server/policy/BarController.java
@@ -0,0 +1,284 @@
+/*
+ * 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.policy;
+
+import android.app.StatusBarManager;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy.WindowState;
+
+import com.android.internal.statusbar.IStatusBarService;
+
+import java.io.PrintWriter;
+
+/**
+ * Controls state/behavior specific to a system bar window.
+ */
+public class BarController {
+ private static final boolean DEBUG = false;
+
+ private static final int TRANSIENT_BAR_NONE = 0;
+ private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1;
+ private static final int TRANSIENT_BAR_SHOWING = 2;
+ private static final int TRANSIENT_BAR_HIDING = 3;
+
+ private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000;
+
+ protected final String mTag;
+ private final int mTransientFlag;
+ private final int mUnhideFlag;
+ private final int mTranslucentFlag;
+ private final int mStatusBarManagerId;
+ private final int mTranslucentWmFlag;
+ protected final Handler mHandler;
+ private final Object mServiceAquireLock = new Object();
+ protected IStatusBarService mStatusBarService;
+
+ private WindowState mWin;
+ private int mState = StatusBarManager.WINDOW_STATE_SHOWING;
+ private int mTransientBarState;
+ private boolean mPendingShow;
+ private long mLastTranslucent;
+
+ public BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag,
+ int statusBarManagerId, int translucentWmFlag) {
+ mTag = "BarController." + tag;
+ mTransientFlag = transientFlag;
+ mUnhideFlag = unhideFlag;
+ mTranslucentFlag = translucentFlag;
+ mStatusBarManagerId = statusBarManagerId;
+ mTranslucentWmFlag = translucentWmFlag;
+ mHandler = new Handler();
+ }
+
+ public void setWindow(WindowState win) {
+ mWin = win;
+ }
+
+ public void showTransient() {
+ if (mWin != null) {
+ setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED);
+ }
+ }
+
+ public boolean isTransientShowing() {
+ return mTransientBarState == TRANSIENT_BAR_SHOWING;
+ }
+
+ public boolean isTransientShowRequested() {
+ return mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED;
+ }
+
+ public boolean wasRecentlyTranslucent() {
+ return (SystemClock.uptimeMillis() - mLastTranslucent) < TRANSLUCENT_ANIMATION_DELAY_MS;
+ }
+
+ public void adjustSystemUiVisibilityLw(int oldVis, int vis) {
+ if (mWin != null && mTransientBarState == TRANSIENT_BAR_SHOWING &&
+ (vis & mTransientFlag) == 0) {
+ // sysui requests hide
+ setTransientBarState(TRANSIENT_BAR_HIDING);
+ setBarShowingLw(false);
+ } else if (mWin != null && (oldVis & mUnhideFlag) != 0 && (vis & mUnhideFlag) == 0) {
+ // sysui ready to unhide
+ setBarShowingLw(true);
+ }
+ }
+
+ public int applyTranslucentFlagLw(WindowState win, int vis, int oldVis) {
+ if (mWin != null) {
+ if (win != null && (win.getAttrs().privateFlags
+ & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) == 0) {
+ int fl = PolicyControl.getWindowFlags(win, null);
+ if ((fl & mTranslucentWmFlag) != 0) {
+ vis |= mTranslucentFlag;
+ } else {
+ vis &= ~mTranslucentFlag;
+ }
+ if ((fl & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+ vis |= View.SYSTEM_UI_TRANSPARENT;
+ } else {
+ vis &= ~View.SYSTEM_UI_TRANSPARENT;
+ }
+ } else {
+ vis = (vis & ~mTranslucentFlag) | (oldVis & mTranslucentFlag);
+ vis = (vis & ~View.SYSTEM_UI_TRANSPARENT) | (oldVis & View.SYSTEM_UI_TRANSPARENT);
+ }
+ }
+ return vis;
+ }
+
+ public boolean setBarShowingLw(final boolean show) {
+ if (mWin == null) return false;
+ if (show && mTransientBarState == TRANSIENT_BAR_HIDING) {
+ mPendingShow = true;
+ return false;
+ }
+ final boolean wasVis = mWin.isVisibleLw();
+ final boolean wasAnim = mWin.isAnimatingLw();
+ final boolean change = show ? mWin.showLw(true) : mWin.hideLw(true);
+ final int state = computeStateLw(wasVis, wasAnim, mWin, change);
+ final boolean stateChanged = updateStateLw(state);
+ return change || stateChanged;
+ }
+
+ private int computeStateLw(boolean wasVis, boolean wasAnim, WindowState win, boolean change) {
+ if (win.hasDrawnLw()) {
+ final boolean vis = win.isVisibleLw();
+ final boolean anim = win.isAnimatingLw();
+ if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) {
+ return StatusBarManager.WINDOW_STATE_HIDDEN;
+ } else if (mState == StatusBarManager.WINDOW_STATE_HIDDEN && vis) {
+ return StatusBarManager.WINDOW_STATE_SHOWING;
+ } else if (change) {
+ if (wasVis && vis && !wasAnim && anim) {
+ return StatusBarManager.WINDOW_STATE_HIDING;
+ } else {
+ return StatusBarManager.WINDOW_STATE_SHOWING;
+ }
+ }
+ }
+ return mState;
+ }
+
+ private boolean updateStateLw(final int state) {
+ if (state != mState) {
+ mState = state;
+ if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state));
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.setWindowState(mStatusBarManagerId, state);
+ }
+ } catch (RemoteException e) {
+ if (DEBUG) Slog.w(mTag, "Error posting window state", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+ });
+ return true;
+ }
+ return false;
+ }
+
+ public boolean checkHiddenLw() {
+ if (mWin != null && mWin.hasDrawnLw()) {
+ if (!mWin.isVisibleLw() && !mWin.isAnimatingLw()) {
+ updateStateLw(StatusBarManager.WINDOW_STATE_HIDDEN);
+ }
+ if (mTransientBarState == TRANSIENT_BAR_HIDING && !mWin.isVisibleLw()) {
+ // Finished animating out, clean up and reset style
+ setTransientBarState(TRANSIENT_BAR_NONE);
+ if (mPendingShow) {
+ setBarShowingLw(true);
+ mPendingShow = false;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean checkShowTransientBarLw() {
+ if (mTransientBarState == TRANSIENT_BAR_SHOWING) {
+ if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown");
+ return false;
+ } else if (mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED) {
+ if (DEBUG) Slog.d(mTag, "Not showing transient bar, already requested");
+ return false;
+ } else if (mWin == null) {
+ if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist");
+ return false;
+ } else if (mWin.isDisplayedLw()) {
+ if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar already visible");
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) {
+ if (mWin == null) return vis;
+ if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested
+ if (transientAllowed) {
+ vis |= mTransientFlag;
+ if ((oldVis & mTransientFlag) == 0) {
+ vis |= mUnhideFlag; // tell sysui we're ready to unhide
+ }
+ setTransientBarState(TRANSIENT_BAR_SHOWING); // request accepted
+ } else {
+ setTransientBarState(TRANSIENT_BAR_NONE); // request denied
+ }
+ }
+ if (mTransientBarState != TRANSIENT_BAR_NONE) {
+ vis |= mTransientFlag; // ignore clear requests until transition completes
+ vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; // never show transient bars in low profile
+ }
+ if ((vis & mTranslucentFlag) != 0 || (oldVis & mTranslucentFlag) != 0 ||
+ ((vis | oldVis) & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) {
+ mLastTranslucent = SystemClock.uptimeMillis();
+ }
+ return vis;
+ }
+
+ private void setTransientBarState(int state) {
+ if (mWin != null && state != mTransientBarState) {
+ if (mTransientBarState == TRANSIENT_BAR_SHOWING || state == TRANSIENT_BAR_SHOWING) {
+ mLastTranslucent = SystemClock.uptimeMillis();
+ }
+ mTransientBarState = state;
+ if (DEBUG) Slog.d(mTag, "mTransientBarState: " + transientBarStateToString(state));
+ }
+ }
+
+ protected IStatusBarService getStatusBarService() {
+ synchronized (mServiceAquireLock) {
+ if (mStatusBarService == null) {
+ mStatusBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService("statusbar"));
+ }
+ return mStatusBarService;
+ }
+ }
+
+ private static String transientBarStateToString(int state) {
+ if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING";
+ if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING";
+ if (state == TRANSIENT_BAR_SHOW_REQUESTED) return "TRANSIENT_BAR_SHOW_REQUESTED";
+ if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE";
+ throw new IllegalArgumentException("Unknown state " + state);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ if (mWin != null) {
+ pw.print(prefix); pw.println(mTag);
+ pw.print(prefix); pw.print(" "); pw.print("mState"); pw.print('=');
+ pw.println(StatusBarManager.windowStateToString(mState));
+ pw.print(prefix); pw.print(" "); pw.print("mTransientBar"); pw.print('=');
+ pw.println(transientBarStateToString(mTransientBarState));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
new file mode 100644
index 0000000..fef1e57
--- /dev/null
+++ b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2015 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.policy;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+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.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.Display;
+import android.view.animation.LinearInterpolator;
+
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+public class BurnInProtectionHelper implements DisplayManager.DisplayListener,
+ Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
+ private static final String TAG = "BurnInProtection";
+
+ // Default value when max burnin radius is not set.
+ public static final int BURN_IN_MAX_RADIUS_DEFAULT = -1;
+
+ private static final long BURNIN_PROTECTION_WAKEUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1);
+ private static final long BURNIN_PROTECTION_MINIMAL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(10);
+
+ private static final boolean DEBUG = false;
+
+ private static final String ACTION_BURN_IN_PROTECTION =
+ "android.internal.policy.action.BURN_IN_PROTECTION";
+
+ private static final int BURN_IN_SHIFT_STEP = 2;
+ private static final long CENTERING_ANIMATION_DURATION_MS = 100;
+ private final ValueAnimator mCenteringAnimator;
+
+ private boolean mBurnInProtectionActive;
+ private boolean mFirstUpdate;
+
+ private final int mMinHorizontalBurnInOffset;
+ private final int mMaxHorizontalBurnInOffset;
+ private final int mMinVerticalBurnInOffset;
+ private final int mMaxVerticalBurnInOffset;
+
+ private final int mBurnInRadiusMaxSquared;
+
+ private int mLastBurnInXOffset = 0;
+ /* 1 means increasing, -1 means decreasing */
+ private int mXOffsetDirection = 1;
+ private int mLastBurnInYOffset = 0;
+ /* 1 means increasing, -1 means decreasing */
+ private int mYOffsetDirection = 1;
+
+ private final AlarmManager mAlarmManager;
+ private final PendingIntent mBurnInProtectionIntent;
+ private final DisplayManagerInternal mDisplayManagerInternal;
+ private final Display mDisplay;
+
+ private BroadcastReceiver mBurnInProtectionReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Slog.d(TAG, "onReceive " + intent);
+ }
+ updateBurnInProtection();
+ }
+ };
+
+ public BurnInProtectionHelper(Context context, int minHorizontalOffset,
+ int maxHorizontalOffset, int minVerticalOffset, int maxVerticalOffset,
+ int maxOffsetRadius) {
+ mMinHorizontalBurnInOffset = minHorizontalOffset;
+ mMaxHorizontalBurnInOffset = maxHorizontalOffset;
+ mMinVerticalBurnInOffset = minVerticalOffset;
+ mMaxVerticalBurnInOffset = maxVerticalOffset;
+ if (maxOffsetRadius != BURN_IN_MAX_RADIUS_DEFAULT) {
+ mBurnInRadiusMaxSquared = maxOffsetRadius * maxOffsetRadius;
+ } else {
+ mBurnInRadiusMaxSquared = BURN_IN_MAX_RADIUS_DEFAULT;
+ }
+
+ mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ context.registerReceiver(mBurnInProtectionReceiver,
+ new IntentFilter(ACTION_BURN_IN_PROTECTION));
+ Intent intent = new Intent(ACTION_BURN_IN_PROTECTION);
+ intent.setPackage(context.getPackageName());
+ intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mBurnInProtectionIntent = PendingIntent.getBroadcast(context, 0,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ DisplayManager displayManager =
+ (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+ mDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ displayManager.registerDisplayListener(this, null /* handler */);
+
+ mCenteringAnimator = ValueAnimator.ofFloat(1f, 0f);
+ mCenteringAnimator.setDuration(CENTERING_ANIMATION_DURATION_MS);
+ mCenteringAnimator.setInterpolator(new LinearInterpolator());
+ mCenteringAnimator.addListener(this);
+ mCenteringAnimator.addUpdateListener(this);
+ }
+
+ public void startBurnInProtection() {
+ if (!mBurnInProtectionActive) {
+ mBurnInProtectionActive = true;
+ mFirstUpdate = true;
+ mCenteringAnimator.cancel();
+ updateBurnInProtection();
+ }
+ }
+
+ private void updateBurnInProtection() {
+ if (mBurnInProtectionActive) {
+ // We don't want to adjust offsets immediately after the device goes into ambient mode.
+ // Instead, we want to wait until it's more likely that the user is not observing the
+ // screen anymore.
+ if (mFirstUpdate) {
+ mFirstUpdate = false;
+ } else {
+ adjustOffsets();
+ mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(),
+ mLastBurnInXOffset, mLastBurnInYOffset);
+ }
+ // We use currentTimeMillis to compute the next wakeup time since we want to wake up at
+ // the same time as we wake up to update ambient mode to minimize power consumption.
+ // However, we use elapsedRealtime to schedule the alarm so that setting the time can't
+ // disable burn-in protection for extended periods.
+ final long nowWall = System.currentTimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ // Next adjustment at least ten seconds in the future.
+ long nextWall = nowWall + BURNIN_PROTECTION_MINIMAL_INTERVAL_MS;
+ // And aligned to the minute.
+ nextWall = nextWall - nextWall % BURNIN_PROTECTION_WAKEUP_INTERVAL_MS
+ + BURNIN_PROTECTION_WAKEUP_INTERVAL_MS;
+ // Use elapsed real time that is adjusted to full minute on wall clock.
+ final long nextElapsed = nowElapsed + (nextWall - nowWall);
+ if (DEBUG) {
+ Slog.d(TAG, "scheduling next wake-up, now wall time " + nowWall
+ + ", next wall: " + nextWall + ", now elapsed: " + nowElapsed
+ + ", next elapsed: " + nextElapsed);
+ }
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, nextElapsed,
+ mBurnInProtectionIntent);
+ } else {
+ mAlarmManager.cancel(mBurnInProtectionIntent);
+ mCenteringAnimator.start();
+ }
+ }
+
+ public void cancelBurnInProtection() {
+ if (mBurnInProtectionActive) {
+ mBurnInProtectionActive = false;
+ updateBurnInProtection();
+ }
+ }
+
+ /**
+ * Gently shifts current burn-in offsets, minimizing the change for the user.
+ *
+ * Shifts are applied in following fashion:
+ * 1) shift horizontally from minimum to the maximum;
+ * 2) shift vertically by one from minimum to the maximum;
+ * 3) shift horizontally from maximum to the minimum;
+ * 4) shift vertically by one from minimum to the maximum.
+ * 5) if you reach the maximum vertically, start shifting back by one from maximum to minimum.
+ *
+ * On top of that, stay within specified radius. If the shift distance from the center is
+ * higher than the radius, skip these values and go the next position that is within the radius.
+ */
+ private void adjustOffsets() {
+ do {
+ // By default, let's just shift the X offset.
+ final int xChange = mXOffsetDirection * BURN_IN_SHIFT_STEP;
+ mLastBurnInXOffset += xChange;
+ if (mLastBurnInXOffset > mMaxHorizontalBurnInOffset
+ || mLastBurnInXOffset < mMinHorizontalBurnInOffset) {
+ // Whoops, we went too far horizontally. Let's retract..
+ mLastBurnInXOffset -= xChange;
+ // change horizontal direction..
+ mXOffsetDirection *= -1;
+ // and let's shift the Y offset.
+ final int yChange = mYOffsetDirection * BURN_IN_SHIFT_STEP;
+ mLastBurnInYOffset += yChange;
+ if (mLastBurnInYOffset > mMaxVerticalBurnInOffset
+ || mLastBurnInYOffset < mMinVerticalBurnInOffset) {
+ // Whoops, we went to far vertically. Let's retract..
+ mLastBurnInYOffset -= yChange;
+ // and change vertical direction.
+ mYOffsetDirection *= -1;
+ }
+ }
+ // If we are outside of the radius, let's try again.
+ } while (mBurnInRadiusMaxSquared != BURN_IN_MAX_RADIUS_DEFAULT
+ && mLastBurnInXOffset * mLastBurnInXOffset + mLastBurnInYOffset * mLastBurnInYOffset
+ > mBurnInRadiusMaxSquared);
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + TAG);
+ prefix += " ";
+ pw.println(prefix + "mBurnInProtectionActive=" + mBurnInProtectionActive);
+ pw.println(prefix + "mHorizontalBurnInOffsetsBounds=(" + mMinHorizontalBurnInOffset + ", "
+ + mMaxHorizontalBurnInOffset + ")");
+ pw.println(prefix + "mVerticalBurnInOffsetsBounds=(" + mMinVerticalBurnInOffset + ", "
+ + mMaxVerticalBurnInOffset + ")");
+ pw.println(prefix + "mBurnInRadiusMaxSquared=" + mBurnInRadiusMaxSquared);
+ pw.println(prefix + "mLastBurnInOffset=(" + mLastBurnInXOffset + ", "
+ + mLastBurnInYOffset + ")");
+ pw.println(prefix + "mOfsetChangeDirections=(" + mXOffsetDirection + ", "
+ + mYOffsetDirection + ")");
+ }
+
+ @Override
+ public void onDisplayAdded(int i) {
+ }
+
+ @Override
+ public void onDisplayRemoved(int i) {
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == mDisplay.getDisplayId()) {
+ if (mDisplay.getState() == Display.STATE_DOZE
+ || mDisplay.getState() == Display.STATE_DOZE_SUSPEND) {
+ startBurnInProtection();
+ } else {
+ cancelBurnInProtection();
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (animator == mCenteringAnimator && !mBurnInProtectionActive) {
+ // No matter how the animation finishes, we want to zero the offsets.
+ mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), 0, 0);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ if (!mBurnInProtectionActive) {
+ final float value = (Float) valueAnimator.getAnimatedValue();
+ mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(),
+ (int) (mLastBurnInXOffset * value), (int) (mLastBurnInYOffset * value));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/EnableAccessibilityController.java b/services/core/java/com/android/server/policy/EnableAccessibilityController.java
new file mode 100644
index 0000000..da9c001
--- /dev/null
+++ b/services/core/java/com/android/server/policy/EnableAccessibilityController.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * 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.policy;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech;
+import android.util.MathUtils;
+import android.view.IWindowManager;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class EnableAccessibilityController {
+
+ private static final int SPEAK_WARNING_DELAY_MILLIS = 2000;
+ private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000;
+
+ public static final int MESSAGE_SPEAK_WARNING = 1;
+ public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2;
+ public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MESSAGE_SPEAK_WARNING: {
+ String text = mContext.getString(R.string.continue_to_enable_accessibility);
+ mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
+ } break;
+ case MESSAGE_SPEAK_ENABLE_CANCELED: {
+ String text = mContext.getString(R.string.enable_accessibility_canceled);
+ mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
+ } break;
+ case MESSAGE_ENABLE_ACCESSIBILITY: {
+ enableAccessibility();
+ mTone.play();
+ mTts.speak(mContext.getString(R.string.accessibility_enabled),
+ TextToSpeech.QUEUE_FLUSH, null);
+ } break;
+ }
+ }
+ };
+
+ private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService("window"));
+
+ private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
+ .Stub.asInterface(ServiceManager.getService("accessibility"));
+
+
+ private final Context mContext;
+ private final Runnable mOnAccessibilityEnabledCallback;
+ private final UserManager mUserManager;
+ private final TextToSpeech mTts;
+ private final Ringtone mTone;
+
+ private final float mTouchSlop;
+
+ private boolean mDestroyed;
+ private boolean mCanceled;
+
+ private float mFirstPointerDownX;
+ private float mFirstPointerDownY;
+ private float mSecondPointerDownX;
+ private float mSecondPointerDownY;
+
+ public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) {
+ mContext = context;
+ mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback;
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
+ @Override
+ public void onInit(int status) {
+ if (mDestroyed) {
+ mTts.shutdown();
+ }
+ }
+ });
+ mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
+ mTone.setStreamType(AudioManager.STREAM_MUSIC);
+ mTouchSlop = context.getResources().getDimensionPixelSize(
+ R.dimen.accessibility_touch_slop);
+ }
+
+ public static boolean canEnableAccessibilityViaGesture(Context context) {
+ AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
+ // Accessibility is enabled and there is an enabled speaking
+ // accessibility service, then we have nothing to do.
+ if (accessibilityManager.isEnabled()
+ && !accessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
+ return false;
+ }
+ // If the global gesture is enabled and there is a speaking service
+ // installed we are good to go, otherwise there is nothing to do.
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
+ && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
+ }
+
+ private static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
+ Context context) {
+ List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>();
+ services.addAll(AccessibilityManager.getInstance(context)
+ .getInstalledAccessibilityServiceList());
+ Iterator<AccessibilityServiceInfo> iterator = services.iterator();
+ while (iterator.hasNext()) {
+ AccessibilityServiceInfo service = iterator.next();
+ if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
+ iterator.remove();
+ }
+ }
+ return services;
+ }
+
+ public void onDestroy() {
+ mDestroyed = true;
+ }
+
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
+ && event.getPointerCount() == 2) {
+ mFirstPointerDownX = event.getX(0);
+ mFirstPointerDownY = event.getY(0);
+ mSecondPointerDownX = event.getX(1);
+ mSecondPointerDownY = event.getY(1);
+ mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
+ SPEAK_WARNING_DELAY_MILLIS);
+ mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
+ ENABLE_ACCESSIBILITY_DELAY_MILLIS);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ final int pointerCount = event.getPointerCount();
+ final int action = event.getActionMasked();
+ if (mCanceled) {
+ if (action == MotionEvent.ACTION_UP) {
+ mCanceled = false;
+ }
+ return true;
+ }
+ switch (action) {
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ if (pointerCount > 2) {
+ cancel();
+ }
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ final float firstPointerMove = MathUtils.dist(event.getX(0),
+ event.getY(0), mFirstPointerDownX, mFirstPointerDownY);
+ if (Math.abs(firstPointerMove) > mTouchSlop) {
+ cancel();
+ }
+ final float secondPointerMove = MathUtils.dist(event.getX(1),
+ event.getY(1), mSecondPointerDownX, mSecondPointerDownY);
+ if (Math.abs(secondPointerMove) > mTouchSlop) {
+ cancel();
+ }
+ } break;
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ cancel();
+ } break;
+ }
+ return true;
+ }
+
+ private void cancel() {
+ mCanceled = true;
+ if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
+ mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
+ } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
+ mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
+ }
+ mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
+ }
+
+ private void enableAccessibility() {
+ List<AccessibilityServiceInfo> services = getInstalledSpeakingAccessibilityServices(
+ mContext);
+ if (services.isEmpty()) {
+ return;
+ }
+ boolean keyguardLocked = false;
+ try {
+ keyguardLocked = mWindowManager.isKeyguardLocked();
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+
+ final boolean hasMoreThanOneUser = mUserManager.getUsers().size() > 1;
+
+ AccessibilityServiceInfo service = services.get(0);
+ boolean enableTouchExploration = (service.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+ // Try to find a service supporting explore by touch.
+ if (!enableTouchExploration) {
+ final int serviceCount = services.size();
+ for (int i = 1; i < serviceCount; i++) {
+ AccessibilityServiceInfo candidate = services.get(i);
+ if ((candidate.flags & AccessibilityServiceInfo
+ .FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0) {
+ enableTouchExploration = true;
+ service = candidate;
+ break;
+ }
+ }
+ }
+
+ ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+ ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ if (!keyguardLocked || !hasMoreThanOneUser) {
+ final int userId = ActivityManager.getCurrentUser();
+ String enabledServiceString = componentName.flattenToString();
+ ContentResolver resolver = mContext.getContentResolver();
+ // Enable one speaking accessibility service.
+ Settings.Secure.putStringForUser(resolver,
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ enabledServiceString, userId);
+ // Allow the services we just enabled to toggle touch exploration.
+ Settings.Secure.putStringForUser(resolver,
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ enabledServiceString, userId);
+ // Enable touch exploration.
+ if (enableTouchExploration) {
+ Settings.Secure.putIntForUser(resolver, Settings.Secure.TOUCH_EXPLORATION_ENABLED,
+ 1, userId);
+ }
+ // Enable accessibility script injection (AndroidVox) for web content.
+ Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+ 1, userId);
+ // Turn on accessibility mode last.
+ Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_ENABLED,
+ 1, userId);
+ } else if (keyguardLocked) {
+ try {
+ mAccessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
+ componentName, enableTouchExploration);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ mOnAccessibilityEnabledCallback.run();
+ }
+}
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
new file mode 100644
index 0000000..b431b33
--- /dev/null
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -0,0 +1,1267 @@
+/*
+ * 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.policy;
+
+import com.android.internal.app.AlertController;
+import com.android.internal.app.AlertController.AlertParams;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+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.UserManager;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicy.WindowManagerFuncs;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper to show the global actions dialog. Each item is an {@link Action} that
+ * may show depending on whether the keyguard is showing, and whether the device
+ * is provisioned.
+ */
+class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+
+ private static final String TAG = "GlobalActions";
+
+ private static final boolean SHOW_SILENT_TOGGLE = true;
+
+ /* Valid settings for global actions keys.
+ * see config.xml config_globalActionList */
+ private static final String GLOBAL_ACTION_KEY_POWER = "power";
+ private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
+ private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
+ private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
+ private static final String GLOBAL_ACTION_KEY_USERS = "users";
+ private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
+ private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
+ private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
+ private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
+
+ private final Context mContext;
+ private final WindowManagerFuncs mWindowManagerFuncs;
+ private final AudioManager mAudioManager;
+ private final IDreamManager mDreamManager;
+
+ private ArrayList<Action> mItems;
+ private GlobalActionsDialog mDialog;
+
+ private Action mSilentModeAction;
+ private ToggleAction mAirplaneModeOn;
+
+ private MyAdapter mAdapter;
+
+ private boolean mKeyguardShowing = false;
+ private boolean mDeviceProvisioned = false;
+ private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
+ private boolean mIsWaitingForEcmExit = false;
+ private boolean mHasTelephony;
+ private boolean mHasVibrator;
+ private final boolean mShowSilentToggle;
+
+ /**
+ * @param context everything needs a context :(
+ */
+ public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
+ mContext = context;
+ mWindowManagerFuncs = windowManagerFuncs;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mDreamManager = IDreamManager.Stub.asInterface(
+ ServiceManager.getService(DreamService.DREAM_SERVICE));
+
+ // receive broadcasts
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+
+ ConnectivityManager cm = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+
+ // get notified of phone state changes
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
+ mAirplaneModeObserver);
+ Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ mHasVibrator = vibrator != null && vibrator.hasVibrator();
+
+ mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_useFixedVolume);
+ }
+
+ /**
+ * Show the global actions dialog (creating if necessary)
+ * @param keyguardShowing True if keyguard is showing
+ */
+ public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
+ mKeyguardShowing = keyguardShowing;
+ mDeviceProvisioned = isDeviceProvisioned;
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ // Show delayed, so that the dismiss of the previous dialog completes
+ mHandler.sendEmptyMessage(MESSAGE_SHOW);
+ } else {
+ handleShow();
+ }
+ }
+
+ private void awakenIfNecessary() {
+ if (mDreamManager != null) {
+ try {
+ if (mDreamManager.isDreaming()) {
+ mDreamManager.awaken();
+ }
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+ }
+
+ private void handleShow() {
+ awakenIfNecessary();
+ mDialog = createDialog();
+ prepareDialog();
+
+ // If we only have 1 item and it's a simple press action, just do this action.
+ if (mAdapter.getCount() == 1
+ && mAdapter.getItem(0) instanceof SinglePressAction
+ && !(mAdapter.getItem(0) instanceof LongPressAction)) {
+ ((SinglePressAction) mAdapter.getItem(0)).onPress();
+ } else {
+ WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
+ attrs.setTitle("GlobalActions");
+ mDialog.getWindow().setAttributes(attrs);
+ mDialog.show();
+ mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
+ }
+ }
+
+ /**
+ * Create the global actions dialog.
+ * @return A new dialog.
+ */
+ private GlobalActionsDialog createDialog() {
+ // Simple toggle style if there's no vibrator, otherwise use a tri-state
+ if (!mHasVibrator) {
+ mSilentModeAction = new SilentModeToggleAction();
+ } else {
+ mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
+ }
+ mAirplaneModeOn = new ToggleAction(
+ R.drawable.ic_lock_airplane_mode,
+ R.drawable.ic_lock_airplane_mode_off,
+ R.string.global_actions_toggle_airplane_mode,
+ R.string.global_actions_airplane_mode_on_status,
+ R.string.global_actions_airplane_mode_off_status) {
+
+ void onToggle(boolean on) {
+ if (mHasTelephony && Boolean.parseBoolean(
+ SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
+ mIsWaitingForEcmExit = true;
+ // Launch ECM exit dialog
+ Intent ecmDialogIntent =
+ new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
+ ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(ecmDialogIntent);
+ } else {
+ changeAirplaneModeSystemSetting(on);
+ }
+ }
+
+ @Override
+ protected void changeStateFromPress(boolean buttonOn) {
+ if (!mHasTelephony) return;
+
+ // In ECM mode airplane state cannot be changed
+ if (!(Boolean.parseBoolean(
+ SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
+ mState = buttonOn ? State.TurningOn : State.TurningOff;
+ mAirplaneState = mState;
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ onAirplaneModeChanged();
+
+ mItems = new ArrayList<Action>();
+ String[] defaultActions = mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_globalActionsList);
+
+ ArraySet<String> addedKeys = new ArraySet<String>();
+ for (int i = 0; i < defaultActions.length; i++) {
+ String actionKey = defaultActions[i];
+ if (addedKeys.contains(actionKey)) {
+ // If we already have added this, don't add it again.
+ continue;
+ }
+ if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
+ mItems.add(new PowerAction());
+ } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
+ mItems.add(mAirplaneModeOn);
+ } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
+ if (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
+ mItems.add(getBugReportAction());
+ }
+ } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
+ if (mShowSilentToggle) {
+ mItems.add(mSilentModeAction);
+ }
+ } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
+ if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
+ addUsersToMenu(mItems);
+ }
+ } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
+ mItems.add(getSettingsAction());
+ } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
+ mItems.add(getLockdownAction());
+ } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
+ mItems.add(getVoiceAssistAction());
+ } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
+ mItems.add(getAssistAction());
+ } else {
+ Log.e(TAG, "Invalid global action key " + actionKey);
+ }
+ // Add here so we don't add more than one.
+ addedKeys.add(actionKey);
+ }
+
+ mAdapter = new MyAdapter();
+
+ AlertParams params = new AlertParams(mContext);
+ params.mAdapter = mAdapter;
+ params.mOnClickListener = this;
+ params.mForceInverseBackground = true;
+
+ GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
+ dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
+
+ dialog.getListView().setItemsCanFocus(true);
+ dialog.getListView().setLongClickable(true);
+ dialog.getListView().setOnItemLongClickListener(
+ new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ final Action action = mAdapter.getItem(position);
+ if (action instanceof LongPressAction) {
+ return ((LongPressAction) action).onLongPress();
+ }
+ return false;
+ }
+ });
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+
+ dialog.setOnDismissListener(this);
+
+ return dialog;
+ }
+
+ private final class PowerAction extends SinglePressAction implements LongPressAction {
+ private PowerAction() {
+ super(com.android.internal.R.drawable.ic_lock_power_off,
+ R.string.global_action_power_off);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ mWindowManagerFuncs.rebootSafeMode(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+
+ @Override
+ public void onPress() {
+ // shutdown by making sure radio and power are handled accordingly.
+ mWindowManagerFuncs.shutdown(false /* confirm */);
+ }
+ }
+
+ private Action getBugReportAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_lock_bugreport,
+ R.string.bugreport_title) {
+
+ public void onPress() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setTitle(com.android.internal.R.string.bugreport_title);
+ builder.setMessage(com.android.internal.R.string.bugreport_message);
+ builder.setNegativeButton(com.android.internal.R.string.cancel, null);
+ builder.setPositiveButton(com.android.internal.R.string.report,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // don't actually trigger the bugreport if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return;
+ }
+ // Add a little delay before executing, to give the
+ // dialog a chance to go away before it takes a
+ // screenshot.
+ mHandler.postDelayed(new Runnable() {
+ @Override public void run() {
+ try {
+ ActivityManagerNative.getDefault()
+ .requestBugReport();
+ } catch (RemoteException e) {
+ }
+ }
+ }, 500);
+ }
+ });
+ AlertDialog dialog = builder.create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ dialog.show();
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ @Override
+ public String getStatus() {
+ return mContext.getString(
+ com.android.internal.R.string.bugreport_status,
+ Build.VERSION.RELEASE,
+ Build.ID);
+ }
+ };
+ }
+
+ private Action getSettingsAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_settings,
+ R.string.global_action_settings) {
+
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Settings.ACTION_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getAssistAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused,
+ R.string.global_action_assist) {
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Intent.ACTION_ASSIST);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getVoiceAssistAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search,
+ R.string.global_action_voice_assist) {
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getLockdownAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
+ R.string.global_action_lockdown) {
+
+ @Override
+ public void onPress() {
+ new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
+ try {
+ WindowManagerGlobal.getWindowManagerService().lockNow(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while trying to lock device.", e);
+ }
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ }
+
+ private UserInfo getCurrentUser() {
+ try {
+ return ActivityManagerNative.getDefault().getCurrentUser();
+ } catch (RemoteException re) {
+ return null;
+ }
+ }
+
+ private boolean isCurrentUserOwner() {
+ UserInfo currentUser = getCurrentUser();
+ return currentUser == null || currentUser.isPrimary();
+ }
+
+ private void addUsersToMenu(ArrayList<Action> items) {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (um.isUserSwitcherEnabled()) {
+ List<UserInfo> users = um.getUsers();
+ UserInfo currentUser = getCurrentUser();
+ for (final UserInfo user : users) {
+ if (user.supportsSwitchTo()) {
+ boolean isCurrentUser = currentUser == null
+ ? user.id == 0 : (currentUser.id == user.id);
+ Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
+ : null;
+ SinglePressAction switchToUser = new SinglePressAction(
+ com.android.internal.R.drawable.ic_menu_cc, icon,
+ (user.name != null ? user.name : "Primary")
+ + (isCurrentUser ? " \u2714" : "")) {
+ public void onPress() {
+ try {
+ ActivityManagerNative.getDefault().switchUser(user.id);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Couldn't switch user " + re);
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ items.add(switchToUser);
+ }
+ }
+ }
+ }
+
+ private void prepareDialog() {
+ refreshSilentMode();
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ if (mShowSilentToggle) {
+ IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ mContext.registerReceiver(mRingerModeReceiver, filter);
+ }
+ }
+
+ private void refreshSilentMode() {
+ if (!mHasVibrator) {
+ final boolean silentModeOn =
+ mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+ ((ToggleAction)mSilentModeAction).updateState(
+ silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onDismiss(DialogInterface dialog) {
+ if (mShowSilentToggle) {
+ try {
+ mContext.unregisterReceiver(mRingerModeReceiver);
+ } catch (IllegalArgumentException ie) {
+ // ignore this
+ Log.w(TAG, ie);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onClick(DialogInterface dialog, int which) {
+ if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
+ dialog.dismiss();
+ }
+ mAdapter.getItem(which).onPress();
+ }
+
+ /**
+ * The adapter used for the list within the global actions dialog, taking
+ * into account whether the keyguard is showing via
+ * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned
+ * via {@link GlobalActions#mDeviceProvisioned}.
+ */
+ private class MyAdapter extends BaseAdapter {
+
+ public int getCount() {
+ int count = 0;
+
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ continue;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ continue;
+ }
+ count++;
+ }
+ return count;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItem(position).isEnabled();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ public Action getItem(int position) {
+
+ int filteredPos = 0;
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ continue;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ continue;
+ }
+ if (filteredPos == position) {
+ return action;
+ }
+ filteredPos++;
+ }
+
+ throw new IllegalArgumentException("position " + position
+ + " out of range of showable actions"
+ + ", filtered count=" + getCount()
+ + ", keyguardshowing=" + mKeyguardShowing
+ + ", provisioned=" + mDeviceProvisioned);
+ }
+
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Action action = getItem(position);
+ return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
+ }
+ }
+
+ // note: the scheme below made more sense when we were planning on having
+ // 8 different things in the global actions dialog. seems overkill with
+ // only 3 items now, but may as well keep this flexible approach so it will
+ // be easy should someone decide at the last minute to include something
+ // else, such as 'enable wifi', or 'enable bluetooth'
+
+ /**
+ * What each item in the global actions dialog must be able to support.
+ */
+ private interface Action {
+ /**
+ * @return Text that will be announced when dialog is created. null
+ * for none.
+ */
+ CharSequence getLabelForAccessibility(Context context);
+
+ View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
+
+ void onPress();
+
+ /**
+ * @return whether this action should appear in the dialog when the keygaurd
+ * is showing.
+ */
+ boolean showDuringKeyguard();
+
+ /**
+ * @return whether this action should appear in the dialog before the
+ * device is provisioned.
+ */
+ boolean showBeforeProvisioning();
+
+ boolean isEnabled();
+ }
+
+ /**
+ * An action that also supports long press.
+ */
+ private interface LongPressAction extends Action {
+ boolean onLongPress();
+ }
+
+ /**
+ * A single press action maintains no state, just responds to a press
+ * and takes an action.
+ */
+ private static abstract class SinglePressAction implements Action {
+ private final int mIconResId;
+ private final Drawable mIcon;
+ private final int mMessageResId;
+ private final CharSequence mMessage;
+
+ protected SinglePressAction(int iconResId, int messageResId) {
+ mIconResId = iconResId;
+ mMessageResId = messageResId;
+ mMessage = null;
+ mIcon = null;
+ }
+
+ protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
+ mIconResId = iconResId;
+ mMessageResId = 0;
+ mMessage = message;
+ mIcon = icon;
+ }
+
+ protected SinglePressAction(int iconResId, CharSequence message) {
+ mIconResId = iconResId;
+ mMessageResId = 0;
+ mMessage = message;
+ mIcon = null;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ public String getStatus() {
+ return null;
+ }
+
+ abstract public void onPress();
+
+ public CharSequence getLabelForAccessibility(Context context) {
+ if (mMessage != null) {
+ return mMessage;
+ } else {
+ return context.getString(mMessageResId);
+ }
+ }
+
+ public View create(
+ Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
+ View v = inflater.inflate(R.layout.global_actions_item, parent, false);
+
+ ImageView icon = (ImageView) v.findViewById(R.id.icon);
+ TextView messageView = (TextView) v.findViewById(R.id.message);
+
+ TextView statusView = (TextView) v.findViewById(R.id.status);
+ final String status = getStatus();
+ if (!TextUtils.isEmpty(status)) {
+ statusView.setText(status);
+ } else {
+ statusView.setVisibility(View.GONE);
+ }
+ if (mIcon != null) {
+ icon.setImageDrawable(mIcon);
+ icon.setScaleType(ScaleType.CENTER_CROP);
+ } else if (mIconResId != 0) {
+ icon.setImageDrawable(context.getDrawable(mIconResId));
+ }
+ if (mMessage != null) {
+ messageView.setText(mMessage);
+ } else {
+ messageView.setText(mMessageResId);
+ }
+
+ return v;
+ }
+ }
+
+ /**
+ * A toggle action knows whether it is on or off, and displays an icon
+ * and status message accordingly.
+ */
+ private static abstract class ToggleAction implements Action {
+
+ enum State {
+ Off(false),
+ TurningOn(true),
+ TurningOff(true),
+ On(false);
+
+ private final boolean inTransition;
+
+ State(boolean intermediate) {
+ inTransition = intermediate;
+ }
+
+ public boolean inTransition() {
+ return inTransition;
+ }
+ }
+
+ protected State mState = State.Off;
+
+ // prefs
+ protected int mEnabledIconResId;
+ protected int mDisabledIconResid;
+ protected int mMessageResId;
+ protected int mEnabledStatusMessageResId;
+ protected int mDisabledStatusMessageResId;
+
+ /**
+ * @param enabledIconResId The icon for when this action is on.
+ * @param disabledIconResid The icon for when this action is off.
+ * @param essage The general information message, e.g 'Silent Mode'
+ * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
+ * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
+ */
+ public ToggleAction(int enabledIconResId,
+ int disabledIconResid,
+ int message,
+ int enabledStatusMessageResId,
+ int disabledStatusMessageResId) {
+ mEnabledIconResId = enabledIconResId;
+ mDisabledIconResid = disabledIconResid;
+ mMessageResId = message;
+ mEnabledStatusMessageResId = enabledStatusMessageResId;
+ mDisabledStatusMessageResId = disabledStatusMessageResId;
+ }
+
+ /**
+ * Override to make changes to resource IDs just before creating the
+ * View.
+ */
+ void willCreate() {
+
+ }
+
+ @Override
+ public CharSequence getLabelForAccessibility(Context context) {
+ return context.getString(mMessageResId);
+ }
+
+ public View create(Context context, View convertView, ViewGroup parent,
+ LayoutInflater inflater) {
+ willCreate();
+
+ View v = inflater.inflate(R
+ .layout.global_actions_item, parent, false);
+
+ ImageView icon = (ImageView) v.findViewById(R.id.icon);
+ TextView messageView = (TextView) v.findViewById(R.id.message);
+ TextView statusView = (TextView) v.findViewById(R.id.status);
+ final boolean enabled = isEnabled();
+
+ if (messageView != null) {
+ messageView.setText(mMessageResId);
+ messageView.setEnabled(enabled);
+ }
+
+ boolean on = ((mState == State.On) || (mState == State.TurningOn));
+ if (icon != null) {
+ icon.setImageDrawable(context.getDrawable(
+ (on ? mEnabledIconResId : mDisabledIconResid)));
+ icon.setEnabled(enabled);
+ }
+
+ if (statusView != null) {
+ statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
+ statusView.setVisibility(View.VISIBLE);
+ statusView.setEnabled(enabled);
+ }
+ v.setEnabled(enabled);
+
+ return v;
+ }
+
+ public final void onPress() {
+ if (mState.inTransition()) {
+ Log.w(TAG, "shouldn't be able to toggle when in transition");
+ return;
+ }
+
+ final boolean nowOn = !(mState == State.On);
+ onToggle(nowOn);
+ changeStateFromPress(nowOn);
+ }
+
+ public boolean isEnabled() {
+ return !mState.inTransition();
+ }
+
+ /**
+ * Implementations may override this if their state can be in on of the intermediate
+ * states until some notification is received (e.g airplane mode is 'turning off' until
+ * we know the wireless connections are back online
+ * @param buttonOn Whether the button was turned on or off
+ */
+ protected void changeStateFromPress(boolean buttonOn) {
+ mState = buttonOn ? State.On : State.Off;
+ }
+
+ abstract void onToggle(boolean on);
+
+ public void updateState(State state) {
+ mState = state;
+ }
+ }
+
+ private class SilentModeToggleAction extends ToggleAction {
+ public SilentModeToggleAction() {
+ super(R.drawable.ic_audio_vol_mute,
+ R.drawable.ic_audio_vol,
+ R.string.global_action_toggle_silent_mode,
+ R.string.global_action_silent_mode_on_status,
+ R.string.global_action_silent_mode_off_status);
+ }
+
+ void onToggle(boolean on) {
+ if (on) {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
+ } else {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
+ private static class SilentModeTriStateAction implements Action, View.OnClickListener {
+
+ private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
+
+ private final AudioManager mAudioManager;
+ private final Handler mHandler;
+ private final Context mContext;
+
+ SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
+ mAudioManager = audioManager;
+ mHandler = handler;
+ mContext = context;
+ }
+
+ private int ringerModeToIndex(int ringerMode) {
+ // They just happen to coincide
+ return ringerMode;
+ }
+
+ private int indexToRingerMode(int index) {
+ // They just happen to coincide
+ return index;
+ }
+
+ @Override
+ public CharSequence getLabelForAccessibility(Context context) {
+ return null;
+ }
+
+ public View create(Context context, View convertView, ViewGroup parent,
+ LayoutInflater inflater) {
+ View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
+
+ int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
+ for (int i = 0; i < 3; i++) {
+ View itemView = v.findViewById(ITEM_IDS[i]);
+ itemView.setSelected(selectedIndex == i);
+ // Set up click handler
+ itemView.setTag(i);
+ itemView.setOnClickListener(this);
+ }
+ return v;
+ }
+
+ public void onPress() {
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ void willCreate() {
+ }
+
+ public void onClick(View v) {
+ if (!(v.getTag() instanceof Integer)) return;
+
+ int index = (Integer) v.getTag();
+ mAudioManager.setRingerMode(indexToRingerMode(index));
+ mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
+ }
+ }
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ || Intent.ACTION_SCREEN_OFF.equals(action)) {
+ String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
+ if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
+ mHandler.sendEmptyMessage(MESSAGE_DISMISS);
+ }
+ } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
+ // Airplane mode can be changed after ECM exits if airplane toggle button
+ // is pressed during ECM mode
+ if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
+ mIsWaitingForEcmExit) {
+ mIsWaitingForEcmExit = false;
+ changeAirplaneModeSystemSetting(true);
+ }
+ }
+ }
+ };
+
+ PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ if (!mHasTelephony) return;
+ final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
+ mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ }
+ };
+
+ private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ mHandler.sendEmptyMessage(MESSAGE_REFRESH);
+ }
+ }
+ };
+
+ private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onAirplaneModeChanged();
+ }
+ };
+
+ private static final int MESSAGE_DISMISS = 0;
+ private static final int MESSAGE_REFRESH = 1;
+ private static final int MESSAGE_SHOW = 2;
+ private static final int DIALOG_DISMISS_DELAY = 300; // ms
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_DISMISS:
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ break;
+ case MESSAGE_REFRESH:
+ refreshSilentMode();
+ mAdapter.notifyDataSetChanged();
+ break;
+ case MESSAGE_SHOW:
+ handleShow();
+ break;
+ }
+ }
+ };
+
+ private void onAirplaneModeChanged() {
+ // Let the service state callbacks handle the state.
+ if (mHasTelephony) return;
+
+ boolean airplaneModeOn = Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON,
+ 0) == 1;
+ mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
+ mAirplaneModeOn.updateState(mAirplaneState);
+ }
+
+ /**
+ * Change the airplane mode system setting
+ */
+ private void changeAirplaneModeSystemSetting(boolean on) {
+ Settings.Global.putInt(
+ mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON,
+ on ? 1 : 0);
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("state", on);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ if (!mHasTelephony) {
+ mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
+ }
+ }
+
+ private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
+ private final Context mContext;
+ private final int mWindowTouchSlop;
+ private final AlertController mAlert;
+ private final MyAdapter mAdapter;
+
+ private EnableAccessibilityController mEnableAccessibilityController;
+
+ private boolean mIntercepted;
+ private boolean mCancelOnUp;
+
+ public GlobalActionsDialog(Context context, AlertParams params) {
+ super(context, getDialogTheme(context));
+ mContext = context;
+ mAlert = new AlertController(mContext, this, getWindow());
+ mAdapter = (MyAdapter) params.mAdapter;
+ mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
+ params.apply(mAlert);
+ }
+
+ private static int getDialogTheme(Context context) {
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
+ outValue, true);
+ return outValue.resourceId;
+ }
+
+ @Override
+ protected void onStart() {
+ // If global accessibility gesture can be performed, we will take care
+ // of dismissing the dialog on touch outside. This is because the dialog
+ // is dismissed on the first down while the global gesture is a long press
+ // with two fingers anywhere on the screen.
+ if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
+ mEnableAccessibilityController = new EnableAccessibilityController(mContext,
+ new Runnable() {
+ @Override
+ public void run() {
+ dismiss();
+ }
+ });
+ super.setCanceledOnTouchOutside(false);
+ } else {
+ mEnableAccessibilityController = null;
+ super.setCanceledOnTouchOutside(true);
+ }
+
+ super.onStart();
+ }
+
+ @Override
+ protected void onStop() {
+ if (mEnableAccessibilityController != null) {
+ mEnableAccessibilityController.onDestroy();
+ }
+ super.onStop();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (mEnableAccessibilityController != null) {
+ final int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ View decor = getWindow().getDecorView();
+ final int eventX = (int) event.getX();
+ final int eventY = (int) event.getY();
+ if (eventX < -mWindowTouchSlop
+ || eventY < -mWindowTouchSlop
+ || eventX >= decor.getWidth() + mWindowTouchSlop
+ || eventY >= decor.getHeight() + mWindowTouchSlop) {
+ mCancelOnUp = true;
+ }
+ }
+ try {
+ if (!mIntercepted) {
+ mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
+ if (mIntercepted) {
+ final long now = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ mCancelOnUp = true;
+ }
+ } else {
+ return mEnableAccessibilityController.onTouchEvent(event);
+ }
+ } finally {
+ if (action == MotionEvent.ACTION_UP) {
+ if (mCancelOnUp) {
+ cancel();
+ }
+ mCancelOnUp = false;
+ mIntercepted = false;
+ }
+ }
+ }
+ return super.dispatchTouchEvent(event);
+ }
+
+ public ListView getListView() {
+ return mAlert.getListView();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAlert.installContent();
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ for (int i = 0; i < mAdapter.getCount(); ++i) {
+ CharSequence label =
+ mAdapter.getItem(i).getLabelForAccessibility(getContext());
+ if (label != null) {
+ event.getText().add(label);
+ }
+ }
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyDown(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mAlert.onKeyUp(keyCode, event)) {
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/GlobalKeyManager.java b/services/core/java/com/android/server/policy/GlobalKeyManager.java
new file mode 100644
index 0000000..e08c004
--- /dev/null
+++ b/services/core/java/com/android/server/policy/GlobalKeyManager.java
@@ -0,0 +1,145 @@
+/*
+ *
+ * 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.policy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Stores a mapping of global keys.
+ * <p>
+ * A global key will NOT go to the foreground application and instead only ever be sent via targeted
+ * broadcast to the specified component. The action of the intent will be
+ * {@link Intent#ACTION_GLOBAL_BUTTON} and the KeyEvent will be included in the intent with
+ * {@link Intent#EXTRA_KEY_EVENT}.
+ */
+final class GlobalKeyManager {
+
+ private static final String TAG = "GlobalKeyManager";
+
+ private static final String TAG_GLOBAL_KEYS = "global_keys";
+ private static final String ATTR_VERSION = "version";
+ private static final String TAG_KEY = "key";
+ private static final String ATTR_KEY_CODE = "keyCode";
+ private static final String ATTR_COMPONENT = "component";
+
+ private static final int GLOBAL_KEY_FILE_VERSION = 1;
+
+ private SparseArray<ComponentName> mKeyMapping;
+
+ public GlobalKeyManager(Context context) {
+ mKeyMapping = new SparseArray<ComponentName>();
+ loadGlobalKeys(context);
+ }
+
+ /**
+ * Broadcasts an intent if the keycode is part of the global key mapping.
+ *
+ * @param context context used to broadcast the event
+ * @param keyCode keyCode which triggered this function
+ * @param event keyEvent which trigged this function
+ * @return {@code true} if this was handled
+ */
+ boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) {
+ if (mKeyMapping.size() > 0) {
+ ComponentName component = mKeyMapping.get(keyCode);
+ if (component != null) {
+ Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON)
+ .setComponent(component)
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(Intent.EXTRA_KEY_EVENT, event);
+ context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if the key will be handled globally.
+ */
+ boolean shouldHandleGlobalKey(int keyCode, KeyEvent event) {
+ return mKeyMapping.get(keyCode) != null;
+ }
+
+ private void loadGlobalKeys(Context context) {
+ XmlResourceParser parser = null;
+ try {
+ parser = context.getResources().getXml(com.android.internal.R.xml.global_keys);
+ XmlUtils.beginDocument(parser, TAG_GLOBAL_KEYS);
+ int version = parser.getAttributeIntValue(null, ATTR_VERSION, 0);
+ if (GLOBAL_KEY_FILE_VERSION == version) {
+ while (true) {
+ XmlUtils.nextElement(parser);
+ String element = parser.getName();
+ if (element == null) {
+ break;
+ }
+ if (TAG_KEY.equals(element)) {
+ String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE);
+ String componentName = parser.getAttributeValue(null, ATTR_COMPONENT);
+ int keyCode = KeyEvent.keyCodeFromString(keyCodeName);
+ if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ mKeyMapping.put(keyCode, ComponentName.unflattenFromString(
+ componentName));
+ }
+ }
+ }
+ }
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "global keys file not found", e);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "XML parser exception reading global keys file", e);
+ } catch (IOException e) {
+ Log.w(TAG, "I/O exception reading global keys file", e);
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ final int numKeys = mKeyMapping.size();
+ if (numKeys == 0) {
+ pw.print(prefix); pw.println("mKeyMapping.size=0");
+ return;
+ }
+ pw.print(prefix); pw.println("mKeyMapping={");
+ for (int i = 0; i < numKeys; ++i) {
+ pw.print(" ");
+ pw.print(prefix);
+ pw.print(KeyEvent.keyCodeToString(mKeyMapping.keyAt(i)));
+ pw.print("=");
+ pw.println(mKeyMapping.valueAt(i).flattenToString());
+ }
+ pw.print(prefix); pw.println("}");
+ }
+}
diff --git a/services/core/java/com/android/server/policy/IconUtilities.java b/services/core/java/com/android/server/policy/IconUtilities.java
new file mode 100644
index 0000000..4658344
--- /dev/null
+++ b/services/core/java/com/android/server/policy/IconUtilities.java
@@ -0,0 +1,190 @@
+/*
+ * 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.policy;
+
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.TableMaskFilter;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.content.res.Resources;
+import android.content.Context;
+
+/**
+ * Various utilities shared amongst the Launcher's classes.
+ */
+final class IconUtilities {
+ private static final String TAG = "IconUtilities";
+
+ private static final int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
+
+ private int mIconWidth = -1;
+ private int mIconHeight = -1;
+ private int mIconTextureWidth = -1;
+ private int mIconTextureHeight = -1;
+
+ private final Paint mPaint = new Paint();
+ private final Paint mBlurPaint = new Paint();
+ private final Paint mGlowColorPressedPaint = new Paint();
+ private final Paint mGlowColorFocusedPaint = new Paint();
+ private final Rect mOldBounds = new Rect();
+ private final Canvas mCanvas = new Canvas();
+ private final DisplayMetrics mDisplayMetrics;
+
+ private int mColorIndex = 0;
+
+ public IconUtilities(Context context) {
+ final Resources resources = context.getResources();
+ DisplayMetrics metrics = mDisplayMetrics = resources.getDisplayMetrics();
+ final float density = metrics.density;
+ final float blurPx = 5 * density;
+
+ mIconWidth = mIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size);
+ mIconTextureWidth = mIconTextureHeight = mIconWidth + (int)(blurPx*2);
+
+ mBlurPaint.setMaskFilter(new BlurMaskFilter(blurPx, BlurMaskFilter.Blur.NORMAL));
+
+ TypedValue value = new TypedValue();
+ mGlowColorPressedPaint.setColor(context.getTheme().resolveAttribute(
+ android.R.attr.colorPressedHighlight, value, true) ? value.data : 0xffffc300);
+ mGlowColorPressedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
+ mGlowColorFocusedPaint.setColor(context.getTheme().resolveAttribute(
+ android.R.attr.colorFocusedHighlight, value, true) ? value.data : 0xffff8e00);
+ mGlowColorFocusedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
+
+ ColorMatrix cm = new ColorMatrix();
+ cm.setSaturation(0.2f);
+
+ mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+ }
+
+ public Drawable createIconDrawable(Drawable src) {
+ Bitmap scaled = createIconBitmap(src);
+
+ StateListDrawable result = new StateListDrawable();
+
+ result.addState(new int[] { android.R.attr.state_focused },
+ new BitmapDrawable(createSelectedBitmap(scaled, false)));
+ result.addState(new int[] { android.R.attr.state_pressed },
+ new BitmapDrawable(createSelectedBitmap(scaled, true)));
+ result.addState(new int[0], new BitmapDrawable(scaled));
+
+ result.setBounds(0, 0, mIconTextureWidth, mIconTextureHeight);
+ return result;
+ }
+
+ /**
+ * Returns a bitmap suitable for the all apps view. The bitmap will be a power
+ * of two sized ARGB_8888 bitmap that can be used as a gl texture.
+ */
+ private Bitmap createIconBitmap(Drawable icon) {
+ int width = mIconWidth;
+ int height = mIconHeight;
+
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(width);
+ painter.setIntrinsicHeight(height);
+ } else if (icon instanceof BitmapDrawable) {
+ // Ensure the bitmap has a density.
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(mDisplayMetrics);
+ }
+ }
+ int sourceWidth = icon.getIntrinsicWidth();
+ int sourceHeight = icon.getIntrinsicHeight();
+
+ if (sourceWidth > 0 && sourceHeight > 0) {
+ // There are intrinsic sizes.
+ if (width < sourceWidth || height < sourceHeight) {
+ // It's too big, scale it down.
+ final float ratio = (float) sourceWidth / sourceHeight;
+ if (sourceWidth > sourceHeight) {
+ height = (int) (width / ratio);
+ } else if (sourceHeight > sourceWidth) {
+ width = (int) (height * ratio);
+ }
+ } else if (sourceWidth < width && sourceHeight < height) {
+ // It's small, use the size they gave us.
+ width = sourceWidth;
+ height = sourceHeight;
+ }
+ }
+
+ // no intrinsic size --> use default size
+ int textureWidth = mIconTextureWidth;
+ int textureHeight = mIconTextureHeight;
+
+ final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+ Bitmap.Config.ARGB_8888);
+ final Canvas canvas = mCanvas;
+ canvas.setBitmap(bitmap);
+
+ final int left = (textureWidth-width) / 2;
+ final int top = (textureHeight-height) / 2;
+
+ if (false) {
+ // draw a big box for the icon for debugging
+ canvas.drawColor(sColors[mColorIndex]);
+ if (++mColorIndex >= sColors.length) mColorIndex = 0;
+ Paint debugPaint = new Paint();
+ debugPaint.setColor(0xffcccc00);
+ canvas.drawRect(left, top, left+width, top+height, debugPaint);
+ }
+
+ mOldBounds.set(icon.getBounds());
+ icon.setBounds(left, top, left+width, top+height);
+ icon.draw(canvas);
+ icon.setBounds(mOldBounds);
+
+ return bitmap;
+ }
+
+ private Bitmap createSelectedBitmap(Bitmap src, boolean pressed) {
+ final Bitmap result = Bitmap.createBitmap(mIconTextureWidth, mIconTextureHeight,
+ Bitmap.Config.ARGB_8888);
+ final Canvas dest = new Canvas(result);
+
+ dest.drawColor(0, PorterDuff.Mode.CLEAR);
+
+ int[] xy = new int[2];
+ Bitmap mask = src.extractAlpha(mBlurPaint, xy);
+
+ dest.drawBitmap(mask, xy[0], xy[1],
+ pressed ? mGlowColorPressedPaint : mGlowColorFocusedPaint);
+
+ mask.recycle();
+
+ dest.drawBitmap(src, 0, 0, mPaint);
+ dest.setBitmap(null);
+
+ return result;
+ }
+}
diff --git a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
new file mode 100644
index 0000000..e720f3e
--- /dev/null
+++ b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
@@ -0,0 +1,349 @@
+/*
+ * 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.policy;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+/**
+ * Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden
+ * entering immersive mode.
+ */
+public class ImmersiveModeConfirmation {
+ private static final String TAG = "ImmersiveModeConfirmation";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution
+ private static final String CONFIRMED = "confirmed";
+
+ private final Context mContext;
+ private final H mHandler;
+ private final long mShowDelayMs;
+ private final long mPanicThresholdMs;
+ private final SparseBooleanArray mUserPanicResets = new SparseBooleanArray();
+
+ private boolean mConfirmed;
+ private ClingWindowView mClingWindow;
+ private long mPanicTime;
+ private WindowManager mWindowManager;
+ private int mCurrentUserId;
+
+ public ImmersiveModeConfirmation(Context context) {
+ mContext = context;
+ mHandler = new H();
+ mShowDelayMs = getNavBarExitDuration() * 3;
+ mPanicThresholdMs = context.getResources()
+ .getInteger(R.integer.config_immersive_mode_confirmation_panic);
+ mWindowManager = (WindowManager)
+ mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ private long getNavBarExitDuration() {
+ Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
+ return exit != null ? exit.getDuration() : 0;
+ }
+
+ public void loadSetting(int currentUserId) {
+ mConfirmed = false;
+ mCurrentUserId = currentUserId;
+ if (DEBUG) Slog.d(TAG, String.format("loadSetting() mCurrentUserId=%d resetForPanic=%s",
+ mCurrentUserId, mUserPanicResets.get(mCurrentUserId, false)));
+ String value = null;
+ try {
+ value = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+ UserHandle.USER_CURRENT);
+ mConfirmed = CONFIRMED.equals(value);
+ if (DEBUG) Slog.d(TAG, "Loaded mConfirmed=" + mConfirmed);
+ } catch (Throwable t) {
+ Slog.w(TAG, "Error loading confirmations, value=" + value, t);
+ }
+ }
+
+ private void saveSetting() {
+ if (DEBUG) Slog.d(TAG, "saveSetting()");
+ try {
+ final String value = mConfirmed ? CONFIRMED : null;
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+ value,
+ UserHandle.USER_CURRENT);
+ if (DEBUG) Slog.d(TAG, "Saved value=" + value);
+ } catch (Throwable t) {
+ Slog.w(TAG, "Error saving confirmations, mConfirmed=" + mConfirmed, t);
+ }
+ }
+
+ public void immersiveModeChanged(String pkg, boolean isImmersiveMode,
+ boolean userSetupComplete) {
+ mHandler.removeMessages(H.SHOW);
+ if (isImmersiveMode) {
+ final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg);
+ if (DEBUG) Slog.d(TAG, String.format("immersiveModeChanged() disabled=%s mConfirmed=%s",
+ disabled, mConfirmed));
+ if (!disabled && (DEBUG_SHOW_EVERY_TIME || !mConfirmed) && userSetupComplete) {
+ mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs);
+ }
+ } else {
+ mHandler.sendEmptyMessage(H.HIDE);
+ }
+ }
+
+ public boolean onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode) {
+ if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
+ // turning the screen back on within the panic threshold
+ mHandler.sendEmptyMessage(H.PANIC);
+ return mClingWindow == null;
+ }
+ if (isScreenOn && inImmersiveMode) {
+ // turning the screen off, remember if we were in immersive mode
+ mPanicTime = time;
+ } else {
+ mPanicTime = 0;
+ }
+ return false;
+ }
+
+ public void confirmCurrentPrompt() {
+ if (mClingWindow != null) {
+ if (DEBUG) Slog.d(TAG, "confirmCurrentPrompt()");
+ mHandler.post(mConfirm);
+ }
+ }
+
+ private void handlePanic() {
+ if (DEBUG) Slog.d(TAG, "handlePanic()");
+ if (mUserPanicResets.get(mCurrentUserId, false)) return; // already reset for panic
+ mUserPanicResets.put(mCurrentUserId, true);
+ mConfirmed = false;
+ saveSetting();
+ }
+
+ private void handleHide() {
+ if (mClingWindow != null) {
+ if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation");
+ mWindowManager.removeView(mClingWindow);
+ mClingWindow = null;
+ }
+ }
+
+ public WindowManager.LayoutParams getClingWindowLayoutParams() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+ 0
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ ,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("ImmersiveModeConfirmation");
+ lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
+ return lp;
+ }
+
+ public FrameLayout.LayoutParams getBubbleLayoutParams() {
+ return new FrameLayout.LayoutParams(
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.immersive_mode_cling_width),
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_HORIZONTAL | Gravity.TOP);
+ }
+
+ private class ClingWindowView extends FrameLayout {
+ private static final int BGCOLOR = 0x80000000;
+ private static final int OFFSET_DP = 96;
+ private static final int ANIMATION_DURATION = 250;
+
+ private final Runnable mConfirm;
+ private final ColorDrawable mColor = new ColorDrawable(0);
+ private final Interpolator mInterpolator;
+ private ValueAnimator mColorAnim;
+ private ViewGroup mClingLayout;
+
+ private Runnable mUpdateLayoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mClingLayout != null && mClingLayout.getParent() != null) {
+ mClingLayout.setLayoutParams(getBubbleLayoutParams());
+ }
+ }
+ };
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ post(mUpdateLayoutRunnable);
+ }
+ }
+ };
+
+ public ClingWindowView(Context context, Runnable confirm) {
+ super(context);
+ mConfirm = confirm;
+ setBackground(mColor);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mInterpolator = AnimationUtils
+ .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ mWindowManager.getDefaultDisplay().getMetrics(metrics);
+ float density = metrics.density;
+
+ // create the confirmation cling
+ mClingLayout = (ViewGroup)
+ View.inflate(getContext(), R.layout.immersive_mode_cling, null);
+
+ final Button ok = (Button) mClingLayout.findViewById(R.id.ok);
+ ok.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mConfirm.run();
+ }
+ });
+ addView(mClingLayout, getBubbleLayoutParams());
+
+ if (ActivityManager.isHighEndGfx()) {
+ final View cling = mClingLayout;
+ cling.setAlpha(0f);
+ cling.setTranslationY(-OFFSET_DP * density);
+
+ postOnAnimation(new Runnable() {
+ @Override
+ public void run() {
+ cling.animate()
+ .alpha(1f)
+ .translationY(0)
+ .setDuration(ANIMATION_DURATION)
+ .setInterpolator(mInterpolator)
+ .withLayer()
+ .start();
+
+ mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR);
+ mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final int c = (Integer) animation.getAnimatedValue();
+ mColor.setColor(c);
+ }
+ });
+ mColorAnim.setDuration(ANIMATION_DURATION);
+ mColorAnim.setInterpolator(mInterpolator);
+ mColorAnim.start();
+ }
+ });
+ } else {
+ mColor.setColor(BGCOLOR);
+ }
+
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent motion) {
+ return true;
+ }
+ }
+
+ private void handleShow() {
+ if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation");
+
+ mClingWindow = new ClingWindowView(mContext, mConfirm);
+
+ // we will be hiding the nav bar, so layout as if it's already hidden
+ mClingWindow.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+
+ // show the confirmation
+ WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+ mWindowManager.addView(mClingWindow, lp);
+ }
+
+ private final Runnable mConfirm = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Slog.d(TAG, "mConfirm.run()");
+ if (!mConfirmed) {
+ mConfirmed = true;
+ saveSetting();
+ }
+ handleHide();
+ }
+ };
+
+ private final class H extends Handler {
+ private static final int SHOW = 1;
+ private static final int HIDE = 2;
+ private static final int PANIC = 3;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case SHOW:
+ handleShow();
+ break;
+ case HIDE:
+ handleHide();
+ break;
+ case PANIC:
+ handlePanic();
+ break;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java b/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java
new file mode 100644
index 0000000..ed5dc6f
--- /dev/null
+++ b/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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.policy;
+
+import android.view.animation.Interpolator;
+
+public class LogDecelerateInterpolator implements Interpolator {
+
+ private int mBase;
+ private int mDrift;
+ private final float mLogScale;
+
+ public LogDecelerateInterpolator(int base, int drift) {
+ mBase = base;
+ mDrift = drift;
+
+ mLogScale = 1f / computeLog(1, mBase, mDrift);
+ }
+
+ private static float computeLog(float t, int base, int drift) {
+ return (float) -Math.pow(base, -t) + 1 + (drift * t);
+ }
+
+ @Override
+ public float getInterpolation(float t) {
+ return computeLog(t, mBase, mDrift) * mLogScale;
+ }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
new file mode 100644
index 0000000..25857c5
--- /dev/null
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -0,0 +1,6602 @@
+/*
+ * 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.policy;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.SleepToken;
+import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
+import android.app.IUiModeManager;
+import android.app.ProgressDialog;
+import android.app.SearchManager;
+import android.app.StatusBarManager;
+import android.app.UiModeManager;
+import android.content.ActivityNotFoundException;
+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.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.ContentObserver;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.media.IAudioService;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.media.session.MediaSessionLegacyHelper;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.FactoryTest;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UEventObserver;
+import android.os.UserHandle;
+import android.os.Vibrator;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.service.dreams.DreamManagerInternal;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.speech.RecognizerIntent;
+import android.telecom.TelecomManager;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
+import android.view.IApplicationToken;
+import android.view.IWindowManager;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.KeyCharacterMap;
+import android.view.KeyCharacterMap.FallbackAction;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.PhoneWindow;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerInternal;
+import android.view.WindowManagerPolicy;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import com.android.internal.R;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.widget.PointerLocationView;
+import com.android.server.LocalServices;
+import com.android.server.policy.keyguard.KeyguardServiceDelegate;
+import com.android.server.policy.keyguard.KeyguardServiceDelegate.ShowListener;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.List;
+
+import static android.view.WindowManager.LayoutParams.*;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
+import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
+
+/**
+ * WindowManagerPolicy implementation for the Android phone UI. This
+ * introduces a new method suffix, Lp, for an internal lock of the
+ * PhoneWindowManager. This is used to protect some internal state, and
+ * can be acquired with either the Lw and Li lock held, so has the restrictions
+ * of both of those when held.
+ */
+public class PhoneWindowManager implements WindowManagerPolicy {
+ static final String TAG = "WindowManager";
+ static final boolean DEBUG = false;
+ static final boolean localLOGV = false;
+ static final boolean DEBUG_INPUT = false;
+ static final boolean DEBUG_KEYGUARD = false;
+ static final boolean DEBUG_LAYOUT = false;
+ static final boolean DEBUG_STARTING_WINDOW = false;
+ static final boolean DEBUG_WAKEUP = false;
+ static final boolean SHOW_STARTING_ANIMATIONS = true;
+ static final boolean SHOW_PROCESSES_ON_ALT_MENU = false;
+
+ // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
+ // No longer recommended for desk docks; still useful in car docks.
+ static final boolean ENABLE_CAR_DOCK_HOME_CAPTURE = true;
+ static final boolean ENABLE_DESK_DOCK_HOME_CAPTURE = false;
+
+ static final int SHORT_PRESS_POWER_NOTHING = 0;
+ static final int SHORT_PRESS_POWER_GO_TO_SLEEP = 1;
+ static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP = 2;
+ static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME = 3;
+ static final int SHORT_PRESS_POWER_GO_HOME = 4;
+
+ static final int LONG_PRESS_POWER_NOTHING = 0;
+ static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
+ static final int LONG_PRESS_POWER_SHUT_OFF = 2;
+ static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
+
+ static final int MULTI_PRESS_POWER_NOTHING = 0;
+ static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
+ static final int MULTI_PRESS_POWER_BRIGHTNESS_BOOST = 2;
+
+ // These need to match the documentation/constant in
+ // core/res/res/values/config.xml
+ static final int LONG_PRESS_HOME_NOTHING = 0;
+ static final int LONG_PRESS_HOME_RECENT_SYSTEM_UI = 1;
+ static final int LONG_PRESS_HOME_ASSIST = 2;
+
+ static final int DOUBLE_TAP_HOME_NOTHING = 0;
+ static final int DOUBLE_TAP_HOME_RECENT_SYSTEM_UI = 1;
+
+ static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP = 0;
+ static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME = 1;
+
+ static final int APPLICATION_MEDIA_SUBLAYER = -2;
+ static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
+ static final int APPLICATION_PANEL_SUBLAYER = 1;
+ static final int APPLICATION_SUB_PANEL_SUBLAYER = 2;
+ static final int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3;
+
+ static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
+ static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
+ static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+ static public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
+ static public final String SYSTEM_DIALOG_REASON_ASSIST = "assist";
+
+ /**
+ * These are the system UI flags that, when changing, can cause the layout
+ * of the screen to change.
+ */
+ static final int SYSTEM_UI_CHANGING_LAYOUT =
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.STATUS_BAR_TRANSLUCENT
+ | View.NAVIGATION_BAR_TRANSLUCENT
+ | View.SYSTEM_UI_TRANSPARENT;
+
+ private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build();
+
+ /**
+ * Keyguard stuff
+ */
+ private WindowState mKeyguardScrim;
+ private boolean mKeyguardHidden;
+ private boolean mKeyguardDrawnOnce;
+
+ /* Table of Application Launch keys. Maps from key codes to intent categories.
+ *
+ * These are special keys that are used to launch particular kinds of applications,
+ * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C)
+ * usage page. We don't support quite that many yet...
+ */
+ static SparseArray<String> sApplicationLaunchKeyCategories;
+ static {
+ sApplicationLaunchKeyCategories = new SparseArray<String>();
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
+ }
+
+ /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */
+ static final int WAITING_FOR_DRAWN_TIMEOUT = 1000;
+
+ /**
+ * Lock protecting internal state. Must not call out into window
+ * manager with lock held. (This lock will be acquired in places
+ * where the window manager is calling in with its own lock held.)
+ */
+ private final Object mLock = new Object();
+
+ Context mContext;
+ IWindowManager mWindowManager;
+ WindowManagerFuncs mWindowManagerFuncs;
+ WindowManagerInternal mWindowManagerInternal;
+ PowerManager mPowerManager;
+ ActivityManagerInternal mActivityManagerInternal;
+ DreamManagerInternal mDreamManagerInternal;
+ IStatusBarService mStatusBarService;
+ boolean mPreloadedRecentApps;
+ final Object mServiceAquireLock = new Object();
+ Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
+ SearchManager mSearchManager;
+ AccessibilityManager mAccessibilityManager;
+ BurnInProtectionHelper mBurnInProtectionHelper;
+
+ // Vibrator pattern for haptic feedback of a long press.
+ long[] mLongPressVibePattern;
+
+ // Vibrator pattern for haptic feedback of virtual key press.
+ long[] mVirtualKeyVibePattern;
+
+ // Vibrator pattern for a short vibration.
+ long[] mKeyboardTapVibePattern;
+
+ // Vibrator pattern for a short vibration when tapping on an hour/minute tick of a Clock.
+ long[] mClockTickVibePattern;
+
+ // Vibrator pattern for a short vibration when tapping on a day/month/year date of a Calendar.
+ long[] mCalendarDateVibePattern;
+
+ // Vibrator pattern for haptic feedback during boot when safe mode is disabled.
+ long[] mSafeModeDisabledVibePattern;
+
+ // Vibrator pattern for haptic feedback during boot when safe mode is enabled.
+ long[] mSafeModeEnabledVibePattern;
+
+ /** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */
+ boolean mEnableShiftMenuBugReports = false;
+
+ boolean mSafeMode;
+ WindowState mStatusBar = null;
+ int mStatusBarHeight;
+ WindowState mNavigationBar = null;
+ boolean mHasNavigationBar = false;
+ boolean mCanHideNavigationBar = false;
+ boolean mNavigationBarCanMove = false; // can the navigation bar ever move to the side?
+ boolean mNavigationBarOnBottom = true; // is the navigation bar on the bottom *right now*?
+ int[] mNavigationBarHeightForRotation = new int[4];
+ int[] mNavigationBarWidthForRotation = new int[4];
+
+ boolean mBootMessageNeedsHiding;
+ KeyguardServiceDelegate mKeyguardDelegate;
+ final Runnable mWindowManagerDrawCallback = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display!");
+ mHandler.sendEmptyMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE);
+ }
+ };
+ final ShowListener mKeyguardDelegateCallback = new ShowListener() {
+ @Override
+ public void onShown(IBinder windowToken) {
+ if (DEBUG_WAKEUP) Slog.d(TAG, "mKeyguardDelegate.ShowListener.onShown.");
+ mHandler.sendEmptyMessage(MSG_KEYGUARD_DRAWN_COMPLETE);
+ }
+ };
+
+ GlobalActions mGlobalActions;
+ Handler mHandler;
+ WindowState mLastInputMethodWindow = null;
+ WindowState mLastInputMethodTargetWindow = null;
+
+ // FIXME This state is shared between the input reader and handler thread.
+ // Technically it's broken and buggy but it has been like this for many years
+ // and we have not yet seen any problems. Someday we'll rewrite this logic
+ // so that only one thread is involved in handling input policy. Unfortunately
+ // it's on a critical path for power management so we can't just post the work to the
+ // handler thread. We'll need to resolve this someday by teaching the input dispatcher
+ // to hold wakelocks during dispatch and eliminating the critical path.
+ volatile boolean mPowerKeyHandled;
+ volatile boolean mBeganFromNonInteractive;
+ volatile int mPowerKeyPressCounter;
+ volatile boolean mEndCallKeyHandled;
+
+ boolean mRecentsVisible;
+ int mRecentAppsHeldModifiers;
+ boolean mLanguageSwitchKeyPressed;
+
+ int mLidState = LID_ABSENT;
+ int mCameraLensCoverState = CAMERA_LENS_COVER_ABSENT;
+ boolean mHaveBuiltInKeyboard;
+
+ boolean mSystemReady;
+ boolean mSystemBooted;
+ boolean mHdmiPlugged;
+ IUiModeManager mUiModeManager;
+ int mUiMode;
+ int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ int mLidOpenRotation;
+ int mCarDockRotation;
+ int mDeskDockRotation;
+ int mUndockedHdmiRotation;
+ int mDemoHdmiRotation;
+ boolean mDemoHdmiRotationLock;
+ int mDemoRotation;
+ boolean mDemoRotationLock;
+
+ boolean mWakeGestureEnabledSetting;
+ MyWakeGestureListener mWakeGestureListener;
+
+ // Default display does not rotate, apps that require non-default orientation will have to
+ // have the orientation emulated.
+ private boolean mForceDefaultOrientation = false;
+
+ int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
+ int mUserRotation = Surface.ROTATION_0;
+ boolean mAccelerometerDefault;
+
+ boolean mSupportAutoRotation;
+ int mAllowAllRotations = -1;
+ boolean mCarDockEnablesAccelerometer;
+ boolean mDeskDockEnablesAccelerometer;
+ int mLidKeyboardAccessibility;
+ int mLidNavigationAccessibility;
+ boolean mLidControlsSleep;
+ int mShortPressOnPowerBehavior;
+ int mLongPressOnPowerBehavior;
+ int mDoublePressOnPowerBehavior;
+ int mTriplePressOnPowerBehavior;
+ int mShortPressOnSleepBehavior;
+ boolean mAwake;
+ boolean mScreenOnEarly;
+ boolean mScreenOnFully;
+ ScreenOnListener mScreenOnListener;
+ boolean mKeyguardDrawComplete;
+ boolean mWindowManagerDrawComplete;
+ boolean mOrientationSensorEnabled = false;
+ int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ boolean mHasSoftInput = false;
+ boolean mTranslucentDecorEnabled = true;
+ boolean mUseTvRouting;
+
+ int mPointerLocationMode = 0; // guarded by mLock
+
+ // The last window we were told about in focusChanged.
+ WindowState mFocusedWindow;
+ IApplicationToken mFocusedApp;
+
+ PointerLocationView mPointerLocationView;
+
+ // The current size of the screen; really; extends into the overscan area of
+ // the screen and doesn't account for any system elements like the status bar.
+ int mOverscanScreenLeft, mOverscanScreenTop;
+ int mOverscanScreenWidth, mOverscanScreenHeight;
+ // The current visible size of the screen; really; (ir)regardless of whether the status
+ // bar can be hidden but not extending into the overscan area.
+ int mUnrestrictedScreenLeft, mUnrestrictedScreenTop;
+ int mUnrestrictedScreenWidth, mUnrestrictedScreenHeight;
+ // Like mOverscanScreen*, but allowed to move into the overscan region where appropriate.
+ int mRestrictedOverscanScreenLeft, mRestrictedOverscanScreenTop;
+ int mRestrictedOverscanScreenWidth, mRestrictedOverscanScreenHeight;
+ // The current size of the screen; these may be different than (0,0)-(dw,dh)
+ // if the status bar can't be hidden; in that case it effectively carves out
+ // that area of the display from all other windows.
+ int mRestrictedScreenLeft, mRestrictedScreenTop;
+ int mRestrictedScreenWidth, mRestrictedScreenHeight;
+ // During layout, the current screen borders accounting for any currently
+ // visible system UI elements.
+ int mSystemLeft, mSystemTop, mSystemRight, mSystemBottom;
+ // For applications requesting stable content insets, these are them.
+ int mStableLeft, mStableTop, mStableRight, mStableBottom;
+ // For applications requesting stable content insets but have also set the
+ // fullscreen window flag, these are the stable dimensions without the status bar.
+ int mStableFullscreenLeft, mStableFullscreenTop;
+ int mStableFullscreenRight, mStableFullscreenBottom;
+ // During layout, the current screen borders with all outer decoration
+ // (status bar, input method dock) accounted for.
+ int mCurLeft, mCurTop, mCurRight, mCurBottom;
+ // During layout, the frame in which content should be displayed
+ // to the user, accounting for all screen decoration except for any
+ // space they deem as available for other content. This is usually
+ // the same as mCur*, but may be larger if the screen decor has supplied
+ // content insets.
+ int mContentLeft, mContentTop, mContentRight, mContentBottom;
+ // During layout, the frame in which voice content should be displayed
+ // to the user, accounting for all screen decoration except for any
+ // space they deem as available for other content.
+ int mVoiceContentLeft, mVoiceContentTop, mVoiceContentRight, mVoiceContentBottom;
+ // During layout, the current screen borders along which input method
+ // windows are placed.
+ int mDockLeft, mDockTop, mDockRight, mDockBottom;
+ // During layout, the layer at which the doc window is placed.
+ int mDockLayer;
+ // During layout, this is the layer of the status bar.
+ int mStatusBarLayer;
+ int mLastSystemUiFlags;
+ // Bits that we are in the process of clearing, so we want to prevent
+ // them from being set by applications until everything has been updated
+ // to have them clear.
+ int mResettingSystemUiFlags = 0;
+ // Bits that we are currently always keeping cleared.
+ int mForceClearedSystemUiFlags = 0;
+ // What we last reported to system UI about whether the compatibility
+ // menu needs to be displayed.
+ boolean mLastFocusNeedsMenu = false;
+
+ FakeWindow mHideNavFakeWindow = null;
+
+ static final Rect mTmpParentFrame = new Rect();
+ static final Rect mTmpDisplayFrame = new Rect();
+ static final Rect mTmpOverscanFrame = new Rect();
+ static final Rect mTmpContentFrame = new Rect();
+ static final Rect mTmpVisibleFrame = new Rect();
+ static final Rect mTmpDecorFrame = new Rect();
+ static final Rect mTmpStableFrame = new Rect();
+ static final Rect mTmpNavigationFrame = new Rect();
+
+ WindowState mTopFullscreenOpaqueWindowState;
+ WindowState mTopFullscreenOpaqueOrDimmingWindowState;
+ HashSet<IApplicationToken> mAppsToBeHidden = new HashSet<IApplicationToken>();
+ HashSet<IApplicationToken> mAppsThatDismissKeyguard = new HashSet<IApplicationToken>();
+ boolean mTopIsFullscreen;
+ boolean mForceStatusBar;
+ boolean mForceStatusBarFromKeyguard;
+ boolean mHideLockScreen;
+ boolean mForcingShowNavBar;
+ int mForcingShowNavBarLayer;
+
+ // States of keyguard dismiss.
+ private static final int DISMISS_KEYGUARD_NONE = 0; // Keyguard not being dismissed.
+ private static final int DISMISS_KEYGUARD_START = 1; // Keyguard needs to be dismissed.
+ private static final int DISMISS_KEYGUARD_CONTINUE = 2; // Keyguard has been dismissed.
+ int mDismissKeyguard = DISMISS_KEYGUARD_NONE;
+
+ /** The window that is currently dismissing the keyguard. Dismissing the keyguard must only
+ * be done once per window. */
+ private WindowState mWinDismissingKeyguard;
+
+ /** The window that is currently showing "over" the keyguard. If there is an app window
+ * belonging to another app on top of this the keyguard shows. If there is a fullscreen
+ * app window under this, still dismiss the keyguard but don't show the app underneath. Show
+ * the wallpaper. */
+ private WindowState mWinShowWhenLocked;
+
+ boolean mShowingLockscreen;
+ boolean mShowingDream;
+ boolean mDreamingLockscreen;
+ boolean mDreamingSleepTokenNeeded;
+ SleepToken mDreamingSleepToken;
+ boolean mKeyguardSecure;
+ boolean mKeyguardSecureIncludingHidden;
+ volatile boolean mKeyguardOccluded;
+ boolean mHomePressed;
+ boolean mHomeConsumed;
+ boolean mHomeDoubleTapPending;
+ Intent mHomeIntent;
+ Intent mCarDockIntent;
+ Intent mDeskDockIntent;
+ boolean mSearchKeyShortcutPending;
+ boolean mConsumeSearchKeyUp;
+ boolean mAssistKeyLongPressed;
+ boolean mPendingMetaAction;
+
+ // support for activating the lock screen while the screen is on
+ boolean mAllowLockscreenWhenOn;
+ int mLockScreenTimeout;
+ boolean mLockScreenTimerActive;
+
+ // Behavior of ENDCALL Button. (See Settings.System.END_BUTTON_BEHAVIOR.)
+ int mEndcallBehavior;
+
+ // Behavior of POWER button while in-call and screen on.
+ // (See Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR.)
+ int mIncallPowerBehavior;
+
+ Display mDisplay;
+
+ int mLandscapeRotation = 0; // default landscape rotation
+ int mSeascapeRotation = 0; // "other" landscape rotation, 180 degrees from mLandscapeRotation
+ int mPortraitRotation = 0; // default portrait rotation
+ int mUpsideDownRotation = 0; // "other" portrait rotation
+
+ int mOverscanLeft = 0;
+ int mOverscanTop = 0;
+ int mOverscanRight = 0;
+ int mOverscanBottom = 0;
+
+ // What we do when the user long presses on home
+ private int mLongPressOnHomeBehavior;
+
+ // What we do when the user double-taps on home
+ private int mDoubleTapOnHomeBehavior;
+
+ // Allowed theater mode wake actions
+ private boolean mAllowTheaterModeWakeFromKey;
+ private boolean mAllowTheaterModeWakeFromPowerKey;
+ private boolean mAllowTheaterModeWakeFromMotion;
+ private boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming;
+ private boolean mAllowTheaterModeWakeFromCameraLens;
+ private boolean mAllowTheaterModeWakeFromLidSwitch;
+ private boolean mAllowTheaterModeWakeFromWakeGesture;
+
+ // Whether to support long press from power button in non-interactive mode
+ private boolean mSupportLongPressPowerWhenNonInteractive;
+
+ // Whether to go to sleep entering theater mode from power button
+ private boolean mGoToSleepOnButtonPressTheaterMode;
+
+ // Screenshot trigger states
+ // Time to volume and power must be pressed within this interval of each other.
+ private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150;
+ // Increase the chord delay when taking a screenshot from the keyguard
+ private static final float KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER = 2.5f;
+ private boolean mScreenshotChordEnabled;
+ private boolean mScreenshotChordVolumeDownKeyTriggered;
+ private long mScreenshotChordVolumeDownKeyTime;
+ private boolean mScreenshotChordVolumeDownKeyConsumed;
+ private boolean mScreenshotChordVolumeUpKeyTriggered;
+ private boolean mScreenshotChordPowerKeyTriggered;
+ private long mScreenshotChordPowerKeyTime;
+
+ /* The number of steps between min and max brightness */
+ private static final int BRIGHTNESS_STEPS = 10;
+
+ SettingsObserver mSettingsObserver;
+ ShortcutManager mShortcutManager;
+ PowerManager.WakeLock mBroadcastWakeLock;
+ PowerManager.WakeLock mPowerKeyWakeLock;
+ boolean mHavePendingMediaKeyRepeatWithWakeLock;
+
+ private int mCurrentUserId;
+
+ // Maps global key codes to the components that will handle them.
+ private GlobalKeyManager mGlobalKeyManager;
+
+ // Fallback actions by key code.
+ private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions =
+ new SparseArray<KeyCharacterMap.FallbackAction>();
+
+ private final LogDecelerateInterpolator mLogDecelerateInterpolator
+ = new LogDecelerateInterpolator(100, 0);
+
+ private static final int MSG_ENABLE_POINTER_LOCATION = 1;
+ private static final int MSG_DISABLE_POINTER_LOCATION = 2;
+ private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
+ private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4;
+ private static final int MSG_KEYGUARD_DRAWN_COMPLETE = 5;
+ private static final int MSG_KEYGUARD_DRAWN_TIMEOUT = 6;
+ private static final int MSG_WINDOW_MANAGER_DRAWN_COMPLETE = 7;
+ private static final int MSG_DISPATCH_SHOW_RECENTS = 9;
+ private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10;
+ private static final int MSG_HIDE_BOOT_MESSAGE = 11;
+ private static final int MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK = 12;
+ private static final int MSG_POWER_DELAYED_PRESS = 13;
+ private static final int MSG_POWER_LONG_PRESS = 14;
+ private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 15;
+
+ private class PolicyHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ENABLE_POINTER_LOCATION:
+ enablePointerLocation();
+ break;
+ case MSG_DISABLE_POINTER_LOCATION:
+ disablePointerLocation();
+ break;
+ case MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK:
+ dispatchMediaKeyWithWakeLock((KeyEvent)msg.obj);
+ break;
+ case MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK:
+ dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj);
+ break;
+ case MSG_DISPATCH_SHOW_RECENTS:
+ showRecentApps(false);
+ break;
+ case MSG_DISPATCH_SHOW_GLOBAL_ACTIONS:
+ showGlobalActionsInternal();
+ break;
+ case MSG_KEYGUARD_DRAWN_COMPLETE:
+ if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mKeyguardDrawComplete");
+ finishKeyguardDrawn();
+ break;
+ case MSG_KEYGUARD_DRAWN_TIMEOUT:
+ Slog.w(TAG, "Keyguard drawn timeout. Setting mKeyguardDrawComplete");
+ finishKeyguardDrawn();
+ break;
+ case MSG_WINDOW_MANAGER_DRAWN_COMPLETE:
+ if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mWindowManagerDrawComplete");
+ finishWindowsDrawn();
+ break;
+ case MSG_HIDE_BOOT_MESSAGE:
+ handleHideBootMessage();
+ break;
+ case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK:
+ launchVoiceAssistWithWakeLock(msg.arg1 != 0);
+ break;
+ case MSG_POWER_DELAYED_PRESS:
+ powerPress((Long)msg.obj, msg.arg1 != 0, msg.arg2);
+ finishPowerKeyPress();
+ break;
+ case MSG_POWER_LONG_PRESS:
+ powerLongPress();
+ break;
+ case MSG_UPDATE_DREAMING_SLEEP_TOKEN:
+ updateDreamingSleepToken(msg.arg1 != 0);
+ break;
+ }
+ }
+ }
+
+ private UEventObserver mHDMIObserver = new UEventObserver() {
+ @Override
+ public void onUEvent(UEventObserver.UEvent event) {
+ setHdmiPlugged("1".equals(event.get("SWITCH_STATE")));
+ }
+ };
+
+ class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void observe() {
+ // Observe all users' changes
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.END_BUTTON_BEHAVIOR), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.WAKE_GESTURE_ENABLED), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.ACCELEROMETER_ROTATION), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.USER_ROTATION), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_OFF_TIMEOUT), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.POINTER_LOCATION), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.DEFAULT_INPUT_METHOD), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.POLICY_CONTROL), false, this,
+ UserHandle.USER_ALL);
+ updateSettings();
+ }
+
+ @Override public void onChange(boolean selfChange) {
+ updateSettings();
+ updateRotation(false);
+ }
+ }
+
+ class MyWakeGestureListener extends WakeGestureListener {
+ MyWakeGestureListener(Context context, Handler handler) {
+ super(context, handler);
+ }
+
+ @Override
+ public void onWakeUp() {
+ synchronized (mLock) {
+ if (shouldEnableWakeGestureLp()) {
+ performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
+ wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture);
+ }
+ }
+ }
+ }
+
+ class MyOrientationListener extends WindowOrientationListener {
+ MyOrientationListener(Context context, Handler handler) {
+ super(context, handler);
+ }
+
+ @Override
+ public void onProposedRotationChanged(int rotation) {
+ if (localLOGV) Slog.v(TAG, "onProposedRotationChanged, rotation=" + rotation);
+ updateRotation(false);
+ }
+ }
+ MyOrientationListener mOrientationListener;
+
+ private final StatusBarController mStatusBarController = new StatusBarController();
+
+ private final BarController mNavigationBarController = new BarController("NavigationBar",
+ View.NAVIGATION_BAR_TRANSIENT,
+ View.NAVIGATION_BAR_UNHIDE,
+ View.NAVIGATION_BAR_TRANSLUCENT,
+ StatusBarManager.WINDOW_NAVIGATION_BAR,
+ WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+
+ private ImmersiveModeConfirmation mImmersiveModeConfirmation;
+
+ private SystemGesturesPointerEventListener mSystemGestures;
+
+ IStatusBarService getStatusBarService() {
+ synchronized (mServiceAquireLock) {
+ if (mStatusBarService == null) {
+ mStatusBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService("statusbar"));
+ }
+ return mStatusBarService;
+ }
+ }
+
+ /*
+ * We always let the sensor be switched on by default except when
+ * the user has explicitly disabled sensor based rotation or when the
+ * screen is switched off.
+ */
+ boolean needSensorRunningLp() {
+ if (mSupportAutoRotation) {
+ if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
+ // If the application has explicitly requested to follow the
+ // orientation, then we need to turn the sensor on.
+ return true;
+ }
+ }
+ if ((mCarDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_CAR) ||
+ (mDeskDockEnablesAccelerometer && (mDockMode == Intent.EXTRA_DOCK_STATE_DESK
+ || mDockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
+ || mDockMode == Intent.EXTRA_DOCK_STATE_HE_DESK))) {
+ // enable accelerometer if we are docked in a dock that enables accelerometer
+ // orientation management,
+ return true;
+ }
+ if (mUserRotationMode == USER_ROTATION_LOCKED) {
+ // If the setting for using the sensor by default is enabled, then
+ // we will always leave it on. Note that the user could go to
+ // a window that forces an orientation that does not use the
+ // sensor and in theory we could turn it off... however, when next
+ // turning it on we won't have a good value for the current
+ // orientation for a little bit, which can cause orientation
+ // changes to lag, so we'd like to keep it always on. (It will
+ // still be turned off when the screen is off.)
+ return false;
+ }
+ return mSupportAutoRotation;
+ }
+
+ /*
+ * Various use cases for invoking this function
+ * screen turning off, should always disable listeners if already enabled
+ * screen turned on and current app has sensor based orientation, enable listeners
+ * if not already enabled
+ * screen turned on and current app does not have sensor orientation, disable listeners if
+ * already enabled
+ * screen turning on and current app has sensor based orientation, enable listeners if needed
+ * screen turning on and current app has nosensor based orientation, do nothing
+ */
+ void updateOrientationListenerLp() {
+ if (!mOrientationListener.canDetectOrientation()) {
+ // If sensor is turned off or nonexistent for some reason
+ return;
+ }
+ //Could have been invoked due to screen turning on or off or
+ //change of the currently visible window's orientation
+ if (localLOGV) Slog.v(TAG, "mScreenOnEarly=" + mScreenOnEarly
+ + ", mAwake=" + mAwake + ", mCurrentAppOrientation=" + mCurrentAppOrientation
+ + ", mOrientationSensorEnabled=" + mOrientationSensorEnabled);
+ boolean disable = true;
+ if (mScreenOnEarly && mAwake) {
+ if (needSensorRunningLp()) {
+ disable = false;
+ //enable listener if not already enabled
+ if (!mOrientationSensorEnabled) {
+ mOrientationListener.enable();
+ if(localLOGV) Slog.v(TAG, "Enabling listeners");
+ mOrientationSensorEnabled = true;
+ }
+ }
+ }
+ //check if sensors need to be disabled
+ if (disable && mOrientationSensorEnabled) {
+ mOrientationListener.disable();
+ if(localLOGV) Slog.v(TAG, "Disabling listeners");
+ mOrientationSensorEnabled = false;
+ }
+ }
+
+ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
+ // Hold a wake lock until the power key is released.
+ if (!mPowerKeyWakeLock.isHeld()) {
+ mPowerKeyWakeLock.acquire();
+ }
+
+ // Cancel multi-press detection timeout.
+ if (mPowerKeyPressCounter != 0) {
+ mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);
+ }
+
+ // Detect user pressing the power button in panic when an application has
+ // taken over the whole screen.
+ boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive,
+ SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags));
+ if (panic) {
+ mHandler.post(mRequestTransientNav);
+ }
+
+ // Latch power key state to detect screenshot chord.
+ if (interactive && !mScreenshotChordPowerKeyTriggered
+ && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ mScreenshotChordPowerKeyTriggered = true;
+ mScreenshotChordPowerKeyTime = event.getDownTime();
+ interceptScreenshotChord();
+ }
+
+ // Stop ringing or end call if configured to do so when power is pressed.
+ TelecomManager telecomManager = getTelecommService();
+ boolean hungUp = false;
+ if (telecomManager != null) {
+ if (telecomManager.isRinging()) {
+ // Pressing Power while there's a ringing incoming
+ // call should silence the ringer.
+ telecomManager.silenceRinger();
+ } else if ((mIncallPowerBehavior
+ & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
+ && telecomManager.isInCall() && interactive) {
+ // Otherwise, if "Power button ends call" is enabled,
+ // the Power button will hang up any current active call.
+ hungUp = telecomManager.endCall();
+ }
+ }
+
+ // If the power key has still not yet been handled, then detect short
+ // press, long press, or multi press and decide what to do.
+ mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
+ || mScreenshotChordVolumeUpKeyTriggered;
+ if (!mPowerKeyHandled) {
+ if (interactive) {
+ // When interactive, we're already awake.
+ // Wait for a long press or for the button to be released to decide what to do.
+ if (hasLongPressOnPowerBehavior()) {
+ Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg,
+ ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+ }
+ } else {
+ wakeUpFromPowerKey(event.getDownTime());
+
+ if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) {
+ Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg,
+ ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+ mBeganFromNonInteractive = true;
+ } else {
+ final int maxCount = getMaxMultiPressPowerCount();
+
+ if (maxCount <= 1) {
+ mPowerKeyHandled = true;
+ } else {
+ mBeganFromNonInteractive = true;
+ }
+ }
+ }
+ }
+ }
+
+ private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) {
+ final boolean handled = canceled || mPowerKeyHandled;
+ mScreenshotChordPowerKeyTriggered = false;
+ cancelPendingScreenshotChordAction();
+ cancelPendingPowerKeyAction();
+
+ if (!handled) {
+ // Figure out how to handle the key now that it has been released.
+ mPowerKeyPressCounter += 1;
+
+ final int maxCount = getMaxMultiPressPowerCount();
+ final long eventTime = event.getDownTime();
+ if (mPowerKeyPressCounter < maxCount) {
+ // This could be a multi-press. Wait a little bit longer to confirm.
+ // Continue holding the wake lock.
+ Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS,
+ interactive ? 1 : 0, mPowerKeyPressCounter, eventTime);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, ViewConfiguration.getDoubleTapTimeout());
+ return;
+ }
+
+ // No other actions. Handle it immediately.
+ powerPress(eventTime, interactive, mPowerKeyPressCounter);
+ }
+
+ // Done. Reset our state.
+ finishPowerKeyPress();
+ }
+
+ private void finishPowerKeyPress() {
+ mBeganFromNonInteractive = false;
+ mPowerKeyPressCounter = 0;
+ if (mPowerKeyWakeLock.isHeld()) {
+ mPowerKeyWakeLock.release();
+ }
+ }
+
+ private void cancelPendingPowerKeyAction() {
+ if (!mPowerKeyHandled) {
+ mPowerKeyHandled = true;
+ mHandler.removeMessages(MSG_POWER_LONG_PRESS);
+ }
+ }
+
+ private void powerPress(long eventTime, boolean interactive, int count) {
+ if (mScreenOnEarly && !mScreenOnFully) {
+ Slog.i(TAG, "Suppressed redundant power key press while "
+ + "already in the process of turning the screen on.");
+ return;
+ }
+
+ if (count == 2) {
+ powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
+ } else if (count == 3) {
+ powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
+ } else if (interactive && !mBeganFromNonInteractive) {
+ switch (mShortPressOnPowerBehavior) {
+ case SHORT_PRESS_POWER_NOTHING:
+ break;
+ case SHORT_PRESS_POWER_GO_TO_SLEEP:
+ mPowerManager.goToSleep(eventTime,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ break;
+ case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
+ mPowerManager.goToSleep(eventTime,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+ PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+ break;
+ case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME:
+ mPowerManager.goToSleep(eventTime,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+ PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+ launchHomeFromHotKey();
+ break;
+ case SHORT_PRESS_POWER_GO_HOME:
+ launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/);
+ break;
+ }
+ }
+ }
+
+ private void powerMultiPressAction(long eventTime, boolean interactive, int behavior) {
+ switch (behavior) {
+ case MULTI_PRESS_POWER_NOTHING:
+ break;
+ case MULTI_PRESS_POWER_THEATER_MODE:
+ if (isTheaterModeEnabled()) {
+ Slog.i(TAG, "Toggling theater mode off.");
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.THEATER_MODE_ON, 0);
+ if (!interactive) {
+ wakeUpFromPowerKey(eventTime);
+ }
+ } else {
+ Slog.i(TAG, "Toggling theater mode on.");
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.THEATER_MODE_ON, 1);
+
+ if (mGoToSleepOnButtonPressTheaterMode && interactive) {
+ mPowerManager.goToSleep(eventTime,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ }
+ }
+ break;
+ case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
+ Slog.i(TAG, "Starting brightness boost.");
+ if (!interactive) {
+ wakeUpFromPowerKey(eventTime);
+ }
+ mPowerManager.boostScreenBrightness(eventTime);
+ break;
+ }
+ }
+
+ private int getMaxMultiPressPowerCount() {
+ if (mTriplePressOnPowerBehavior != MULTI_PRESS_POWER_NOTHING) {
+ return 3;
+ }
+ if (mDoublePressOnPowerBehavior != MULTI_PRESS_POWER_NOTHING) {
+ return 2;
+ }
+ return 1;
+ }
+
+ private void powerLongPress() {
+ final int behavior = getResolvedLongPressOnPowerBehavior();
+ switch (behavior) {
+ case LONG_PRESS_POWER_NOTHING:
+ break;
+ case LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ mPowerKeyHandled = true;
+ if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
+ performAuditoryFeedbackForAccessibilityIfNeed();
+ }
+ showGlobalActionsInternal();
+ break;
+ case LONG_PRESS_POWER_SHUT_OFF:
+ case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
+ mPowerKeyHandled = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
+ mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
+ break;
+ }
+ }
+
+ private void sleepPress(KeyEvent event) {
+ switch (mShortPressOnSleepBehavior) {
+ case SHORT_PRESS_SLEEP_GO_TO_SLEEP:
+ mPowerManager.goToSleep(event.getEventTime(),
+ PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
+ break;
+ case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
+ launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/);
+ mPowerManager.goToSleep(event.getEventTime(),
+ PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
+ break;
+ }
+ }
+
+ private int getResolvedLongPressOnPowerBehavior() {
+ if (FactoryTest.isLongPressOnPowerOffEnabled()) {
+ return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
+ }
+ return mLongPressOnPowerBehavior;
+ }
+
+ private boolean hasLongPressOnPowerBehavior() {
+ return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
+ }
+
+ private void interceptScreenshotChord() {
+ if (mScreenshotChordEnabled
+ && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered
+ && !mScreenshotChordVolumeUpKeyTriggered) {
+ final long now = SystemClock.uptimeMillis();
+ if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
+ && now <= mScreenshotChordPowerKeyTime
+ + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
+ mScreenshotChordVolumeDownKeyConsumed = true;
+ cancelPendingPowerKeyAction();
+
+ mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
+ }
+ }
+ }
+
+ private long getScreenshotChordLongPressDelay() {
+ if (mKeyguardDelegate.isShowing()) {
+ // Double the time it takes to take a screenshot from the keyguard
+ return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER *
+ ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+ }
+ return ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout();
+ }
+
+ private void cancelPendingScreenshotChordAction() {
+ mHandler.removeCallbacks(mScreenshotRunnable);
+ }
+
+ private final Runnable mEndCallLongPress = new Runnable() {
+ @Override
+ public void run() {
+ mEndCallKeyHandled = true;
+ if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
+ performAuditoryFeedbackForAccessibilityIfNeed();
+ }
+ showGlobalActionsInternal();
+ }
+ };
+
+ private final Runnable mScreenshotRunnable = new Runnable() {
+ @Override
+ public void run() {
+ takeScreenshot();
+ }
+ };
+
+ @Override
+ public void showGlobalActions() {
+ mHandler.removeMessages(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS);
+ mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS);
+ }
+
+ void showGlobalActionsInternal() {
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
+ if (mGlobalActions == null) {
+ mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
+ }
+ final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
+ mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
+ if (keyguardShowing) {
+ // since it took two seconds of long press to bring this up,
+ // poke the wake lock so they have some time to see the dialog.
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ }
+ }
+
+ boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+
+ boolean isUserSetupComplete() {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+ }
+
+ private void handleShortPressOnHome() {
+ // If there's a dream running then use home to escape the dream
+ // but don't actually go home.
+ if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
+ mDreamManagerInternal.stopDream(false /*immediate*/);
+ return;
+ }
+
+ // Go home!
+ launchHomeFromHotKey();
+ }
+
+ private void handleLongPressOnHome() {
+ if (mLongPressOnHomeBehavior != LONG_PRESS_HOME_NOTHING) {
+ mHomeConsumed = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+
+ if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI) {
+ toggleRecentApps();
+ } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_ASSIST) {
+ launchAssistAction();
+ }
+ }
+ }
+
+ private void handleDoubleTapOnHome() {
+ if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
+ mHomeConsumed = true;
+ toggleRecentApps();
+ }
+ }
+
+ private final Runnable mHomeDoubleTapTimeoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mHomeDoubleTapPending) {
+ mHomeDoubleTapPending = false;
+ handleShortPressOnHome();
+ }
+ }
+ };
+
+ private boolean isRoundWindow() {
+ return mContext.getResources().getBoolean(com.android.internal.R.bool.config_windowIsRound)
+ || (Build.HARDWARE.contains("goldfish")
+ && SystemProperties.getBoolean(ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void init(Context context, IWindowManager windowManager,
+ WindowManagerFuncs windowManagerFuncs) {
+ mContext = context;
+ mWindowManager = windowManager;
+ mWindowManagerFuncs = windowManagerFuncs;
+ mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
+
+ // Init display burn-in protection
+ boolean burnInProtectionEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableBurnInProtection);
+ // Allow a system property to override this. Used by developer settings.
+ boolean burnInProtectionDevMode =
+ SystemProperties.getBoolean("persist.debug.force_burn_in", false);
+ if (burnInProtectionEnabled || burnInProtectionDevMode) {
+ final int minHorizontal;
+ final int maxHorizontal;
+ final int minVertical;
+ final int maxVertical;
+ final int maxRadius;
+ if (burnInProtectionDevMode) {
+ minHorizontal = -8;
+ maxHorizontal = 8;
+ minVertical = -8;
+ maxVertical = -4;
+ maxRadius = (isRoundWindow()) ? 6 : -1;
+ } else {
+ Resources resources = context.getResources();
+ minHorizontal = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset);
+ maxHorizontal = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset);
+ minVertical = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset);
+ maxVertical = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset);
+ maxRadius = resources.getInteger(
+ com.android.internal.R.integer.config_burnInProtectionMaxRadius);
+ }
+ mBurnInProtectionHelper = new BurnInProtectionHelper(
+ context, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
+ }
+
+ mHandler = new PolicyHandler();
+ mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
+ mOrientationListener = new MyOrientationListener(mContext, mHandler);
+ try {
+ mOrientationListener.setCurrentRotation(windowManager.getRotation());
+ } catch (RemoteException ex) { }
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mSettingsObserver.observe();
+ mShortcutManager = new ShortcutManager(context);
+ mUiMode = context.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultUiModeType);
+ mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
+ mHomeIntent.addCategory(Intent.CATEGORY_HOME);
+ mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ mCarDockIntent = new Intent(Intent.ACTION_MAIN, null);
+ mCarDockIntent.addCategory(Intent.CATEGORY_CAR_DOCK);
+ mCarDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ mDeskDockIntent = new Intent(Intent.ACTION_MAIN, null);
+ mDeskDockIntent.addCategory(Intent.CATEGORY_DESK_DOCK);
+ mDeskDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+
+ mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mBroadcastWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "PhoneWindowManager.mBroadcastWakeLock");
+ mPowerKeyWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "PhoneWindowManager.mPowerKeyWakeLock");
+ mEnableShiftMenuBugReports = "1".equals(SystemProperties.get("ro.debuggable"));
+ mSupportAutoRotation = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_supportAutoRotation);
+ mLidOpenRotation = readRotation(
+ com.android.internal.R.integer.config_lidOpenRotation);
+ mCarDockRotation = readRotation(
+ com.android.internal.R.integer.config_carDockRotation);
+ mDeskDockRotation = readRotation(
+ com.android.internal.R.integer.config_deskDockRotation);
+ mUndockedHdmiRotation = readRotation(
+ com.android.internal.R.integer.config_undockedHdmiRotation);
+ mCarDockEnablesAccelerometer = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_carDockEnablesAccelerometer);
+ mDeskDockEnablesAccelerometer = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_deskDockEnablesAccelerometer);
+ mLidKeyboardAccessibility = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_lidKeyboardAccessibility);
+ mLidNavigationAccessibility = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_lidNavigationAccessibility);
+ mLidControlsSleep = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_lidControlsSleep);
+ mTranslucentDecorEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableTranslucentDecor);
+
+ mAllowTheaterModeWakeFromKey = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromKey);
+ mAllowTheaterModeWakeFromPowerKey = mAllowTheaterModeWakeFromKey
+ || mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey);
+ mAllowTheaterModeWakeFromMotion = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion);
+ mAllowTheaterModeWakeFromMotionWhenNotDreaming = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming);
+ mAllowTheaterModeWakeFromCameraLens = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens);
+ mAllowTheaterModeWakeFromLidSwitch = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch);
+ mAllowTheaterModeWakeFromWakeGesture = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture);
+
+ mGoToSleepOnButtonPressTheaterMode = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_goToSleepOnButtonPressTheaterMode);
+
+ mSupportLongPressPowerWhenNonInteractive = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_supportLongPressPowerWhenNonInteractive);
+
+ mShortPressOnPowerBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_shortPressOnPowerBehavior);
+ mLongPressOnPowerBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnPowerBehavior);
+ mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_doublePressOnPowerBehavior);
+ mTriplePressOnPowerBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_triplePressOnPowerBehavior);
+ mShortPressOnSleepBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_shortPressOnSleepBehavior);
+
+ mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION;
+
+ readConfigurationDependentBehaviors();
+
+ mAccessibilityManager = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+
+ // register for dock events
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE);
+ filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
+ filter.addAction(UiModeManager.ACTION_ENTER_DESK_MODE);
+ filter.addAction(UiModeManager.ACTION_EXIT_DESK_MODE);
+ filter.addAction(Intent.ACTION_DOCK_EVENT);
+ Intent intent = context.registerReceiver(mDockReceiver, filter);
+ if (intent != null) {
+ // Retrieve current sticky dock event broadcast.
+ mDockMode = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ }
+
+ // register for dream-related broadcasts
+ filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_DREAMING_STARTED);
+ filter.addAction(Intent.ACTION_DREAMING_STOPPED);
+ context.registerReceiver(mDreamReceiver, filter);
+
+ // register for multiuser-relevant broadcasts
+ filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+ context.registerReceiver(mMultiuserReceiver, filter);
+
+ // monitor for system gestures
+ mSystemGestures = new SystemGesturesPointerEventListener(context,
+ new SystemGesturesPointerEventListener.Callbacks() {
+ @Override
+ public void onSwipeFromTop() {
+ if (mStatusBar != null) {
+ requestTransientBars(mStatusBar);
+ }
+ }
+ @Override
+ public void onSwipeFromBottom() {
+ if (mNavigationBar != null && mNavigationBarOnBottom) {
+ requestTransientBars(mNavigationBar);
+ }
+ }
+ @Override
+ public void onSwipeFromRight() {
+ if (mNavigationBar != null && !mNavigationBarOnBottom) {
+ requestTransientBars(mNavigationBar);
+ }
+ }
+ @Override
+ public void onDebug() {
+ // no-op
+ }
+ @Override
+ public void onDown() {
+ mOrientationListener.onTouchStart();
+ }
+ @Override
+ public void onUpOrCancel() {
+ mOrientationListener.onTouchEnd();
+ }
+ });
+ mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext);
+ mWindowManagerFuncs.registerPointerEventListener(mSystemGestures);
+
+ mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
+ mLongPressVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_longPressVibePattern);
+ mVirtualKeyVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_virtualKeyVibePattern);
+ mKeyboardTapVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_keyboardTapVibePattern);
+ mClockTickVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_clockTickVibePattern);
+ mCalendarDateVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_calendarDateVibePattern);
+ mSafeModeDisabledVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_safeModeDisabledVibePattern);
+ mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
+ com.android.internal.R.array.config_safeModeEnabledVibePattern);
+
+ mScreenshotChordEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableScreenshotChord);
+
+ mGlobalKeyManager = new GlobalKeyManager(mContext);
+
+ // Controls rotation and the like.
+ initializeHdmiState();
+
+ // Match current screen state.
+ if (!mPowerManager.isInteractive()) {
+ goingToSleep(WindowManagerPolicy.OFF_BECAUSE_OF_USER);
+ }
+
+ mWindowManagerInternal.registerAppTransitionListener(
+ mStatusBarController.getAppTransitionListener());
+ }
+
+ /**
+ * Read values from config.xml that may be overridden depending on
+ * the configuration of the device.
+ * eg. Disable long press on home goes to recents on sw600dp.
+ */
+ private void readConfigurationDependentBehaviors() {
+ mLongPressOnHomeBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnHomeBehavior);
+ if (mLongPressOnHomeBehavior < LONG_PRESS_HOME_NOTHING ||
+ mLongPressOnHomeBehavior > LONG_PRESS_HOME_ASSIST) {
+ mLongPressOnHomeBehavior = LONG_PRESS_HOME_NOTHING;
+ }
+
+ mDoubleTapOnHomeBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_doubleTapOnHomeBehavior);
+ if (mDoubleTapOnHomeBehavior < DOUBLE_TAP_HOME_NOTHING ||
+ mDoubleTapOnHomeBehavior > DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
+ mDoubleTapOnHomeBehavior = LONG_PRESS_HOME_NOTHING;
+ }
+ }
+
+ @Override
+ public void setInitialDisplaySize(Display display, int width, int height, int density) {
+ // This method might be called before the policy has been fully initialized
+ // or for other displays we don't care about.
+ if (mContext == null || display.getDisplayId() != Display.DEFAULT_DISPLAY) {
+ return;
+ }
+ mDisplay = display;
+
+ final Resources res = mContext.getResources();
+ int shortSize, longSize;
+ if (width > height) {
+ shortSize = height;
+ longSize = width;
+ mLandscapeRotation = Surface.ROTATION_0;
+ mSeascapeRotation = Surface.ROTATION_180;
+ if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
+ mPortraitRotation = Surface.ROTATION_90;
+ mUpsideDownRotation = Surface.ROTATION_270;
+ } else {
+ mPortraitRotation = Surface.ROTATION_270;
+ mUpsideDownRotation = Surface.ROTATION_90;
+ }
+ } else {
+ shortSize = width;
+ longSize = height;
+ mPortraitRotation = Surface.ROTATION_0;
+ mUpsideDownRotation = Surface.ROTATION_180;
+ if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
+ mLandscapeRotation = Surface.ROTATION_270;
+ mSeascapeRotation = Surface.ROTATION_90;
+ } else {
+ mLandscapeRotation = Surface.ROTATION_90;
+ mSeascapeRotation = Surface.ROTATION_270;
+ }
+ }
+
+ mStatusBarHeight =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+
+ // Height of the navigation bar when presented horizontally at bottom
+ mNavigationBarHeightForRotation[mPortraitRotation] =
+ mNavigationBarHeightForRotation[mUpsideDownRotation] =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
+ mNavigationBarHeightForRotation[mLandscapeRotation] =
+ mNavigationBarHeightForRotation[mSeascapeRotation] = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height_landscape);
+
+ // Width of the navigation bar when presented vertically along one side
+ mNavigationBarWidthForRotation[mPortraitRotation] =
+ mNavigationBarWidthForRotation[mUpsideDownRotation] =
+ mNavigationBarWidthForRotation[mLandscapeRotation] =
+ mNavigationBarWidthForRotation[mSeascapeRotation] =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
+
+ // SystemUI (status bar) layout policy
+ int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / density;
+ int longSizeDp = longSize * DisplayMetrics.DENSITY_DEFAULT / density;
+
+ // Allow the navigation bar to move on non-square small devices (phones).
+ mNavigationBarCanMove = width != height && shortSizeDp < 600;
+
+ mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
+ // Allow a system property to override this. Used by the emulator.
+ // See also hasNavigationBar().
+ String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
+ if ("1".equals(navBarOverride)) {
+ mHasNavigationBar = false;
+ } else if ("0".equals(navBarOverride)) {
+ mHasNavigationBar = true;
+ }
+
+ // For demo purposes, allow the rotation of the HDMI display to be controlled.
+ // By default, HDMI locks rotation to landscape.
+ if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
+ mDemoHdmiRotation = mPortraitRotation;
+ } else {
+ mDemoHdmiRotation = mLandscapeRotation;
+ }
+ mDemoHdmiRotationLock = SystemProperties.getBoolean("persist.demo.hdmirotationlock", false);
+
+ // For demo purposes, allow the rotation of the remote display to be controlled.
+ // By default, remote display locks rotation to landscape.
+ if ("portrait".equals(SystemProperties.get("persist.demo.remoterotation"))) {
+ mDemoRotation = mPortraitRotation;
+ } else {
+ mDemoRotation = mLandscapeRotation;
+ }
+ mDemoRotationLock = SystemProperties.getBoolean(
+ "persist.demo.rotationlock", false);
+
+ // Only force the default orientation if the screen is xlarge, at least 960dp x 720dp, per
+ // http://developer.android.com/guide/practices/screens_support.html#range
+ mForceDefaultOrientation = longSizeDp >= 960 && shortSizeDp >= 720 &&
+ res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) &&
+ // For debug purposes the next line turns this feature off with:
+ // $ adb shell setprop config.override_forced_orient true
+ // $ adb shell wm size reset
+ !"true".equals(SystemProperties.get("config.override_forced_orient"));
+ }
+
+ /**
+ * @return whether the navigation bar can be hidden, e.g. the device has a
+ * navigation bar and touch exploration is not enabled
+ */
+ private boolean canHideNavigationBar() {
+ return mHasNavigationBar
+ && !mAccessibilityManager.isTouchExplorationEnabled();
+ }
+
+ @Override
+ public boolean isDefaultOrientationForced() {
+ return mForceDefaultOrientation;
+ }
+
+ @Override
+ public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
+ if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mOverscanLeft = left;
+ mOverscanTop = top;
+ mOverscanRight = right;
+ mOverscanBottom = bottom;
+ }
+ }
+
+ public void updateSettings() {
+ ContentResolver resolver = mContext.getContentResolver();
+ boolean updateRotation = false;
+ synchronized (mLock) {
+ mEndcallBehavior = Settings.System.getIntForUser(resolver,
+ Settings.System.END_BUTTON_BEHAVIOR,
+ Settings.System.END_BUTTON_BEHAVIOR_DEFAULT,
+ UserHandle.USER_CURRENT);
+ mIncallPowerBehavior = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT,
+ UserHandle.USER_CURRENT);
+
+ // Configure wake gesture.
+ boolean wakeGestureEnabledSetting = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.WAKE_GESTURE_ENABLED, 0,
+ UserHandle.USER_CURRENT) != 0;
+ if (mWakeGestureEnabledSetting != wakeGestureEnabledSetting) {
+ mWakeGestureEnabledSetting = wakeGestureEnabledSetting;
+ updateWakeGestureListenerLp();
+ }
+
+ // Configure rotation lock.
+ int userRotation = Settings.System.getIntForUser(resolver,
+ Settings.System.USER_ROTATION, Surface.ROTATION_0,
+ UserHandle.USER_CURRENT);
+ if (mUserRotation != userRotation) {
+ mUserRotation = userRotation;
+ updateRotation = true;
+ }
+ int userRotationMode = Settings.System.getIntForUser(resolver,
+ Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) != 0 ?
+ WindowManagerPolicy.USER_ROTATION_FREE :
+ WindowManagerPolicy.USER_ROTATION_LOCKED;
+ if (mUserRotationMode != userRotationMode) {
+ mUserRotationMode = userRotationMode;
+ updateRotation = true;
+ updateOrientationListenerLp();
+ }
+
+ if (mSystemReady) {
+ int pointerLocation = Settings.System.getIntForUser(resolver,
+ Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT);
+ if (mPointerLocationMode != pointerLocation) {
+ mPointerLocationMode = pointerLocation;
+ mHandler.sendEmptyMessage(pointerLocation != 0 ?
+ MSG_ENABLE_POINTER_LOCATION : MSG_DISABLE_POINTER_LOCATION);
+ }
+ }
+ // use screen off timeout setting as the timeout for the lockscreen
+ mLockScreenTimeout = Settings.System.getIntForUser(resolver,
+ Settings.System.SCREEN_OFF_TIMEOUT, 0, UserHandle.USER_CURRENT);
+ String imId = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.DEFAULT_INPUT_METHOD, UserHandle.USER_CURRENT);
+ boolean hasSoftInput = imId != null && imId.length() > 0;
+ if (mHasSoftInput != hasSoftInput) {
+ mHasSoftInput = hasSoftInput;
+ updateRotation = true;
+ }
+ if (mImmersiveModeConfirmation != null) {
+ mImmersiveModeConfirmation.loadSetting(mCurrentUserId);
+ }
+ PolicyControl.reloadFromSetting(mContext);
+ }
+ if (updateRotation) {
+ updateRotation(true);
+ }
+ }
+
+ private void updateWakeGestureListenerLp() {
+ if (shouldEnableWakeGestureLp()) {
+ mWakeGestureListener.requestWakeUpTrigger();
+ } else {
+ mWakeGestureListener.cancelWakeUpTrigger();
+ }
+ }
+
+ private boolean shouldEnableWakeGestureLp() {
+ return mWakeGestureEnabledSetting && !mAwake
+ && (!mLidControlsSleep || mLidState != LID_CLOSED)
+ && mWakeGestureListener.isSupported();
+ }
+
+ private void enablePointerLocation() {
+ if (mPointerLocationView == null) {
+ mPointerLocationView = new PointerLocationView(mContext);
+ mPointerLocationView.setPrintCoords(false);
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT);
+ lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+ lp.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+ if (ActivityManager.isHighEndGfx()) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ lp.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
+ }
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle("PointerLocation");
+ WindowManager wm = (WindowManager)
+ mContext.getSystemService(Context.WINDOW_SERVICE);
+ lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+ wm.addView(mPointerLocationView, lp);
+ mWindowManagerFuncs.registerPointerEventListener(mPointerLocationView);
+ }
+ }
+
+ private void disablePointerLocation() {
+ if (mPointerLocationView != null) {
+ mWindowManagerFuncs.unregisterPointerEventListener(mPointerLocationView);
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.removeView(mPointerLocationView);
+ mPointerLocationView = null;
+ }
+ }
+
+ private int readRotation(int resID) {
+ try {
+ int rotation = mContext.getResources().getInteger(resID);
+ switch (rotation) {
+ case 0:
+ return Surface.ROTATION_0;
+ case 90:
+ return Surface.ROTATION_90;
+ case 180:
+ return Surface.ROTATION_180;
+ case 270:
+ return Surface.ROTATION_270;
+ }
+ } catch (Resources.NotFoundException e) {
+ // fall through
+ }
+ return -1;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
+ int type = attrs.type;
+
+ outAppOp[0] = AppOpsManager.OP_NONE;
+
+ if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW)
+ || (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW)
+ || (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) {
+ return WindowManagerGlobal.ADD_INVALID_TYPE;
+ }
+
+ if (type < FIRST_SYSTEM_WINDOW || type > LAST_SYSTEM_WINDOW) {
+ // Window manager will make sure these are okay.
+ return WindowManagerGlobal.ADD_OKAY;
+ }
+ String permission = null;
+ switch (type) {
+ case TYPE_TOAST:
+ // XXX right now the app process has complete control over
+ // this... should introduce a token to let the system
+ // monitor/control what they are doing.
+ outAppOp[0] = AppOpsManager.OP_TOAST_WINDOW;
+ break;
+ case TYPE_DREAM:
+ case TYPE_INPUT_METHOD:
+ case TYPE_WALLPAPER:
+ case TYPE_PRIVATE_PRESENTATION:
+ case TYPE_VOICE_INTERACTION:
+ case TYPE_ACCESSIBILITY_OVERLAY:
+ // The window manager will check these.
+ break;
+ case TYPE_PHONE:
+ case TYPE_PRIORITY_PHONE:
+ case TYPE_SYSTEM_ALERT:
+ case TYPE_SYSTEM_ERROR:
+ case TYPE_SYSTEM_OVERLAY:
+ permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
+ outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+ break;
+ default:
+ permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+ }
+ if (permission != null) {
+ if (mContext.checkCallingOrSelfPermission(permission)
+ != PackageManager.PERMISSION_GRANTED) {
+ return WindowManagerGlobal.ADD_PERMISSION_DENIED;
+ }
+ }
+ return WindowManagerGlobal.ADD_OKAY;
+ }
+
+ @Override
+ public boolean checkShowToOwnerOnly(WindowManager.LayoutParams attrs) {
+
+ // If this switch statement is modified, modify the comment in the declarations of
+ // the type in {@link WindowManager.LayoutParams} as well.
+ switch (attrs.type) {
+ default:
+ // These are the windows that by default are shown only to the user that created
+ // them. If this needs to be overridden, set
+ // {@link WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS} in
+ // {@link WindowManager.LayoutParams}. Note that permission
+ // {@link android.Manifest.permission.INTERNAL_SYSTEM_WINDOW} is required as well.
+ if ((attrs.privateFlags & PRIVATE_FLAG_SHOW_FOR_ALL_USERS) == 0) {
+ return true;
+ }
+ break;
+
+ // These are the windows that by default are shown to all users. However, to
+ // protect against spoofing, check permissions below.
+ case TYPE_APPLICATION_STARTING:
+ case TYPE_BOOT_PROGRESS:
+ case TYPE_DISPLAY_OVERLAY:
+ case TYPE_HIDDEN_NAV_CONSUMER:
+ case TYPE_KEYGUARD_SCRIM:
+ case TYPE_KEYGUARD_DIALOG:
+ case TYPE_MAGNIFICATION_OVERLAY:
+ case TYPE_NAVIGATION_BAR:
+ case TYPE_NAVIGATION_BAR_PANEL:
+ case TYPE_PHONE:
+ case TYPE_POINTER:
+ case TYPE_PRIORITY_PHONE:
+ case TYPE_SEARCH_BAR:
+ case TYPE_STATUS_BAR:
+ case TYPE_STATUS_BAR_PANEL:
+ case TYPE_STATUS_BAR_SUB_PANEL:
+ case TYPE_SYSTEM_DIALOG:
+ case TYPE_VOLUME_OVERLAY:
+ case TYPE_PRIVATE_PRESENTATION:
+ break;
+ }
+
+ // Check if third party app has set window to system window type.
+ return mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ != PackageManager.PERMISSION_GRANTED;
+ }
+
+ @Override
+ public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
+ switch (attrs.type) {
+ case TYPE_SYSTEM_OVERLAY:
+ case TYPE_SECURE_SYSTEM_OVERLAY:
+ // These types of windows can't receive input events.
+ attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+ break;
+ case TYPE_STATUS_BAR:
+
+ // If the Keyguard is in a hidden state (occluded by another window), we force to
+ // remove the wallpaper and keyguard flag so that any change in-flight after setting
+ // the keyguard as occluded wouldn't set these flags again.
+ // See {@link #processKeyguardSetHiddenResultLw}.
+ if (mKeyguardHidden) {
+ attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+ }
+ break;
+ }
+
+ if (attrs.type != TYPE_STATUS_BAR) {
+ // The status bar is the only window allowed to exhibit keyguard behavior.
+ attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+ }
+
+ if (ActivityManager.isHighEndGfx()
+ && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+ attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ }
+ }
+
+ void readLidState() {
+ mLidState = mWindowManagerFuncs.getLidState();
+ }
+
+ private void readCameraLensCoverState() {
+ mCameraLensCoverState = mWindowManagerFuncs.getCameraLensCoverState();
+ }
+
+ private boolean isHidden(int accessibilityMode) {
+ switch (accessibilityMode) {
+ case 1:
+ return mLidState == LID_CLOSED;
+ case 2:
+ return mLidState == LID_OPEN;
+ default:
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void adjustConfigurationLw(Configuration config, int keyboardPresence,
+ int navigationPresence) {
+ mHaveBuiltInKeyboard = (keyboardPresence & PRESENCE_INTERNAL) != 0;
+
+ readConfigurationDependentBehaviors();
+ readLidState();
+ applyLidSwitchState();
+
+ if (config.keyboard == Configuration.KEYBOARD_NOKEYS
+ || (keyboardPresence == PRESENCE_INTERNAL
+ && isHidden(mLidKeyboardAccessibility))) {
+ config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
+ if (!mHasSoftInput) {
+ config.keyboardHidden = Configuration.KEYBOARDHIDDEN_YES;
+ }
+ }
+
+ if (config.navigation == Configuration.NAVIGATION_NONAV
+ || (navigationPresence == PRESENCE_INTERNAL
+ && isHidden(mLidNavigationAccessibility))) {
+ config.navigationHidden = Configuration.NAVIGATIONHIDDEN_YES;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int windowTypeToLayerLw(int type) {
+ if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
+ return 2;
+ }
+ switch (type) {
+ case TYPE_PRIVATE_PRESENTATION:
+ return 2;
+ case TYPE_WALLPAPER:
+ // wallpaper is at the bottom, though the window manager may move it.
+ return 2;
+ case TYPE_PHONE:
+ return 3;
+ case TYPE_SEARCH_BAR:
+ case TYPE_VOICE_INTERACTION_STARTING:
+ return 4;
+ case TYPE_VOICE_INTERACTION:
+ // voice interaction layer is almost immediately above apps.
+ return 5;
+ case TYPE_SYSTEM_DIALOG:
+ return 6;
+ case TYPE_TOAST:
+ // toasts and the plugged-in battery thing
+ return 7;
+ case TYPE_PRIORITY_PHONE:
+ // SIM errors and unlock. Not sure if this really should be in a high layer.
+ return 8;
+ case TYPE_DREAM:
+ // used for Dreams (screensavers with TYPE_DREAM windows)
+ return 9;
+ case TYPE_SYSTEM_ALERT:
+ // like the ANR / app crashed dialogs
+ return 10;
+ case TYPE_INPUT_METHOD:
+ // on-screen keyboards and other such input method user interfaces go here.
+ return 11;
+ case TYPE_INPUT_METHOD_DIALOG:
+ // on-screen keyboards and other such input method user interfaces go here.
+ return 12;
+ case TYPE_KEYGUARD_SCRIM:
+ // the safety window that shows behind keyguard while keyguard is starting
+ return 13;
+ case TYPE_STATUS_BAR_SUB_PANEL:
+ return 14;
+ case TYPE_STATUS_BAR:
+ return 15;
+ case TYPE_STATUS_BAR_PANEL:
+ return 16;
+ case TYPE_KEYGUARD_DIALOG:
+ return 17;
+ case TYPE_VOLUME_OVERLAY:
+ // the on-screen volume indicator and controller shown when the user
+ // changes the device volume
+ return 18;
+ case TYPE_SYSTEM_OVERLAY:
+ // the on-screen volume indicator and controller shown when the user
+ // changes the device volume
+ return 19;
+ case TYPE_NAVIGATION_BAR:
+ // the navigation bar, if available, shows atop most things
+ return 20;
+ case TYPE_NAVIGATION_BAR_PANEL:
+ // some panels (e.g. search) need to show on top of the navigation bar
+ return 21;
+ case TYPE_SYSTEM_ERROR:
+ // system-level error dialogs
+ return 22;
+ case TYPE_MAGNIFICATION_OVERLAY:
+ // used to highlight the magnified portion of a display
+ return 23;
+ case TYPE_DISPLAY_OVERLAY:
+ // used to simulate secondary display devices
+ return 24;
+ case TYPE_DRAG:
+ // the drag layer: input for drag-and-drop is associated with this window,
+ // which sits above all other focusable windows
+ return 25;
+ case TYPE_ACCESSIBILITY_OVERLAY:
+ // overlay put by accessibility services to intercept user interaction
+ return 26;
+ case TYPE_SECURE_SYSTEM_OVERLAY:
+ return 27;
+ case TYPE_BOOT_PROGRESS:
+ return 28;
+ case TYPE_POINTER:
+ // the (mouse) pointer layer
+ return 29;
+ case TYPE_HIDDEN_NAV_CONSUMER:
+ return 30;
+ }
+ Log.e(TAG, "Unknown window type: " + type);
+ return 2;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int subWindowTypeToLayerLw(int type) {
+ switch (type) {
+ case TYPE_APPLICATION_PANEL:
+ case TYPE_APPLICATION_ATTACHED_DIALOG:
+ return APPLICATION_PANEL_SUBLAYER;
+ case TYPE_APPLICATION_MEDIA:
+ return APPLICATION_MEDIA_SUBLAYER;
+ case TYPE_APPLICATION_MEDIA_OVERLAY:
+ return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+ case TYPE_APPLICATION_SUB_PANEL:
+ return APPLICATION_SUB_PANEL_SUBLAYER;
+ case TYPE_APPLICATION_ABOVE_SUB_PANEL:
+ return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;
+ }
+ Log.e(TAG, "Unknown sub-window type: " + type);
+ return 0;
+ }
+
+ @Override
+ public int getMaxWallpaperLayer() {
+ return windowTypeToLayerLw(TYPE_STATUS_BAR);
+ }
+
+ @Override
+ public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation) {
+ if (mHasNavigationBar) {
+ // For a basic navigation bar, when we are in landscape mode we place
+ // the navigation bar to the side.
+ if (mNavigationBarCanMove && fullWidth > fullHeight) {
+ return fullWidth - mNavigationBarWidthForRotation[rotation];
+ }
+ }
+ return fullWidth;
+ }
+
+ @Override
+ public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation) {
+ if (mHasNavigationBar) {
+ // For a basic navigation bar, when we are in portrait mode we place
+ // the navigation bar to the bottom.
+ if (!mNavigationBarCanMove || fullWidth < fullHeight) {
+ return fullHeight - mNavigationBarHeightForRotation[rotation];
+ }
+ }
+ return fullHeight;
+ }
+
+ @Override
+ public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation) {
+ return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation);
+ }
+
+ @Override
+ public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation) {
+ // There is a separate status bar at the top of the display. We don't count that as part
+ // of the fixed decor, since it can hide; however, for purposes of configurations,
+ // we do want to exclude it since applications can't generally use that part
+ // of the screen.
+ return getNonDecorDisplayHeight(fullWidth, fullHeight, rotation) - mStatusBarHeight;
+ }
+
+ @Override
+ public boolean isForceHiding(WindowManager.LayoutParams attrs) {
+ return (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
+ (isKeyguardHostWindow(attrs) &&
+ (mKeyguardDelegate != null && mKeyguardDelegate.isShowing())) ||
+ (attrs.type == TYPE_KEYGUARD_SCRIM);
+ }
+
+ @Override
+ public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) {
+ return attrs.type == TYPE_STATUS_BAR;
+ }
+
+ @Override
+ public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs) {
+ switch (attrs.type) {
+ case TYPE_STATUS_BAR:
+ case TYPE_NAVIGATION_BAR:
+ case TYPE_WALLPAPER:
+ case TYPE_DREAM:
+ case TYPE_KEYGUARD_SCRIM:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ @Override
+ public WindowState getWinShowWhenLockedLw() {
+ return mWinShowWhenLocked;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public View addStartingWindow(IBinder appToken, String packageName, int theme,
+ CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+ int icon, int logo, int windowFlags) {
+ if (!SHOW_STARTING_ANIMATIONS) {
+ return null;
+ }
+ if (packageName == null) {
+ return null;
+ }
+
+ WindowManager wm = null;
+ View view = null;
+
+ try {
+ Context context = mContext;
+ if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "addStartingWindow " + packageName
+ + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
+ + Integer.toHexString(theme));
+ if (theme != context.getThemeResId() || labelRes != 0) {
+ try {
+ context = context.createPackageContext(packageName, 0);
+ context.setTheme(theme);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore
+ }
+ }
+
+ Window win = new PhoneWindow(context);
+ final TypedArray ta = win.getWindowStyle();
+ if (ta.getBoolean(
+ com.android.internal.R.styleable.Window_windowDisablePreview, false)
+ || ta.getBoolean(
+ com.android.internal.R.styleable.Window_windowShowWallpaper,false)) {
+ return null;
+ }
+
+ Resources r = context.getResources();
+ win.setTitle(r.getText(labelRes, nonLocalizedLabel));
+
+ win.setType(
+ WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
+ // Force the window flags: this is a fake window, so it is not really
+ // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
+ // flag because we do know that the next window will take input
+ // focus, so we want to get the IME window up on top of us right away.
+ win.setFlags(
+ windowFlags|
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ windowFlags|
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+
+ win.setDefaultIcon(icon);
+ win.setDefaultLogo(logo);
+
+ win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT);
+
+ final WindowManager.LayoutParams params = win.getAttributes();
+ params.token = appToken;
+ params.packageName = packageName;
+ params.windowAnimations = win.getWindowStyle().getResourceId(
+ com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
+ params.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+
+ if (!compatInfo.supportsScreen()) {
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+ }
+
+ params.setTitle("Starting " + packageName);
+
+ wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ view = win.getDecorView();
+
+ if (win.isFloating()) {
+ // Whoops, there is no way to display an animation/preview
+ // of such a thing! After all that work... let's skip it.
+ // (Note that we must do this here because it is in
+ // getDecorView() where the theme is evaluated... maybe
+ // we should peek the floating attribute from the theme
+ // earlier.)
+ return null;
+ }
+
+ if (DEBUG_STARTING_WINDOW) Slog.d(
+ TAG, "Adding starting window for " + packageName
+ + " / " + appToken + ": "
+ + (view.getParent() != null ? view : null));
+
+ wm.addView(view, params);
+
+ // Only return the view if it was successfully added to the
+ // window manager... which we can tell by it having a parent.
+ return view.getParent() != null ? view : null;
+ } catch (WindowManager.BadTokenException e) {
+ // ignore
+ Log.w(TAG, appToken + " already running, starting window not displayed. " +
+ e.getMessage());
+ } catch (RuntimeException e) {
+ // don't crash if something else bad happens, for example a
+ // failure loading resources because we are loading from an app
+ // on external storage that has been unmounted.
+ Log.w(TAG, appToken + " failed creating starting window", e);
+ } finally {
+ if (view != null && view.getParent() == null) {
+ Log.w(TAG, "view not successfully added to wm, removing view");
+ wm.removeViewImmediate(view);
+ }
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void removeStartingWindow(IBinder appToken, View window) {
+ if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Removing starting window for " + appToken + ": "
+ + window + " Callers=" + Debug.getCallers(4));
+
+ if (window != null) {
+ WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.removeView(window);
+ }
+ }
+
+ /**
+ * Preflight adding a window to the system.
+ *
+ * Currently enforces that three window types are singletons:
+ * <ul>
+ * <li>STATUS_BAR_TYPE</li>
+ * <li>KEYGUARD_TYPE</li>
+ * </ul>
+ *
+ * @param win The window to be added
+ * @param attrs Information about the window to be added
+ *
+ * @return If ok, WindowManagerImpl.ADD_OKAY. If too many singletons,
+ * WindowManagerImpl.ADD_MULTIPLE_SINGLETON
+ */
+ @Override
+ public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
+ switch (attrs.type) {
+ case TYPE_STATUS_BAR:
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.STATUS_BAR_SERVICE,
+ "PhoneWindowManager");
+ if (mStatusBar != null) {
+ if (mStatusBar.isAlive()) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+ }
+ }
+ mStatusBar = win;
+ mStatusBarController.setWindow(win);
+ break;
+ case TYPE_NAVIGATION_BAR:
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.STATUS_BAR_SERVICE,
+ "PhoneWindowManager");
+ if (mNavigationBar != null) {
+ if (mNavigationBar.isAlive()) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+ }
+ }
+ mNavigationBar = win;
+ mNavigationBarController.setWindow(win);
+ if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
+ break;
+ case TYPE_NAVIGATION_BAR_PANEL:
+ case TYPE_STATUS_BAR_PANEL:
+ case TYPE_STATUS_BAR_SUB_PANEL:
+ case TYPE_VOICE_INTERACTION_STARTING:
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.STATUS_BAR_SERVICE,
+ "PhoneWindowManager");
+ break;
+ case TYPE_KEYGUARD_SCRIM:
+ if (mKeyguardScrim != null) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+ }
+ mKeyguardScrim = win;
+ break;
+ }
+ return WindowManagerGlobal.ADD_OKAY;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void removeWindowLw(WindowState win) {
+ if (mStatusBar == win) {
+ mStatusBar = null;
+ mStatusBarController.setWindow(null);
+ mKeyguardDelegate.showScrim();
+ } else if (mKeyguardScrim == win) {
+ Log.v(TAG, "Removing keyguard scrim");
+ mKeyguardScrim = null;
+ } if (mNavigationBar == win) {
+ mNavigationBar = null;
+ mNavigationBarController.setWindow(null);
+ }
+ }
+
+ static final boolean PRINT_ANIM = false;
+
+ /** {@inheritDoc} */
+ @Override
+ public int selectAnimationLw(WindowState win, int transit) {
+ if (PRINT_ANIM) Log.i(TAG, "selectAnimation in " + win
+ + ": transit=" + transit);
+ if (win == mStatusBar) {
+ boolean isKeyguard = (win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
+ if (transit == TRANSIT_EXIT
+ || transit == TRANSIT_HIDE) {
+ return isKeyguard ? -1 : R.anim.dock_top_exit;
+ } else if (transit == TRANSIT_ENTER
+ || transit == TRANSIT_SHOW) {
+ return isKeyguard ? -1 : R.anim.dock_top_enter;
+ }
+ } else if (win == mNavigationBar) {
+ // This can be on either the bottom or the right.
+ if (mNavigationBarOnBottom) {
+ if (transit == TRANSIT_EXIT
+ || transit == TRANSIT_HIDE) {
+ return R.anim.dock_bottom_exit;
+ } else if (transit == TRANSIT_ENTER
+ || transit == TRANSIT_SHOW) {
+ return R.anim.dock_bottom_enter;
+ }
+ } else {
+ if (transit == TRANSIT_EXIT
+ || transit == TRANSIT_HIDE) {
+ return R.anim.dock_right_exit;
+ } else if (transit == TRANSIT_ENTER
+ || transit == TRANSIT_SHOW) {
+ return R.anim.dock_right_enter;
+ }
+ }
+ }
+
+ if (transit == TRANSIT_PREVIEW_DONE) {
+ if (win.hasAppShownWindows()) {
+ if (PRINT_ANIM) Log.i(TAG, "**** STARTING EXIT");
+ return com.android.internal.R.anim.app_starting_exit;
+ }
+ } else if (win.getAttrs().type == TYPE_DREAM && mDreamingLockscreen
+ && transit == TRANSIT_ENTER) {
+ // Special case: we are animating in a dream, while the keyguard
+ // is shown. We don't want an animation on the dream, because
+ // we need it shown immediately with the keyguard animating away
+ // to reveal it.
+ return -1;
+ }
+
+ return 0;
+ }
+
+ @Override
+ public void selectRotationAnimationLw(int anim[]) {
+ if (PRINT_ANIM) Slog.i(TAG, "selectRotationAnimation mTopFullscreen="
+ + mTopFullscreenOpaqueWindowState + " rotationAnimation="
+ + (mTopFullscreenOpaqueWindowState == null ?
+ "0" : mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation));
+ if (mTopFullscreenOpaqueWindowState != null && mTopIsFullscreen) {
+ switch (mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation) {
+ case ROTATION_ANIMATION_CROSSFADE:
+ anim[0] = R.anim.rotation_animation_xfade_exit;
+ anim[1] = R.anim.rotation_animation_enter;
+ break;
+ case ROTATION_ANIMATION_JUMPCUT:
+ anim[0] = R.anim.rotation_animation_jump_exit;
+ anim[1] = R.anim.rotation_animation_enter;
+ break;
+ case ROTATION_ANIMATION_ROTATE:
+ default:
+ anim[0] = anim[1] = 0;
+ break;
+ }
+ } else {
+ anim[0] = anim[1] = 0;
+ }
+ }
+
+ @Override
+ public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
+ boolean forceDefault) {
+ switch (exitAnimId) {
+ case R.anim.rotation_animation_xfade_exit:
+ case R.anim.rotation_animation_jump_exit:
+ // These are the only cases that matter.
+ if (forceDefault) {
+ return false;
+ }
+ int anim[] = new int[2];
+ selectRotationAnimationLw(anim);
+ return (exitAnimId == anim[0] && enterAnimId == anim[1]);
+ default:
+ return true;
+ }
+ }
+
+ @Override
+ public Animation createForceHideEnterAnimation(boolean onWallpaper,
+ boolean goingToNotificationShade) {
+ if (goingToNotificationShade) {
+ return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_behind_enter_fade_in);
+ }
+
+ AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(mContext, onWallpaper ?
+ R.anim.lock_screen_behind_enter_wallpaper :
+ R.anim.lock_screen_behind_enter);
+
+ // TODO: Use XML interpolators when we have log interpolators available in XML.
+ final List<Animation> animations = set.getAnimations();
+ for (int i = animations.size() - 1; i >= 0; --i) {
+ animations.get(i).setInterpolator(mLogDecelerateInterpolator);
+ }
+
+ return set;
+ }
+
+
+ @Override
+ public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade) {
+ if (goingToNotificationShade) {
+ return null;
+ } else {
+ return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_wallpaper_exit);
+ }
+ }
+
+ private static void awakenDreams() {
+ IDreamManager dreamManager = getDreamManager();
+ if (dreamManager != null) {
+ try {
+ dreamManager.awaken();
+ } catch (RemoteException e) {
+ // fine, stay asleep then
+ }
+ }
+ }
+
+ static IDreamManager getDreamManager() {
+ return IDreamManager.Stub.asInterface(
+ ServiceManager.checkService(DreamService.DREAM_SERVICE));
+ }
+
+ TelecomManager getTelecommService() {
+ return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ }
+
+ static IAudioService getAudioService() {
+ IAudioService audioService = IAudioService.Stub.asInterface(
+ ServiceManager.checkService(Context.AUDIO_SERVICE));
+ if (audioService == null) {
+ Log.w(TAG, "Unable to find IAudioService interface.");
+ }
+ return audioService;
+ }
+
+ boolean keyguardOn() {
+ return isKeyguardShowingAndNotOccluded() || inKeyguardRestrictedKeyInputMode();
+ }
+
+ private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = {
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
+ };
+
+ /** {@inheritDoc} */
+ @Override
+ public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
+ final boolean keyguardOn = keyguardOn();
+ final int keyCode = event.getKeyCode();
+ final int repeatCount = event.getRepeatCount();
+ final int metaState = event.getMetaState();
+ final int flags = event.getFlags();
+ final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ final boolean canceled = event.isCanceled();
+
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
+ + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed
+ + " canceled=" + canceled);
+ }
+
+ // If we think we might have a volume down & power key chord on the way
+ // but we're not sure, then tell the dispatcher to wait a little while and
+ // try again later before dispatching.
+ if (mScreenshotChordEnabled && (flags & KeyEvent.FLAG_FALLBACK) == 0) {
+ if (mScreenshotChordVolumeDownKeyTriggered && !mScreenshotChordPowerKeyTriggered) {
+ final long now = SystemClock.uptimeMillis();
+ final long timeoutTime = mScreenshotChordVolumeDownKeyTime
+ + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS;
+ if (now < timeoutTime) {
+ return timeoutTime - now;
+ }
+ }
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+ && mScreenshotChordVolumeDownKeyConsumed) {
+ if (!down) {
+ mScreenshotChordVolumeDownKeyConsumed = false;
+ }
+ return -1;
+ }
+ }
+
+ // Cancel any pending meta actions if we see any other keys being pressed between the down
+ // of the meta key and its corresponding up.
+ if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
+ mPendingMetaAction = false;
+ }
+
+ // First we always handle the home key here, so applications
+ // can never break it, although if keyguard is on, we do let
+ // it handle it, because that gives us the correct 5 second
+ // timeout.
+ if (keyCode == KeyEvent.KEYCODE_HOME) {
+
+ // If we have released the home key, and didn't do anything else
+ // while it was pressed, then it is time to go home!
+ if (!down) {
+ cancelPreloadRecentApps();
+
+ mHomePressed = false;
+ if (mHomeConsumed) {
+ mHomeConsumed = false;
+ return -1;
+ }
+
+ if (canceled) {
+ Log.i(TAG, "Ignoring HOME; event canceled.");
+ return -1;
+ }
+
+ // If an incoming call is ringing, HOME is totally disabled.
+ // (The user is already on the InCallUI at this point,
+ // and his ONLY options are to answer or reject the call.)
+ TelecomManager telecomManager = getTelecommService();
+ if (telecomManager != null && telecomManager.isRinging()) {
+ Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
+ return -1;
+ }
+
+ // Delay handling home if a double-tap is possible.
+ if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) {
+ mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case
+ mHomeDoubleTapPending = true;
+ mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
+ ViewConfiguration.getDoubleTapTimeout());
+ return -1;
+ }
+
+ handleShortPressOnHome();
+ return -1;
+ }
+
+ // If a system window has focus, then it doesn't make sense
+ // right now to interact with applications.
+ WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
+ if (attrs != null) {
+ final int type = attrs.type;
+ if (type == WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM
+ || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+ || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+ // the "app" is keyguard, so give it the key
+ return 0;
+ }
+ final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
+ for (int i=0; i<typeCount; i++) {
+ if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
+ // don't do anything, but also don't pass it to the app
+ return -1;
+ }
+ }
+ }
+
+ // Remember that home is pressed and handle special actions.
+ if (repeatCount == 0) {
+ mHomePressed = true;
+ if (mHomeDoubleTapPending) {
+ mHomeDoubleTapPending = false;
+ mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
+ handleDoubleTapOnHome();
+ } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI
+ || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
+ preloadRecentApps();
+ }
+ } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
+ if (!keyguardOn) {
+ handleLongPressOnHome();
+ }
+ }
+ return -1;
+ } else if (keyCode == KeyEvent.KEYCODE_MENU) {
+ // Hijack modified menu keys for debugging features
+ final int chordBug = KeyEvent.META_SHIFT_ON;
+
+ if (down && repeatCount == 0) {
+ if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) {
+ Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
+ null, null, null, 0, null, null);
+ return -1;
+ } else if (SHOW_PROCESSES_ON_ALT_MENU &&
+ (metaState & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON) {
+ Intent service = new Intent();
+ service.setClassName(mContext, "com.android.server.LoadAverageService");
+ ContentResolver res = mContext.getContentResolver();
+ boolean shown = Settings.Global.getInt(
+ res, Settings.Global.SHOW_PROCESSES, 0) != 0;
+ if (!shown) {
+ mContext.startService(service);
+ } else {
+ mContext.stopService(service);
+ }
+ Settings.Global.putInt(
+ res, Settings.Global.SHOW_PROCESSES, shown ? 0 : 1);
+ return -1;
+ }
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+ if (down) {
+ if (repeatCount == 0) {
+ mSearchKeyShortcutPending = true;
+ mConsumeSearchKeyUp = false;
+ }
+ } else {
+ mSearchKeyShortcutPending = false;
+ if (mConsumeSearchKeyUp) {
+ mConsumeSearchKeyUp = false;
+ return -1;
+ }
+ }
+ return 0;
+ } else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) {
+ if (!keyguardOn) {
+ if (down && repeatCount == 0) {
+ preloadRecentApps();
+ } else if (!down) {
+ toggleRecentApps();
+ }
+ }
+ return -1;
+ } else if (keyCode == KeyEvent.KEYCODE_ASSIST) {
+ if (down) {
+ if (repeatCount == 0) {
+ mAssistKeyLongPressed = false;
+ } else if (repeatCount == 1) {
+ mAssistKeyLongPressed = true;
+ if (!keyguardOn) {
+ launchAssistLongPressAction();
+ }
+ }
+ } else {
+ if (mAssistKeyLongPressed) {
+ mAssistKeyLongPressed = false;
+ } else {
+ if (!keyguardOn) {
+ launchAssistAction();
+ }
+ }
+ }
+ return -1;
+ } else if (keyCode == KeyEvent.KEYCODE_VOICE_ASSIST) {
+ if (!down) {
+ Intent voiceIntent;
+ if (!keyguardOn) {
+ voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ } else {
+ voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true);
+ }
+ startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF);
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
+ if (down && repeatCount == 0) {
+ mHandler.post(mScreenshotRunnable);
+ }
+ return -1;
+ } else if (keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP
+ || keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN) {
+ if (down) {
+ int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1;
+
+ // Disable autobrightness if it's on
+ int auto = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
+ UserHandle.USER_CURRENT_OR_SELF);
+ if (auto != 0) {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
+ UserHandle.USER_CURRENT_OR_SELF);
+ }
+
+ int min = mPowerManager.getMinimumScreenBrightnessSetting();
+ int max = mPowerManager.getMaximumScreenBrightnessSetting();
+ int step = (max - min + BRIGHTNESS_STEPS - 1) / BRIGHTNESS_STEPS * direction;
+ int brightness = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS,
+ mPowerManager.getDefaultScreenBrightnessSetting(),
+ UserHandle.USER_CURRENT_OR_SELF);
+ brightness += step;
+ // Make sure we don't go beyond the limits.
+ brightness = Math.min(max, brightness);
+ brightness = Math.max(min, brightness);
+
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, brightness,
+ UserHandle.USER_CURRENT_OR_SELF);
+ startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
+ UserHandle.CURRENT_OR_SELF);
+ }
+ return -1;
+ } else if (KeyEvent.isMetaKey(keyCode)) {
+ if (down) {
+ mPendingMetaAction = true;
+ } else if (mPendingMetaAction) {
+ launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD);
+ }
+ return -1;
+ }
+
+ // Shortcuts are invoked through Search+key, so intercept those here
+ // Any printing key that is chorded with Search should be consumed
+ // even if no shortcut was invoked. This prevents text from being
+ // inadvertently inserted when using a keyboard that has built-in macro
+ // shortcut keys (that emit Search+x) and some of them are not registered.
+ if (mSearchKeyShortcutPending) {
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ if (kcm.isPrintingKey(keyCode)) {
+ mConsumeSearchKeyUp = true;
+ mSearchKeyShortcutPending = false;
+ if (down && repeatCount == 0 && !keyguardOn) {
+ Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState);
+ if (shortcutIntent != null) {
+ shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
+ } catch (ActivityNotFoundException ex) {
+ Slog.w(TAG, "Dropping shortcut key combination because "
+ + "the activity to which it is registered was not found: "
+ + "SEARCH+" + KeyEvent.keyCodeToString(keyCode), ex);
+ }
+ } else {
+ Slog.i(TAG, "Dropping unregistered shortcut key combination: "
+ + "SEARCH+" + KeyEvent.keyCodeToString(keyCode));
+ }
+ }
+ return -1;
+ }
+ }
+
+ // Invoke shortcuts using Meta.
+ if (down && repeatCount == 0 && !keyguardOn
+ && (metaState & KeyEvent.META_META_ON) != 0) {
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ if (kcm.isPrintingKey(keyCode)) {
+ Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode,
+ metaState & ~(KeyEvent.META_META_ON
+ | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON));
+ if (shortcutIntent != null) {
+ shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ startActivityAsUser(shortcutIntent, UserHandle.CURRENT);
+ } catch (ActivityNotFoundException ex) {
+ Slog.w(TAG, "Dropping shortcut key combination because "
+ + "the activity to which it is registered was not found: "
+ + "META+" + KeyEvent.keyCodeToString(keyCode), ex);
+ }
+ return -1;
+ }
+ }
+ }
+
+ // Handle application launch keys.
+ if (down && repeatCount == 0 && !keyguardOn) {
+ String category = sApplicationLaunchKeyCategories.get(keyCode);
+ if (category != null) {
+ Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ startActivityAsUser(intent, UserHandle.CURRENT);
+ } catch (ActivityNotFoundException ex) {
+ Slog.w(TAG, "Dropping application launch key because "
+ + "the activity to which it is registered was not found: "
+ + "keyCode=" + keyCode + ", category=" + category, ex);
+ }
+ return -1;
+ }
+ }
+
+ // Display task switcher for ALT-TAB.
+ if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_TAB) {
+ if (mRecentAppsHeldModifiers == 0 && !keyguardOn) {
+ final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
+ if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)) {
+ mRecentAppsHeldModifiers = shiftlessModifiers;
+ showRecentApps(true);
+ return -1;
+ }
+ }
+ } else if (!down && mRecentAppsHeldModifiers != 0
+ && (metaState & mRecentAppsHeldModifiers) == 0) {
+ mRecentAppsHeldModifiers = 0;
+ hideRecentApps(true, false);
+ }
+
+ // Handle keyboard language switching.
+ if (down && repeatCount == 0
+ && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
+ || (keyCode == KeyEvent.KEYCODE_SPACE
+ && (metaState & KeyEvent.META_CTRL_MASK) != 0))) {
+ int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+ mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
+ return -1;
+ }
+ if (mLanguageSwitchKeyPressed && !down
+ && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH
+ || keyCode == KeyEvent.KEYCODE_SPACE)) {
+ mLanguageSwitchKeyPressed = false;
+ return -1;
+ }
+
+ if (isValidGlobalKey(keyCode)
+ && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
+ return -1;
+ }
+
+ // Reserve all the META modifier combos for system behavior
+ if ((metaState & KeyEvent.META_META_ON) != 0) {
+ return -1;
+ }
+
+ // Let the application handle the key.
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
+ // Note: This method is only called if the initial down was unhandled.
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "Unhandled key: win=" + win + ", action=" + event.getAction()
+ + ", flags=" + event.getFlags()
+ + ", keyCode=" + event.getKeyCode()
+ + ", scanCode=" + event.getScanCode()
+ + ", metaState=" + event.getMetaState()
+ + ", repeatCount=" + event.getRepeatCount()
+ + ", policyFlags=" + policyFlags);
+ }
+
+ KeyEvent fallbackEvent = null;
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ final int keyCode = event.getKeyCode();
+ final int metaState = event.getMetaState();
+ final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0;
+
+ // Check for fallback actions specified by the key character map.
+ final FallbackAction fallbackAction;
+ if (initialDown) {
+ fallbackAction = kcm.getFallbackAction(keyCode, metaState);
+ } else {
+ fallbackAction = mFallbackActions.get(keyCode);
+ }
+
+ if (fallbackAction != null) {
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode
+ + " metaState=" + Integer.toHexString(fallbackAction.metaState));
+ }
+
+ final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
+ fallbackEvent = KeyEvent.obtain(
+ event.getDownTime(), event.getEventTime(),
+ event.getAction(), fallbackAction.keyCode,
+ event.getRepeatCount(), fallbackAction.metaState,
+ event.getDeviceId(), event.getScanCode(),
+ flags, event.getSource(), null);
+
+ if (!interceptFallback(win, fallbackEvent, policyFlags)) {
+ fallbackEvent.recycle();
+ fallbackEvent = null;
+ }
+
+ if (initialDown) {
+ mFallbackActions.put(keyCode, fallbackAction);
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ mFallbackActions.remove(keyCode);
+ fallbackAction.recycle();
+ }
+ }
+ }
+
+ if (DEBUG_INPUT) {
+ if (fallbackEvent == null) {
+ Slog.d(TAG, "No fallback.");
+ } else {
+ Slog.d(TAG, "Performing fallback: " + fallbackEvent);
+ }
+ }
+ return fallbackEvent;
+ }
+
+ private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
+ int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
+ if ((actions & ACTION_PASS_TO_USER) != 0) {
+ long delayMillis = interceptKeyBeforeDispatching(
+ win, fallbackEvent, policyFlags);
+ if (delayMillis == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void launchAssistLongPressAction() {
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);
+
+ // launch the search activity
+ Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ // TODO: This only stops the factory-installed search manager.
+ // Need to formalize an API to handle others
+ SearchManager searchManager = getSearchManager();
+ if (searchManager != null) {
+ searchManager.stopSearch();
+ }
+ startActivityAsUser(intent, UserHandle.CURRENT);
+ } catch (ActivityNotFoundException e) {
+ Slog.w(TAG, "No activity to handle assist long press action.", e);
+ }
+ }
+
+ private void launchAssistAction() {
+ launchAssistAction(null);
+ }
+
+ private void launchAssistAction(String hint) {
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);
+ Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
+ if (intent != null) {
+ if (hint != null) {
+ intent.putExtra(hint, true);
+ }
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ try {
+ startActivityAsUser(intent, UserHandle.CURRENT);
+ } catch (ActivityNotFoundException e) {
+ Slog.w(TAG, "No activity to handle assist action.", e);
+ }
+ }
+ }
+
+ private void startActivityAsUser(Intent intent, UserHandle handle) {
+ if (isUserSetupComplete()) {
+ mContext.startActivityAsUser(intent, handle);
+ } else {
+ Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent);
+ }
+ }
+
+ private SearchManager getSearchManager() {
+ if (mSearchManager == null) {
+ mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+ }
+ return mSearchManager;
+ }
+
+ private void preloadRecentApps() {
+ mPreloadedRecentApps = true;
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.preloadRecentApps();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when preloading recent apps", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+
+ private void cancelPreloadRecentApps() {
+ if (mPreloadedRecentApps) {
+ mPreloadedRecentApps = false;
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.cancelPreloadRecentApps();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when cancelling recent apps preload", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+ }
+
+ private void toggleRecentApps() {
+ mPreloadedRecentApps = false; // preloading no longer needs to be canceled
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.toggleRecentApps();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when toggling recent apps", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+
+ @Override
+ public void showRecentApps() {
+ mHandler.removeMessages(MSG_DISPATCH_SHOW_RECENTS);
+ mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_RECENTS);
+ }
+
+ private void showRecentApps(boolean triggeredFromAltTab) {
+ mPreloadedRecentApps = false; // preloading no longer needs to be canceled
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.showRecentApps(triggeredFromAltTab);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when showing recent apps", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+
+ private void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHome) {
+ mPreloadedRecentApps = false; // preloading no longer needs to be canceled
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.hideRecentApps(triggeredFromAltTab, triggeredFromHome);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when closing recent apps", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+
+ void launchHomeFromHotKey() {
+ launchHomeFromHotKey(true /* awakenFromDreams */, true /*respectKeyguard*/);
+ }
+
+ /**
+ * A home key -> launch home action was detected. Take the appropriate action
+ * given the situation with the keyguard.
+ */
+ void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) {
+ if (respectKeyguard) {
+ if (isKeyguardShowingAndNotOccluded()) {
+ // don't launch home if keyguard showing
+ return;
+ }
+
+ if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
+ // when in keyguard restricted mode, must first verify unlock
+ // before launching home
+ mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
+ @Override
+ public void onKeyguardExitResult(boolean success) {
+ if (success) {
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
+ }
+ }
+ });
+ return;
+ }
+ }
+
+ // no keyguard stuff to worry about, just launch home!
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ if (mRecentsVisible) {
+ // Hide Recents and notify it to launch Home
+ if (awakenFromDreams) {
+ awakenDreams();
+ }
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ hideRecentApps(false, true);
+ } else {
+ // Otherwise, just launch Home
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
+ }
+ }
+
+ private final Runnable mClearHideNavigationFlag = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
+ // Clear flags.
+ mForceClearedSystemUiFlags &=
+ ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+ }
+ mWindowManagerFuncs.reevaluateStatusBarVisibility();
+ }
+ };
+
+ /**
+ * Input handler used while nav bar is hidden. Captures any touch on the screen,
+ * to determine when the nav bar should be shown and prevent applications from
+ * receiving those touches.
+ */
+ final class HideNavInputEventReceiver extends InputEventReceiver {
+ public HideNavInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = false;
+ try {
+ if (event instanceof MotionEvent
+ && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
+ // When the user taps down, we re-show the nav bar.
+ boolean changed = false;
+ synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
+ // Any user activity always causes us to show the
+ // navigation controls, if they had been hidden.
+ // We also clear the low profile and only content
+ // flags so that tapping on the screen will atomically
+ // restore all currently hidden screen decorations.
+ int newVal = mResettingSystemUiFlags |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LOW_PROFILE |
+ View.SYSTEM_UI_FLAG_FULLSCREEN;
+ if (mResettingSystemUiFlags != newVal) {
+ mResettingSystemUiFlags = newVal;
+ changed = true;
+ }
+ // We don't allow the system's nav bar to be hidden
+ // again for 1 second, to prevent applications from
+ // spamming us and keeping it from being shown.
+ newVal = mForceClearedSystemUiFlags |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+ if (mForceClearedSystemUiFlags != newVal) {
+ mForceClearedSystemUiFlags = newVal;
+ changed = true;
+ mHandler.postDelayed(mClearHideNavigationFlag, 1000);
+ }
+ }
+ if (changed) {
+ mWindowManagerFuncs.reevaluateStatusBarVisibility();
+ }
+ }
+ }
+ } finally {
+ finishInputEvent(event, handled);
+ }
+ }
+ }
+ final InputEventReceiver.Factory mHideNavInputEventReceiverFactory =
+ new InputEventReceiver.Factory() {
+ @Override
+ public InputEventReceiver createInputEventReceiver(
+ InputChannel inputChannel, Looper looper) {
+ return new HideNavInputEventReceiver(inputChannel, looper);
+ }
+ };
+
+ @Override
+ public int adjustSystemUiVisibilityLw(int visibility) {
+ mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
+ mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
+ mRecentsVisible = (visibility & View.RECENT_APPS_VISIBLE) > 0;
+
+ // Reset any bits in mForceClearingStatusBarVisibility that
+ // are now clear.
+ mResettingSystemUiFlags &= visibility;
+ // Clear any bits in the new visibility that are currently being
+ // force cleared, before reporting it.
+ return visibility & ~mResettingSystemUiFlags
+ & ~mForceClearedSystemUiFlags;
+ }
+
+ @Override
+ public void getInsetHintLw(WindowManager.LayoutParams attrs, Rect outContentInsets,
+ Rect outStableInsets) {
+ final int fl = PolicyControl.getWindowFlags(null, attrs);
+ final int sysuiVis = PolicyControl.getSystemUiVisibility(null, attrs);
+ final int systemUiVisibility = (sysuiVis | attrs.subtreeSystemUiVisibility);
+
+ if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))
+ == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
+ int availRight, availBottom;
+ if (canHideNavigationBar() &&
+ (systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
+ availRight = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+ availBottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ } else {
+ availRight = mRestrictedScreenLeft + mRestrictedScreenWidth;
+ availBottom = mRestrictedScreenTop + mRestrictedScreenHeight;
+ }
+ if ((systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+ if ((fl & FLAG_FULLSCREEN) != 0) {
+ outContentInsets.set(mStableFullscreenLeft, mStableFullscreenTop,
+ availRight - mStableFullscreenRight,
+ availBottom - mStableFullscreenBottom);
+ } else {
+ outContentInsets.set(mStableLeft, mStableTop,
+ availRight - mStableRight, availBottom - mStableBottom);
+ }
+ } else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {
+ outContentInsets.setEmpty();
+ } else if ((systemUiVisibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) == 0) {
+ outContentInsets.set(mCurLeft, mCurTop,
+ availRight - mCurRight, availBottom - mCurBottom);
+ } else {
+ outContentInsets.set(mCurLeft, mCurTop,
+ availRight - mCurRight, availBottom - mCurBottom);
+ }
+
+ outStableInsets.set(mStableLeft, mStableTop,
+ availRight - mStableRight, availBottom - mStableBottom);
+ return;
+ }
+ outContentInsets.setEmpty();
+ outStableInsets.setEmpty();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
+ int displayRotation) {
+ final int overscanLeft, overscanTop, overscanRight, overscanBottom;
+ if (isDefaultDisplay) {
+ switch (displayRotation) {
+ case Surface.ROTATION_90:
+ overscanLeft = mOverscanTop;
+ overscanTop = mOverscanRight;
+ overscanRight = mOverscanBottom;
+ overscanBottom = mOverscanLeft;
+ break;
+ case Surface.ROTATION_180:
+ overscanLeft = mOverscanRight;
+ overscanTop = mOverscanBottom;
+ overscanRight = mOverscanLeft;
+ overscanBottom = mOverscanTop;
+ break;
+ case Surface.ROTATION_270:
+ overscanLeft = mOverscanBottom;
+ overscanTop = mOverscanLeft;
+ overscanRight = mOverscanTop;
+ overscanBottom = mOverscanRight;
+ break;
+ default:
+ overscanLeft = mOverscanLeft;
+ overscanTop = mOverscanTop;
+ overscanRight = mOverscanRight;
+ overscanBottom = mOverscanBottom;
+ break;
+ }
+ } else {
+ overscanLeft = 0;
+ overscanTop = 0;
+ overscanRight = 0;
+ overscanBottom = 0;
+ }
+ mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
+ mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
+ mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
+ mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
+ mSystemLeft = 0;
+ mSystemTop = 0;
+ mSystemRight = displayWidth;
+ mSystemBottom = displayHeight;
+ mUnrestrictedScreenLeft = overscanLeft;
+ mUnrestrictedScreenTop = overscanTop;
+ mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;
+ mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
+ mRestrictedScreenLeft = mUnrestrictedScreenLeft;
+ mRestrictedScreenTop = mUnrestrictedScreenTop;
+ mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
+ mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
+ mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft
+ = mCurLeft = mUnrestrictedScreenLeft;
+ mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop
+ = mCurTop = mUnrestrictedScreenTop;
+ mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight
+ = mCurRight = displayWidth - overscanRight;
+ mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom
+ = mCurBottom = displayHeight - overscanBottom;
+ mDockLayer = 0x10000000;
+ mStatusBarLayer = -1;
+
+ // start with the current dock rect, which will be (0,0,displayWidth,displayHeight)
+ final Rect pf = mTmpParentFrame;
+ final Rect df = mTmpDisplayFrame;
+ final Rect of = mTmpOverscanFrame;
+ final Rect vf = mTmpVisibleFrame;
+ final Rect dcf = mTmpDecorFrame;
+ pf.left = df.left = of.left = vf.left = mDockLeft;
+ pf.top = df.top = of.top = vf.top = mDockTop;
+ pf.right = df.right = of.right = vf.right = mDockRight;
+ pf.bottom = df.bottom = of.bottom = vf.bottom = mDockBottom;
+ dcf.setEmpty(); // Decor frame N/A for system bars.
+
+ if (isDefaultDisplay) {
+ // For purposes of putting out fake window up to steal focus, we will
+ // drive nav being hidden only by whether it is requested.
+ final int sysui = mLastSystemUiFlags;
+ boolean navVisible = (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+ boolean navTranslucent = (sysui
+ & (View.NAVIGATION_BAR_TRANSLUCENT | View.SYSTEM_UI_TRANSPARENT)) != 0;
+ boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
+ boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
+ boolean navAllowedHidden = immersive || immersiveSticky;
+ navTranslucent &= !immersiveSticky; // transient trumps translucent
+ boolean isKeyguardShowing = isStatusBarKeyguard() && !mHideLockScreen;
+ if (!isKeyguardShowing) {
+ navTranslucent &= areTranslucentBarsAllowed();
+ }
+
+ // When the navigation bar isn't visible, we put up a fake
+ // input window to catch all touch events. This way we can
+ // detect when the user presses anywhere to bring back the nav
+ // bar and ensure the application doesn't see the event.
+ if (navVisible || navAllowedHidden) {
+ if (mHideNavFakeWindow != null) {
+ mHideNavFakeWindow.dismiss();
+ mHideNavFakeWindow = null;
+ }
+ } else if (mHideNavFakeWindow == null) {
+ mHideNavFakeWindow = mWindowManagerFuncs.addFakeWindow(
+ mHandler.getLooper(), mHideNavInputEventReceiverFactory,
+ "hidden nav", WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER, 0,
+ 0, false, false, true);
+ }
+
+ // For purposes of positioning and showing the nav bar, if we have
+ // decided that it can't be hidden (because of the screen aspect ratio),
+ // then take that into account.
+ navVisible |= !canHideNavigationBar();
+
+ boolean updateSysUiVisibility = false;
+ if (mNavigationBar != null) {
+ boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
+ // Force the navigation bar to its appropriate place and
+ // size. We need to do this directly, instead of relying on
+ // it to bubble up from the nav bar, because this needs to
+ // change atomically with screen rotations.
+ mNavigationBarOnBottom = (!mNavigationBarCanMove || displayWidth < displayHeight);
+ if (mNavigationBarOnBottom) {
+ // It's a system nav bar or a portrait screen; nav bar goes on bottom.
+ int top = displayHeight - overscanBottom
+ - mNavigationBarHeightForRotation[displayRotation];
+ mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom);
+ mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top;
+ if (transientNavBarShowing) {
+ mNavigationBarController.setBarShowingLw(true);
+ } else if (navVisible) {
+ mNavigationBarController.setBarShowingLw(true);
+ mDockBottom = mTmpNavigationFrame.top;
+ mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
+ mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop;
+ } else {
+ // We currently want to hide the navigation UI.
+ mNavigationBarController.setBarShowingLw(false);
+ }
+ if (navVisible && !navTranslucent && !navAllowedHidden
+ && !mNavigationBar.isAnimatingLw()
+ && !mNavigationBarController.wasRecentlyTranslucent()) {
+ // If the opaque nav bar is currently requested to be visible,
+ // and not in the process of animating on or off, then
+ // we can tell the app that it is covered by it.
+ mSystemBottom = mTmpNavigationFrame.top;
+ }
+ } else {
+ // Landscape screen; nav bar goes to the right.
+ int left = displayWidth - overscanRight
+ - mNavigationBarWidthForRotation[displayRotation];
+ mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight);
+ mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left;
+ if (transientNavBarShowing) {
+ mNavigationBarController.setBarShowingLw(true);
+ } else if (navVisible) {
+ mNavigationBarController.setBarShowingLw(true);
+ mDockRight = mTmpNavigationFrame.left;
+ mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
+ mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
+ } else {
+ // We currently want to hide the navigation UI.
+ mNavigationBarController.setBarShowingLw(false);
+ }
+ if (navVisible && !navTranslucent && !mNavigationBar.isAnimatingLw()
+ && !mNavigationBarController.wasRecentlyTranslucent()) {
+ // If the nav bar is currently requested to be visible,
+ // and not in the process of animating on or off, then
+ // we can tell the app that it is covered by it.
+ mSystemRight = mTmpNavigationFrame.left;
+ }
+ }
+ // Make sure the content and current rectangles are updated to
+ // account for the restrictions from the navigation bar.
+ mContentTop = mVoiceContentTop = mCurTop = mDockTop;
+ mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
+ mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
+ mContentRight = mVoiceContentRight = mCurRight = mDockRight;
+ mStatusBarLayer = mNavigationBar.getSurfaceLayer();
+ // And compute the final frame.
+ mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
+ mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
+ mTmpNavigationFrame);
+ if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
+ if (mNavigationBarController.checkHiddenLw()) {
+ updateSysUiVisibility = true;
+ }
+ }
+ if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
+ mDockLeft, mDockTop, mDockRight, mDockBottom));
+
+ // decide where the status bar goes ahead of time
+ if (mStatusBar != null) {
+ // apply any navigation bar insets
+ pf.left = df.left = of.left = mUnrestrictedScreenLeft;
+ pf.top = df.top = of.top = mUnrestrictedScreenTop;
+ pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
+ pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight
+ + mUnrestrictedScreenTop;
+ vf.left = mStableLeft;
+ vf.top = mStableTop;
+ vf.right = mStableRight;
+ vf.bottom = mStableBottom;
+
+ mStatusBarLayer = mStatusBar.getSurfaceLayer();
+
+ // Let the status bar determine its size.
+ mStatusBar.computeFrameLw(pf, df, vf, vf, vf, dcf, vf);
+
+ // For layout, the status bar is always at the top with our fixed height.
+ mStableTop = mUnrestrictedScreenTop + mStatusBarHeight;
+
+ boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
+ boolean statusBarTranslucent = (sysui
+ & (View.STATUS_BAR_TRANSLUCENT | View.SYSTEM_UI_TRANSPARENT)) != 0;
+ if (!isKeyguardShowing) {
+ statusBarTranslucent &= areTranslucentBarsAllowed();
+ }
+
+ // If the status bar is hidden, we don't want to cause
+ // windows behind it to scroll.
+ if (mStatusBar.isVisibleLw() && !statusBarTransient) {
+ // Status bar may go away, so the screen area it occupies
+ // is available to apps but just covering them when the
+ // status bar is visible.
+ mDockTop = mUnrestrictedScreenTop + mStatusBarHeight;
+
+ mContentTop = mVoiceContentTop = mCurTop = mDockTop;
+ mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
+ mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
+ mContentRight = mVoiceContentRight = mCurRight = mDockRight;
+
+ if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " +
+ String.format(
+ "dock=[%d,%d][%d,%d] content=[%d,%d][%d,%d] cur=[%d,%d][%d,%d]",
+ mDockLeft, mDockTop, mDockRight, mDockBottom,
+ mContentLeft, mContentTop, mContentRight, mContentBottom,
+ mCurLeft, mCurTop, mCurRight, mCurBottom));
+ }
+ if (mStatusBar.isVisibleLw() && !mStatusBar.isAnimatingLw()
+ && !statusBarTransient && !statusBarTranslucent
+ && !mStatusBarController.wasRecentlyTranslucent()) {
+ // If the opaque status bar is currently requested to be visible,
+ // and not in the process of animating on or off, then
+ // we can tell the app that it is covered by it.
+ mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight;
+ }
+ if (mStatusBarController.checkHiddenLw()) {
+ updateSysUiVisibility = true;
+ }
+ }
+ if (updateSysUiVisibility) {
+ updateSystemUiVisibilityLw();
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getSystemDecorLayerLw() {
+ if (mStatusBar != null && mStatusBar.isVisibleLw()) {
+ return mStatusBar.getSurfaceLayer();
+ }
+
+ if (mNavigationBar != null && mNavigationBar.isVisibleLw()) {
+ return mNavigationBar.getSurfaceLayer();
+ }
+
+ return 0;
+ }
+
+ @Override
+ public void getContentRectLw(Rect r) {
+ r.set(mContentLeft, mContentTop, mContentRight, mContentBottom);
+ }
+
+ void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
+ boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf) {
+ if (win.getSurfaceLayer() > mDockLayer && attached.getSurfaceLayer() < mDockLayer) {
+ // Here's a special case: if this attached window is a panel that is
+ // above the dock window, and the window it is attached to is below
+ // the dock window, then the frames we computed for the window it is
+ // attached to can not be used because the dock is effectively part
+ // of the underlying window and the attached window is floating on top
+ // of the whole thing. So, we ignore the attached window and explicitly
+ // compute the frames that would be appropriate without the dock.
+ df.left = of.left = cf.left = vf.left = mDockLeft;
+ df.top = of.top = cf.top = vf.top = mDockTop;
+ df.right = of.right = cf.right = vf.right = mDockRight;
+ df.bottom = of.bottom = cf.bottom = vf.bottom = mDockBottom;
+ } else {
+ // The effective display frame of the attached window depends on
+ // whether it is taking care of insetting its content. If not,
+ // we need to use the parent's content frame so that the entire
+ // window is positioned within that content. Otherwise we can use
+ // the overscan frame and let the attached window take care of
+ // positioning its content appropriately.
+ if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
+ // Set the content frame of the attached window to the parent's decor frame
+ // (same as content frame when IME isn't present) if specifically requested by
+ // setting {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR} flag.
+ // Otherwise, use the overscan frame.
+ cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0
+ ? attached.getContentFrameLw() : attached.getOverscanFrameLw());
+ } else {
+ // If the window is resizing, then we want to base the content
+ // frame on our attached content frame to resize... however,
+ // things can be tricky if the attached window is NOT in resize
+ // mode, in which case its content frame will be larger.
+ // Ungh. So to deal with that, make sure the content frame
+ // we end up using is not covering the IM dock.
+ cf.set(attached.getContentFrameLw());
+ if (attached.isVoiceInteraction()) {
+ if (cf.left < mVoiceContentLeft) cf.left = mVoiceContentLeft;
+ if (cf.top < mVoiceContentTop) cf.top = mVoiceContentTop;
+ if (cf.right > mVoiceContentRight) cf.right = mVoiceContentRight;
+ if (cf.bottom > mVoiceContentBottom) cf.bottom = mVoiceContentBottom;
+ } else if (attached.getSurfaceLayer() < mDockLayer) {
+ if (cf.left < mContentLeft) cf.left = mContentLeft;
+ if (cf.top < mContentTop) cf.top = mContentTop;
+ if (cf.right > mContentRight) cf.right = mContentRight;
+ if (cf.bottom > mContentBottom) cf.bottom = mContentBottom;
+ }
+ }
+ df.set(insetDecors ? attached.getDisplayFrameLw() : cf);
+ of.set(insetDecors ? attached.getOverscanFrameLw() : cf);
+ vf.set(attached.getVisibleFrameLw());
+ }
+ // The LAYOUT_IN_SCREEN flag is used to determine whether the attached
+ // window should be positioned relative to its parent or the entire
+ // screen.
+ pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0
+ ? attached.getFrameLw() : df);
+ }
+
+ private void applyStableConstraints(int sysui, int fl, Rect r) {
+ if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+ // If app is requesting a stable layout, don't let the
+ // content insets go below the stable values.
+ if ((fl & FLAG_FULLSCREEN) != 0) {
+ if (r.left < mStableFullscreenLeft) r.left = mStableFullscreenLeft;
+ if (r.top < mStableFullscreenTop) r.top = mStableFullscreenTop;
+ if (r.right > mStableFullscreenRight) r.right = mStableFullscreenRight;
+ if (r.bottom > mStableFullscreenBottom) r.bottom = mStableFullscreenBottom;
+ } else {
+ if (r.left < mStableLeft) r.left = mStableLeft;
+ if (r.top < mStableTop) r.top = mStableTop;
+ if (r.right > mStableRight) r.right = mStableRight;
+ if (r.bottom > mStableBottom) r.bottom = mStableBottom;
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void layoutWindowLw(WindowState win, WindowState attached) {
+ // we've already done the status bar
+ final WindowManager.LayoutParams attrs = win.getAttrs();
+ if ((win == mStatusBar && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) == 0) ||
+ win == mNavigationBar) {
+ return;
+ }
+ final boolean isDefaultDisplay = win.isDefaultDisplay();
+ final boolean needsToOffsetInputMethodTarget = isDefaultDisplay &&
+ (win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
+ if (needsToOffsetInputMethodTarget) {
+ if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state");
+ offsetInputMethodWindowLw(mLastInputMethodWindow);
+ }
+
+ final int fl = PolicyControl.getWindowFlags(win, attrs);
+ final int sim = attrs.softInputMode;
+ final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null);
+
+ final Rect pf = mTmpParentFrame;
+ final Rect df = mTmpDisplayFrame;
+ final Rect of = mTmpOverscanFrame;
+ final Rect cf = mTmpContentFrame;
+ final Rect vf = mTmpVisibleFrame;
+ final Rect dcf = mTmpDecorFrame;
+ final Rect sf = mTmpStableFrame;
+ dcf.setEmpty();
+
+ final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
+ && mNavigationBar != null && mNavigationBar.isVisibleLw());
+
+ final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
+
+ if (isDefaultDisplay) {
+ sf.set(mStableLeft, mStableTop, mStableRight, mStableBottom);
+ } else {
+ sf.set(mOverscanLeft, mOverscanTop, mOverscanRight, mOverscanBottom);
+ }
+
+ if (!isDefaultDisplay) {
+ if (attached != null) {
+ // If this window is attached to another, our display
+ // frame is the same as the one we are attached to.
+ setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
+ } else {
+ // Give the window full screen.
+ pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
+ pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
+ pf.right = df.right = of.right = cf.right
+ = mOverscanScreenLeft + mOverscanScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom
+ = mOverscanScreenTop + mOverscanScreenHeight;
+ }
+ } else if (attrs.type == TYPE_INPUT_METHOD) {
+ pf.left = df.left = of.left = cf.left = vf.left = mDockLeft;
+ pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
+ pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
+ // IM dock windows layout below the nav bar...
+ pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ // ...with content insets above the nav bar
+ cf.bottom = vf.bottom = mStableBottom;
+ // IM dock windows always go to the bottom of the screen.
+ attrs.gravity = Gravity.BOTTOM;
+ mDockLayer = win.getSurfaceLayer();
+ } else if (attrs.type == TYPE_VOICE_INTERACTION) {
+ pf.left = df.left = of.left = cf.left = vf.left = mUnrestrictedScreenLeft;
+ pf.top = df.top = of.top = mUnrestrictedScreenTop;
+ pf.right = df.right = of.right = cf.right = vf.right = mUnrestrictedScreenLeft
+ + mUnrestrictedScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop
+ + mUnrestrictedScreenHeight;
+ cf.bottom = vf.bottom = mStableBottom;
+ cf.top = vf.top = mStableTop;
+ } else if (win == mStatusBar && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+ pf.left = df.left = of.left = mUnrestrictedScreenLeft;
+ pf.top = df.top = of.top = mUnrestrictedScreenTop;
+ pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
+ pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight + mUnrestrictedScreenTop;
+ cf.left = vf.left = mStableLeft;
+ cf.top = vf.top = mStableTop;
+ cf.right = vf.right = mStableRight;
+ vf.bottom = mStableBottom;
+ cf.bottom = mContentBottom;
+ } else {
+
+ // Default policy decor for the default display
+ dcf.left = mSystemLeft;
+ dcf.top = mSystemTop;
+ dcf.right = mSystemRight;
+ dcf.bottom = mSystemBottom;
+ final boolean inheritTranslucentDecor = (attrs.privateFlags
+ & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
+ final boolean isAppWindow =
+ attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW &&
+ attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+ final boolean topAtRest =
+ win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
+ if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
+ if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
+ && (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0
+ && (fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) == 0
+ && (fl & WindowManager.LayoutParams.
+ FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
+ // Ensure policy decor includes status bar
+ dcf.top = mStableTop;
+ }
+ if ((fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == 0
+ && (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
+ && (fl & WindowManager.LayoutParams.
+ FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
+ // Ensure policy decor includes navigation bar
+ dcf.bottom = mStableBottom;
+ dcf.right = mStableRight;
+ }
+ }
+
+ if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))
+ == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+ + "): IN_SCREEN, INSET_DECOR");
+ // This is the case for a normal activity window: we want it
+ // to cover all of the screen space, and it can take care of
+ // moving its contents to account for screen decorations that
+ // intrude into that space.
+ if (attached != null) {
+ // If this window is attached to another, our display
+ // frame is the same as the one we are attached to.
+ setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
+ } else {
+ if (attrs.type == TYPE_STATUS_BAR_PANEL
+ || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
+ // Status bar panels are the only windows who can go on top of
+ // the status bar. They are protected by the STATUS_BAR_SERVICE
+ // permission, so they have the same privileges as the status
+ // bar itself.
+ //
+ // However, they should still dodge the navigation bar if it exists.
+
+ pf.left = df.left = of.left = hasNavBar
+ ? mDockLeft : mUnrestrictedScreenLeft;
+ pf.top = df.top = of.top = mUnrestrictedScreenTop;
+ pf.right = df.right = of.right = hasNavBar
+ ? mRestrictedScreenLeft+mRestrictedScreenWidth
+ : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+ pf.bottom = df.bottom = of.bottom = hasNavBar
+ ? mRestrictedScreenTop+mRestrictedScreenHeight
+ : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+
+ if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
+ "Laying out status bar window: (%d,%d - %d,%d)",
+ pf.left, pf.top, pf.right, pf.bottom));
+ } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
+ && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+ && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ // Asking to layout into the overscan region, so give it that pure
+ // unrestricted area.
+ pf.left = df.left = of.left = mOverscanScreenLeft;
+ pf.top = df.top = of.top = mOverscanScreenTop;
+ pf.right = df.right = of.right = mOverscanScreenLeft + mOverscanScreenWidth;
+ pf.bottom = df.bottom = of.bottom = mOverscanScreenTop
+ + mOverscanScreenHeight;
+ } else if (canHideNavigationBar()
+ && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+ && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+ && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ // Asking for layout as if the nav bar is hidden, lets the
+ // application extend into the unrestricted overscan screen area. We
+ // only do this for application windows to ensure no window that
+ // can be above the nav bar can do this.
+ pf.left = df.left = mOverscanScreenLeft;
+ pf.top = df.top = mOverscanScreenTop;
+ pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
+ pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
+ // We need to tell the app about where the frame inside the overscan
+ // is, so it can inset its content by that amount -- it didn't ask
+ // to actually extend itself into the overscan region.
+ of.left = mUnrestrictedScreenLeft;
+ of.top = mUnrestrictedScreenTop;
+ of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+ of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ } else {
+ pf.left = df.left = mRestrictedOverscanScreenLeft;
+ pf.top = df.top = mRestrictedOverscanScreenTop;
+ pf.right = df.right = mRestrictedOverscanScreenLeft
+ + mRestrictedOverscanScreenWidth;
+ pf.bottom = df.bottom = mRestrictedOverscanScreenTop
+ + mRestrictedOverscanScreenHeight;
+ // We need to tell the app about where the frame inside the overscan
+ // is, so it can inset its content by that amount -- it didn't ask
+ // to actually extend itself into the overscan region.
+ of.left = mUnrestrictedScreenLeft;
+ of.top = mUnrestrictedScreenTop;
+ of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+ of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ }
+
+ if ((fl & FLAG_FULLSCREEN) == 0) {
+ if (win.isVoiceInteraction()) {
+ cf.left = mVoiceContentLeft;
+ cf.top = mVoiceContentTop;
+ cf.right = mVoiceContentRight;
+ cf.bottom = mVoiceContentBottom;
+ } else {
+ if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
+ cf.left = mDockLeft;
+ cf.top = mDockTop;
+ cf.right = mDockRight;
+ cf.bottom = mDockBottom;
+ } else {
+ cf.left = mContentLeft;
+ cf.top = mContentTop;
+ cf.right = mContentRight;
+ cf.bottom = mContentBottom;
+ }
+ }
+ } else {
+ // Full screen windows are always given a layout that is as if the
+ // status bar and other transient decors are gone. This is to avoid
+ // bad states when moving from a window that is not hding the
+ // status bar to one that is.
+ cf.left = mRestrictedScreenLeft;
+ cf.top = mRestrictedScreenTop;
+ cf.right = mRestrictedScreenLeft + mRestrictedScreenWidth;
+ cf.bottom = mRestrictedScreenTop + mRestrictedScreenHeight;
+ }
+ applyStableConstraints(sysUiFl, fl, cf);
+ if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+ vf.left = mCurLeft;
+ vf.top = mCurTop;
+ vf.right = mCurRight;
+ vf.bottom = mCurBottom;
+ } else {
+ vf.set(cf);
+ }
+ }
+ } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
+ & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
+ "): IN_SCREEN");
+ // A window that has requested to fill the entire screen just
+ // gets everything, period.
+ if (attrs.type == TYPE_STATUS_BAR_PANEL
+ || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
+ pf.left = df.left = of.left = cf.left = hasNavBar
+ ? mDockLeft : mUnrestrictedScreenLeft;
+ pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
+ pf.right = df.right = of.right = cf.right = hasNavBar
+ ? mRestrictedScreenLeft+mRestrictedScreenWidth
+ : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom = hasNavBar
+ ? mRestrictedScreenTop+mRestrictedScreenHeight
+ : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
+ "Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
+ pf.left, pf.top, pf.right, pf.bottom));
+ } else if (attrs.type == TYPE_NAVIGATION_BAR
+ || attrs.type == TYPE_NAVIGATION_BAR_PANEL) {
+ // The navigation bar has Real Ultimate Power.
+ pf.left = df.left = of.left = mUnrestrictedScreenLeft;
+ pf.top = df.top = of.top = mUnrestrictedScreenTop;
+ pf.right = df.right = of.right = mUnrestrictedScreenLeft
+ + mUnrestrictedScreenWidth;
+ pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop
+ + mUnrestrictedScreenHeight;
+ if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
+ "Laying out navigation bar window: (%d,%d - %d,%d)",
+ pf.left, pf.top, pf.right, pf.bottom));
+ } else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY
+ || attrs.type == TYPE_BOOT_PROGRESS)
+ && ((fl & FLAG_FULLSCREEN) != 0)) {
+ // Fullscreen secure system overlays get what they ask for.
+ pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
+ pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
+ pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
+ + mOverscanScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
+ + mOverscanScreenHeight;
+ } else if (attrs.type == TYPE_BOOT_PROGRESS) {
+ // Boot progress screen always covers entire display.
+ pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
+ pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
+ pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
+ + mOverscanScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
+ + mOverscanScreenHeight;
+ } else if (attrs.type == TYPE_WALLPAPER) {
+ // The wallpaper also has Real Ultimate Power, but we want to tell
+ // it about the overscan area.
+ pf.left = df.left = mOverscanScreenLeft;
+ pf.top = df.top = mOverscanScreenTop;
+ pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
+ pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
+ of.left = cf.left = mUnrestrictedScreenLeft;
+ of.top = cf.top = mUnrestrictedScreenTop;
+ of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+ of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+ } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
+ && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+ && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ // Asking to layout into the overscan region, so give it that pure
+ // unrestricted area.
+ pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
+ pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
+ pf.right = df.right = of.right = cf.right
+ = mOverscanScreenLeft + mOverscanScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom
+ = mOverscanScreenTop + mOverscanScreenHeight;
+ } else if (canHideNavigationBar()
+ && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+ && (attrs.type == TYPE_STATUS_BAR
+ || attrs.type == TYPE_TOAST
+ || attrs.type == TYPE_VOICE_INTERACTION_STARTING
+ || (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+ && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
+ // Asking for layout as if the nav bar is hidden, lets the
+ // application extend into the unrestricted screen area. We
+ // only do this for application windows (or toasts) to ensure no window that
+ // can be above the nav bar can do this.
+ // XXX This assumes that an app asking for this will also
+ // ask for layout in only content. We can't currently figure out
+ // what the screen would be if only laying out to hide the nav bar.
+ pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
+ pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
+ pf.right = df.right = of.right = cf.right = mUnrestrictedScreenLeft
+ + mUnrestrictedScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop
+ + mUnrestrictedScreenHeight;
+ } else {
+ pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
+ pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
+ pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
+ + mRestrictedScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
+ + mRestrictedScreenHeight;
+ }
+
+ applyStableConstraints(sysUiFl, fl, cf);
+
+ if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+ vf.left = mCurLeft;
+ vf.top = mCurTop;
+ vf.right = mCurRight;
+ vf.bottom = mCurBottom;
+ } else {
+ vf.set(cf);
+ }
+ } else if (attached != null) {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
+ "): attached to " + attached);
+ // A child window should be placed inside of the same visible
+ // frame that its parent had.
+ setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf);
+ } else {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
+ "): normal window");
+ // Otherwise, a normal window must be placed inside the content
+ // of all screen decorations.
+ if (attrs.type == TYPE_STATUS_BAR_PANEL) {
+ // Status bar panels are the only windows who can go on top of
+ // the status bar. They are protected by the STATUS_BAR_SERVICE
+ // permission, so they have the same privileges as the status
+ // bar itself.
+ pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
+ pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
+ pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
+ + mRestrictedScreenWidth;
+ pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
+ + mRestrictedScreenHeight;
+ } else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT
+ || attrs.type == TYPE_VOLUME_OVERLAY) {
+ // These dialogs are stable to interim decor changes.
+ pf.left = df.left = of.left = cf.left = mStableLeft;
+ pf.top = df.top = of.top = cf.top = mStableTop;
+ pf.right = df.right = of.right = cf.right = mStableRight;
+ pf.bottom = df.bottom = of.bottom = cf.bottom = mStableBottom;
+ } else {
+ pf.left = mContentLeft;
+ pf.top = mContentTop;
+ pf.right = mContentRight;
+ pf.bottom = mContentBottom;
+ if (win.isVoiceInteraction()) {
+ df.left = of.left = cf.left = mVoiceContentLeft;
+ df.top = of.top = cf.top = mVoiceContentTop;
+ df.right = of.right = cf.right = mVoiceContentRight;
+ df.bottom = of.bottom = cf.bottom = mVoiceContentBottom;
+ } else if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
+ df.left = of.left = cf.left = mDockLeft;
+ df.top = of.top = cf.top = mDockTop;
+ df.right = of.right = cf.right = mDockRight;
+ df.bottom = of.bottom = cf.bottom = mDockBottom;
+ } else {
+ df.left = of.left = cf.left = mContentLeft;
+ df.top = of.top = cf.top = mContentTop;
+ df.right = of.right = cf.right = mContentRight;
+ df.bottom = of.bottom = cf.bottom = mContentBottom;
+ }
+ if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+ vf.left = mCurLeft;
+ vf.top = mCurTop;
+ vf.right = mCurRight;
+ vf.bottom = mCurBottom;
+ } else {
+ vf.set(cf);
+ }
+ }
+ }
+ }
+
+ // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
+ if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR) {
+ df.left = df.top = -10000;
+ df.right = df.bottom = 10000;
+ if (attrs.type != TYPE_WALLPAPER) {
+ of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
+ of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
+ }
+ }
+
+ if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
+ + ": sim=#" + Integer.toHexString(sim)
+ + " attach=" + attached + " type=" + attrs.type
+ + String.format(" flags=0x%08x", fl)
+ + " pf=" + pf.toShortString() + " df=" + df.toShortString()
+ + " of=" + of.toShortString()
+ + " cf=" + cf.toShortString() + " vf=" + vf.toShortString()
+ + " dcf=" + dcf.toShortString()
+ + " sf=" + sf.toShortString());
+
+ win.computeFrameLw(pf, df, of, cf, vf, dcf, sf);
+
+ // Dock windows carve out the bottom of the screen, so normal windows
+ // can't appear underneath them.
+ if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleOrBehindKeyguardLw()
+ && !win.getGivenInsetsPendingLw()) {
+ setLastInputMethodWindowLw(null, null);
+ offsetInputMethodWindowLw(win);
+ }
+ if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleOrBehindKeyguardLw()
+ && !win.getGivenInsetsPendingLw()) {
+ offsetVoiceInputWindowLw(win);
+ }
+ }
+
+ private void offsetInputMethodWindowLw(WindowState win) {
+ int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
+ top += win.getGivenContentInsetsLw().top;
+ if (mContentBottom > top) {
+ mContentBottom = top;
+ }
+ if (mVoiceContentBottom > top) {
+ mVoiceContentBottom = top;
+ }
+ top = win.getVisibleFrameLw().top;
+ top += win.getGivenVisibleInsetsLw().top;
+ if (mCurBottom > top) {
+ mCurBottom = top;
+ }
+ if (DEBUG_LAYOUT) Slog.v(TAG, "Input method: mDockBottom="
+ + mDockBottom + " mContentBottom="
+ + mContentBottom + " mCurBottom=" + mCurBottom);
+ }
+
+ private void offsetVoiceInputWindowLw(WindowState win) {
+ int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
+ top += win.getGivenContentInsetsLw().top;
+ if (mVoiceContentBottom > top) {
+ mVoiceContentBottom = top;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void finishLayoutLw() {
+ return;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) {
+ mTopFullscreenOpaqueWindowState = null;
+ mTopFullscreenOpaqueOrDimmingWindowState = null;
+ mAppsToBeHidden.clear();
+ mAppsThatDismissKeyguard.clear();
+ mForceStatusBar = false;
+ mForceStatusBarFromKeyguard = false;
+ mForcingShowNavBar = false;
+ mForcingShowNavBarLayer = -1;
+
+ mHideLockScreen = false;
+ mAllowLockscreenWhenOn = false;
+ mDismissKeyguard = DISMISS_KEYGUARD_NONE;
+ mShowingLockscreen = false;
+ mShowingDream = false;
+ mWinShowWhenLocked = null;
+ mKeyguardSecure = isKeyguardSecure();
+ mKeyguardSecureIncludingHidden = mKeyguardSecure
+ && (mKeyguardDelegate != null && mKeyguardDelegate.isShowing());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs) {
+ if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": isVisibleOrBehindKeyguardLw="
+ + win.isVisibleOrBehindKeyguardLw());
+ final int fl = PolicyControl.getWindowFlags(win, attrs);
+ if (mTopFullscreenOpaqueWindowState == null
+ && win.isVisibleLw() && attrs.type == TYPE_INPUT_METHOD) {
+ mForcingShowNavBar = true;
+ mForcingShowNavBarLayer = win.getSurfaceLayer();
+ }
+ if (attrs.type == TYPE_STATUS_BAR && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+ mForceStatusBarFromKeyguard = true;
+ }
+ if (mTopFullscreenOpaqueWindowState == null &&
+ win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw()) {
+ if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
+ if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+ mForceStatusBarFromKeyguard = true;
+ } else {
+ mForceStatusBar = true;
+ }
+ }
+ if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+ mShowingLockscreen = true;
+ }
+ boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
+ && attrs.type < FIRST_SYSTEM_WINDOW;
+ if (attrs.type == TYPE_DREAM) {
+ // If the lockscreen was showing when the dream started then wait
+ // for the dream to draw before hiding the lockscreen.
+ if (!mDreamingLockscreen
+ || (win.isVisibleLw() && win.hasDrawnLw())) {
+ mShowingDream = true;
+ appWindow = true;
+ }
+ }
+
+ final boolean showWhenLocked = (fl & FLAG_SHOW_WHEN_LOCKED) != 0;
+ final boolean dismissKeyguard = (fl & FLAG_DISMISS_KEYGUARD) != 0;
+ if (appWindow) {
+ final IApplicationToken appToken = win.getAppToken();
+ if (showWhenLocked) {
+ // Remove any previous windows with the same appToken.
+ mAppsToBeHidden.remove(appToken);
+ mAppsThatDismissKeyguard.remove(appToken);
+ if (mAppsToBeHidden.isEmpty()) {
+ if (dismissKeyguard && !mKeyguardSecure) {
+ mAppsThatDismissKeyguard.add(appToken);
+ } else {
+ mWinShowWhenLocked = win;
+ mHideLockScreen = true;
+ mForceStatusBarFromKeyguard = false;
+ }
+ }
+ } else if (dismissKeyguard) {
+ if (mKeyguardSecure) {
+ mAppsToBeHidden.add(appToken);
+ } else {
+ mAppsToBeHidden.remove(appToken);
+ }
+ mAppsThatDismissKeyguard.add(appToken);
+ } else {
+ mAppsToBeHidden.add(appToken);
+ }
+ if (attrs.x == 0 && attrs.y == 0
+ && attrs.width == WindowManager.LayoutParams.MATCH_PARENT
+ && attrs.height == WindowManager.LayoutParams.MATCH_PARENT) {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win);
+ mTopFullscreenOpaqueWindowState = win;
+ if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
+ mTopFullscreenOpaqueOrDimmingWindowState = win;
+ }
+ if (!mAppsThatDismissKeyguard.isEmpty() &&
+ mDismissKeyguard == DISMISS_KEYGUARD_NONE) {
+ if (DEBUG_LAYOUT) Slog.v(TAG,
+ "Setting mDismissKeyguard true by win " + win);
+ mDismissKeyguard = mWinDismissingKeyguard == win ?
+ DISMISS_KEYGUARD_CONTINUE : DISMISS_KEYGUARD_START;
+ mWinDismissingKeyguard = win;
+ mForceStatusBarFromKeyguard = mShowingLockscreen && mKeyguardSecure;
+ } else if (mAppsToBeHidden.isEmpty() && showWhenLocked) {
+ if (DEBUG_LAYOUT) Slog.v(TAG,
+ "Setting mHideLockScreen to true by win " + win);
+ mHideLockScreen = true;
+ mForceStatusBarFromKeyguard = false;
+ }
+ if ((fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
+ mAllowLockscreenWhenOn = true;
+ }
+ }
+
+ if (mWinShowWhenLocked != null &&
+ mWinShowWhenLocked.getAppToken() != win.getAppToken()) {
+ win.hideLw(false);
+ }
+ }
+ }
+ if (mTopFullscreenOpaqueOrDimmingWindowState == null
+ && win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw()
+ && win.isDimming()) {
+ mTopFullscreenOpaqueOrDimmingWindowState = win;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int finishPostLayoutPolicyLw() {
+ if (mWinShowWhenLocked != null && mTopFullscreenOpaqueWindowState != null &&
+ mWinShowWhenLocked.getAppToken() != mTopFullscreenOpaqueWindowState.getAppToken()
+ && isKeyguardLocked()) {
+ // A dialog is dismissing the keyguard. Put the wallpaper behind it and hide the
+ // fullscreen window.
+ // TODO: Make sure FLAG_SHOW_WALLPAPER is restored when dialog is dismissed. Or not.
+ mWinShowWhenLocked.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
+ mTopFullscreenOpaqueWindowState.hideLw(false);
+ mTopFullscreenOpaqueWindowState = mWinShowWhenLocked;
+ }
+
+ int changes = 0;
+ boolean topIsFullscreen = false;
+
+ final WindowManager.LayoutParams lp = (mTopFullscreenOpaqueWindowState != null)
+ ? mTopFullscreenOpaqueWindowState.getAttrs()
+ : null;
+
+ // If we are not currently showing a dream then remember the current
+ // lockscreen state. We will use this to determine whether the dream
+ // started while the lockscreen was showing and remember this state
+ // while the dream is showing.
+ if (!mShowingDream) {
+ mDreamingLockscreen = mShowingLockscreen;
+ if (mDreamingSleepTokenNeeded) {
+ mDreamingSleepTokenNeeded = false;
+ mHandler.obtainMessage(MSG_UPDATE_DREAMING_SLEEP_TOKEN, 0, 1).sendToTarget();
+ }
+ } else {
+ if (!mDreamingSleepTokenNeeded) {
+ mDreamingSleepTokenNeeded = true;
+ mHandler.obtainMessage(MSG_UPDATE_DREAMING_SLEEP_TOKEN, 1, 1).sendToTarget();
+ }
+ }
+
+ if (mStatusBar != null) {
+ if (DEBUG_LAYOUT) Slog.i(TAG, "force=" + mForceStatusBar
+ + " forcefkg=" + mForceStatusBarFromKeyguard
+ + " top=" + mTopFullscreenOpaqueWindowState);
+ if (mForceStatusBar || mForceStatusBarFromKeyguard) {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "Showing status bar: forced");
+ if (mStatusBarController.setBarShowingLw(true)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT;
+ }
+ // Maintain fullscreen layout until incoming animation is complete.
+ topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw();
+ // Transient status bar on the lockscreen is not allowed
+ if (mForceStatusBarFromKeyguard && mStatusBarController.isTransientShowing()) {
+ mStatusBarController.updateVisibilityLw(false /*transientAllowed*/,
+ mLastSystemUiFlags, mLastSystemUiFlags);
+ }
+ } else if (mTopFullscreenOpaqueWindowState != null) {
+ final int fl = PolicyControl.getWindowFlags(null, lp);
+ if (localLOGV) {
+ Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
+ + " shown frame: " + mTopFullscreenOpaqueWindowState.getShownFrameLw());
+ Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
+ + " lp.flags=0x" + Integer.toHexString(fl));
+ }
+ topIsFullscreen = (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0
+ || (mLastSystemUiFlags & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
+ // The subtle difference between the window for mTopFullscreenOpaqueWindowState
+ // and mTopIsFullscreen is that mTopIsFullscreen is set only if the window
+ // has the FLAG_FULLSCREEN set. Not sure if there is another way that to be the
+ // case though.
+ if (mStatusBarController.isTransientShowing()) {
+ if (mStatusBarController.setBarShowingLw(true)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT;
+ }
+ } else if (topIsFullscreen) {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar");
+ if (mStatusBarController.setBarShowingLw(false)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT;
+ } else {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar already hiding");
+ }
+ } else {
+ if (DEBUG_LAYOUT) Slog.v(TAG, "** SHOWING status bar: top is not fullscreen");
+ if (mStatusBarController.setBarShowingLw(true)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT;
+ }
+ }
+ }
+ }
+
+ if (mTopIsFullscreen != topIsFullscreen) {
+ if (!topIsFullscreen) {
+ // Force another layout when status bar becomes fully shown.
+ changes |= FINISH_LAYOUT_REDO_LAYOUT;
+ }
+ mTopIsFullscreen = topIsFullscreen;
+ }
+
+ // Hide the key guard if a visible window explicitly specifies that it wants to be
+ // displayed when the screen is locked.
+ if (mKeyguardDelegate != null && mStatusBar != null) {
+ if (localLOGV) Slog.v(TAG, "finishPostLayoutPolicyLw: mHideKeyguard="
+ + mHideLockScreen);
+ if (mDismissKeyguard != DISMISS_KEYGUARD_NONE && !mKeyguardSecure) {
+ mKeyguardHidden = true;
+ if (setKeyguardOccludedLw(true)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT
+ | FINISH_LAYOUT_REDO_CONFIG
+ | FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ if (mKeyguardDelegate.isShowing()) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardDelegate.keyguardDone(false, false);
+ }
+ });
+ }
+ } else if (mHideLockScreen) {
+ mKeyguardHidden = true;
+ if (setKeyguardOccludedLw(true)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT
+ | FINISH_LAYOUT_REDO_CONFIG
+ | FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ } else if (mDismissKeyguard != DISMISS_KEYGUARD_NONE) {
+ // This is the case of keyguard isSecure() and not mHideLockScreen.
+ if (mDismissKeyguard == DISMISS_KEYGUARD_START) {
+ // Only launch the next keyguard unlock window once per window.
+ mKeyguardHidden = false;
+ if (setKeyguardOccludedLw(false)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT
+ | FINISH_LAYOUT_REDO_CONFIG
+ | FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardDelegate.dismiss();
+ }
+ });
+ }
+ } else {
+ mWinDismissingKeyguard = null;
+ mKeyguardHidden = false;
+ if (setKeyguardOccludedLw(false)) {
+ changes |= FINISH_LAYOUT_REDO_LAYOUT
+ | FINISH_LAYOUT_REDO_CONFIG
+ | FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ }
+ }
+
+ if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) {
+ // If the navigation bar has been hidden or shown, we need to do another
+ // layout pass to update that window.
+ changes |= FINISH_LAYOUT_REDO_LAYOUT;
+ }
+
+ // update since mAllowLockscreenWhenOn might have changed
+ updateLockScreenTimeout();
+ return changes;
+ }
+
+ /**
+ * Updates the occluded state of the Keyguard.
+ *
+ * @return Whether the flags have changed and we have to redo the layout.
+ */
+ private boolean setKeyguardOccludedLw(boolean isOccluded) {
+ boolean wasOccluded = mKeyguardOccluded;
+ boolean showing = mKeyguardDelegate.isShowing();
+ if (wasOccluded && !isOccluded && showing) {
+ mKeyguardOccluded = false;
+ mKeyguardDelegate.setOccluded(false);
+ mStatusBar.getAttrs().privateFlags |= PRIVATE_FLAG_KEYGUARD;
+ mStatusBar.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
+ return true;
+ } else if (!wasOccluded && isOccluded && showing) {
+ mKeyguardOccluded = true;
+ mKeyguardDelegate.setOccluded(true);
+ mStatusBar.getAttrs().privateFlags &= ~PRIVATE_FLAG_KEYGUARD;
+ mStatusBar.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean isStatusBarKeyguard() {
+ return mStatusBar != null
+ && (mStatusBar.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
+ }
+
+ @Override
+ public boolean allowAppAnimationsLw() {
+ if (isStatusBarKeyguard() || mShowingDream) {
+ // If keyguard or dreams is currently visible, no reason to animate behind it.
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int focusChangedLw(WindowState lastFocus, WindowState newFocus) {
+ mFocusedWindow = newFocus;
+ if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) {
+ // If the navigation bar has been hidden or shown, we need to do another
+ // layout pass to update that window.
+ return FINISH_LAYOUT_REDO_LAYOUT;
+ }
+ return 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+ // lid changed state
+ final int newLidState = lidOpen ? LID_OPEN : LID_CLOSED;
+ if (newLidState == mLidState) {
+ return;
+ }
+
+ mLidState = newLidState;
+ applyLidSwitchState();
+ updateRotation(true);
+
+ if (lidOpen) {
+ wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromLidSwitch);
+ } else if (!mLidControlsSleep) {
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ }
+ }
+
+ @Override
+ public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) {
+ int lensCoverState = lensCovered ? CAMERA_LENS_COVERED : CAMERA_LENS_UNCOVERED;
+ if (mCameraLensCoverState == lensCoverState) {
+ return;
+ }
+ if (mCameraLensCoverState == CAMERA_LENS_COVERED &&
+ lensCoverState == CAMERA_LENS_UNCOVERED) {
+ Intent intent;
+ final boolean keyguardActive = mKeyguardDelegate == null ? false :
+ mKeyguardDelegate.isShowing();
+ if (keyguardActive) {
+ intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
+ } else {
+ intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
+ }
+ wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromCameraLens);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ }
+ mCameraLensCoverState = lensCoverState;
+ }
+
+ void setHdmiPlugged(boolean plugged) {
+ if (mHdmiPlugged != plugged) {
+ mHdmiPlugged = plugged;
+ updateRotation(true, true);
+ Intent intent = new Intent(ACTION_HDMI_PLUGGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(EXTRA_HDMI_PLUGGED_STATE, plugged);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+ }
+
+ void initializeHdmiState() {
+ boolean plugged = false;
+ // watch for HDMI plug messages if the hdmi switch exists
+ if (new File("/sys/devices/virtual/switch/hdmi/state").exists()) {
+ mHDMIObserver.startObserving("DEVPATH=/devices/virtual/switch/hdmi");
+
+ final String filename = "/sys/class/switch/hdmi/state";
+ FileReader reader = null;
+ try {
+ reader = new FileReader(filename);
+ char[] buf = new char[15];
+ int n = reader.read(buf);
+ if (n > 1) {
+ plugged = 0 != Integer.parseInt(new String(buf, 0, n-1));
+ }
+ } catch (IOException ex) {
+ Slog.w(TAG, "Couldn't read hdmi state from " + filename + ": " + ex);
+ } catch (NumberFormatException ex) {
+ Slog.w(TAG, "Couldn't read hdmi state from " + filename + ": " + ex);
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException ex) {
+ }
+ }
+ }
+ }
+ // This dance forces the code in setHdmiPlugged to run.
+ // Always do this so the sticky intent is stuck (to false) if there is no hdmi.
+ mHdmiPlugged = !plugged;
+ setHdmiPlugged(!mHdmiPlugged);
+ }
+
+ final Object mScreenshotLock = new Object();
+ ServiceConnection mScreenshotConnection = null;
+
+ final Runnable mScreenshotTimeout = new Runnable() {
+ @Override public void run() {
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection != null) {
+ mContext.unbindService(mScreenshotConnection);
+ mScreenshotConnection = null;
+ }
+ }
+ }
+ };
+
+ // Assume this is called from the Handler thread.
+ private void takeScreenshot() {
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection != null) {
+ return;
+ }
+ ComponentName cn = new ComponentName("com.android.systemui",
+ "com.android.systemui.screenshot.TakeScreenshotService");
+ Intent intent = new Intent();
+ intent.setComponent(cn);
+ ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection != this) {
+ return;
+ }
+ Messenger messenger = new Messenger(service);
+ Message msg = Message.obtain(null, 1);
+ final ServiceConnection myConn = this;
+ Handler h = new Handler(mHandler.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection == myConn) {
+ mContext.unbindService(mScreenshotConnection);
+ mScreenshotConnection = null;
+ mHandler.removeCallbacks(mScreenshotTimeout);
+ }
+ }
+ }
+ };
+ msg.replyTo = new Messenger(h);
+ msg.arg1 = msg.arg2 = 0;
+ if (mStatusBar != null && mStatusBar.isVisibleLw())
+ msg.arg1 = 1;
+ if (mNavigationBar != null && mNavigationBar.isVisibleLw())
+ msg.arg2 = 1;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+ };
+ if (mContext.bindServiceAsUser(
+ intent, conn, Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
+ mScreenshotConnection = conn;
+ mHandler.postDelayed(mScreenshotTimeout, 10000);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
+ if (!mSystemBooted) {
+ // If we have not yet booted, don't let key events do anything.
+ return 0;
+ }
+
+ final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
+ final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ final boolean canceled = event.isCanceled();
+ final int keyCode = event.getKeyCode();
+
+ final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;
+
+ // If screen is off then we treat the case where the keyguard is open but hidden
+ // the same as if it were open and in front.
+ // This will prevent any keys other than the power button from waking the screen
+ // when the keyguard is hidden by another activity.
+ final boolean keyguardActive = (mKeyguardDelegate == null ? false :
+ (interactive ?
+ isKeyguardShowingAndNotOccluded() :
+ mKeyguardDelegate.isShowing()));
+
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "interceptKeyTq keycode=" + keyCode
+ + " interactive=" + interactive + " keyguardActive=" + keyguardActive
+ + " policyFlags=" + Integer.toHexString(policyFlags));
+ }
+
+ // Basic policy based on interactive state.
+ int result;
+ boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
+ || event.isWakeKey();
+ if (interactive || (isInjected && !isWakeKey)) {
+ // When the device is interactive or the key is injected pass the
+ // key to the application.
+ result = ACTION_PASS_TO_USER;
+ isWakeKey = false;
+ } else if (!interactive && shouldDispatchInputWhenNonInteractive()) {
+ // If we're currently dozing with the screen on and the keyguard showing, pass the key
+ // to the application but preserve its wake key status to make sure we still move
+ // from dozing to fully interactive if we would normally go from off to fully
+ // interactive.
+ result = ACTION_PASS_TO_USER;
+ } else {
+ // When the screen is off and the key is not injected, determine whether
+ // to wake the device but don't pass the key to the application.
+ result = 0;
+ if (isWakeKey && (!down || !isWakeKeyWhenScreenOff(keyCode))) {
+ isWakeKey = false;
+ }
+ }
+
+ // If the key would be handled globally, just return the result, don't worry about special
+ // key processing.
+ if (isValidGlobalKey(keyCode)
+ && mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) {
+ if (isWakeKey) {
+ wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey);
+ }
+ return result;
+ }
+
+ boolean useHapticFeedback = down
+ && (policyFlags & WindowManagerPolicy.FLAG_VIRTUAL) != 0
+ && event.getRepeatCount() == 0;
+
+ // Handle special keys.
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ if (mUseTvRouting) {
+ // On TVs volume keys never go to the foreground app
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ if (down) {
+ if (interactive && !mScreenshotChordVolumeDownKeyTriggered
+ && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ mScreenshotChordVolumeDownKeyTriggered = true;
+ mScreenshotChordVolumeDownKeyTime = event.getDownTime();
+ mScreenshotChordVolumeDownKeyConsumed = false;
+ cancelPendingPowerKeyAction();
+ interceptScreenshotChord();
+ }
+ } else {
+ mScreenshotChordVolumeDownKeyTriggered = false;
+ cancelPendingScreenshotChordAction();
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ if (down) {
+ if (interactive && !mScreenshotChordVolumeUpKeyTriggered
+ && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ mScreenshotChordVolumeUpKeyTriggered = true;
+ cancelPendingPowerKeyAction();
+ cancelPendingScreenshotChordAction();
+ }
+ } else {
+ mScreenshotChordVolumeUpKeyTriggered = false;
+ cancelPendingScreenshotChordAction();
+ }
+ }
+ if (down) {
+ TelecomManager telecomManager = getTelecommService();
+ if (telecomManager != null) {
+ if (telecomManager.isRinging()) {
+ // If an incoming call is ringing, either VOLUME key means
+ // "silence ringer". We handle these keys here, rather than
+ // in the InCallScreen, to make sure we'll respond to them
+ // even if the InCallScreen hasn't come to the foreground yet.
+ // Look for the DOWN event here, to agree with the "fallback"
+ // behavior in the InCallScreen.
+ Log.i(TAG, "interceptKeyBeforeQueueing:"
+ + " VOLUME key-down while ringing: Silence ringer!");
+
+ // Silence the ringer. (It's safe to call this
+ // even if the ringer has already been silenced.)
+ telecomManager.silenceRinger();
+
+ // And *don't* pass this key thru to the current activity
+ // (which is probably the InCallScreen.)
+ result &= ~ACTION_PASS_TO_USER;
+ break;
+ }
+ if (telecomManager.isInCall()
+ && (result & ACTION_PASS_TO_USER) == 0) {
+ // If we are in call but we decided not to pass the key to
+ // the application, just pass it to the session service.
+
+ MediaSessionLegacyHelper.getHelper(mContext)
+ .sendVolumeKeyEvent(event, false);
+ break;
+ }
+ }
+
+ if ((result & ACTION_PASS_TO_USER) == 0) {
+ if (mUseTvRouting) {
+ dispatchDirectAudioEvent(event);
+ } else {
+ // If we aren't passing to the user and no one else
+ // handled it send it to the session manager to
+ // figure out.
+ MediaSessionLegacyHelper.getHelper(mContext)
+ .sendVolumeKeyEvent(event, true);
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ case KeyEvent.KEYCODE_ENDCALL: {
+ result &= ~ACTION_PASS_TO_USER;
+ if (down) {
+ TelecomManager telecomManager = getTelecommService();
+ boolean hungUp = false;
+ if (telecomManager != null) {
+ hungUp = telecomManager.endCall();
+ }
+ if (interactive && !hungUp) {
+ mEndCallKeyHandled = false;
+ mHandler.postDelayed(mEndCallLongPress,
+ ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+ } else {
+ mEndCallKeyHandled = true;
+ }
+ } else {
+ if (!mEndCallKeyHandled) {
+ mHandler.removeCallbacks(mEndCallLongPress);
+ if (!canceled) {
+ if ((mEndcallBehavior
+ & Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0) {
+ if (goHome()) {
+ break;
+ }
+ }
+ if ((mEndcallBehavior
+ & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) {
+ mPowerManager.goToSleep(event.getEventTime(),
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ isWakeKey = false;
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case KeyEvent.KEYCODE_POWER: {
+ result &= ~ACTION_PASS_TO_USER;
+ isWakeKey = false; // wake-up will be handled separately
+ if (down) {
+ interceptPowerKeyDown(event, interactive);
+ } else {
+ interceptPowerKeyUp(event, interactive, canceled);
+ }
+ break;
+ }
+
+ case KeyEvent.KEYCODE_SLEEP: {
+ result &= ~ACTION_PASS_TO_USER;
+ isWakeKey = false;
+ if (!mPowerManager.isInteractive()) {
+ useHapticFeedback = false; // suppress feedback if already non-interactive
+ }
+ sleepPress(event);
+ break;
+ }
+
+ case KeyEvent.KEYCODE_WAKEUP: {
+ result &= ~ACTION_PASS_TO_USER;
+ isWakeKey = true;
+ break;
+ }
+
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {
+ // If the global session is active pass all media keys to it
+ // instead of the active window.
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ if ((result & ACTION_PASS_TO_USER) == 0) {
+ // Only do this if we would otherwise not pass it to the user. In that
+ // case, the PhoneWindow class will do the same thing, except it will
+ // only do it if the showing app doesn't process the key on its own.
+ // Note that we need to make a copy of the key event here because the
+ // original key event will be recycled when we return.
+ mBroadcastWakeLock.acquire();
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK,
+ new KeyEvent(event));
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ break;
+ }
+
+ case KeyEvent.KEYCODE_CALL: {
+ if (down) {
+ TelecomManager telecomManager = getTelecommService();
+ if (telecomManager != null) {
+ if (telecomManager.isRinging()) {
+ Log.i(TAG, "interceptKeyBeforeQueueing:"
+ + " CALL key-down while ringing: Answer the call!");
+ telecomManager.acceptRingingCall();
+
+ // And *don't* pass this key thru to the current activity
+ // (which is presumably the InCallScreen.)
+ result &= ~ACTION_PASS_TO_USER;
+ }
+ }
+ }
+ break;
+ }
+ case KeyEvent.KEYCODE_VOICE_ASSIST: {
+ // Only do this if we would otherwise not pass it to the user. In that case,
+ // interceptKeyBeforeDispatching would apply a similar but different policy in
+ // order to invoke voice assist actions. Note that we need to make a copy of the
+ // key event here because the original key event will be recycled when we return.
+ if ((result & ACTION_PASS_TO_USER) == 0 && !down) {
+ mBroadcastWakeLock.acquire();
+ Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK,
+ keyguardActive ? 1 : 0, 0);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+ }
+
+ if (useHapticFeedback) {
+ performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
+ }
+
+ if (isWakeKey) {
+ wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns true if the key can have global actions attached to it.
+ * We reserve all power management keys for the system since they require
+ * very careful handling.
+ */
+ private static boolean isValidGlobalKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_POWER:
+ case KeyEvent.KEYCODE_WAKEUP:
+ case KeyEvent.KEYCODE_SLEEP:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * When the screen is off we ignore some keys that might otherwise typically
+ * be considered wake keys. We filter them out here.
+ *
+ * {@link KeyEvent#KEYCODE_POWER} is notably absent from this list because it
+ * is always considered a wake key.
+ */
+ private boolean isWakeKeyWhenScreenOff(int keyCode) {
+ switch (keyCode) {
+ // ignore volume keys unless docked
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ return mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+ // ignore media and camera keys
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
+ case KeyEvent.KEYCODE_CAMERA:
+ return false;
+ }
+ return true;
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) {
+ if ((policyFlags & FLAG_WAKE) != 0) {
+ if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion)) {
+ return 0;
+ }
+ }
+
+ if (shouldDispatchInputWhenNonInteractive()) {
+ return ACTION_PASS_TO_USER;
+ }
+
+ // If we have not passed the action up and we are in theater mode without dreaming,
+ // there will be no dream to intercept the touch and wake into ambient. The device should
+ // wake up in this case.
+ if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
+ wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming);
+ }
+
+ return 0;
+ }
+
+ private boolean shouldDispatchInputWhenNonInteractive() {
+ // Send events to keyguard while the screen is on.
+ if (isKeyguardShowingAndNotOccluded() && mDisplay != null
+ && mDisplay.getState() != Display.STATE_OFF) {
+ return true;
+ }
+
+ // Send events to a dozing dream even if the screen is off since the dream
+ // is in control of the state of the screen.
+ IDreamManager dreamManager = getDreamManager();
+
+ try {
+ if (dreamManager != null && dreamManager.isDreaming()) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when checking if dreaming", e);
+ }
+
+ // Otherwise, consume events since the user can't see what is being
+ // interacted with.
+ return false;
+ }
+
+ private void dispatchDirectAudioEvent(KeyEvent event) {
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return;
+ }
+ int keyCode = event.getKeyCode();
+ int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
+ | AudioManager.FLAG_FROM_KEY;
+ String pkgName = mContext.getOpPackageName();
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ try {
+ getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error dispatching volume up in dispatchTvAudioEvent.", e);
+ }
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ try {
+ getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e);
+ }
+ break;
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ try {
+ if (event.getRepeatCount() == 0) {
+ getAudioService().adjustSuggestedStreamVolume(
+ AudioManager.ADJUST_TOGGLE_MUTE,
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e);
+ }
+ break;
+ }
+ }
+
+ void dispatchMediaKeyWithWakeLock(KeyEvent event) {
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "dispatchMediaKeyWithWakeLock: " + event);
+ }
+
+ if (mHavePendingMediaKeyRepeatWithWakeLock) {
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "dispatchMediaKeyWithWakeLock: canceled repeat");
+ }
+
+ mHandler.removeMessages(MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK);
+ mHavePendingMediaKeyRepeatWithWakeLock = false;
+ mBroadcastWakeLock.release(); // pending repeat was holding onto the wake lock
+ }
+
+ dispatchMediaKeyWithWakeLockToAudioService(event);
+
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0) {
+ mHavePendingMediaKeyRepeatWithWakeLock = true;
+
+ Message msg = mHandler.obtainMessage(
+ MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK, event);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, ViewConfiguration.getKeyRepeatTimeout());
+ } else {
+ mBroadcastWakeLock.release();
+ }
+ }
+
+ void dispatchMediaKeyRepeatWithWakeLock(KeyEvent event) {
+ mHavePendingMediaKeyRepeatWithWakeLock = false;
+
+ KeyEvent repeatEvent = KeyEvent.changeTimeRepeat(event,
+ SystemClock.uptimeMillis(), 1, event.getFlags() | KeyEvent.FLAG_LONG_PRESS);
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "dispatchMediaKeyRepeatWithWakeLock: " + repeatEvent);
+ }
+
+ dispatchMediaKeyWithWakeLockToAudioService(repeatEvent);
+ mBroadcastWakeLock.release();
+ }
+
+ void dispatchMediaKeyWithWakeLockToAudioService(KeyEvent event) {
+ if (ActivityManagerNative.isSystemReady()) {
+ MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(event, true);
+ }
+ }
+
+ void launchVoiceAssistWithWakeLock(boolean keyguardActive) {
+ Intent voiceIntent =
+ new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, keyguardActive);
+ startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF);
+ mBroadcastWakeLock.release();
+ }
+
+ BroadcastReceiver mDockReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+ mDockMode = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ } else {
+ try {
+ IUiModeManager uiModeService = IUiModeManager.Stub.asInterface(
+ ServiceManager.getService(Context.UI_MODE_SERVICE));
+ mUiMode = uiModeService.getCurrentModeType();
+ } catch (RemoteException e) {
+ }
+ }
+ updateRotation(true);
+ synchronized (mLock) {
+ updateOrientationListenerLp();
+ }
+ }
+ };
+
+ BroadcastReceiver mDreamReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_DREAMING_STARTED.equals(intent.getAction())) {
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.onDreamingStarted();
+ }
+ } else if (Intent.ACTION_DREAMING_STOPPED.equals(intent.getAction())) {
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.onDreamingStopped();
+ }
+ }
+ }
+ };
+
+ BroadcastReceiver mMultiuserReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
+ // tickle the settings observer: this first ensures that we're
+ // observing the relevant settings for the newly-active user,
+ // and then updates our own bookkeeping based on the now-
+ // current user.
+ mSettingsObserver.onChange(false);
+
+ // force a re-application of focused window sysui visibility.
+ // the window may never have been shown for this user
+ // e.g. the keyguard when going through the new-user setup flow
+ synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
+ mLastSystemUiFlags = 0;
+ updateSystemUiVisibilityLw();
+ }
+ }
+ }
+ };
+
+ private final Runnable mRequestTransientNav = new Runnable() {
+ @Override
+ public void run() {
+ requestTransientBars(mNavigationBar);
+ }
+ };
+
+ private void requestTransientBars(WindowState swipeTarget) {
+ synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
+ if (!isUserSetupComplete()) {
+ // Swipe-up for navigation bar is disabled during setup
+ return;
+ }
+ boolean sb = mStatusBarController.checkShowTransientBarLw();
+ boolean nb = mNavigationBarController.checkShowTransientBarLw();
+ if (sb || nb) {
+ // Don't show status bar when swiping on already visible navigation bar
+ if (!nb && swipeTarget == mNavigationBar) {
+ if (DEBUG) Slog.d(TAG, "Not showing transient bar, wrong swipe target");
+ return;
+ }
+ if (sb) mStatusBarController.showTransient();
+ if (nb) mNavigationBarController.showTransient();
+ mImmersiveModeConfirmation.confirmCurrentPrompt();
+ updateSystemUiVisibilityLw();
+ }
+ }
+ }
+
+ // Called on the PowerManager's Notifier thread.
+ @Override
+ public void goingToSleep(int why) {
+ EventLog.writeEvent(70000, 0);
+ if (DEBUG_WAKEUP) Slog.i(TAG, "Going to sleep...");
+
+ // We must get this work done here because the power manager will drop
+ // the wake lock and let the system suspend once this function returns.
+ synchronized (mLock) {
+ mAwake = false;
+ mKeyguardDrawComplete = false;
+ updateWakeGestureListenerLp();
+ updateOrientationListenerLp();
+ updateLockScreenTimeout();
+ }
+
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.onScreenTurnedOff(why);
+ }
+ }
+
+ private void wakeUpFromPowerKey(long eventTime) {
+ wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey);
+ }
+
+ private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode) {
+ if (!wakeInTheaterMode && isTheaterModeEnabled()) {
+ return false;
+ }
+
+ mPowerManager.wakeUp(wakeTime);
+ return true;
+ }
+
+ // Called on the PowerManager's Notifier thread.
+ @Override
+ public void wakingUp() {
+ EventLog.writeEvent(70000, 1);
+ if (DEBUG_WAKEUP) Slog.i(TAG, "Waking up...");
+
+ // Since goToSleep performs these functions synchronously, we must
+ // do the same here. We cannot post this work to a handler because
+ // that might cause it to become reordered with respect to what
+ // may happen in a future call to goToSleep.
+ synchronized (mLock) {
+ mAwake = true;
+ mKeyguardDrawComplete = false;
+ if (mKeyguardDelegate != null) {
+ mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT);
+ mHandler.sendEmptyMessageDelayed(MSG_KEYGUARD_DRAWN_TIMEOUT, 1000);
+ }
+
+ updateWakeGestureListenerLp();
+ updateOrientationListenerLp();
+ updateLockScreenTimeout();
+ }
+
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.onScreenTurnedOn(mKeyguardDelegateCallback);
+ // ... eventually calls finishKeyguardDrawn
+ } else {
+ if (DEBUG_WAKEUP) Slog.d(TAG, "null mKeyguardDelegate: setting mKeyguardDrawComplete.");
+ finishKeyguardDrawn();
+ }
+ }
+
+ private void finishKeyguardDrawn() {
+ synchronized (mLock) {
+ if (!mAwake || mKeyguardDrawComplete) {
+ return; // spurious
+ }
+
+ mKeyguardDrawComplete = true;
+ if (mKeyguardDelegate != null) {
+ mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT);
+ }
+ }
+
+ finishScreenTurningOn();
+ }
+
+ // Called on the DisplayManager's DisplayPowerController thread.
+ @Override
+ public void screenTurnedOff() {
+ if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turned off...");
+
+ synchronized (mLock) {
+ mScreenOnEarly = false;
+ mScreenOnFully = false;
+ mWindowManagerDrawComplete = false;
+ mScreenOnListener = null;
+ updateOrientationListenerLp();
+ }
+ }
+
+ // Called on the DisplayManager's DisplayPowerController thread.
+ @Override
+ public void screenTurningOn(final ScreenOnListener screenOnListener) {
+ if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turning on...");
+
+ synchronized (mLock) {
+ mScreenOnEarly = true;
+ mScreenOnFully = false;
+ mWindowManagerDrawComplete = false;
+ mScreenOnListener = screenOnListener;
+ updateOrientationListenerLp();
+ }
+
+ mWindowManagerInternal.waitForAllWindowsDrawn(mWindowManagerDrawCallback,
+ WAITING_FOR_DRAWN_TIMEOUT);
+ // ... eventually calls finishWindowsDrawn
+ }
+
+ private void finishWindowsDrawn() {
+ synchronized (mLock) {
+ if (!mScreenOnEarly || mWindowManagerDrawComplete) {
+ return; // spurious
+ }
+
+ mWindowManagerDrawComplete = true;
+ }
+
+ finishScreenTurningOn();
+ }
+
+ private void finishScreenTurningOn() {
+ final ScreenOnListener listener;
+ final boolean enableScreen;
+ synchronized (mLock) {
+ if (DEBUG_WAKEUP) Slog.d(TAG,
+ "finishScreenTurningOn: mAwake=" + mAwake
+ + ", mScreenOnEarly=" + mScreenOnEarly
+ + ", mScreenOnFully=" + mScreenOnFully
+ + ", mKeyguardDrawComplete=" + mKeyguardDrawComplete
+ + ", mWindowManagerDrawComplete=" + mWindowManagerDrawComplete);
+
+ if (mScreenOnFully || !mScreenOnEarly || !mWindowManagerDrawComplete
+ || (mAwake && !mKeyguardDrawComplete)) {
+ return; // spurious or not ready yet
+ }
+
+ if (DEBUG_WAKEUP) Slog.i(TAG, "Finished screen turning on...");
+ listener = mScreenOnListener;
+ mScreenOnListener = null;
+ mScreenOnFully = true;
+
+ // Remember the first time we draw the keyguard so we know when we're done with
+ // the main part of booting and can enable the screen and hide boot messages.
+ if (!mKeyguardDrawnOnce && mAwake) {
+ mKeyguardDrawnOnce = true;
+ enableScreen = true;
+ if (mBootMessageNeedsHiding) {
+ mBootMessageNeedsHiding = false;
+ hideBootMessages();
+ }
+ } else {
+ enableScreen = false;
+ }
+ }
+
+ if (listener != null) {
+ listener.onScreenOn();
+ }
+
+ if (enableScreen) {
+ try {
+ mWindowManager.enableScreenIfNeeded();
+ } catch (RemoteException unhandled) {
+ }
+ }
+ }
+
+ private void handleHideBootMessage() {
+ synchronized (mLock) {
+ if (!mKeyguardDrawnOnce) {
+ mBootMessageNeedsHiding = true;
+ return; // keyguard hasn't drawn the first time yet, not done booting
+ }
+ }
+
+ if (mBootMsgDialog != null) {
+ if (DEBUG_WAKEUP) Slog.d(TAG, "handleHideBootMessage: dismissing");
+ mBootMsgDialog.dismiss();
+ mBootMsgDialog = null;
+ }
+ }
+
+ @Override
+ public boolean isScreenOn() {
+ return mScreenOnFully;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void enableKeyguard(boolean enabled) {
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.setKeyguardEnabled(enabled);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void exitKeyguardSecurely(OnKeyguardExitResult callback) {
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.verifyUnlock(callback);
+ }
+ }
+
+ private boolean isKeyguardShowingAndNotOccluded() {
+ if (mKeyguardDelegate == null) return false;
+ return mKeyguardDelegate.isShowing() && !mKeyguardOccluded;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isKeyguardLocked() {
+ return keyguardOn();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isKeyguardSecure() {
+ if (mKeyguardDelegate == null) return false;
+ return mKeyguardDelegate.isSecure();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean inKeyguardRestrictedKeyInputMode() {
+ if (mKeyguardDelegate == null) return false;
+ return mKeyguardDelegate.isInputRestricted();
+ }
+
+ @Override
+ public void dismissKeyguardLw() {
+ if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
+ if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.dismissKeyguardLw");
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // ask the keyguard to prompt the user to authenticate if necessary
+ mKeyguardDelegate.dismiss();
+ }
+ });
+ }
+ }
+
+ public void notifyActivityDrawnForKeyguardLw() {
+ if (mKeyguardDelegate != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardDelegate.onActivityDrawn();
+ }
+ });
+ }
+ }
+
+ @Override
+ public boolean isKeyguardDrawnLw() {
+ synchronized (mLock) {
+ return mKeyguardDrawnOnce;
+ }
+ }
+
+ @Override
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ if (mKeyguardDelegate != null) {
+ if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.startKeyguardExitAnimation");
+ mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration);
+ }
+ }
+
+ void sendCloseSystemWindows() {
+ PhoneWindow.sendCloseSystemWindows(mContext, null);
+ }
+
+ void sendCloseSystemWindows(String reason) {
+ PhoneWindow.sendCloseSystemWindows(mContext, reason);
+ }
+
+ @Override
+ public int rotationForOrientationLw(int orientation, int lastRotation) {
+ if (false) {
+ Slog.v(TAG, "rotationForOrientationLw(orient="
+ + orientation + ", last=" + lastRotation
+ + "); user=" + mUserRotation + " "
+ + ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED)
+ ? "USER_ROTATION_LOCKED" : "")
+ );
+ }
+
+ if (mForceDefaultOrientation) {
+ return Surface.ROTATION_0;
+ }
+
+ synchronized (mLock) {
+ int sensorRotation = mOrientationListener.getProposedRotation(); // may be -1
+ if (sensorRotation < 0) {
+ sensorRotation = lastRotation;
+ }
+
+ final int preferredRotation;
+ if (mLidState == LID_OPEN && mLidOpenRotation >= 0) {
+ // Ignore sensor when lid switch is open and rotation is forced.
+ preferredRotation = mLidOpenRotation;
+ } else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR
+ && (mCarDockEnablesAccelerometer || mCarDockRotation >= 0)) {
+ // Ignore sensor when in car dock unless explicitly enabled.
+ // This case can override the behavior of NOSENSOR, and can also
+ // enable 180 degree rotation while docked.
+ preferredRotation = mCarDockEnablesAccelerometer
+ ? sensorRotation : mCarDockRotation;
+ } else if ((mDockMode == Intent.EXTRA_DOCK_STATE_DESK
+ || mDockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
+ || mDockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
+ && (mDeskDockEnablesAccelerometer || mDeskDockRotation >= 0)) {
+ // Ignore sensor when in desk dock unless explicitly enabled.
+ // This case can override the behavior of NOSENSOR, and can also
+ // enable 180 degree rotation while docked.
+ preferredRotation = mDeskDockEnablesAccelerometer
+ ? sensorRotation : mDeskDockRotation;
+ } else if (mHdmiPlugged && mDemoHdmiRotationLock) {
+ // Ignore sensor when plugged into HDMI when demo HDMI rotation lock enabled.
+ // Note that the dock orientation overrides the HDMI orientation.
+ preferredRotation = mDemoHdmiRotation;
+ } else if (mHdmiPlugged && mDockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED
+ && mUndockedHdmiRotation >= 0) {
+ // Ignore sensor when plugged into HDMI and an undocked orientation has
+ // been specified in the configuration (only for legacy devices without
+ // full multi-display support).
+ // Note that the dock orientation overrides the HDMI orientation.
+ preferredRotation = mUndockedHdmiRotation;
+ } else if (mDemoRotationLock) {
+ // Ignore sensor when demo rotation lock is enabled.
+ // Note that the dock orientation and HDMI rotation lock override this.
+ preferredRotation = mDemoRotation;
+ } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
+ // Application just wants to remain locked in the last rotation.
+ preferredRotation = lastRotation;
+ } else if (!mSupportAutoRotation) {
+ // If we don't support auto-rotation then bail out here and ignore
+ // the sensor and any rotation lock settings.
+ preferredRotation = -1;
+ } else if ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
+ && (orientation == ActivityInfo.SCREEN_ORIENTATION_USER
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER))
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
+ // Otherwise, use sensor only if requested by the application or enabled
+ // by default for USER or UNSPECIFIED modes. Does not apply to NOSENSOR.
+ if (mAllowAllRotations < 0) {
+ // Can't read this during init() because the context doesn't
+ // have display metrics at that time so we cannot determine
+ // tablet vs. phone then.
+ mAllowAllRotations = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowAllRotations) ? 1 : 0;
+ }
+ if (sensorRotation != Surface.ROTATION_180
+ || mAllowAllRotations == 1
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
+ preferredRotation = sensorRotation;
+ } else {
+ preferredRotation = lastRotation;
+ }
+ } else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
+ && orientation != ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
+ // Apply rotation lock. Does not apply to NOSENSOR.
+ // The idea is that the user rotation expresses a weak preference for the direction
+ // of gravity and as NOSENSOR is never affected by gravity, then neither should
+ // NOSENSOR be affected by rotation lock (although it will be affected by docks).
+ preferredRotation = mUserRotation;
+ } else {
+ // No overriding preference.
+ // We will do exactly what the application asked us to do.
+ preferredRotation = -1;
+ }
+
+ switch (orientation) {
+ case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
+ // Return portrait unless overridden.
+ if (isAnyPortrait(preferredRotation)) {
+ return preferredRotation;
+ }
+ return mPortraitRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
+ // Return landscape unless overridden.
+ if (isLandscapeOrSeascape(preferredRotation)) {
+ return preferredRotation;
+ }
+ return mLandscapeRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ // Return reverse portrait unless overridden.
+ if (isAnyPortrait(preferredRotation)) {
+ return preferredRotation;
+ }
+ return mUpsideDownRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ // Return seascape unless overridden.
+ if (isLandscapeOrSeascape(preferredRotation)) {
+ return preferredRotation;
+ }
+ return mSeascapeRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE:
+ // Return either landscape rotation.
+ if (isLandscapeOrSeascape(preferredRotation)) {
+ return preferredRotation;
+ }
+ if (isLandscapeOrSeascape(lastRotation)) {
+ return lastRotation;
+ }
+ return mLandscapeRotation;
+
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT:
+ // Return either portrait rotation.
+ if (isAnyPortrait(preferredRotation)) {
+ return preferredRotation;
+ }
+ if (isAnyPortrait(lastRotation)) {
+ return lastRotation;
+ }
+ return mPortraitRotation;
+
+ default:
+ // For USER, UNSPECIFIED, NOSENSOR, SENSOR and FULL_SENSOR,
+ // just return the preferred orientation we already calculated.
+ if (preferredRotation >= 0) {
+ return preferredRotation;
+ }
+ return Surface.ROTATION_0;
+ }
+ }
+ }
+
+ @Override
+ public boolean rotationHasCompatibleMetricsLw(int orientation, int rotation) {
+ switch (orientation) {
+ case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ return isAnyPortrait(rotation);
+
+ case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ return isLandscapeOrSeascape(rotation);
+
+ default:
+ return true;
+ }
+ }
+
+ @Override
+ public void setRotationLw(int rotation) {
+ mOrientationListener.setCurrentRotation(rotation);
+ }
+
+ private boolean isLandscapeOrSeascape(int rotation) {
+ return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
+ }
+
+ private boolean isAnyPortrait(int rotation) {
+ return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
+ }
+
+ @Override
+ public int getUserRotationMode() {
+ return Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) != 0 ?
+ WindowManagerPolicy.USER_ROTATION_FREE :
+ WindowManagerPolicy.USER_ROTATION_LOCKED;
+ }
+
+ // User rotation: to be used when all else fails in assigning an orientation to the device
+ @Override
+ public void setUserRotationMode(int mode, int rot) {
+ ContentResolver res = mContext.getContentResolver();
+
+ // mUserRotationMode and mUserRotation will be assigned by the content observer
+ if (mode == WindowManagerPolicy.USER_ROTATION_LOCKED) {
+ Settings.System.putIntForUser(res,
+ Settings.System.USER_ROTATION,
+ rot,
+ UserHandle.USER_CURRENT);
+ Settings.System.putIntForUser(res,
+ Settings.System.ACCELEROMETER_ROTATION,
+ 0,
+ UserHandle.USER_CURRENT);
+ } else {
+ Settings.System.putIntForUser(res,
+ Settings.System.ACCELEROMETER_ROTATION,
+ 1,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
+ @Override
+ public void setSafeMode(boolean safeMode) {
+ mSafeMode = safeMode;
+ performHapticFeedbackLw(null, safeMode
+ ? HapticFeedbackConstants.SAFE_MODE_ENABLED
+ : HapticFeedbackConstants.SAFE_MODE_DISABLED, true);
+ }
+
+ static long[] getLongIntArray(Resources r, int resid) {
+ int[] ar = r.getIntArray(resid);
+ if (ar == null) {
+ return null;
+ }
+ long[] out = new long[ar.length];
+ for (int i=0; i<ar.length; i++) {
+ out[i] = ar[i];
+ }
+ return out;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void systemReady() {
+ mKeyguardDelegate = new KeyguardServiceDelegate(mContext);
+ mKeyguardDelegate.onSystemReady();
+
+ readCameraLensCoverState();
+ updateUiMode();
+ synchronized (mLock) {
+ updateOrientationListenerLp();
+ mSystemReady = true;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ updateSettings();
+ }
+ });
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void systemBooted() {
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.bindService(mContext);
+ mKeyguardDelegate.onBootCompleted();
+ }
+ synchronized (mLock) {
+ mSystemBooted = true;
+ }
+ wakingUp();
+ screenTurningOn(null);
+ }
+
+ ProgressDialog mBootMsgDialog = null;
+
+ /** {@inheritDoc} */
+ @Override
+ public void showBootMessage(final CharSequence msg, final boolean always) {
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ if (mBootMsgDialog == null) {
+ int theme;
+ if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH)) {
+ theme = com.android.internal.R.style.Theme_Micro_Dialog_Alert;
+ } else if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEVISION)) {
+ theme = com.android.internal.R.style.Theme_Leanback_Dialog_Alert;
+ } else {
+ theme = 0;
+ }
+
+ mBootMsgDialog = new ProgressDialog(mContext, theme) {
+ // This dialog will consume all events coming in to
+ // it, to avoid it trying to do things too early in boot.
+ @Override public boolean dispatchKeyEvent(KeyEvent event) {
+ return true;
+ }
+ @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ return true;
+ }
+ @Override public boolean dispatchTouchEvent(MotionEvent ev) {
+ return true;
+ }
+ @Override public boolean dispatchTrackballEvent(MotionEvent ev) {
+ return true;
+ }
+ @Override public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ return true;
+ }
+ @Override public boolean dispatchPopulateAccessibilityEvent(
+ AccessibilityEvent event) {
+ return true;
+ }
+ };
+ if (mContext.getPackageManager().isUpgrade()) {
+ mBootMsgDialog.setTitle(R.string.android_upgrading_title);
+ } else {
+ mBootMsgDialog.setTitle(R.string.android_start_title);
+ }
+ mBootMsgDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mBootMsgDialog.setIndeterminate(true);
+ mBootMsgDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_BOOT_PROGRESS);
+ mBootMsgDialog.getWindow().addFlags(
+ WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
+ mBootMsgDialog.getWindow().setDimAmount(1);
+ WindowManager.LayoutParams lp = mBootMsgDialog.getWindow().getAttributes();
+ lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ mBootMsgDialog.getWindow().setAttributes(lp);
+ mBootMsgDialog.setCancelable(false);
+ mBootMsgDialog.show();
+ }
+ mBootMsgDialog.setMessage(msg);
+ }
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void hideBootMessages() {
+ mHandler.sendEmptyMessage(MSG_HIDE_BOOT_MESSAGE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void userActivity() {
+ // ***************************************
+ // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
+ // ***************************************
+ // THIS IS CALLED FROM DEEP IN THE POWER MANAGER
+ // WITH ITS LOCKS HELD.
+ //
+ // This code must be VERY careful about the locks
+ // it acquires.
+ // In fact, the current code acquires way too many,
+ // and probably has lurking deadlocks.
+
+ synchronized (mScreenLockTimeout) {
+ if (mLockScreenTimerActive) {
+ // reset the timer
+ mHandler.removeCallbacks(mScreenLockTimeout);
+ mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout);
+ }
+ }
+ }
+
+ class ScreenLockTimeout implements Runnable {
+ Bundle options;
+
+ @Override
+ public void run() {
+ synchronized (this) {
+ if (localLOGV) Log.v(TAG, "mScreenLockTimeout activating keyguard");
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.doKeyguardTimeout(options);
+ }
+ mLockScreenTimerActive = false;
+ options = null;
+ }
+ }
+
+ public void setLockOptions(Bundle options) {
+ this.options = options;
+ }
+ }
+
+ ScreenLockTimeout mScreenLockTimeout = new ScreenLockTimeout();
+
+ @Override
+ public void lockNow(Bundle options) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+ mHandler.removeCallbacks(mScreenLockTimeout);
+ if (options != null) {
+ // In case multiple calls are made to lockNow, we don't wipe out the options
+ // until the runnable actually executes.
+ mScreenLockTimeout.setLockOptions(options);
+ }
+ mHandler.post(mScreenLockTimeout);
+ }
+
+ private void updateLockScreenTimeout() {
+ synchronized (mScreenLockTimeout) {
+ boolean enable = (mAllowLockscreenWhenOn && mAwake &&
+ mKeyguardDelegate != null && mKeyguardDelegate.isSecure());
+ if (mLockScreenTimerActive != enable) {
+ if (enable) {
+ if (localLOGV) Log.v(TAG, "setting lockscreen timer");
+ mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout);
+ } else {
+ if (localLOGV) Log.v(TAG, "clearing lockscreen timer");
+ mHandler.removeCallbacks(mScreenLockTimeout);
+ }
+ mLockScreenTimerActive = enable;
+ }
+ }
+ }
+
+ private void updateDreamingSleepToken(boolean acquire) {
+ if (acquire) {
+ if (mDreamingSleepToken == null) {
+ mDreamingSleepToken = mActivityManagerInternal.acquireSleepToken("Dream");
+ }
+ } else {
+ if (mDreamingSleepToken != null) {
+ mDreamingSleepToken.release();
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void enableScreenAfterBoot() {
+ readLidState();
+ applyLidSwitchState();
+ updateRotation(true);
+ }
+
+ private void applyLidSwitchState() {
+ if (mLidState == LID_CLOSED && mLidControlsSleep) {
+ mPowerManager.goToSleep(SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH,
+ PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+ }
+
+ synchronized (mLock) {
+ updateWakeGestureListenerLp();
+ }
+ }
+
+ void updateUiMode() {
+ if (mUiModeManager == null) {
+ mUiModeManager = IUiModeManager.Stub.asInterface(
+ ServiceManager.getService(Context.UI_MODE_SERVICE));
+ }
+ try {
+ mUiMode = mUiModeManager.getCurrentModeType();
+ } catch (RemoteException e) {
+ }
+ }
+
+ void updateRotation(boolean alwaysSendConfiguration) {
+ try {
+ //set orientation on WindowManager
+ mWindowManager.updateRotation(alwaysSendConfiguration, false);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
+ try {
+ //set orientation on WindowManager
+ mWindowManager.updateRotation(alwaysSendConfiguration, forceRelayout);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Return an Intent to launch the currently active dock app as home. Returns
+ * null if the standard home should be launched, which is the case if any of the following is
+ * true:
+ * <ul>
+ * <li>The device is not in either car mode or desk mode
+ * <li>The device is in car mode but ENABLE_CAR_DOCK_HOME_CAPTURE is false
+ * <li>The device is in desk mode but ENABLE_DESK_DOCK_HOME_CAPTURE is false
+ * <li>The device is in car mode but there's no CAR_DOCK app with METADATA_DOCK_HOME
+ * <li>The device is in desk mode but there's no DESK_DOCK app with METADATA_DOCK_HOME
+ * </ul>
+ * @return A dock intent.
+ */
+ Intent createHomeDockIntent() {
+ Intent intent = null;
+
+ // What home does is based on the mode, not the dock state. That
+ // is, when in car mode you should be taken to car home regardless
+ // of whether we are actually in a car dock.
+ if (mUiMode == Configuration.UI_MODE_TYPE_CAR) {
+ if (ENABLE_CAR_DOCK_HOME_CAPTURE) {
+ intent = mCarDockIntent;
+ }
+ } else if (mUiMode == Configuration.UI_MODE_TYPE_DESK) {
+ if (ENABLE_DESK_DOCK_HOME_CAPTURE) {
+ intent = mDeskDockIntent;
+ }
+ } else if (mUiMode == Configuration.UI_MODE_TYPE_WATCH
+ && (mDockMode == Intent.EXTRA_DOCK_STATE_DESK
+ || mDockMode == Intent.EXTRA_DOCK_STATE_HE_DESK
+ || mDockMode == Intent.EXTRA_DOCK_STATE_LE_DESK)) {
+ // Always launch dock home from home when watch is docked, if it exists.
+ intent = mDeskDockIntent;
+ }
+
+ if (intent == null) {
+ return null;
+ }
+
+ ActivityInfo ai = null;
+ ResolveInfo info = mContext.getPackageManager().resolveActivityAsUser(
+ intent,
+ PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
+ mCurrentUserId);
+ if (info != null) {
+ ai = info.activityInfo;
+ }
+ if (ai != null
+ && ai.metaData != null
+ && ai.metaData.getBoolean(Intent.METADATA_DOCK_HOME)) {
+ intent = new Intent(intent);
+ intent.setClassName(ai.packageName, ai.name);
+ return intent;
+ }
+
+ return null;
+ }
+
+ void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) {
+ if (awakenFromDreams) {
+ awakenDreams();
+ }
+
+ Intent dock = createHomeDockIntent();
+ if (dock != null) {
+ try {
+ if (fromHomeKey) {
+ dock.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey);
+ }
+ startActivityAsUser(dock, UserHandle.CURRENT);
+ return;
+ } catch (ActivityNotFoundException e) {
+ }
+ }
+
+ Intent intent;
+
+ if (fromHomeKey) {
+ intent = new Intent(mHomeIntent);
+ intent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey);
+ } else {
+ intent = mHomeIntent;
+ }
+
+ startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+
+ /**
+ * goes to the home screen
+ * @return whether it did anything
+ */
+ boolean goHome() {
+ if (!isUserSetupComplete()) {
+ Slog.i(TAG, "Not going home because user setup is in progress.");
+ return false;
+ }
+ if (false) {
+ // This code always brings home to the front.
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ sendCloseSystemWindows();
+ startDockOrHome(false /*fromHomeKey*/, true /* awakenFromDreams */);
+ } else {
+ // This code brings home to the front or, if it is already
+ // at the front, puts the device to sleep.
+ try {
+ if (SystemProperties.getInt("persist.sys.uts-test-mode", 0) == 1) {
+ /// Roll back EndcallBehavior as the cupcake design to pass P1 lab entry.
+ Log.d(TAG, "UTS-TEST-MODE");
+ } else {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ sendCloseSystemWindows();
+ Intent dock = createHomeDockIntent();
+ if (dock != null) {
+ int result = ActivityManagerNative.getDefault()
+ .startActivityAsUser(null, null, dock,
+ dock.resolveTypeIfNeeded(mContext.getContentResolver()),
+ null, null, 0,
+ ActivityManager.START_FLAG_ONLY_IF_NEEDED,
+ null, null, UserHandle.USER_CURRENT);
+ if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) {
+ return false;
+ }
+ }
+ }
+ int result = ActivityManagerNative.getDefault()
+ .startActivityAsUser(null, null, mHomeIntent,
+ mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ null, null, 0,
+ ActivityManager.START_FLAG_ONLY_IF_NEEDED,
+ null, null, UserHandle.USER_CURRENT);
+ if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) {
+ return false;
+ }
+ } catch (RemoteException ex) {
+ // bummer, the activity manager, which is in this process, is dead
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void setCurrentOrientationLw(int newOrientation) {
+ synchronized (mLock) {
+ if (newOrientation != mCurrentAppOrientation) {
+ mCurrentAppOrientation = newOrientation;
+ updateOrientationListenerLp();
+ }
+ }
+ }
+
+ private void performAuditoryFeedbackForAccessibilityIfNeed() {
+ if (!isGlobalAccessibilityGestureEnabled()) {
+ return;
+ }
+ AudioManager audioManager = (AudioManager) mContext.getSystemService(
+ Context.AUDIO_SERVICE);
+ if (audioManager.isSilentMode()) {
+ return;
+ }
+ Ringtone ringTone = RingtoneManager.getRingtone(mContext,
+ Settings.System.DEFAULT_NOTIFICATION_URI);
+ ringTone.setStreamType(AudioManager.STREAM_MUSIC);
+ ringTone.play();
+ }
+
+ private boolean isTheaterModeEnabled() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.THEATER_MODE_ON, 0) == 1;
+ }
+
+ private boolean isGlobalAccessibilityGestureEnabled() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1;
+ }
+
+ @Override
+ public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always) {
+ if (!mVibrator.hasVibrator()) {
+ return false;
+ }
+ final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
+ if (hapticsDisabled && !always) {
+ return false;
+ }
+ long[] pattern = null;
+ switch (effectId) {
+ case HapticFeedbackConstants.LONG_PRESS:
+ pattern = mLongPressVibePattern;
+ break;
+ case HapticFeedbackConstants.VIRTUAL_KEY:
+ pattern = mVirtualKeyVibePattern;
+ break;
+ case HapticFeedbackConstants.KEYBOARD_TAP:
+ pattern = mKeyboardTapVibePattern;
+ break;
+ case HapticFeedbackConstants.CLOCK_TICK:
+ pattern = mClockTickVibePattern;
+ break;
+ case HapticFeedbackConstants.CALENDAR_DATE:
+ pattern = mCalendarDateVibePattern;
+ break;
+ case HapticFeedbackConstants.SAFE_MODE_DISABLED:
+ pattern = mSafeModeDisabledVibePattern;
+ break;
+ case HapticFeedbackConstants.SAFE_MODE_ENABLED:
+ pattern = mSafeModeEnabledVibePattern;
+ break;
+ default:
+ return false;
+ }
+ int owningUid;
+ String owningPackage;
+ if (win != null) {
+ owningUid = win.getOwningUid();
+ owningPackage = win.getOwningPackage();
+ } else {
+ owningUid = android.os.Process.myUid();
+ owningPackage = mContext.getOpPackageName();
+ }
+ if (pattern.length == 1) {
+ // One-shot vibration
+ mVibrator.vibrate(owningUid, owningPackage, pattern[0], VIBRATION_ATTRIBUTES);
+ } else {
+ // Pattern vibration
+ mVibrator.vibrate(owningUid, owningPackage, pattern, -1, VIBRATION_ATTRIBUTES);
+ }
+ return true;
+ }
+
+ @Override
+ public void keepScreenOnStartedLw() {
+ }
+
+ @Override
+ public void keepScreenOnStoppedLw() {
+ if (isKeyguardShowingAndNotOccluded()) {
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ }
+ }
+
+ private int updateSystemUiVisibilityLw() {
+ // If there is no window focused, there will be nobody to handle the events
+ // anyway, so just hang on in whatever state we're in until things settle down.
+ final WindowState win = mFocusedWindow != null ? mFocusedWindow
+ : mTopFullscreenOpaqueWindowState;
+ if (win == null) {
+ return 0;
+ }
+ if ((win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 && mHideLockScreen == true) {
+ // We are updating at a point where the keyguard has gotten
+ // focus, but we were last in a state where the top window is
+ // hiding it. This is probably because the keyguard as been
+ // shown while the top window was displayed, so we want to ignore
+ // it here because this is just a very transient change and it
+ // will quickly lose focus once it correctly gets hidden.
+ return 0;
+ }
+
+ int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null)
+ & ~mResettingSystemUiFlags
+ & ~mForceClearedSystemUiFlags;
+ if (mForcingShowNavBar && win.getSurfaceLayer() < mForcingShowNavBarLayer) {
+ tmpVisibility &= ~PolicyControl.adjustClearableFlags(win, View.SYSTEM_UI_CLEARABLE_FLAGS);
+ }
+ tmpVisibility = updateLightStatusBarLw(tmpVisibility);
+ final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
+ final int diff = visibility ^ mLastSystemUiFlags;
+ final boolean needsMenu = win.getNeedsMenuLw(mTopFullscreenOpaqueWindowState);
+ if (diff == 0 && mLastFocusNeedsMenu == needsMenu
+ && mFocusedApp == win.getAppToken()) {
+ return 0;
+ }
+ mLastSystemUiFlags = visibility;
+ mLastFocusNeedsMenu = needsMenu;
+ mFocusedApp = win.getAppToken();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.setSystemUiVisibility(visibility, 0xffffffff, win.toString());
+ statusbar.topAppWindowChanged(needsMenu);
+ }
+ } catch (RemoteException e) {
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+ });
+ return diff;
+ }
+
+ private int updateLightStatusBarLw(int vis) {
+ WindowState statusColorWin = isStatusBarKeyguard() && !mHideLockScreen
+ ? mStatusBar
+ : mTopFullscreenOpaqueOrDimmingWindowState;
+
+ if (statusColorWin != null) {
+ if (statusColorWin == mTopFullscreenOpaqueWindowState) {
+ // If the top fullscreen-or-dimming window is also the top fullscreen, respect
+ // its light flag.
+ vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null)
+ & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ } else if (statusColorWin != null && statusColorWin.isDimming()) {
+ // Otherwise if it's dimming, clear the light flag.
+ vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ }
+ }
+ return vis;
+ }
+
+ private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
+ // apply translucent bar vis flags
+ WindowState transWin = isStatusBarKeyguard() && !mHideLockScreen
+ ? mStatusBar
+ : mTopFullscreenOpaqueWindowState;
+ vis = mStatusBarController.applyTranslucentFlagLw(transWin, vis, oldVis);
+ vis = mNavigationBarController.applyTranslucentFlagLw(transWin, vis, oldVis);
+
+ // prevent status bar interaction from clearing certain flags
+ boolean statusBarHasFocus = win.getAttrs().type == TYPE_STATUS_BAR;
+ if (statusBarHasFocus && !isStatusBarKeyguard()) {
+ int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_IMMERSIVE
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ if (mHideLockScreen) {
+ flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT;
+ }
+ vis = (vis & ~flags) | (oldVis & flags);
+ }
+
+ if (!areTranslucentBarsAllowed() && transWin != mStatusBar) {
+ vis &= ~(View.NAVIGATION_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSLUCENT
+ | View.SYSTEM_UI_TRANSPARENT);
+ }
+
+ // update status bar
+ boolean immersiveSticky =
+ (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
+ boolean hideStatusBarWM =
+ mTopFullscreenOpaqueWindowState != null &&
+ (PolicyControl.getWindowFlags(mTopFullscreenOpaqueWindowState, null)
+ & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
+ boolean hideStatusBarSysui =
+ (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
+ boolean hideNavBarSysui =
+ (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+
+ boolean transientStatusBarAllowed =
+ mStatusBar != null && (
+ hideStatusBarWM
+ || (hideStatusBarSysui && immersiveSticky)
+ || statusBarHasFocus);
+
+ boolean transientNavBarAllowed =
+ mNavigationBar != null &&
+ hideNavBarSysui && immersiveSticky;
+
+ boolean denyTransientStatus = mStatusBarController.isTransientShowRequested()
+ && !transientStatusBarAllowed && hideStatusBarSysui;
+ boolean denyTransientNav = mNavigationBarController.isTransientShowRequested()
+ && !transientNavBarAllowed;
+ if (denyTransientStatus || denyTransientNav) {
+ // clear the clearable flags instead
+ clearClearableFlagsLw();
+ vis &= ~View.SYSTEM_UI_CLEARABLE_FLAGS;
+ }
+
+ vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis);
+
+ // update navigation bar
+ boolean oldImmersiveMode = isImmersiveMode(oldVis);
+ boolean newImmersiveMode = isImmersiveMode(vis);
+ if (win != null && oldImmersiveMode != newImmersiveMode) {
+ final String pkg = win.getOwningPackage();
+ mImmersiveModeConfirmation.immersiveModeChanged(pkg, newImmersiveMode,
+ isUserSetupComplete());
+ }
+
+ vis = mNavigationBarController.updateVisibilityLw(transientNavBarAllowed, oldVis, vis);
+
+ return vis;
+ }
+
+ private void clearClearableFlagsLw() {
+ int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS;
+ if (newVal != mResettingSystemUiFlags) {
+ mResettingSystemUiFlags = newVal;
+ mWindowManagerFuncs.reevaluateStatusBarVisibility();
+ }
+ }
+
+ private boolean isImmersiveMode(int vis) {
+ final int flags = View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ return mNavigationBar != null
+ && (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
+ && (vis & flags) != 0
+ && canHideNavigationBar();
+ }
+
+ /**
+ * @return whether the navigation or status bar can be made translucent
+ *
+ * This should return true unless touch exploration is not enabled or
+ * R.boolean.config_enableTranslucentDecor is false.
+ */
+ private boolean areTranslucentBarsAllowed() {
+ return mTranslucentDecorEnabled
+ && !mAccessibilityManager.isTouchExplorationEnabled();
+ }
+
+ // Use this instead of checking config_showNavigationBar so that it can be consistently
+ // overridden by qemu.hw.mainkeys in the emulator.
+ @Override
+ public boolean hasNavigationBar() {
+ return mHasNavigationBar;
+ }
+
+ @Override
+ public void setLastInputMethodWindowLw(WindowState ime, WindowState target) {
+ mLastInputMethodWindow = ime;
+ mLastInputMethodTargetWindow = target;
+ }
+
+ @Override
+ public int getInputMethodWindowVisibleHeightLw() {
+ return mDockBottom - mCurBottom;
+ }
+
+ @Override
+ public void setCurrentUserLw(int newUserId) {
+ mCurrentUserId = newUserId;
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.setCurrentUser(newUserId);
+ }
+ if (mStatusBarService != null) {
+ try {
+ mStatusBarService.setCurrentUser(newUserId);
+ } catch (RemoteException e) {
+ // oh well
+ }
+ }
+ setLastInputMethodWindowLw(null, null);
+ }
+
+ @Override
+ public boolean canMagnifyWindow(int windowType) {
+ switch (windowType) {
+ case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
+ case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG:
+ case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
+ case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isTopLevelWindow(int windowType) {
+ if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
+ && windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ return (windowType == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG);
+ }
+ return true;
+ }
+
+ @Override
+ public void dump(String prefix, PrintWriter pw, String[] args) {
+ pw.print(prefix); pw.print("mSafeMode="); pw.print(mSafeMode);
+ pw.print(" mSystemReady="); pw.print(mSystemReady);
+ pw.print(" mSystemBooted="); pw.println(mSystemBooted);
+ pw.print(prefix); pw.print("mLidState="); pw.print(mLidState);
+ pw.print(" mLidOpenRotation="); pw.print(mLidOpenRotation);
+ pw.print(" mCameraLensCoverState="); pw.print(mCameraLensCoverState);
+ pw.print(" mHdmiPlugged="); pw.println(mHdmiPlugged);
+ if (mLastSystemUiFlags != 0 || mResettingSystemUiFlags != 0
+ || mForceClearedSystemUiFlags != 0) {
+ pw.print(prefix); pw.print("mLastSystemUiFlags=0x");
+ pw.print(Integer.toHexString(mLastSystemUiFlags));
+ pw.print(" mResettingSystemUiFlags=0x");
+ pw.print(Integer.toHexString(mResettingSystemUiFlags));
+ pw.print(" mForceClearedSystemUiFlags=0x");
+ pw.println(Integer.toHexString(mForceClearedSystemUiFlags));
+ }
+ if (mLastFocusNeedsMenu) {
+ pw.print(prefix); pw.print("mLastFocusNeedsMenu=");
+ pw.println(mLastFocusNeedsMenu);
+ }
+ pw.print(prefix); pw.print("mWakeGestureEnabledSetting=");
+ pw.println(mWakeGestureEnabledSetting);
+
+ pw.print(prefix); pw.print("mSupportAutoRotation="); pw.println(mSupportAutoRotation);
+ pw.print(prefix); pw.print("mUiMode="); pw.print(mUiMode);
+ pw.print(" mDockMode="); pw.print(mDockMode);
+ pw.print(" mCarDockRotation="); pw.print(mCarDockRotation);
+ pw.print(" mDeskDockRotation="); pw.println(mDeskDockRotation);
+ pw.print(prefix); pw.print("mUserRotationMode="); pw.print(mUserRotationMode);
+ pw.print(" mUserRotation="); pw.print(mUserRotation);
+ pw.print(" mAllowAllRotations="); pw.println(mAllowAllRotations);
+ pw.print(prefix); pw.print("mCurrentAppOrientation="); pw.println(mCurrentAppOrientation);
+ pw.print(prefix); pw.print("mCarDockEnablesAccelerometer=");
+ pw.print(mCarDockEnablesAccelerometer);
+ pw.print(" mDeskDockEnablesAccelerometer=");
+ pw.println(mDeskDockEnablesAccelerometer);
+ pw.print(prefix); pw.print("mLidKeyboardAccessibility=");
+ pw.print(mLidKeyboardAccessibility);
+ pw.print(" mLidNavigationAccessibility="); pw.print(mLidNavigationAccessibility);
+ pw.print(" mLidControlsSleep="); pw.println(mLidControlsSleep);
+ pw.print(prefix);
+ pw.print("mShortPressOnPowerBehavior="); pw.print(mShortPressOnPowerBehavior);
+ pw.print(" mLongPressOnPowerBehavior="); pw.println(mLongPressOnPowerBehavior);
+ pw.print(prefix);
+ pw.print("mDoublePressOnPowerBehavior="); pw.print(mDoublePressOnPowerBehavior);
+ pw.print(" mTriplePressOnPowerBehavior="); pw.println(mTriplePressOnPowerBehavior);
+ pw.print(prefix); pw.print("mHasSoftInput="); pw.println(mHasSoftInput);
+ pw.print(prefix); pw.print("mAwake="); pw.println(mAwake);
+ pw.print(prefix); pw.print("mScreenOnEarly="); pw.print(mScreenOnEarly);
+ pw.print(" mScreenOnFully="); pw.println(mScreenOnFully);
+ pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
+ pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
+ pw.print(prefix); pw.print("mOrientationSensorEnabled=");
+ pw.println(mOrientationSensorEnabled);
+ pw.print(prefix); pw.print("mOverscanScreen=("); pw.print(mOverscanScreenLeft);
+ pw.print(","); pw.print(mOverscanScreenTop);
+ pw.print(") "); pw.print(mOverscanScreenWidth);
+ pw.print("x"); pw.println(mOverscanScreenHeight);
+ if (mOverscanLeft != 0 || mOverscanTop != 0
+ || mOverscanRight != 0 || mOverscanBottom != 0) {
+ pw.print(prefix); pw.print("mOverscan left="); pw.print(mOverscanLeft);
+ pw.print(" top="); pw.print(mOverscanTop);
+ pw.print(" right="); pw.print(mOverscanRight);
+ pw.print(" bottom="); pw.println(mOverscanBottom);
+ }
+ pw.print(prefix); pw.print("mRestrictedOverscanScreen=(");
+ pw.print(mRestrictedOverscanScreenLeft);
+ pw.print(","); pw.print(mRestrictedOverscanScreenTop);
+ pw.print(") "); pw.print(mRestrictedOverscanScreenWidth);
+ pw.print("x"); pw.println(mRestrictedOverscanScreenHeight);
+ pw.print(prefix); pw.print("mUnrestrictedScreen=("); pw.print(mUnrestrictedScreenLeft);
+ pw.print(","); pw.print(mUnrestrictedScreenTop);
+ pw.print(") "); pw.print(mUnrestrictedScreenWidth);
+ pw.print("x"); pw.println(mUnrestrictedScreenHeight);
+ pw.print(prefix); pw.print("mRestrictedScreen=("); pw.print(mRestrictedScreenLeft);
+ pw.print(","); pw.print(mRestrictedScreenTop);
+ pw.print(") "); pw.print(mRestrictedScreenWidth);
+ pw.print("x"); pw.println(mRestrictedScreenHeight);
+ pw.print(prefix); pw.print("mStableFullscreen=("); pw.print(mStableFullscreenLeft);
+ pw.print(","); pw.print(mStableFullscreenTop);
+ pw.print(")-("); pw.print(mStableFullscreenRight);
+ pw.print(","); pw.print(mStableFullscreenBottom); pw.println(")");
+ pw.print(prefix); pw.print("mStable=("); pw.print(mStableLeft);
+ pw.print(","); pw.print(mStableTop);
+ pw.print(")-("); pw.print(mStableRight);
+ pw.print(","); pw.print(mStableBottom); pw.println(")");
+ pw.print(prefix); pw.print("mSystem=("); pw.print(mSystemLeft);
+ pw.print(","); pw.print(mSystemTop);
+ pw.print(")-("); pw.print(mSystemRight);
+ pw.print(","); pw.print(mSystemBottom); pw.println(")");
+ pw.print(prefix); pw.print("mCur=("); pw.print(mCurLeft);
+ pw.print(","); pw.print(mCurTop);
+ pw.print(")-("); pw.print(mCurRight);
+ pw.print(","); pw.print(mCurBottom); pw.println(")");
+ pw.print(prefix); pw.print("mContent=("); pw.print(mContentLeft);
+ pw.print(","); pw.print(mContentTop);
+ pw.print(")-("); pw.print(mContentRight);
+ pw.print(","); pw.print(mContentBottom); pw.println(")");
+ pw.print(prefix); pw.print("mVoiceContent=("); pw.print(mVoiceContentLeft);
+ pw.print(","); pw.print(mVoiceContentTop);
+ pw.print(")-("); pw.print(mVoiceContentRight);
+ pw.print(","); pw.print(mVoiceContentBottom); pw.println(")");
+ pw.print(prefix); pw.print("mDock=("); pw.print(mDockLeft);
+ pw.print(","); pw.print(mDockTop);
+ pw.print(")-("); pw.print(mDockRight);
+ pw.print(","); pw.print(mDockBottom); pw.println(")");
+ pw.print(prefix); pw.print("mDockLayer="); pw.print(mDockLayer);
+ pw.print(" mStatusBarLayer="); pw.println(mStatusBarLayer);
+ pw.print(prefix); pw.print("mShowingLockscreen="); pw.print(mShowingLockscreen);
+ pw.print(" mShowingDream="); pw.print(mShowingDream);
+ pw.print(" mDreamingLockscreen="); pw.print(mDreamingLockscreen);
+ pw.print(" mDreamingSleepToken="); pw.println(mDreamingSleepToken);
+ if (mLastInputMethodWindow != null) {
+ pw.print(prefix); pw.print("mLastInputMethodWindow=");
+ pw.println(mLastInputMethodWindow);
+ }
+ if (mLastInputMethodTargetWindow != null) {
+ pw.print(prefix); pw.print("mLastInputMethodTargetWindow=");
+ pw.println(mLastInputMethodTargetWindow);
+ }
+ if (mStatusBar != null) {
+ pw.print(prefix); pw.print("mStatusBar=");
+ pw.print(mStatusBar); pw.print(" isStatusBarKeyguard=");
+ pw.println(isStatusBarKeyguard());
+ }
+ if (mNavigationBar != null) {
+ pw.print(prefix); pw.print("mNavigationBar=");
+ pw.println(mNavigationBar);
+ }
+ if (mFocusedWindow != null) {
+ pw.print(prefix); pw.print("mFocusedWindow=");
+ pw.println(mFocusedWindow);
+ }
+ if (mFocusedApp != null) {
+ pw.print(prefix); pw.print("mFocusedApp=");
+ pw.println(mFocusedApp);
+ }
+ if (mWinDismissingKeyguard != null) {
+ pw.print(prefix); pw.print("mWinDismissingKeyguard=");
+ pw.println(mWinDismissingKeyguard);
+ }
+ if (mTopFullscreenOpaqueWindowState != null) {
+ pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
+ pw.println(mTopFullscreenOpaqueWindowState);
+ }
+ if (mTopFullscreenOpaqueOrDimmingWindowState != null) {
+ pw.print(prefix); pw.print("mTopFullscreenOpaqueOrDimmingWindowState=");
+ pw.println(mTopFullscreenOpaqueOrDimmingWindowState);
+ }
+ if (mForcingShowNavBar) {
+ pw.print(prefix); pw.print("mForcingShowNavBar=");
+ pw.println(mForcingShowNavBar); pw.print( "mForcingShowNavBarLayer=");
+ pw.println(mForcingShowNavBarLayer);
+ }
+ pw.print(prefix); pw.print("mTopIsFullscreen="); pw.print(mTopIsFullscreen);
+ pw.print(" mHideLockScreen="); pw.println(mHideLockScreen);
+ pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
+ pw.print(" mForceStatusBarFromKeyguard=");
+ pw.println(mForceStatusBarFromKeyguard);
+ pw.print(prefix); pw.print("mDismissKeyguard="); pw.print(mDismissKeyguard);
+ pw.print(" mWinDismissingKeyguard="); pw.print(mWinDismissingKeyguard);
+ pw.print(" mHomePressed="); pw.println(mHomePressed);
+ pw.print(prefix); pw.print("mAllowLockscreenWhenOn="); pw.print(mAllowLockscreenWhenOn);
+ pw.print(" mLockScreenTimeout="); pw.print(mLockScreenTimeout);
+ pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
+ pw.print(prefix); pw.print("mEndcallBehavior="); pw.print(mEndcallBehavior);
+ pw.print(" mIncallPowerBehavior="); pw.print(mIncallPowerBehavior);
+ pw.print(" mLongPressOnHomeBehavior="); pw.println(mLongPressOnHomeBehavior);
+ pw.print(prefix); pw.print("mLandscapeRotation="); pw.print(mLandscapeRotation);
+ pw.print(" mSeascapeRotation="); pw.println(mSeascapeRotation);
+ pw.print(prefix); pw.print("mPortraitRotation="); pw.print(mPortraitRotation);
+ pw.print(" mUpsideDownRotation="); pw.println(mUpsideDownRotation);
+ pw.print(prefix); pw.print("mDemoHdmiRotation="); pw.print(mDemoHdmiRotation);
+ pw.print(" mDemoHdmiRotationLock="); pw.println(mDemoHdmiRotationLock);
+ pw.print(prefix); pw.print("mUndockedHdmiRotation="); pw.println(mUndockedHdmiRotation);
+
+ mGlobalKeyManager.dump(prefix, pw);
+ mStatusBarController.dump(pw, prefix);
+ mNavigationBarController.dump(pw, prefix);
+ PolicyControl.dump(prefix, pw);
+
+ if (mWakeGestureListener != null) {
+ mWakeGestureListener.dump(pw, prefix);
+ }
+ if (mOrientationListener != null) {
+ mOrientationListener.dump(pw, prefix);
+ }
+ if (mBurnInProtectionHelper != null) {
+ mBurnInProtectionHelper.dump(prefix, pw);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/PolicyControl.java b/services/core/java/com/android/server/policy/PolicyControl.java
new file mode 100644
index 0000000..dbafc42
--- /dev/null
+++ b/services/core/java/com/android/server/policy/PolicyControl.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2014 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.policy;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerPolicy.WindowState;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Runtime adjustments applied to the global window policy.
+ *
+ * This includes forcing immersive mode behavior for one or both system bars (based on a package
+ * list) and permanently disabling immersive mode confirmations for specific packages.
+ *
+ * Control by setting {@link Settings.Global.POLICY_CONTROL} to one or more name-value pairs.
+ * e.g.
+ * to force immersive mode everywhere:
+ * "immersive.full=*"
+ * to force transient status for all apps except a specific package:
+ * "immersive.status=apps,-com.package"
+ * to disable the immersive mode confirmations for specific packages:
+ * "immersive.preconfirms=com.package.one,com.package.two"
+ *
+ * Separate multiple name-value pairs with ':'
+ * e.g. "immersive.status=apps:immersive.preconfirms=*"
+ */
+public class PolicyControl {
+ private static String TAG = "PolicyControl";
+ private static boolean DEBUG = false;
+
+ private static final String NAME_IMMERSIVE_FULL = "immersive.full";
+ private static final String NAME_IMMERSIVE_STATUS = "immersive.status";
+ private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation";
+ private static final String NAME_IMMERSIVE_PRECONFIRMATIONS = "immersive.preconfirms";
+
+ private static String sSettingValue;
+ private static Filter sImmersivePreconfirmationsFilter;
+ private static Filter sImmersiveStatusFilter;
+ private static Filter sImmersiveNavigationFilter;
+
+ public static int getSystemUiVisibility(WindowState win, LayoutParams attrs) {
+ attrs = attrs != null ? attrs : win.getAttrs();
+ int vis = win != null ? win.getSystemUiVisibility() : attrs.systemUiVisibility;
+ if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
+ vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.STATUS_BAR_TRANSLUCENT);
+ }
+ if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) {
+ vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+ vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.NAVIGATION_BAR_TRANSLUCENT);
+ }
+ return vis;
+ }
+
+ public static int getWindowFlags(WindowState win, LayoutParams attrs) {
+ attrs = attrs != null ? attrs : win.getAttrs();
+ int flags = attrs.flags;
+ if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
+ flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
+ flags &= ~(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
+ | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+ }
+ if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) {
+ flags &= ~WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+ }
+ return flags;
+ }
+
+ public static int adjustClearableFlags(WindowState win, int clearableFlags) {
+ final LayoutParams attrs = win != null ? win.getAttrs() : null;
+ if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
+ clearableFlags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
+ }
+ return clearableFlags;
+ }
+
+ public static boolean disableImmersiveConfirmation(String pkg) {
+ return (sImmersivePreconfirmationsFilter != null
+ && sImmersivePreconfirmationsFilter.matches(pkg))
+ || ActivityManager.isRunningInTestHarness();
+ }
+
+ public static void reloadFromSetting(Context context) {
+ if (DEBUG) Slog.d(TAG, "reloadFromSetting()");
+ String value = null;
+ try {
+ value = Settings.Global.getStringForUser(context.getContentResolver(),
+ Settings.Global.POLICY_CONTROL,
+ UserHandle.USER_CURRENT);
+ if (sSettingValue != null && sSettingValue.equals(value)) return;
+ setFilters(value);
+ sSettingValue = value;
+ } catch (Throwable t) {
+ Slog.w(TAG, "Error loading policy control, value=" + value, t);
+ }
+ }
+
+ public static void dump(String prefix, PrintWriter pw) {
+ dump("sImmersiveStatusFilter", sImmersiveStatusFilter, prefix, pw);
+ dump("sImmersiveNavigationFilter", sImmersiveNavigationFilter, prefix, pw);
+ dump("sImmersivePreconfirmationsFilter", sImmersivePreconfirmationsFilter, prefix, pw);
+ }
+
+ private static void dump(String name, Filter filter, String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("PolicyControl."); pw.print(name); pw.print('=');
+ if (filter == null) {
+ pw.println("null");
+ } else {
+ filter.dump(pw); pw.println();
+ }
+ }
+
+ private static void setFilters(String value) {
+ if (DEBUG) Slog.d(TAG, "setFilters: " + value);
+ sImmersiveStatusFilter = null;
+ sImmersiveNavigationFilter = null;
+ sImmersivePreconfirmationsFilter = null;
+ if (value != null) {
+ String[] nvps = value.split(":");
+ for (String nvp : nvps) {
+ int i = nvp.indexOf('=');
+ if (i == -1) continue;
+ String n = nvp.substring(0, i);
+ String v = nvp.substring(i + 1);
+ if (n.equals(NAME_IMMERSIVE_FULL)) {
+ Filter f = Filter.parse(v);
+ sImmersiveStatusFilter = sImmersiveNavigationFilter = f;
+ if (sImmersivePreconfirmationsFilter == null) {
+ sImmersivePreconfirmationsFilter = f;
+ }
+ } else if (n.equals(NAME_IMMERSIVE_STATUS)) {
+ Filter f = Filter.parse(v);
+ sImmersiveStatusFilter = f;
+ } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) {
+ Filter f = Filter.parse(v);
+ sImmersiveNavigationFilter = f;
+ if (sImmersivePreconfirmationsFilter == null) {
+ sImmersivePreconfirmationsFilter = f;
+ }
+ } else if (n.equals(NAME_IMMERSIVE_PRECONFIRMATIONS)) {
+ Filter f = Filter.parse(v);
+ sImmersivePreconfirmationsFilter = f;
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter);
+ Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter);
+ Slog.d(TAG, "immersivePreconfirmationsFilter: " + sImmersivePreconfirmationsFilter);
+ }
+ }
+
+ private static class Filter {
+ private static final String ALL = "*";
+ private static final String APPS = "apps";
+
+ private final ArraySet<String> mWhitelist;
+ private final ArraySet<String> mBlacklist;
+
+ private Filter(ArraySet<String> whitelist, ArraySet<String> blacklist) {
+ mWhitelist = whitelist;
+ mBlacklist = blacklist;
+ }
+
+ boolean matches(LayoutParams attrs) {
+ if (attrs == null) return false;
+ boolean isApp = attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+ && attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+ if (isApp && mBlacklist.contains(APPS)) return false;
+ if (onBlacklist(attrs.packageName)) return false;
+ if (isApp && mWhitelist.contains(APPS)) return true;
+ return onWhitelist(attrs.packageName);
+ }
+
+ boolean matches(String packageName) {
+ return !onBlacklist(packageName) && onWhitelist(packageName);
+ }
+
+ private boolean onBlacklist(String packageName) {
+ return mBlacklist.contains(packageName) || mBlacklist.contains(ALL);
+ }
+
+ private boolean onWhitelist(String packageName) {
+ return mWhitelist.contains(ALL) || mWhitelist.contains(packageName);
+ }
+
+ void dump(PrintWriter pw) {
+ pw.print("Filter[");
+ dump("whitelist", mWhitelist, pw); pw.print(',');
+ dump("blacklist", mBlacklist, pw); pw.print(']');
+ }
+
+ private void dump(String name, ArraySet<String> set, PrintWriter pw) {
+ pw.print(name); pw.print("=(");
+ final int n = set.size();
+ for (int i = 0; i < n; i++) {
+ if (i > 0) pw.print(',');
+ pw.print(set.valueAt(i));
+ }
+ pw.print(')');
+ }
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ dump(new PrintWriter(sw, true));
+ return sw.toString();
+ }
+
+ // value = comma-delimited list of tokens, where token = (package name|apps|*)
+ // e.g. "com.package1", or "apps, com.android.keyguard" or "*"
+ static Filter parse(String value) {
+ if (value == null) return null;
+ ArraySet<String> whitelist = new ArraySet<String>();
+ ArraySet<String> blacklist = new ArraySet<String>();
+ for (String token : value.split(",")) {
+ token = token.trim();
+ if (token.startsWith("-") && token.length() > 1) {
+ token = token.substring(1);
+ blacklist.add(token);
+ } else {
+ whitelist.add(token);
+ }
+ }
+ return new Filter(whitelist, blacklist);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/RecentApplicationsBackground.java b/services/core/java/com/android/server/policy/RecentApplicationsBackground.java
new file mode 100644
index 0000000..694a110
--- /dev/null
+++ b/services/core/java/com/android/server/policy/RecentApplicationsBackground.java
@@ -0,0 +1,157 @@
+/*
+ * 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.policy;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * A vertical linear layout. However, instead of drawing the background
+ * behnd the items, it draws the background outside the items based on the
+ * padding. If there isn't enough room to draw both, it clips the background
+ * instead of the contents.
+ */
+public class RecentApplicationsBackground extends LinearLayout {
+ private static final String TAG = "RecentApplicationsBackground";
+
+ private boolean mBackgroundSizeChanged;
+ private Drawable mBackground;
+ private Rect mTmp0 = new Rect();
+ private Rect mTmp1 = new Rect();
+
+ public RecentApplicationsBackground(Context context) {
+ this(context, null);
+ init();
+ }
+
+ public RecentApplicationsBackground(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ mBackground = getBackground();
+ setBackgroundDrawable(null);
+ setPadding(0, 0, 0, 0);
+ setGravity(Gravity.CENTER);
+ }
+
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ setWillNotDraw(false);
+ if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
+ mBackgroundSizeChanged = true;
+ }
+ return super.setFrame(left, top, right, bottom);
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mBackground || super.verifyDrawable(who);
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ if (mBackground != null) mBackground.jumpToCurrentState();
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ Drawable d = mBackground;
+ if (d != null && d.isStateful()) {
+ d.setState(getDrawableState());
+ }
+ super.drawableStateChanged();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final Drawable background = mBackground;
+ if (background != null) {
+ if (mBackgroundSizeChanged) {
+ mBackgroundSizeChanged = false;
+ Rect chld = mTmp0;
+ Rect bkg = mTmp1;
+ mBackground.getPadding(bkg);
+ getChildBounds(chld);
+ // This doesn't clamp to this view's bounds, which is what we want,
+ // so that the drawing is clipped.
+ final int top = chld.top - bkg.top;
+ final int bottom = chld.bottom + bkg.bottom;
+ // The background here is a gradient that wants to
+ // extend the full width of the screen (whatever that
+ // may be).
+ int left, right;
+ if (false) {
+ // This limits the width of the drawable.
+ left = chld.left - bkg.left;
+ right = chld.right + bkg.right;
+ } else {
+ // This expands it to full width.
+ left = 0;
+ right = getRight();
+ }
+ background.setBounds(left, top, right, bottom);
+ }
+ }
+ mBackground.draw(canvas);
+
+ if (false) {
+ android.graphics.Paint p = new android.graphics.Paint();
+ p.setColor(0x88ffff00);
+ canvas.drawRect(background.getBounds(), p);
+ }
+ canvas.drawARGB((int)(0.75*0xff), 0, 0, 0);
+
+ super.draw(canvas);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mBackground.setCallback(this);
+ setWillNotDraw(false);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mBackground.setCallback(null);
+ }
+
+ private void getChildBounds(Rect r) {
+ r.left = r.top = Integer.MAX_VALUE;
+ r.bottom = r.right = Integer.MIN_VALUE;
+ final int N = getChildCount();
+ for (int i=0; i<N; i++) {
+ View v = getChildAt(i);
+ if (v.getVisibility() == View.VISIBLE) {
+ r.left = Math.min(r.left, v.getLeft());
+ r.top = Math.min(r.top, v.getTop());
+ r.right = Math.max(r.right, v.getRight());
+ r.bottom = Math.max(r.bottom, v.getBottom());
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java
new file mode 100644
index 0000000..76f56bc
--- /dev/null
+++ b/services/core/java/com/android/server/policy/ShortcutManager.java
@@ -0,0 +1,177 @@
+/*
+ * 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.policy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Manages quick launch shortcuts by:
+ * <li> Keeping the local copy in sync with the database (this is an observer)
+ * <li> Returning a shortcut-matching intent to clients
+ */
+class ShortcutManager {
+ private static final String TAG = "ShortcutManager";
+
+ private static final String TAG_BOOKMARKS = "bookmarks";
+ private static final String TAG_BOOKMARK = "bookmark";
+
+ private static final String ATTRIBUTE_PACKAGE = "package";
+ private static final String ATTRIBUTE_CLASS = "class";
+ private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+ private static final String ATTRIBUTE_CATEGORY = "category";
+
+ private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>();
+
+ private final Context mContext;
+
+ public ShortcutManager(Context context) {
+ mContext = context;
+ loadShortcuts();
+ }
+
+ /**
+ * Gets the shortcut intent for a given keycode+modifier. Make sure you
+ * strip whatever modifier is used for invoking shortcuts (for example,
+ * if 'Sym+A' should invoke a shortcut on 'A', you should strip the
+ * 'Sym' bit from the modifiers before calling this method.
+ * <p>
+ * This will first try an exact match (with modifiers), and then try a
+ * match without modifiers (primary character on a key).
+ *
+ * @param kcm The key character map of the device on which the key was pressed.
+ * @param keyCode The key code.
+ * @param metaState The meta state, omitting any modifiers that were used
+ * to invoke the shortcut.
+ * @return The intent that matches the shortcut, or null if not found.
+ */
+ public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) {
+ ShortcutInfo shortcut = null;
+
+ // First try the exact keycode (with modifiers).
+ int shortcutChar = kcm.get(keyCode, metaState);
+ if (shortcutChar != 0) {
+ shortcut = mShortcuts.get(shortcutChar);
+ }
+
+ // Next try the primary character on that key.
+ if (shortcut == null) {
+ shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+ if (shortcutChar != 0) {
+ shortcut = mShortcuts.get(shortcutChar);
+ }
+ }
+
+ return (shortcut != null) ? shortcut.intent : null;
+ }
+
+ private void loadShortcuts() {
+ PackageManager packageManager = mContext.getPackageManager();
+ try {
+ XmlResourceParser parser = mContext.getResources().getXml(
+ com.android.internal.R.xml.bookmarks);
+ XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+
+ if (!TAG_BOOKMARK.equals(parser.getName())) {
+ break;
+ }
+
+ String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+ String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+ String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+ String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+
+ if (TextUtils.isEmpty(shortcutName)) {
+ Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className);
+ continue;
+ }
+
+ final int shortcutChar = shortcutName.charAt(0);
+
+ final Intent intent;
+ final String title;
+ if (packageName != null && className != null) {
+ ActivityInfo info = null;
+ ComponentName componentName = new ComponentName(packageName, className);
+ try {
+ info = packageManager.getActivityInfo(componentName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ String[] packages = packageManager.canonicalToCurrentPackageNames(
+ new String[] { packageName });
+ componentName = new ComponentName(packages[0], className);
+ try {
+ info = packageManager.getActivityInfo(componentName, 0);
+ } catch (PackageManager.NameNotFoundException e1) {
+ Log.w(TAG, "Unable to add bookmark: " + packageName
+ + "/" + className, e);
+ continue;
+ }
+ }
+
+ intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(componentName);
+ title = info.loadLabel(packageManager).toString();
+ } else if (categoryName != null) {
+ intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
+ title = "";
+ } else {
+ Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
+ + ": missing package/class or category attributes");
+ continue;
+ }
+
+ ShortcutInfo shortcut = new ShortcutInfo(title, intent);
+ mShortcuts.put(shortcutChar, shortcut);
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Got exception parsing bookmarks.", e);
+ } catch (IOException e) {
+ Log.w(TAG, "Got exception parsing bookmarks.", e);
+ }
+ }
+
+ private static final class ShortcutInfo {
+ public final String title;
+ public final Intent intent;
+
+ public ShortcutInfo(String title, Intent intent) {
+ this.title = title;
+ this.intent = intent;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java
new file mode 100644
index 0000000..d1b50da
--- /dev/null
+++ b/services/core/java/com/android/server/policy/StatusBarController.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2015 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.policy;
+
+import android.app.StatusBarManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.Interpolator;
+import android.view.animation.TranslateAnimation;
+
+import com.android.internal.statusbar.IStatusBarService;
+
+import static android.view.WindowManagerInternal.*;
+
+/**
+ * Implements status bar specific behavior.
+ */
+public class StatusBarController extends BarController {
+
+ private static final long TRANSITION_DURATION = 120L;
+
+ private final AppTransitionListener mAppTransitionListener
+ = new AppTransitionListener() {
+
+ @Override
+ public void onAppTransitionPendingLocked() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.appTransitionPending();
+ }
+ } catch (RemoteException e) {
+ Slog.e(mTag, "RemoteException when app transition is pending", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAppTransitionStartingLocked(IBinder openToken, IBinder closeToken,
+ final Animation openAnimation, final Animation closeAnimation) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ long startTime = calculateStatusBarTransitionStartTime(openAnimation,
+ closeAnimation);
+ statusbar.appTransitionStarting(startTime, TRANSITION_DURATION);
+ }
+ } catch (RemoteException e) {
+ Slog.e(mTag, "RemoteException when app transition is starting", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAppTransitionCancelledLocked() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ IStatusBarService statusbar = getStatusBarService();
+ if (statusbar != null) {
+ statusbar.appTransitionCancelled();
+ }
+ } catch (RemoteException e) {
+ Slog.e(mTag, "RemoteException when app transition is cancelled", e);
+ // re-acquire status bar service next time it is needed.
+ mStatusBarService = null;
+ }
+ }
+ });
+ }
+ };
+
+ public StatusBarController() {
+ super("StatusBar",
+ View.STATUS_BAR_TRANSIENT,
+ View.STATUS_BAR_UNHIDE,
+ View.STATUS_BAR_TRANSLUCENT,
+ StatusBarManager.WINDOW_STATUS_BAR,
+ WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ }
+
+ public AppTransitionListener getAppTransitionListener() {
+ return mAppTransitionListener;
+ }
+
+ /**
+ * For a given app transition with {@code openAnimation} and {@code closeAnimation}, this
+ * calculates the timings for the corresponding status bar transition.
+ *
+ * @return the desired start time of the status bar transition, in uptime millis
+ */
+ private long calculateStatusBarTransitionStartTime(Animation openAnimation,
+ Animation closeAnimation) {
+ if (openAnimation != null && closeAnimation != null) {
+ TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation);
+ TranslateAnimation closeTranslateAnimation = findTranslateAnimation(closeAnimation);
+ if (openTranslateAnimation != null) {
+
+ // Some interpolators are extremely quickly mostly finished, but not completely. For
+ // our purposes, we need to find the fraction for which ther interpolator is mostly
+ // there, and use that value for the calculation.
+ float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
+ return SystemClock.uptimeMillis()
+ + openTranslateAnimation.getStartOffset()
+ + (long)(openTranslateAnimation.getDuration()*t) - TRANSITION_DURATION;
+ } else if (closeTranslateAnimation != null) {
+ return SystemClock.uptimeMillis();
+ } else {
+ return SystemClock.uptimeMillis();
+ }
+ } else {
+ return SystemClock.uptimeMillis();
+ }
+ }
+
+ /**
+ * Tries to find a {@link TranslateAnimation} inside the {@code animation}.
+ *
+ * @return the found animation, {@code null} otherwise
+ */
+ private TranslateAnimation findTranslateAnimation(Animation animation) {
+ if (animation instanceof TranslateAnimation) {
+ return (TranslateAnimation) animation;
+ } else if (animation instanceof AnimationSet) {
+ AnimationSet set = (AnimationSet) animation;
+ for (int i = 0; i < set.getAnimations().size(); i++) {
+ Animation a = set.getAnimations().get(i);
+ if (a instanceof TranslateAnimation) {
+ return (TranslateAnimation) a;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
+ * {@code interpolator(t + eps) > 0.99}.
+ */
+ private float findAlmostThereFraction(Interpolator interpolator) {
+ float val = 0.5f;
+ float adj = 0.25f;
+ while (adj >= 0.01f) {
+ if (interpolator.getInterpolation(val) < 0.99f) {
+ val += adj;
+ } else {
+ val -= adj;
+ }
+ adj /= 2;
+ }
+ return val;
+ }
+}
diff --git a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
new file mode 100644
index 0000000..627b328
--- /dev/null
+++ b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
@@ -0,0 +1,201 @@
+/*
+ * 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.policy;
+
+import android.content.Context;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicy.PointerEventListener;
+
+/*
+ * Listens for system-wide input gestures, firing callbacks when detected.
+ * @hide
+ */
+public class SystemGesturesPointerEventListener implements PointerEventListener {
+ private static final String TAG = "SystemGestures";
+ private static final boolean DEBUG = false;
+ private static final long SWIPE_TIMEOUT_MS = 500;
+ private static final int MAX_TRACKED_POINTERS = 32; // max per input system
+ private static final int UNTRACKED_POINTER = -1;
+
+ private static final int SWIPE_NONE = 0;
+ private static final int SWIPE_FROM_TOP = 1;
+ private static final int SWIPE_FROM_BOTTOM = 2;
+ private static final int SWIPE_FROM_RIGHT = 3;
+
+ private final int mSwipeStartThreshold;
+ private final int mSwipeDistanceThreshold;
+ private final Callbacks mCallbacks;
+ private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
+ private final float[] mDownX = new float[MAX_TRACKED_POINTERS];
+ private final float[] mDownY = new float[MAX_TRACKED_POINTERS];
+ private final long[] mDownTime = new long[MAX_TRACKED_POINTERS];
+
+ int screenHeight;
+ int screenWidth;
+ private int mDownPointers;
+ private boolean mSwipeFireable;
+ private boolean mDebugFireable;
+
+ public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) {
+ mCallbacks = checkNull("callbacks", callbacks);
+ mSwipeStartThreshold = checkNull("context", context).getResources()
+ .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+ mSwipeDistanceThreshold = mSwipeStartThreshold;
+ if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold
+ + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
+ }
+
+ private static <T> T checkNull(String name, T arg) {
+ if (arg == null) {
+ throw new IllegalArgumentException(name + " must not be null");
+ }
+ return arg;
+ }
+
+ @Override
+ public void onPointerEvent(MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mSwipeFireable = true;
+ mDebugFireable = true;
+ mDownPointers = 0;
+ captureDown(event, 0);
+ mCallbacks.onDown();
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ captureDown(event, event.getActionIndex());
+ if (mDebugFireable) {
+ mDebugFireable = event.getPointerCount() < 5;
+ if (!mDebugFireable) {
+ if (DEBUG) Slog.d(TAG, "Firing debug");
+ mCallbacks.onDebug();
+ }
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mSwipeFireable) {
+ final int swipe = detectSwipe(event);
+ mSwipeFireable = swipe == SWIPE_NONE;
+ if (swipe == SWIPE_FROM_TOP) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
+ mCallbacks.onSwipeFromTop();
+ } else if (swipe == SWIPE_FROM_BOTTOM) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
+ mCallbacks.onSwipeFromBottom();
+ } else if (swipe == SWIPE_FROM_RIGHT) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
+ mCallbacks.onSwipeFromRight();
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mSwipeFireable = false;
+ mDebugFireable = false;
+ mCallbacks.onUpOrCancel();
+ break;
+ default:
+ if (DEBUG) Slog.d(TAG, "Ignoring " + event);
+ }
+ }
+
+ private void captureDown(MotionEvent event, int pointerIndex) {
+ final int pointerId = event.getPointerId(pointerIndex);
+ final int i = findIndex(pointerId);
+ if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
+ " down pointerIndex=" + pointerIndex + " trackingIndex=" + i);
+ if (i != UNTRACKED_POINTER) {
+ mDownX[i] = event.getX(pointerIndex);
+ mDownY[i] = event.getY(pointerIndex);
+ mDownTime[i] = event.getEventTime();
+ if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
+ " down x=" + mDownX[i] + " y=" + mDownY[i]);
+ }
+ }
+
+ private int findIndex(int pointerId) {
+ for (int i = 0; i < mDownPointers; i++) {
+ if (mDownPointerId[i] == pointerId) {
+ return i;
+ }
+ }
+ if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) {
+ return UNTRACKED_POINTER;
+ }
+ mDownPointerId[mDownPointers++] = pointerId;
+ return mDownPointers - 1;
+ }
+
+ private int detectSwipe(MotionEvent move) {
+ final int historySize = move.getHistorySize();
+ final int pointerCount = move.getPointerCount();
+ for (int p = 0; p < pointerCount; p++) {
+ final int pointerId = move.getPointerId(p);
+ final int i = findIndex(pointerId);
+ if (i != UNTRACKED_POINTER) {
+ for (int h = 0; h < historySize; h++) {
+ final long time = move.getHistoricalEventTime(h);
+ final float x = move.getHistoricalX(p, h);
+ final float y = move.getHistoricalY(p, h);
+ final int swipe = detectSwipe(i, time, x, y);
+ if (swipe != SWIPE_NONE) {
+ return swipe;
+ }
+ }
+ final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p));
+ if (swipe != SWIPE_NONE) {
+ return swipe;
+ }
+ }
+ }
+ return SWIPE_NONE;
+ }
+
+ private int detectSwipe(int i, long time, float x, float y) {
+ final float fromX = mDownX[i];
+ final float fromY = mDownY[i];
+ final long elapsed = time - mDownTime[i];
+ if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
+ + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
+ if (fromY <= mSwipeStartThreshold
+ && y > fromY + mSwipeDistanceThreshold
+ && elapsed < SWIPE_TIMEOUT_MS) {
+ return SWIPE_FROM_TOP;
+ }
+ if (fromY >= screenHeight - mSwipeStartThreshold
+ && y < fromY - mSwipeDistanceThreshold
+ && elapsed < SWIPE_TIMEOUT_MS) {
+ return SWIPE_FROM_BOTTOM;
+ }
+ if (fromX >= screenWidth - mSwipeStartThreshold
+ && x < fromX - mSwipeDistanceThreshold
+ && elapsed < SWIPE_TIMEOUT_MS) {
+ return SWIPE_FROM_RIGHT;
+ }
+ return SWIPE_NONE;
+ }
+
+ interface Callbacks {
+ void onSwipeFromTop();
+ void onSwipeFromBottom();
+ void onSwipeFromRight();
+ void onDown();
+ void onUpOrCancel();
+ void onDebug();
+ }
+}
diff --git a/services/core/java/com/android/server/policy/WakeGestureListener.java b/services/core/java/com/android/server/policy/WakeGestureListener.java
new file mode 100644
index 0000000..1d5d7ba
--- /dev/null
+++ b/services/core/java/com/android/server/policy/WakeGestureListener.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 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.policy;
+
+import android.os.Handler;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+
+import java.io.PrintWriter;
+
+/**
+ * Watches for wake gesture sensor events then invokes the listener.
+ */
+public abstract class WakeGestureListener {
+ private static final String TAG = "WakeGestureListener";
+
+ private final SensorManager mSensorManager;
+ private final Handler mHandler;
+
+ private final Object mLock = new Object();
+
+ private boolean mTriggerRequested;
+ private Sensor mSensor;
+
+ public WakeGestureListener(Context context, Handler handler) {
+ mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mHandler = handler;
+
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_WAKE_GESTURE);
+ }
+
+ public abstract void onWakeUp();
+
+ public boolean isSupported() {
+ synchronized (mLock) {
+ return mSensor != null;
+ }
+ }
+
+ public void requestWakeUpTrigger() {
+ synchronized (mLock) {
+ if (mSensor != null && !mTriggerRequested) {
+ mTriggerRequested = true;
+ mSensorManager.requestTriggerSensor(mListener, mSensor);
+ }
+ }
+ }
+
+ public void cancelWakeUpTrigger() {
+ synchronized (mLock) {
+ if (mSensor != null && mTriggerRequested) {
+ mTriggerRequested = false;
+ mSensorManager.cancelTriggerSensor(mListener, mSensor);
+ }
+ }
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.println(prefix + TAG);
+ prefix += " ";
+ pw.println(prefix + "mTriggerRequested=" + mTriggerRequested);
+ pw.println(prefix + "mSensor=" + mSensor);
+ }
+ }
+
+ private final TriggerEventListener mListener = new TriggerEventListener() {
+ @Override
+ public void onTrigger(TriggerEvent event) {
+ synchronized (mLock) {
+ mTriggerRequested = false;
+ mHandler.post(mWakeUpRunnable);
+ }
+ }
+ };
+
+ private final Runnable mWakeUpRunnable = new Runnable() {
+ @Override
+ public void run() {
+ onWakeUp();
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
new file mode 100644
index 0000000..c8fd82e
--- /dev/null
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -0,0 +1,847 @@
+/*
+ * 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.policy;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+
+/**
+ * A special helper class used by the WindowManager
+ * for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ *
+ * NOTE: If changing anything here, please run the API demo
+ * "App/Activity/Screen Orientation" to ensure that all orientation
+ * modes still work correctly.
+ *
+ * You can also visualize the behavior of the WindowOrientationListener.
+ * Refer to frameworks/base/tools/orientationplot/README.txt for details.
+ *
+ * @hide
+ */
+public abstract class WindowOrientationListener {
+ private static final String TAG = "WindowOrientationListener";
+ private static final boolean LOG = SystemProperties.getBoolean(
+ "debug.orientation.log", false);
+
+ private static final boolean USE_GRAVITY_SENSOR = false;
+
+ private Handler mHandler;
+ private SensorManager mSensorManager;
+ private boolean mEnabled;
+ private int mRate;
+ private Sensor mSensor;
+ private SensorEventListenerImpl mSensorEventListener;
+ private int mCurrentRotation = -1;
+
+ private final Object mLock = new Object();
+
+ /**
+ * Creates a new WindowOrientationListener.
+ *
+ * @param context for the WindowOrientationListener.
+ * @param handler Provides the Looper for receiving sensor updates.
+ */
+ public WindowOrientationListener(Context context, Handler handler) {
+ this(context, handler, SensorManager.SENSOR_DELAY_UI);
+ }
+
+ /**
+ * Creates a new WindowOrientationListener.
+ *
+ * @param context for the WindowOrientationListener.
+ * @param handler Provides the Looper for receiving sensor updates.
+ * @param rate at which sensor events are processed (see also
+ * {@link android.hardware.SensorManager SensorManager}). Use the default
+ * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
+ * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+ *
+ * This constructor is private since no one uses it.
+ */
+ private WindowOrientationListener(Context context, Handler handler, int rate) {
+ mHandler = handler;
+ mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mRate = rate;
+ mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
+ ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
+ if (mSensor != null) {
+ // Create listener only if sensors do exist
+ mSensorEventListener = new SensorEventListenerImpl();
+ }
+ }
+
+ /**
+ * Enables the WindowOrientationListener so it will monitor the sensor and call
+ * {@link #onProposedRotationChanged(int)} when the device orientation changes.
+ */
+ public void enable() {
+ synchronized (mLock) {
+ if (mSensor == null) {
+ Log.w(TAG, "Cannot detect sensors. Not enabled");
+ return;
+ }
+ if (mEnabled == false) {
+ if (LOG) {
+ Log.d(TAG, "WindowOrientationListener enabled");
+ }
+ mSensorEventListener.resetLocked();
+ mSensorManager.registerListener(mSensorEventListener, mSensor, mRate, mHandler);
+ mEnabled = true;
+ }
+ }
+ }
+
+ /**
+ * Disables the WindowOrientationListener.
+ */
+ public void disable() {
+ synchronized (mLock) {
+ if (mSensor == null) {
+ Log.w(TAG, "Cannot detect sensors. Invalid disable");
+ return;
+ }
+ if (mEnabled == true) {
+ if (LOG) {
+ Log.d(TAG, "WindowOrientationListener disabled");
+ }
+ mSensorManager.unregisterListener(mSensorEventListener);
+ mEnabled = false;
+ }
+ }
+ }
+
+ public void onTouchStart() {
+ synchronized (mLock) {
+ if (mSensorEventListener != null) {
+ mSensorEventListener.onTouchStartLocked();
+ }
+ }
+ }
+
+ public void onTouchEnd() {
+ long whenElapsedNanos = SystemClock.elapsedRealtimeNanos();
+
+ synchronized (mLock) {
+ if (mSensorEventListener != null) {
+ mSensorEventListener.onTouchEndLocked(whenElapsedNanos);
+ }
+ }
+ }
+
+ /**
+ * Sets the current rotation.
+ *
+ * @param rotation The current rotation.
+ */
+ public void setCurrentRotation(int rotation) {
+ synchronized (mLock) {
+ mCurrentRotation = rotation;
+ }
+ }
+
+ /**
+ * Gets the proposed rotation.
+ *
+ * This method only returns a rotation if the orientation listener is certain
+ * of its proposal. If the rotation is indeterminate, returns -1.
+ *
+ * @return The proposed rotation, or -1 if unknown.
+ */
+ public int getProposedRotation() {
+ synchronized (mLock) {
+ if (mEnabled) {
+ return mSensorEventListener.getProposedRotationLocked();
+ }
+ return -1;
+ }
+ }
+
+ /**
+ * Returns true if sensor is enabled and false otherwise
+ */
+ public boolean canDetectOrientation() {
+ synchronized (mLock) {
+ return mSensor != null;
+ }
+ }
+
+ /**
+ * Called when the rotation view of the device has changed.
+ *
+ * This method is called whenever the orientation becomes certain of an orientation.
+ * It is called each time the orientation determination transitions from being
+ * uncertain to being certain again, even if it is the same orientation as before.
+ *
+ * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
+ * @see android.view.Surface
+ */
+ public abstract void onProposedRotationChanged(int rotation);
+
+ public void dump(PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.println(prefix + TAG);
+ prefix += " ";
+ pw.println(prefix + "mEnabled=" + mEnabled);
+ pw.println(prefix + "mCurrentRotation=" + mCurrentRotation);
+ pw.println(prefix + "mSensor=" + mSensor);
+ pw.println(prefix + "mRate=" + mRate);
+
+ if (mSensorEventListener != null) {
+ mSensorEventListener.dumpLocked(pw, prefix);
+ }
+ }
+ }
+
+ /**
+ * This class filters the raw accelerometer data and tries to detect actual changes in
+ * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
+ * but here's the outline:
+ *
+ * - Low-pass filter the accelerometer vector in cartesian coordinates. We do it in
+ * cartesian space because the orientation calculations are sensitive to the
+ * absolute magnitude of the acceleration. In particular, there are singularities
+ * in the calculation as the magnitude approaches 0. By performing the low-pass
+ * filtering early, we can eliminate most spurious high-frequency impulses due to noise.
+ *
+ * - Convert the acceleromter vector from cartesian to spherical coordinates.
+ * Since we're dealing with rotation of the device, this is the sensible coordinate
+ * system to work in. The zenith direction is the Z-axis, the direction the screen
+ * is facing. The radial distance is referred to as the magnitude below.
+ * The elevation angle is referred to as the "tilt" below.
+ * The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
+ * the Y-axis).
+ * See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
+ *
+ * - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing.
+ * The orientation angle is not meaningful when the device is nearly horizontal.
+ * The tilt angle thresholds are set differently for each orientation and different
+ * limits are applied when the device is facing down as opposed to when it is facing
+ * forward or facing up.
+ *
+ * - When the orientation angle reaches a certain threshold, consider transitioning
+ * to the corresponding orientation. These thresholds have some hysteresis built-in
+ * to avoid oscillations between adjacent orientations.
+ *
+ * - Wait for the device to settle for a little bit. Once that happens, issue the
+ * new orientation proposal.
+ *
+ * Details are explained inline.
+ *
+ * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
+ * signal processing background.
+ */
+ final class SensorEventListenerImpl implements SensorEventListener {
+ // We work with all angles in degrees in this class.
+ private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
+
+ // Number of nanoseconds per millisecond.
+ private static final long NANOS_PER_MS = 1000000;
+
+ // Indices into SensorEvent.values for the accelerometer sensor.
+ private static final int ACCELEROMETER_DATA_X = 0;
+ private static final int ACCELEROMETER_DATA_Y = 1;
+ private static final int ACCELEROMETER_DATA_Z = 2;
+
+ // The minimum amount of time that a predicted rotation must be stable before it
+ // is accepted as a valid rotation proposal. This value can be quite small because
+ // the low-pass filter already suppresses most of the noise so we're really just
+ // looking for quick confirmation that the last few samples are in agreement as to
+ // the desired orientation.
+ private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS;
+
+ // The minimum amount of time that must have elapsed since the device last exited
+ // the flat state (time since it was picked up) before the proposed rotation
+ // can change.
+ private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS;
+
+ // The minimum amount of time that must have elapsed since the device stopped
+ // swinging (time since device appeared to be in the process of being put down
+ // or put away into a pocket) before the proposed rotation can change.
+ private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS;
+
+ // The minimum amount of time that must have elapsed since the device stopped
+ // undergoing external acceleration before the proposed rotation can change.
+ private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS =
+ 500 * NANOS_PER_MS;
+
+ // The minimum amount of time that must have elapsed since the screen was last touched
+ // before the proposed rotation can change.
+ private static final long PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS =
+ 500 * NANOS_PER_MS;
+
+ // If the tilt angle remains greater than the specified angle for a minimum of
+ // the specified time, then the device is deemed to be lying flat
+ // (just chillin' on a table).
+ private static final float FLAT_ANGLE = 75;
+ private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS;
+
+ // If the tilt angle has increased by at least delta degrees within the specified amount
+ // of time, then the device is deemed to be swinging away from the user
+ // down towards flat (tilt = 90).
+ private static final float SWING_AWAY_ANGLE_DELTA = 20;
+ private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS;
+
+ // The maximum sample inter-arrival time in milliseconds.
+ // If the acceleration samples are further apart than this amount in time, we reset the
+ // state of the low-pass filter and orientation properties. This helps to handle
+ // boundary conditions when the device is turned on, wakes from suspend or there is
+ // a significant gap in samples.
+ private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS;
+
+ // The acceleration filter time constant.
+ //
+ // This time constant is used to tune the acceleration filter such that
+ // impulses and vibrational noise (think car dock) is suppressed before we
+ // try to calculate the tilt and orientation angles.
+ //
+ // The filter time constant is related to the filter cutoff frequency, which is the
+ // frequency at which signals are attenuated by 3dB (half the passband power).
+ // Each successive octave beyond this frequency is attenuated by an additional 6dB.
+ //
+ // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz
+ // is given by Fc = 1 / (2pi * t).
+ //
+ // The higher the time constant, the lower the cutoff frequency, so more noise
+ // will be suppressed.
+ //
+ // Filtering adds latency proportional the time constant (inversely proportional
+ // to the cutoff frequency) so we don't want to make the time constant too
+ // large or we can lose responsiveness. Likewise we don't want to make it too
+ // small or we do a poor job suppressing acceleration spikes.
+ // Empirically, 100ms seems to be too small and 500ms is too large.
+ private static final float FILTER_TIME_CONSTANT_MS = 200.0f;
+
+ /* State for orientation detection. */
+
+ // Thresholds for minimum and maximum allowable deviation from gravity.
+ //
+ // If the device is undergoing external acceleration (being bumped, in a car
+ // that is turning around a corner or a plane taking off) then the magnitude
+ // may be substantially more or less than gravity. This can skew our orientation
+ // detection by making us think that up is pointed in a different direction.
+ //
+ // Conversely, if the device is in freefall, then there will be no gravity to
+ // measure at all. This is problematic because we cannot detect the orientation
+ // without gravity to tell us which way is up. A magnitude near 0 produces
+ // singularities in the tilt and orientation calculations.
+ //
+ // In both cases, we postpone choosing an orientation.
+ //
+ // However, we need to tolerate some acceleration because the angular momentum
+ // of turning the device can skew the observed acceleration for a short period of time.
+ private static final float NEAR_ZERO_MAGNITUDE = 1; // m/s^2
+ private static final float ACCELERATION_TOLERANCE = 4; // m/s^2
+ private static final float MIN_ACCELERATION_MAGNITUDE =
+ SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE;
+ private static final float MAX_ACCELERATION_MAGNITUDE =
+ SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
+
+ // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
+ // when screen is facing the sky or ground), we completely ignore orientation data.
+ private static final int MAX_TILT = 75;
+
+ // The tilt angle range in degrees for each orientation.
+ // Beyond these tilt angles, we don't even consider transitioning into the
+ // specified orientation. We place more stringent requirements on unnatural
+ // orientations than natural ones to make it less likely to accidentally transition
+ // into those states.
+ // The first value of each pair is negative so it applies a limit when the device is
+ // facing down (overhead reading in bed).
+ // The second value of each pair is positive so it applies a limit when the device is
+ // facing up (resting on a table).
+ // The ideal tilt angle is 0 (when the device is vertical) so the limits establish
+ // how close to vertical the device must be in order to change orientation.
+ private final int[][] TILT_TOLERANCE = new int[][] {
+ /* ROTATION_0 */ { -25, 70 },
+ /* ROTATION_90 */ { -25, 65 },
+ /* ROTATION_180 */ { -25, 60 },
+ /* ROTATION_270 */ { -25, 65 }
+ };
+
+ // The tilt angle below which we conclude that the user is holding the device
+ // overhead reading in bed and lock into that state.
+ private final int TILT_OVERHEAD_ENTER = -40;
+
+ // The tilt angle above which we conclude that the user would like a rotation
+ // change to occur and unlock from the overhead state.
+ private final int TILT_OVERHEAD_EXIT = -15;
+
+ // The gap angle in degrees between adjacent orientation angles for hysteresis.
+ // This creates a "dead zone" between the current orientation and a proposed
+ // adjacent orientation. No orientation proposal is made when the orientation
+ // angle is within the gap between the current orientation and the adjacent
+ // orientation.
+ private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45;
+
+ // Timestamp and value of the last accelerometer sample.
+ private long mLastFilteredTimestampNanos;
+ private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
+
+ // The last proposed rotation, -1 if unknown.
+ private int mProposedRotation;
+
+ // Value of the current predicted rotation, -1 if unknown.
+ private int mPredictedRotation;
+
+ // Timestamp of when the predicted rotation most recently changed.
+ private long mPredictedRotationTimestampNanos;
+
+ // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed).
+ private long mFlatTimestampNanos;
+ private boolean mFlat;
+
+ // Timestamp when the device last appeared to be swinging.
+ private long mSwingTimestampNanos;
+ private boolean mSwinging;
+
+ // Timestamp when the device last appeared to be undergoing external acceleration.
+ private long mAccelerationTimestampNanos;
+ private boolean mAccelerating;
+
+ // Timestamp when the last touch to the touch screen ended
+ private long mTouchEndedTimestampNanos = Long.MIN_VALUE;
+ private boolean mTouched;
+
+ // Whether we are locked into an overhead usage mode.
+ private boolean mOverhead;
+
+ // History of observed tilt angles.
+ private static final int TILT_HISTORY_SIZE = 40;
+ private float[] mTiltHistory = new float[TILT_HISTORY_SIZE];
+ private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE];
+ private int mTiltHistoryIndex;
+
+ public int getProposedRotationLocked() {
+ return mProposedRotation;
+ }
+
+ public void dumpLocked(PrintWriter pw, String prefix) {
+ pw.println(prefix + "mProposedRotation=" + mProposedRotation);
+ pw.println(prefix + "mPredictedRotation=" + mPredictedRotation);
+ pw.println(prefix + "mLastFilteredX=" + mLastFilteredX);
+ pw.println(prefix + "mLastFilteredY=" + mLastFilteredY);
+ pw.println(prefix + "mLastFilteredZ=" + mLastFilteredZ);
+ pw.println(prefix + "mTiltHistory={last: " + getLastTiltLocked() + "}");
+ pw.println(prefix + "mFlat=" + mFlat);
+ pw.println(prefix + "mSwinging=" + mSwinging);
+ pw.println(prefix + "mAccelerating=" + mAccelerating);
+ pw.println(prefix + "mOverhead=" + mOverhead);
+ pw.println(prefix + "mTouched=" + mTouched);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ int proposedRotation;
+ int oldProposedRotation;
+
+ synchronized (mLock) {
+ // The vector given in the SensorEvent points straight up (towards the sky) under
+ // ideal conditions (the phone is not accelerating). I'll call this up vector
+ // elsewhere.
+ float x = event.values[ACCELEROMETER_DATA_X];
+ float y = event.values[ACCELEROMETER_DATA_Y];
+ float z = event.values[ACCELEROMETER_DATA_Z];
+
+ if (LOG) {
+ Slog.v(TAG, "Raw acceleration vector: "
+ + "x=" + x + ", y=" + y + ", z=" + z
+ + ", magnitude=" + Math.sqrt(x * x + y * y + z * z));
+ }
+
+ // Apply a low-pass filter to the acceleration up vector in cartesian space.
+ // Reset the orientation listener state if the samples are too far apart in time
+ // or when we see values of (0, 0, 0) which indicates that we polled the
+ // accelerometer too soon after turning it on and we don't have any data yet.
+ final long now = event.timestamp;
+ final long then = mLastFilteredTimestampNanos;
+ final float timeDeltaMS = (now - then) * 0.000001f;
+ final boolean skipSample;
+ if (now < then
+ || now > then + MAX_FILTER_DELTA_TIME_NANOS
+ || (x == 0 && y == 0 && z == 0)) {
+ if (LOG) {
+ Slog.v(TAG, "Resetting orientation listener.");
+ }
+ resetLocked();
+ skipSample = true;
+ } else {
+ final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
+ x = alpha * (x - mLastFilteredX) + mLastFilteredX;
+ y = alpha * (y - mLastFilteredY) + mLastFilteredY;
+ z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
+ if (LOG) {
+ Slog.v(TAG, "Filtered acceleration vector: "
+ + "x=" + x + ", y=" + y + ", z=" + z
+ + ", magnitude=" + Math.sqrt(x * x + y * y + z * z));
+ }
+ skipSample = false;
+ }
+ mLastFilteredTimestampNanos = now;
+ mLastFilteredX = x;
+ mLastFilteredY = y;
+ mLastFilteredZ = z;
+
+ boolean isAccelerating = false;
+ boolean isFlat = false;
+ boolean isSwinging = false;
+ if (!skipSample) {
+ // Calculate the magnitude of the acceleration vector.
+ final float magnitude = (float) Math.sqrt(x * x + y * y + z * z);
+ if (magnitude < NEAR_ZERO_MAGNITUDE) {
+ if (LOG) {
+ Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero.");
+ }
+ clearPredictedRotationLocked();
+ } else {
+ // Determine whether the device appears to be undergoing external
+ // acceleration.
+ if (isAcceleratingLocked(magnitude)) {
+ isAccelerating = true;
+ mAccelerationTimestampNanos = now;
+ }
+
+ // Calculate the tilt angle.
+ // This is the angle between the up vector and the x-y plane (the plane of
+ // the screen) in a range of [-90, 90] degrees.
+ // -90 degrees: screen horizontal and facing the ground (overhead)
+ // 0 degrees: screen vertical
+ // 90 degrees: screen horizontal and facing the sky (on table)
+ final int tiltAngle = (int) Math.round(
+ Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
+ addTiltHistoryEntryLocked(now, tiltAngle);
+
+ // Determine whether the device appears to be flat or swinging.
+ if (isFlatLocked(now)) {
+ isFlat = true;
+ mFlatTimestampNanos = now;
+ }
+ if (isSwingingLocked(now, tiltAngle)) {
+ isSwinging = true;
+ mSwingTimestampNanos = now;
+ }
+
+ // If the tilt angle is too close to horizontal then we cannot determine
+ // the orientation angle of the screen.
+ if (tiltAngle <= TILT_OVERHEAD_ENTER) {
+ mOverhead = true;
+ } else if (tiltAngle >= TILT_OVERHEAD_EXIT) {
+ mOverhead = false;
+ }
+ if (mOverhead) {
+ if (LOG) {
+ Slog.v(TAG, "Ignoring sensor data, device is overhead: "
+ + "tiltAngle=" + tiltAngle);
+ }
+ clearPredictedRotationLocked();
+ } else if (Math.abs(tiltAngle) > MAX_TILT) {
+ if (LOG) {
+ Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
+ + "tiltAngle=" + tiltAngle);
+ }
+ clearPredictedRotationLocked();
+ } else {
+ // Calculate the orientation angle.
+ // This is the angle between the x-y projection of the up vector onto
+ // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
+ int orientationAngle = (int) Math.round(
+ -Math.atan2(-x, y) * RADIANS_TO_DEGREES);
+ if (orientationAngle < 0) {
+ // atan2 returns [-180, 180]; normalize to [0, 360]
+ orientationAngle += 360;
+ }
+
+ // Find the nearest rotation.
+ int nearestRotation = (orientationAngle + 45) / 90;
+ if (nearestRotation == 4) {
+ nearestRotation = 0;
+ }
+
+ // Determine the predicted orientation.
+ if (isTiltAngleAcceptableLocked(nearestRotation, tiltAngle)
+ && isOrientationAngleAcceptableLocked(nearestRotation,
+ orientationAngle)) {
+ updatePredictedRotationLocked(now, nearestRotation);
+ if (LOG) {
+ Slog.v(TAG, "Predicted: "
+ + "tiltAngle=" + tiltAngle
+ + ", orientationAngle=" + orientationAngle
+ + ", predictedRotation=" + mPredictedRotation
+ + ", predictedRotationAgeMS="
+ + ((now - mPredictedRotationTimestampNanos)
+ * 0.000001f));
+ }
+ } else {
+ if (LOG) {
+ Slog.v(TAG, "Ignoring sensor data, no predicted rotation: "
+ + "tiltAngle=" + tiltAngle
+ + ", orientationAngle=" + orientationAngle);
+ }
+ clearPredictedRotationLocked();
+ }
+ }
+ }
+ }
+ mFlat = isFlat;
+ mSwinging = isSwinging;
+ mAccelerating = isAccelerating;
+
+ // Determine new proposed rotation.
+ oldProposedRotation = mProposedRotation;
+ if (mPredictedRotation < 0 || isPredictedRotationAcceptableLocked(now)) {
+ mProposedRotation = mPredictedRotation;
+ }
+ proposedRotation = mProposedRotation;
+
+ // Write final statistics about where we are in the orientation detection process.
+ if (LOG) {
+ Slog.v(TAG, "Result: currentRotation=" + mCurrentRotation
+ + ", proposedRotation=" + proposedRotation
+ + ", predictedRotation=" + mPredictedRotation
+ + ", timeDeltaMS=" + timeDeltaMS
+ + ", isAccelerating=" + isAccelerating
+ + ", isFlat=" + isFlat
+ + ", isSwinging=" + isSwinging
+ + ", isOverhead=" + mOverhead
+ + ", isTouched=" + mTouched
+ + ", timeUntilSettledMS=" + remainingMS(now,
+ mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
+ + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now,
+ mAccelerationTimestampNanos + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS)
+ + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now,
+ mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS)
+ + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now,
+ mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS)
+ + ", timeUntilTouchDelayExpiredMS=" + remainingMS(now,
+ mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS));
+ }
+ }
+
+ // Tell the listener.
+ if (proposedRotation != oldProposedRotation && proposedRotation >= 0) {
+ if (LOG) {
+ Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + proposedRotation
+ + ", oldProposedRotation=" + oldProposedRotation);
+ }
+ onProposedRotationChanged(proposedRotation);
+ }
+ }
+
+ /**
+ * Returns true if the tilt angle is acceptable for a given predicted rotation.
+ */
+ private boolean isTiltAngleAcceptableLocked(int rotation, int tiltAngle) {
+ return tiltAngle >= TILT_TOLERANCE[rotation][0]
+ && tiltAngle <= TILT_TOLERANCE[rotation][1];
+ }
+
+ /**
+ * Returns true if the orientation angle is acceptable for a given predicted rotation.
+ *
+ * This function takes into account the gap between adjacent orientations
+ * for hysteresis.
+ */
+ private boolean isOrientationAngleAcceptableLocked(int rotation, int orientationAngle) {
+ // If there is no current rotation, then there is no gap.
+ // The gap is used only to introduce hysteresis among advertised orientation
+ // changes to avoid flapping.
+ final int currentRotation = mCurrentRotation;
+ if (currentRotation >= 0) {
+ // If the specified rotation is the same or is counter-clockwise adjacent
+ // to the current rotation, then we set a lower bound on the orientation angle.
+ // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90,
+ // then we want to check orientationAngle > 45 + GAP / 2.
+ if (rotation == currentRotation
+ || rotation == (currentRotation + 1) % 4) {
+ int lowerBound = rotation * 90 - 45
+ + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (rotation == 0) {
+ if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
+ return false;
+ }
+ } else {
+ if (orientationAngle < lowerBound) {
+ return false;
+ }
+ }
+ }
+
+ // If the specified rotation is the same or is clockwise adjacent,
+ // then we set an upper bound on the orientation angle.
+ // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270,
+ // then we want to check orientationAngle < 315 - GAP / 2.
+ if (rotation == currentRotation
+ || rotation == (currentRotation + 3) % 4) {
+ int upperBound = rotation * 90 + 45
+ - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (rotation == 0) {
+ if (orientationAngle <= 45 && orientationAngle > upperBound) {
+ return false;
+ }
+ } else {
+ if (orientationAngle > upperBound) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if the predicted rotation is ready to be advertised as a
+ * proposed rotation.
+ */
+ private boolean isPredictedRotationAcceptableLocked(long now) {
+ // The predicted rotation must have settled long enough.
+ if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
+ return false;
+ }
+
+ // The last flat state (time since picked up) must have been sufficiently long ago.
+ if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
+ return false;
+ }
+
+ // The last swing state (time since last movement to put down) must have been
+ // sufficiently long ago.
+ if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
+ return false;
+ }
+
+ // The last acceleration state must have been sufficiently long ago.
+ if (now < mAccelerationTimestampNanos
+ + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) {
+ return false;
+ }
+
+ // The last touch must have ended sufficiently long ago.
+ if (mTouched || now < mTouchEndedTimestampNanos
+ + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS) {
+ return false;
+ }
+
+ // Looks good!
+ return true;
+ }
+
+ private void resetLocked() {
+ mLastFilteredTimestampNanos = Long.MIN_VALUE;
+ mProposedRotation = -1;
+ mFlatTimestampNanos = Long.MIN_VALUE;
+ mFlat = false;
+ mSwingTimestampNanos = Long.MIN_VALUE;
+ mSwinging = false;
+ mAccelerationTimestampNanos = Long.MIN_VALUE;
+ mAccelerating = false;
+ mOverhead = false;
+ clearPredictedRotationLocked();
+ clearTiltHistoryLocked();
+ }
+
+ private void clearPredictedRotationLocked() {
+ mPredictedRotation = -1;
+ mPredictedRotationTimestampNanos = Long.MIN_VALUE;
+ }
+
+ private void updatePredictedRotationLocked(long now, int rotation) {
+ if (mPredictedRotation != rotation) {
+ mPredictedRotation = rotation;
+ mPredictedRotationTimestampNanos = now;
+ }
+ }
+
+ private boolean isAcceleratingLocked(float magnitude) {
+ return magnitude < MIN_ACCELERATION_MAGNITUDE
+ || magnitude > MAX_ACCELERATION_MAGNITUDE;
+ }
+
+ private void clearTiltHistoryLocked() {
+ mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE;
+ mTiltHistoryIndex = 1;
+ }
+
+ private void addTiltHistoryEntryLocked(long now, float tilt) {
+ mTiltHistory[mTiltHistoryIndex] = tilt;
+ mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now;
+ mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE;
+ mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE;
+ }
+
+ private boolean isFlatLocked(long now) {
+ for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) {
+ if (mTiltHistory[i] < FLAT_ANGLE) {
+ break;
+ }
+ if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) {
+ // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isSwingingLocked(long now, float tilt) {
+ for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) {
+ if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) {
+ break;
+ }
+ if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
+ // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int nextTiltHistoryIndexLocked(int index) {
+ index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
+ return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1;
+ }
+
+ private float getLastTiltLocked() {
+ int index = nextTiltHistoryIndexLocked(mTiltHistoryIndex);
+ return index >= 0 ? mTiltHistory[index] : Float.NaN;
+ }
+
+ private float remainingMS(long now, long until) {
+ return now >= until ? 0 : (until - now) * 0.000001f;
+ }
+
+ private void onTouchStartLocked() {
+ mTouched = true;
+ }
+
+ private void onTouchEndLocked(long whenElapsedNanos) {
+ mTouched = false;
+ mTouchEndedTimestampNanos = whenElapsedNanos;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
new file mode 100644
index 0000000..1a52933
--- /dev/null
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -0,0 +1,338 @@
+package com.android.server.policy.keyguard;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy.OnKeyguardExitResult;
+
+import com.android.internal.policy.IKeyguardExitCallback;
+import com.android.internal.policy.IKeyguardService;
+import com.android.internal.policy.IKeyguardShowCallback;
+
+/**
+ * A local class that keeps a cache of keyguard state that can be restored in the event
+ * keyguard crashes. It currently also allows runtime-selectable
+ * local or remote instances of keyguard.
+ */
+public class KeyguardServiceDelegate {
+ private static final String TAG = "KeyguardServiceDelegate";
+ private static final boolean DEBUG = true;
+
+ protected KeyguardServiceWrapper mKeyguardService;
+ private final Context mContext;
+ private final View mScrim; // shown if keyguard crashes
+ private final KeyguardState mKeyguardState = new KeyguardState();
+ private ShowListener mShowListenerWhenConnect;
+
+ /* package */ static final class KeyguardState {
+ KeyguardState() {
+ // Assume keyguard is showing and secure until we know for sure. This is here in
+ // the event something checks before the service is actually started.
+ // KeyguardService itself should default to this state until the real state is known.
+ showing = true;
+ showingAndNotOccluded = true;
+ secure = true;
+ deviceHasKeyguard = true;
+ }
+ boolean showing;
+ boolean showingAndNotOccluded;
+ boolean inputRestricted;
+ boolean occluded;
+ boolean secure;
+ boolean dreaming;
+ boolean systemIsReady;
+ boolean deviceHasKeyguard;
+ public boolean enabled;
+ public boolean dismissable;
+ public int offReason;
+ public int currentUser;
+ public boolean screenIsOn;
+ public boolean bootCompleted;
+ };
+
+ public interface ShowListener {
+ public void onShown(IBinder windowToken);
+ }
+
+ // A delegate class to map a particular invocation with a ShowListener object.
+ private final class KeyguardShowDelegate extends IKeyguardShowCallback.Stub {
+ private ShowListener mShowListener;
+
+ KeyguardShowDelegate(ShowListener showListener) {
+ mShowListener = showListener;
+ }
+
+ @Override
+ public void onShown(IBinder windowToken) throws RemoteException {
+ if (DEBUG) Log.v(TAG, "**** SHOWN CALLED ****");
+ if (mShowListener != null) {
+ mShowListener.onShown(windowToken);
+ }
+ hideScrim();
+ }
+ };
+
+ // A delegate class to map a particular invocation with an OnKeyguardExitResult object.
+ private final class KeyguardExitDelegate extends IKeyguardExitCallback.Stub {
+ private OnKeyguardExitResult mOnKeyguardExitResult;
+
+ KeyguardExitDelegate(OnKeyguardExitResult onKeyguardExitResult) {
+ mOnKeyguardExitResult = onKeyguardExitResult;
+ }
+
+ @Override
+ public void onKeyguardExitResult(boolean success) throws RemoteException {
+ if (DEBUG) Log.v(TAG, "**** onKeyguardExitResult(" + success +") CALLED ****");
+ if (mOnKeyguardExitResult != null) {
+ mOnKeyguardExitResult.onKeyguardExitResult(success);
+ }
+ }
+ };
+
+ public KeyguardServiceDelegate(Context context) {
+ mContext = context;
+ mScrim = createScrim(context);
+ }
+
+ public void bindService(Context context) {
+ Intent intent = new Intent();
+ final Resources resources = context.getApplicationContext().getResources();
+
+ final ComponentName keyguardComponent = ComponentName.unflattenFromString(
+ resources.getString(com.android.internal.R.string.config_keyguardComponent));
+ intent.setComponent(keyguardComponent);
+
+ if (!context.bindServiceAsUser(intent, mKeyguardConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
+ Log.v(TAG, "*** Keyguard: can't bind to " + keyguardComponent);
+ mKeyguardState.showing = false;
+ mKeyguardState.showingAndNotOccluded = false;
+ mKeyguardState.secure = false;
+ mKeyguardState.deviceHasKeyguard = false;
+ hideScrim();
+ } else {
+ if (DEBUG) Log.v(TAG, "*** Keyguard started");
+ }
+ }
+
+ private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) Log.v(TAG, "*** Keyguard connected (yay!)");
+ mKeyguardService = new KeyguardServiceWrapper(mContext,
+ IKeyguardService.Stub.asInterface(service));
+ if (mKeyguardState.systemIsReady) {
+ // If the system is ready, it means keyguard crashed and restarted.
+ mKeyguardService.onSystemReady();
+ // This is used to hide the scrim once keyguard displays.
+ mKeyguardService.onScreenTurnedOn(new KeyguardShowDelegate(
+ mShowListenerWhenConnect));
+ mShowListenerWhenConnect = null;
+ }
+ if (mKeyguardState.bootCompleted) {
+ mKeyguardService.onBootCompleted();
+ }
+ if (mKeyguardState.occluded) {
+ mKeyguardService.setOccluded(mKeyguardState.occluded);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) Log.v(TAG, "*** Keyguard disconnected (boo!)");
+ mKeyguardService = null;
+ }
+
+ };
+
+ public boolean isShowing() {
+ if (mKeyguardService != null) {
+ mKeyguardState.showing = mKeyguardService.isShowing();
+ }
+ return mKeyguardState.showing;
+ }
+
+ public boolean isInputRestricted() {
+ if (mKeyguardService != null) {
+ mKeyguardState.inputRestricted = mKeyguardService.isInputRestricted();
+ }
+ return mKeyguardState.inputRestricted;
+ }
+
+ public void verifyUnlock(final OnKeyguardExitResult onKeyguardExitResult) {
+ if (mKeyguardService != null) {
+ mKeyguardService.verifyUnlock(new KeyguardExitDelegate(onKeyguardExitResult));
+ }
+ }
+
+ public void keyguardDone(boolean authenticated, boolean wakeup) {
+ if (mKeyguardService != null) {
+ mKeyguardService.keyguardDone(authenticated, wakeup);
+ }
+ }
+
+ public void setOccluded(boolean isOccluded) {
+ if (mKeyguardService != null) {
+ mKeyguardService.setOccluded(isOccluded);
+ }
+ mKeyguardState.occluded = isOccluded;
+ }
+
+ public void dismiss() {
+ if (mKeyguardService != null) {
+ mKeyguardService.dismiss();
+ }
+ }
+
+ public boolean isSecure() {
+ if (mKeyguardService != null) {
+ mKeyguardState.secure = mKeyguardService.isSecure();
+ }
+ return mKeyguardState.secure;
+ }
+
+ public void onDreamingStarted() {
+ if (mKeyguardService != null) {
+ mKeyguardService.onDreamingStarted();
+ }
+ mKeyguardState.dreaming = true;
+ }
+
+ public void onDreamingStopped() {
+ if (mKeyguardService != null) {
+ mKeyguardService.onDreamingStopped();
+ }
+ mKeyguardState.dreaming = false;
+ }
+
+ public void onScreenTurnedOn(final ShowListener showListener) {
+ if (mKeyguardService != null) {
+ if (DEBUG) Log.v(TAG, "onScreenTurnedOn(showListener = " + showListener + ")");
+ mKeyguardService.onScreenTurnedOn(new KeyguardShowDelegate(showListener));
+ } else {
+ // try again when we establish a connection
+ Slog.w(TAG, "onScreenTurnedOn(): no keyguard service!");
+ // This shouldn't happen, but if it does, show the scrim immediately and
+ // invoke the listener's callback after the service actually connects.
+ mShowListenerWhenConnect = showListener;
+ showScrim();
+ }
+ mKeyguardState.screenIsOn = true;
+ }
+
+ public void onScreenTurnedOff(int why) {
+ if (mKeyguardService != null) {
+ mKeyguardService.onScreenTurnedOff(why);
+ }
+ mKeyguardState.offReason = why;
+ mKeyguardState.screenIsOn = false;
+ }
+
+ public void setKeyguardEnabled(boolean enabled) {
+ if (mKeyguardService != null) {
+ mKeyguardService.setKeyguardEnabled(enabled);
+ }
+ mKeyguardState.enabled = enabled;
+ }
+
+ public void onSystemReady() {
+ if (mKeyguardService != null) {
+ mKeyguardService.onSystemReady();
+ } else {
+ mKeyguardState.systemIsReady = true;
+ }
+ }
+
+ public void doKeyguardTimeout(Bundle options) {
+ if (mKeyguardService != null) {
+ mKeyguardService.doKeyguardTimeout(options);
+ }
+ }
+
+ public void setCurrentUser(int newUserId) {
+ if (mKeyguardService != null) {
+ mKeyguardService.setCurrentUser(newUserId);
+ }
+ mKeyguardState.currentUser = newUserId;
+ }
+
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ if (mKeyguardService != null) {
+ mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
+ }
+ }
+
+ private static final View createScrim(Context context) {
+ View view = new View(context);
+
+ int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER
+ ;
+
+ final int stretch = ViewGroup.LayoutParams.MATCH_PARENT;
+ final int type = WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM;
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ stretch, stretch, type, flags, PixelFormat.TRANSLUCENT);
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
+ lp.setTitle("KeyguardScrim");
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.addView(view, lp);
+ // Disable pretty much everything in statusbar until keyguard comes back and we know
+ // the state of the world.
+ view.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME
+ | View.STATUS_BAR_DISABLE_BACK
+ | View.STATUS_BAR_DISABLE_RECENT
+ | View.STATUS_BAR_DISABLE_EXPAND
+ | View.STATUS_BAR_DISABLE_SEARCH);
+ return view;
+ }
+
+ public void showScrim() {
+ if (!mKeyguardState.deviceHasKeyguard) return;
+ mScrim.post(new Runnable() {
+ @Override
+ public void run() {
+ mScrim.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+
+ public void hideScrim() {
+ mScrim.post(new Runnable() {
+ @Override
+ public void run() {
+ mScrim.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ public void onBootCompleted() {
+ if (mKeyguardService != null) {
+ mKeyguardService.onBootCompleted();
+ }
+ mKeyguardState.bootCompleted = true;
+ }
+
+ public void onActivityDrawn() {
+ if (mKeyguardService != null) {
+ mKeyguardService.onActivityDrawn();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
new file mode 100644
index 0000000..2dc685b
--- /dev/null
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -0,0 +1,206 @@
+/*
+ * 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.policy.keyguard;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.policy.IKeyguardExitCallback;
+import com.android.internal.policy.IKeyguardService;
+import com.android.internal.policy.IKeyguardShowCallback;
+import com.android.internal.policy.IKeyguardStateCallback;
+
+/**
+ * A wrapper class for KeyguardService. It implements IKeyguardService to ensure the interface
+ * remains consistent.
+ *
+ */
+public class KeyguardServiceWrapper implements IKeyguardService {
+ private KeyguardStateMonitor mKeyguardStateMonitor;
+ private IKeyguardService mService;
+ private String TAG = "KeyguardServiceWrapper";
+
+ public KeyguardServiceWrapper(Context context, IKeyguardService service) {
+ mService = service;
+ mKeyguardStateMonitor = new KeyguardStateMonitor(context, service);
+ }
+
+ @Override // Binder interface
+ public void verifyUnlock(IKeyguardExitCallback callback) {
+ try {
+ mService.verifyUnlock(callback);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void keyguardDone(boolean authenticated, boolean wakeup) {
+ try {
+ mService.keyguardDone(authenticated, wakeup);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void setOccluded(boolean isOccluded) {
+ try {
+ mService.setOccluded(isOccluded);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override
+ public void addStateMonitorCallback(IKeyguardStateCallback callback) {
+ try {
+ mService.addStateMonitorCallback(callback);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void dismiss() {
+ try {
+ mService.dismiss();
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void onDreamingStarted() {
+ try {
+ mService.onDreamingStarted();
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void onDreamingStopped() {
+ try {
+ mService.onDreamingStopped();
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void onScreenTurnedOff(int reason) {
+ try {
+ mService.onScreenTurnedOff(reason);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void onScreenTurnedOn(IKeyguardShowCallback result) {
+ try {
+ mService.onScreenTurnedOn(result);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void setKeyguardEnabled(boolean enabled) {
+ try {
+ mService.setKeyguardEnabled(enabled);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void onSystemReady() {
+ try {
+ mService.onSystemReady();
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void doKeyguardTimeout(Bundle options) {
+ try {
+ mService.doKeyguardTimeout(options);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void setCurrentUser(int userId) {
+ mKeyguardStateMonitor.setCurrentUser(userId);
+ try {
+ mService.setCurrentUser(userId);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void onBootCompleted() {
+ try {
+ mService.onBootCompleted();
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ try {
+ mService.startKeyguardExitAnimation(startTime, fadeoutDuration);
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public void onActivityDrawn() {
+ try {
+ mService.onActivityDrawn();
+ } catch (RemoteException e) {
+ Slog.w(TAG , "Remote Exception", e);
+ }
+ }
+
+ @Override // Binder interface
+ public IBinder asBinder() {
+ return mService.asBinder();
+ }
+
+ public boolean isShowing() {
+ return mKeyguardStateMonitor.isShowing();
+ }
+
+ public boolean isSecure() {
+ return mKeyguardStateMonitor.isSecure();
+ }
+
+ public boolean isInputRestricted() {
+ return mKeyguardStateMonitor.isInputRestricted();
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
new file mode 100644
index 0000000..926090e
--- /dev/null
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -0,0 +1,86 @@
+/*
+ * 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.policy.keyguard;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.policy.IKeyguardService;
+import com.android.internal.policy.IKeyguardStateCallback;
+import com.android.internal.widget.LockPatternUtils;
+
+/**
+ * Maintains a cached copy of Keyguard's state.
+ * @hide
+ */
+public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub {
+ private static final String TAG = "KeyguardStateMonitor";
+
+ // These cache the current state of Keyguard to improve performance and avoid deadlock. After
+ // Keyguard changes its state, it always triggers a layout in window manager. Because
+ // IKeyguardStateCallback is synchronous and because these states are declared volatile, it's
+ // guaranteed that window manager picks up the new state all the time in the layout caused by
+ // the state change of Keyguard.
+ private volatile boolean mIsShowing;
+ private volatile boolean mSimSecure;
+ private volatile boolean mInputRestricted;
+
+ private final LockPatternUtils mLockPatternUtils;
+
+ public KeyguardStateMonitor(Context context, IKeyguardService service) {
+ mLockPatternUtils = new LockPatternUtils(context);
+ mLockPatternUtils.setCurrentUser(ActivityManager.getCurrentUser());
+ try {
+ service.addStateMonitorCallback(this);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Remote Exception", e);
+ }
+ }
+
+ public boolean isShowing() {
+ return mIsShowing;
+ }
+
+ public boolean isSecure() {
+ return mLockPatternUtils.isSecure() || mSimSecure;
+ }
+
+ public boolean isInputRestricted() {
+ return mInputRestricted;
+ }
+
+ @Override // Binder interface
+ public void onShowingStateChanged(boolean showing) {
+ mIsShowing = showing;
+ }
+
+ @Override // Binder interface
+ public void onSimSecureStateChanged(boolean simSecure) {
+ mSimSecure = simSecure;
+ }
+
+ public void setCurrentUser(int userId) {
+ mLockPatternUtils.setCurrentUser(userId);
+ }
+
+ @Override // Binder interface
+ public void onInputRestrictedStateChanged(boolean inputRestricted) {
+ mInputRestricted = inputRestricted;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/power/DeviceIdleController.java b/services/core/java/com/android/server/power/DeviceIdleController.java
new file mode 100644
index 0000000..6b29b9a
--- /dev/null
+++ b/services/core/java/com/android/server/power/DeviceIdleController.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2015 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.power;
+
+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.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+import android.hardware.display.DisplayManager;
+import android.net.INetworkPolicyManager;
+import android.os.Binder;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.TimeUtils;
+import android.view.Display;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.SystemService;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.EventLogTags;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Keeps track of device idleness and drives low power mode based on that.
+ */
+public class DeviceIdleController extends SystemService {
+ private static final String TAG = "DeviceIdleController";
+
+ private static final String ACTION_STEP_IDLE_STATE =
+ "com.android.server.device_idle.STEP_IDLE_STATE";
+
+ // TODO: These need to be moved to system settings.
+
+ /**
+ * This is the time, after becoming inactive, at which we start looking at the
+ * motion sensor to determine if the device is being left alone. We don't do this
+ * immediately after going inactive just because we don't want to be continually running
+ * the significant motion sensor whenever the screen is off.
+ */
+ private static final long DEFAULT_INACTIVE_TIMEOUT = 30*60*1000L;
+ /**
+ * This is the time, after seeing motion, that we wait after becoming inactive from
+ * that until we start looking for motion again.
+ */
+ private static final long DEFAULT_MOTION_INACTIVE_TIMEOUT = 10*60*1000L;
+ /**
+ * This is the time, after the inactive timeout elapses, that we will wait looking
+ * for significant motion until we truly consider the device to be idle.
+ */
+ private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT = 30*60*1000L;
+ /**
+ * This is the initial time, after being idle, that we will allow ourself to be back
+ * in the IDLE_PENDING state allowing the system to run normally until we return to idle.
+ */
+ private static final long DEFAULT_IDLE_PENDING_TIMEOUT = 5*60*1000L;
+ /**
+ * Maximum pending idle timeout (time spent running) we will be allowed to use.
+ */
+ private static final long DEFAULT_MAX_IDLE_PENDING_TIMEOUT = 10*60*1000L;
+ /**
+ * Scaling factor to apply to current pending idle timeout each time we cycle through
+ * that state.
+ */
+ private static final float DEFAULT_IDLE_PENDING_FACTOR = 2f;
+ /**
+ * This is the initial time that we want to sit in the idle state before waking up
+ * again to return to pending idle and allowing normal work to run.
+ */
+ private static final long DEFAULT_IDLE_TIMEOUT = 60*60*1000L;
+ /**
+ * Maximum idle duration we will be allowed to use.
+ */
+ private static final long DEFAULT_MAX_IDLE_TIMEOUT = 6*60*60*1000L;
+ /**
+ * Scaling factor to apply to current idle timeout each time we cycle through that state.
+ */
+ private static final float DEFAULT_IDLE_FACTOR = 2f;
+ /**
+ * This is the minimum time we will allow until the next upcoming alarm for us to
+ * actually go in to idle mode.
+ */
+ private static final long DEFAULT_MIN_TIME_TO_ALARM = 60*60*1000L;
+
+ private AlarmManager mAlarmManager;
+ private IBatteryStats mBatteryStats;
+ private PowerManagerInternal mLocalPowerManager;
+ private INetworkPolicyManager mNetworkPolicyManager;
+ private DisplayManager mDisplayManager;
+ private SensorManager mSensorManager;
+ private Sensor mSigMotionSensor;
+ private PendingIntent mAlarmIntent;
+ private Intent mIdleIntent;
+ private Display mCurDisplay;
+ private boolean mScreenOn;
+ private boolean mCharging;
+ private boolean mSigMotionActive;
+
+ /** Device is currently active. */
+ private static final int STATE_ACTIVE = 0;
+ /** Device is inactve (screen off, no motion) and we are waiting to for idle. */
+ private static final int STATE_INACTIVE = 1;
+ /** Device is past the initial inactive period, and waiting for the next idle period. */
+ private static final int STATE_IDLE_PENDING = 2;
+ /** Device is in the idle state, trying to stay asleep as much as possible. */
+ private static final int STATE_IDLE = 3;
+ private static String stateToString(int state) {
+ switch (state) {
+ case STATE_ACTIVE: return "ACTIVE";
+ case STATE_INACTIVE: return "INACTIVE";
+ case STATE_IDLE_PENDING: return "IDLE_PENDING";
+ case STATE_IDLE: return "IDLE";
+ default: return Integer.toString(state);
+ }
+ }
+
+ private int mState;
+
+ private long mInactiveTimeout;
+ private long mNextAlarmTime;
+ private long mNextIdlePendingDelay;
+ private long mNextIdleDelay;
+
+ private final Binder mBinder = new Binder() {
+ @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ DeviceIdleController.this.dump(fd, pw, args);
+ }
+ };
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+ int plugged = intent.getIntExtra("plugged", 0);
+ updateChargingLocked(plugged != 0);
+ } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
+ synchronized (DeviceIdleController.this) {
+ stepIdleStateLocked();
+ }
+ }
+ }
+ };
+
+ private final DisplayManager.DisplayListener mDisplayListener
+ = new DisplayManager.DisplayListener() {
+ @Override public void onDisplayAdded(int displayId) {
+ }
+
+ @Override public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override public void onDisplayChanged(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ synchronized (DeviceIdleController.this) {
+ updateDisplayLocked();
+ }
+ }
+ }
+ };
+
+ private final TriggerEventListener mSigMotionListener = new TriggerEventListener() {
+ @Override public void onTrigger(TriggerEvent event) {
+ synchronized (DeviceIdleController.this) {
+ significantMotionLocked();
+ }
+ }
+ };
+
+ public DeviceIdleController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ synchronized (this) {
+ mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+ mBatteryStats = BatteryStatsService.getService();
+ mLocalPowerManager = getLocalService(PowerManagerInternal.class);
+ mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+ mDisplayManager = (DisplayManager) getContext().getSystemService(
+ Context.DISPLAY_SERVICE);
+ mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
+ mSigMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+
+ Intent intent = new Intent(ACTION_STEP_IDLE_STATE)
+ .setPackage("android")
+ .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
+
+ mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(ACTION_STEP_IDLE_STATE);
+ getContext().registerReceiver(mReceiver, filter);
+
+ mDisplayManager.registerDisplayListener(mDisplayListener, null);
+
+ mScreenOn = true;
+ // Start out assuming we are charging. If we aren't, we will at least get
+ // a battery update the next time the level drops.
+ mCharging = true;
+ mState = STATE_ACTIVE;
+ mInactiveTimeout = DEFAULT_INACTIVE_TIMEOUT;
+ updateDisplayLocked();
+ }
+
+ publishBinderService("deviceidle", mBinder);
+ }
+
+ void updateDisplayLocked() {
+ mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ // We consider any situation where the display is showing something to be it on,
+ // because if there is anything shown we are going to be updating it at some
+ // frequency so can't be allowed to go into deep sleeps.
+ boolean screenOn = mCurDisplay.getState() != Display.STATE_OFF;;
+ if (!screenOn && mScreenOn) {
+ mScreenOn = false;
+ becomeInactiveIfAppropriateLocked();
+ } else if (screenOn) {
+ mScreenOn = true;
+ becomeActiveLocked("screen");
+ }
+ }
+
+ void updateChargingLocked(boolean charging) {
+ if (!charging && mCharging) {
+ mCharging = false;
+ becomeInactiveIfAppropriateLocked();
+ } else if (charging) {
+ mCharging = charging;
+ becomeActiveLocked("charging");
+ }
+ }
+
+ void becomeActiveLocked(String reason) {
+ if (mState != STATE_ACTIVE) {
+ EventLogTags.writeDeviceIdle(STATE_ACTIVE, reason);
+ mLocalPowerManager.setDeviceIdleMode(false);
+ try {
+ mNetworkPolicyManager.setDeviceIdleMode(false);
+ mBatteryStats.noteDeviceIdleMode(false, true, false);
+ } catch (RemoteException e) {
+ }
+ if (mState == STATE_IDLE) {
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ }
+ mState = STATE_ACTIVE;
+ mInactiveTimeout = DEFAULT_INACTIVE_TIMEOUT;
+ mNextIdlePendingDelay = 0;
+ mNextIdleDelay = 0;
+ cancelAlarmLocked();
+ stopMonitoringSignificantMotion();
+ }
+ }
+
+ void becomeInactiveIfAppropriateLocked() {
+ if (!mScreenOn && !mCharging && mState == STATE_ACTIVE) {
+ // Screen has turned off; we are now going to become inactive and start
+ // waiting to see if we will ultimately go idle.
+ mState = STATE_INACTIVE;
+ mNextIdlePendingDelay = 0;
+ mNextIdleDelay = 0;
+ scheduleAlarmLocked(mInactiveTimeout, false);
+ EventLogTags.writeDeviceIdle(mState, "no activity");
+ }
+ }
+
+ void stepIdleStateLocked() {
+ EventLogTags.writeDeviceIdleStep();
+
+ final long now = SystemClock.elapsedRealtime();
+ if ((now+DEFAULT_MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
+ // Whoops, there is an upcoming alarm. We don't actually want to go idle.
+ if (mState != STATE_ACTIVE) {
+ becomeActiveLocked("alarm");
+ }
+ return;
+ }
+
+ switch (mState) {
+ case STATE_INACTIVE:
+ // We have now been inactive long enough, it is time to start looking
+ // for significant motion and sleep some more while doing so.
+ startMonitoringSignificantMotion();
+ scheduleAlarmLocked(DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT, false);
+ // Reset the upcoming idle delays.
+ mNextIdlePendingDelay = DEFAULT_IDLE_PENDING_TIMEOUT;
+ mNextIdleDelay = DEFAULT_IDLE_TIMEOUT;
+ mState = STATE_IDLE_PENDING;
+ EventLogTags.writeDeviceIdle(mState, "step");
+ break;
+ case STATE_IDLE_PENDING:
+ // We have been waiting to become idle, and now it is time! This is the
+ // only case where we want to use a wakeup alarm, because we do want to
+ // drag the device out of its sleep state in this case to do the next
+ // scheduled work.
+ scheduleAlarmLocked(mNextIdleDelay, true);
+ mNextIdleDelay = (long)(mNextIdleDelay*DEFAULT_IDLE_FACTOR);
+ if (mNextIdleDelay > DEFAULT_MAX_IDLE_TIMEOUT) {
+ mNextIdleDelay = DEFAULT_MAX_IDLE_TIMEOUT;
+ }
+ mState = STATE_IDLE;
+ EventLogTags.writeDeviceIdle(mState, "step");
+ mLocalPowerManager.setDeviceIdleMode(true);
+ try {
+ mNetworkPolicyManager.setDeviceIdleMode(true);
+ mBatteryStats.noteDeviceIdleMode(true, false, false);
+ } catch (RemoteException e) {
+ }
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ break;
+ case STATE_IDLE:
+ // We have been idling long enough, now it is time to do some work.
+ scheduleAlarmLocked(mNextIdlePendingDelay, false);
+ mNextIdlePendingDelay = (long)(mNextIdlePendingDelay*DEFAULT_IDLE_PENDING_FACTOR);
+ if (mNextIdlePendingDelay > DEFAULT_MAX_IDLE_PENDING_TIMEOUT) {
+ mNextIdlePendingDelay = DEFAULT_MAX_IDLE_PENDING_TIMEOUT;
+ }
+ mState = STATE_IDLE_PENDING;
+ EventLogTags.writeDeviceIdle(mState, "step");
+ mLocalPowerManager.setDeviceIdleMode(false);
+ try {
+ mNetworkPolicyManager.setDeviceIdleMode(false);
+ mBatteryStats.noteDeviceIdleMode(false, false, false);
+ } catch (RemoteException e) {
+ }
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ break;
+ }
+ }
+
+ void significantMotionLocked() {
+ // When the sensor goes off, its trigger is automatically removed.
+ mSigMotionActive = false;
+ // The device is not yet active, so we want to go back to the pending idle
+ // state to wait again for no motion. Note that we only monitor for significant
+ // motion after moving out of the inactive state, so no need to worry about that.
+ if (mState != STATE_ACTIVE) {
+ mLocalPowerManager.setDeviceIdleMode(false);
+ try {
+ mNetworkPolicyManager.setDeviceIdleMode(false);
+ mBatteryStats.noteDeviceIdleMode(false, false, true);
+ } catch (RemoteException e) {
+ }
+ if (mState == STATE_IDLE) {
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ }
+ mState = STATE_ACTIVE;
+ mInactiveTimeout = DEFAULT_MOTION_INACTIVE_TIMEOUT;
+ EventLogTags.writeDeviceIdle(mState, "motion");
+ becomeInactiveIfAppropriateLocked();
+ }
+ }
+
+ void startMonitoringSignificantMotion() {
+ if (mSigMotionSensor != null && !mSigMotionActive) {
+ mSensorManager.requestTriggerSensor(mSigMotionListener, mSigMotionSensor);
+ mSigMotionActive = true;
+ }
+ }
+
+ void stopMonitoringSignificantMotion() {
+ if (mSigMotionActive) {
+ mSensorManager.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor);
+ mSigMotionActive = false;
+ }
+ }
+
+ void cancelAlarmLocked() {
+ if (mNextAlarmTime != 0) {
+ mNextAlarmTime = 0;
+ mAlarmManager.cancel(mAlarmIntent);
+ }
+ }
+
+ void scheduleAlarmLocked(long delay, boolean idleUntil) {
+ if (mSigMotionSensor == null) {
+ // If there is no significant motion sensor on this device, then we won't schedule
+ // alarms, because we can't determine if the device is not moving. This effectively
+ // turns off normal exeuction of device idling, although it is still possible to
+ // manually poke it by pretending like the alarm is going off.
+ return;
+ }
+ mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
+ if (idleUntil) {
+ mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mAlarmIntent);
+ } else {
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mAlarmIntent);
+ }
+ }
+
+ private void dumpHelp(PrintWriter pw) {
+ pw.println("Device idle controller (deviceidle) dump options:");
+ pw.println(" [-h] [CMD]");
+ pw.println(" -h: print this help text.");
+ pw.println("Commands:");
+ pw.println(" step");
+ pw.println(" Immediately step to next state, without waiting for alarm.");
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump DeviceIdleController from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " without permission " + android.Manifest.permission.DUMP);
+ return;
+ }
+
+ if (args != null) {
+ for (int i=0; i<args.length; i++) {
+ String arg = args[i];
+ if ("-h".equals(arg)) {
+ dumpHelp(pw);
+ return;
+ } else if ("step".equals(arg)) {
+ synchronized (this) {
+ stepIdleStateLocked();
+ pw.print("Stepped to: "); pw.println(stateToString(mState));
+ }
+ return;
+ } else if (arg.length() > 0 && arg.charAt(0) == '-'){
+ pw.println("Unknown option: " + arg);
+ dumpHelp(pw);
+ return;
+ } else {
+ pw.println("Unknown command: " + arg);
+ dumpHelp(pw);
+ return;
+ }
+ }
+ }
+
+ synchronized (this) {
+ pw.print(" mSigMotionSensor="); pw.println(mSigMotionSensor);
+ pw.print(" mCurDisplay="); pw.println(mCurDisplay);
+ pw.print(" mScreenOn="); pw.println(mScreenOn);
+ pw.print(" mCharging="); pw.println(mCharging);
+ pw.print(" mSigMotionActive="); pw.println(mSigMotionActive);
+ pw.print(" mState="); pw.println(stateToString(mState));
+ pw.print(" mInactiveTimeout="); TimeUtils.formatDuration(mInactiveTimeout, pw);
+ pw.println();
+ if (mNextAlarmTime != 0) {
+ pw.print(" mNextAlarmTime=");
+ TimeUtils.formatDuration(mNextAlarmTime, SystemClock.elapsedRealtime(), pw);
+ pw.println();
+ }
+ if (mNextIdlePendingDelay != 0) {
+ pw.print(" mNextIdlePendingDelay=");
+ TimeUtils.formatDuration(mNextIdlePendingDelay, pw);
+ pw.println();
+ }
+ if (mNextIdleDelay != 0) {
+ pw.print(" mNextIdleDelay=");
+ TimeUtils.formatDuration(mNextIdleDelay, pw);
+ pw.println();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 1349926..c48367e 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -78,6 +78,7 @@ final class Notifier {
private static final int MSG_USER_ACTIVITY = 1;
private static final int MSG_BROADCAST = 2;
private static final int MSG_WIRELESS_CHARGING_STARTED = 3;
+ private static final int MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED = 4;
private final Object mLock = new Object();
@@ -92,6 +93,7 @@ final class Notifier {
private final NotifierHandler mHandler;
private final Intent mScreenOnIntent;
private final Intent mScreenOffIntent;
+ private final Intent mScreenBrightnessBoostIntent;
// The current interactive state.
private int mActualInteractiveState;
@@ -128,6 +130,10 @@ final class Notifier {
mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
mScreenOffIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ mScreenBrightnessBoostIntent =
+ new Intent(PowerManager.ACTION_SCREEN_BRIGHTNESS_BOOST_CHANGED);
+ mScreenBrightnessBoostIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
// Initialize interactive state for battery stats.
try {
@@ -349,6 +355,19 @@ final class Notifier {
}
/**
+ * Called when screen brightness boost begins or ends.
+ */
+ public void onScreenBrightnessBoostChanged() {
+ if (DEBUG) {
+ Slog.d(TAG, "onScreenBrightnessBoostChanged");
+ }
+
+ mSuspendBlocker.acquire();
+ Message msg = mHandler.obtainMessage(MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+ /**
* Called when there has been user activity.
*/
public void onUserActivity(int event, int uid) {
@@ -457,6 +476,22 @@ final class Notifier {
}
}
+ private void sendBrightnessBoostChangedBroadcast() {
+ if (DEBUG) {
+ Slog.d(TAG, "Sending brightness boost changed broadcast.");
+ }
+
+ mContext.sendOrderedBroadcastAsUser(mScreenBrightnessBoostIntent, UserHandle.ALL, null,
+ mScreeBrightnessBoostChangedDone, mHandler, 0, null, null);
+ }
+
+ private final BroadcastReceiver mScreeBrightnessBoostChangedDone = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mSuspendBlocker.release();
+ }
+ };
+
private void sendWakeUpBroadcast() {
if (DEBUG) {
Slog.d(TAG, "Sending wake up broadcast.");
@@ -539,6 +574,9 @@ final class Notifier {
case MSG_WIRELESS_CHARGING_STARTED:
playWirelessChargingStartedSound();
break;
+ case MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED:
+ sendBrightnessBoostChangedBroadcast();
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9786b42..6c8959c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -128,6 +128,7 @@ public final class PowerManagerService extends SystemService
private static final int WAKE_LOCK_PROXIMITY_SCREEN_OFF = 1 << 4;
private static final int WAKE_LOCK_STAY_AWAKE = 1 << 5; // only set if already awake
private static final int WAKE_LOCK_DOZE = 1 << 6;
+ private static final int WAKE_LOCK_DRAW = 1 << 7;
// Summarizes the user activity state.
private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0;
@@ -419,6 +420,9 @@ public final class PowerManagerService extends SystemService
// True if the battery level is currently considered low.
private boolean mBatteryLevelLow;
+ // True if we are currently in device idle mode.
+ private boolean mDeviceIdleMode;
+
// True if theater mode is enabled
private boolean mTheaterModeEnabled;
@@ -1079,6 +1083,9 @@ public final class PowerManagerService extends SystemService
case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON:
Slog.i(TAG, "Going to sleep due to power button (uid " + uid +")...");
break;
+ case PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON:
+ Slog.i(TAG, "Going to sleep due to sleep button (uid " + uid +")...");
+ break;
case PowerManager.GO_TO_SLEEP_REASON_HDMI:
Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")...");
break;
@@ -1398,12 +1405,15 @@ public final class PowerManagerService extends SystemService
case PowerManager.DOZE_WAKE_LOCK:
mWakeLockSummary |= WAKE_LOCK_DOZE;
break;
+ case PowerManager.DRAW_WAKE_LOCK:
+ mWakeLockSummary |= WAKE_LOCK_DRAW;
+ break;
}
}
// Cancel wake locks that make no sense based on the current state.
if (mWakefulness != WAKEFULNESS_DOZING) {
- mWakeLockSummary &= ~WAKE_LOCK_DOZE;
+ mWakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW);
}
if (mWakefulness == WAKEFULNESS_ASLEEP
|| (mWakeLockSummary & WAKE_LOCK_DOZE) != 0) {
@@ -1422,6 +1432,9 @@ public final class PowerManagerService extends SystemService
mWakeLockSummary |= WAKE_LOCK_CPU;
}
}
+ if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+ mWakeLockSummary |= WAKE_LOCK_CPU;
+ }
if (DEBUG_SPEW) {
Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness="
@@ -1845,6 +1858,10 @@ public final class PowerManagerService extends SystemService
if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
+ if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+ && (mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+ mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+ }
mDisplayPowerRequest.dozeScreenBrightness =
mDozeScreenBrightnessOverrideFromDreamManager;
} else {
@@ -1886,6 +1903,7 @@ public final class PowerManagerService extends SystemService
}
}
mScreenBrightnessBoostInProgress = false;
+ mNotifier.onScreenBrightnessBoostChanged();
userActivityNoUpdateLocked(now,
PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
}
@@ -2164,6 +2182,12 @@ public final class PowerManagerService extends SystemService
}
}
+ private boolean isDeviceIdleModeInternal() {
+ synchronized (mLock) {
+ return mDeviceIdleMode;
+ }
+ }
+
private void handleBatteryStateChangedLocked() {
mDirty |= DIRTY_BATTERY_STATE;
updatePowerStateLocked();
@@ -2261,7 +2285,10 @@ public final class PowerManagerService extends SystemService
Slog.i(TAG, "Brightness boost activated (uid " + uid +")...");
mLastScreenBrightnessBoostTime = eventTime;
- mScreenBrightnessBoostInProgress = true;
+ if (!mScreenBrightnessBoostInProgress) {
+ mScreenBrightnessBoostInProgress = true;
+ mNotifier.onScreenBrightnessBoostChanged();
+ }
mDirty |= DIRTY_SCREEN_BRIGHTNESS_BOOST;
userActivityNoUpdateLocked(eventTime,
@@ -2270,6 +2297,12 @@ public final class PowerManagerService extends SystemService
}
}
+ private boolean isScreenBrightnessBoostedInternal() {
+ synchronized (mLock) {
+ return mScreenBrightnessBoostInProgress;
+ }
+ }
+
/**
* Called when a screen brightness boost timeout has occurred.
*
@@ -2712,6 +2745,8 @@ public final class PowerManagerService extends SystemService
return "PROXIMITY_SCREEN_OFF_WAKE_LOCK";
case PowerManager.DOZE_WAKE_LOCK:
return "DOZE_WAKE_LOCK ";
+ case PowerManager.DRAW_WAKE_LOCK:
+ return "DRAW_WAKE_LOCK ";
default:
return "??? ";
}
@@ -3034,6 +3069,16 @@ public final class PowerManagerService extends SystemService
}
}
+ @Override // Binder call
+ public boolean isDeviceIdleMode() {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isDeviceIdleModeInternal();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* Reboots the device.
*
@@ -3202,6 +3247,16 @@ public final class PowerManagerService extends SystemService
}
@Override // Binder call
+ public boolean isScreenBrightnessBoosted() {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isScreenBrightnessBoostedInternal();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -3279,5 +3334,12 @@ public final class PowerManagerService extends SystemService
mLowPowerModeListeners.add(listener);
}
}
+
+ @Override
+ public void setDeviceIdleMode(boolean enabled) {
+ synchronized (mLock) {
+ mDeviceIdleMode = enabled;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index da11387..1e0185d 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -39,6 +39,7 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.Vibrator;
import android.os.SystemVibrator;
import android.os.storage.IMountService;
@@ -202,6 +203,11 @@ public final class ShutdownThread extends Thread {
* @param confirm true if user confirmation is needed before shutting down.
*/
public static void rebootSafeMode(final Context context, boolean confirm) {
+ UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ return;
+ }
+
mReboot = true;
mRebootSafeMode = true;
mRebootReason = null;
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java
index ddf02e9..2b2b2ac 100644
--- a/services/core/java/com/android/server/search/SearchManagerService.java
+++ b/services/core/java/com/android/server/search/SearchManagerService.java
@@ -34,7 +34,6 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -265,7 +264,7 @@ public class SearchManagerService extends ISearchManager.Stub {
}
@Override
- public boolean launchAssistAction(int requestType, String hint, int userHandle) {
+ public boolean launchAssistAction(String hint, int userHandle) {
ComponentName comp = getAssistIntent(userHandle);
if (comp == null) {
return false;
@@ -275,7 +274,8 @@ public class SearchManagerService extends ISearchManager.Stub {
Intent intent = new Intent(Intent.ACTION_ASSIST);
intent.setComponent(comp);
IActivityManager am = ActivityManagerNative.getDefault();
- return am.launchAssistIntent(intent, requestType, hint, userHandle);
+ return am.launchAssistIntent(intent, ActivityManager.ASSIST_CONTEXT_BASIC, hint,
+ userHandle);
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index cf2ed07..184224b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -79,7 +79,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
public void binderDied() {
Slog.i(TAG, "binder died for pkg=" + pkg);
- disableInternal(userId, 0, token, pkg);
+ disableForUser(0, token, pkg, userId);
token.unlinkToDeath(this, 0);
}
}
@@ -194,10 +194,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
@Override
public void disable(int what, IBinder token, String pkg) {
- disableInternal(mCurrentUserId, what, token, pkg);
+ disableForUser(what, token, pkg, mCurrentUserId);
}
- private void disableInternal(int userId, int what, IBinder token, String pkg) {
+ @Override
+ public void disableForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
synchronized (mLock) {
@@ -454,6 +455,35 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
}
+ @Override
+ public void appTransitionPending() {
+ if (mBar != null) {
+ try {
+ mBar.appTransitionPending();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
+ public void appTransitionCancelled() {
+ if (mBar != null) {
+ try {
+ mBar.appTransitionCancelled();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
+ public void appTransitionStarting(long statusBarAnimationsStartTime,
+ long statusBarAnimationsDuration) {
+ if (mBar != null) {
+ try {
+ mBar.appTransitionStarting(
+ statusBarAnimationsStartTime, statusBarAnimationsDuration);
+ } catch (RemoteException ex) {}
+ }
+ }
+
private void enforceStatusBar() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR,
"StatusBarManagerService");
@@ -625,7 +655,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
}
-
// ================================================================================
// Can be called from any thread
// ================================================================================
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 111c09b..cbbcb0e 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -18,8 +18,7 @@ package com.android.server.storage;
import com.android.server.EventLogTags;
import com.android.server.SystemService;
-import com.android.server.pm.PackageManagerService;
-
+import com.android.server.pm.InstructionSets;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -75,6 +74,8 @@ import dalvik.system.VMRuntime;
public class DeviceStorageMonitorService extends SystemService {
static final String TAG = "DeviceStorageMonitorService";
+ // TODO: extend to watch and manage caches on all private volumes
+
static final boolean DEBUG = false;
static final boolean localLOGV = false;
@@ -221,7 +222,7 @@ public class DeviceStorageMonitorService extends SystemService {
try {
if (localLOGV) Slog.i(TAG, "Clearing cache");
IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
- freeStorageAndNotify(mMemCacheTrimToThreshold, mClearCacheObserver);
+ freeStorageAndNotify(null, mMemCacheTrimToThreshold, mClearCacheObserver);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
mClearingCache = false;
@@ -341,7 +342,7 @@ public class DeviceStorageMonitorService extends SystemService {
}
private static boolean isBootImageOnDisk() {
- for (String instructionSet : PackageManagerService.getAllDexCodeInstructionSets()) {
+ for (String instructionSet : InstructionSets.getAllDexCodeInstructionSets()) {
if (!VMRuntime.isBootClassPathOnDisk(instructionSet)) {
return false;
}
@@ -472,7 +473,7 @@ public class DeviceStorageMonitorService extends SystemService {
Notification notification = new Notification.Builder(context)
.setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full)
.setTicker(title)
- .setColor(context.getResources().getColor(
+ .setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)
.setContentText(details)
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 57b204d..fb7d186 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -40,6 +40,7 @@ import android.util.Slog;
import android.service.trust.ITrustAgentService;
import android.service.trust.ITrustAgentServiceCallback;
+import java.util.Collections;
import java.util.List;
/**
@@ -115,7 +116,7 @@ public class TrustAgentWrapper {
}
mTrusted = true;
mMessage = (CharSequence) msg.obj;
- boolean initiatedByUser = msg.arg1 != 0;
+ int flags = msg.arg1;
long durationMs = msg.getData().getLong(DATA_DURATION);
if (durationMs > 0) {
final long duration;
@@ -140,8 +141,8 @@ public class TrustAgentWrapper {
}
mTrustManagerService.mArchive.logGrantTrust(mUserId, mName,
(mMessage != null ? mMessage.toString() : null),
- durationMs, initiatedByUser);
- mTrustManagerService.updateTrust(mUserId, initiatedByUser);
+ durationMs, flags);
+ mTrustManagerService.updateTrust(mUserId, flags);
break;
case MSG_TRUST_TIMEOUT:
if (DEBUG) Slog.v(TAG, "Trust timed out : " + mName.flattenToShortString());
@@ -155,7 +156,7 @@ public class TrustAgentWrapper {
if (msg.what == MSG_REVOKE_TRUST) {
mTrustManagerService.mArchive.logRevokeTrust(mUserId, mName);
}
- mTrustManagerService.updateTrust(mUserId, false);
+ mTrustManagerService.updateTrust(mUserId, 0);
break;
case MSG_RESTART_TIMEOUT:
destroy();
@@ -170,7 +171,7 @@ public class TrustAgentWrapper {
if (DEBUG) Log.v(TAG, "Re-enabling agent because it acknowledged "
+ "enabled features: " + mName);
mTrustDisabledByDpm = false;
- mTrustManagerService.updateTrust(mUserId, false);
+ mTrustManagerService.updateTrust(mUserId, 0);
}
} else {
if (DEBUG) Log.w(TAG, "Ignoring MSG_SET_TRUST_AGENT_FEATURES_COMPLETED "
@@ -184,7 +185,7 @@ public class TrustAgentWrapper {
mMessage = null;
}
mTrustManagerService.mArchive.logManagingTrust(mUserId, mName, mManagingTrust);
- mTrustManagerService.updateTrust(mUserId, false);
+ mTrustManagerService.updateTrust(mUserId, 0);
break;
}
}
@@ -193,12 +194,12 @@ public class TrustAgentWrapper {
private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() {
@Override
- public void grantTrust(CharSequence userMessage, long durationMs, boolean initiatedByUser) {
+ public void grantTrust(CharSequence userMessage, long durationMs, int flags) {
if (DEBUG) Slog.v(TAG, "enableTrust(" + userMessage + ", durationMs = " + durationMs
- + ", initiatedByUser = " + initiatedByUser + ")");
+ + ", flags = " + flags + ")");
Message msg = mHandler.obtainMessage(
- MSG_GRANT_TRUST, initiatedByUser ? 1 : 0, 0, userMessage);
+ MSG_GRANT_TRUST, flags, 0, userMessage);
msg.getData().putLong(DATA_DURATION, durationMs);
msg.sendToTarget();
}
@@ -271,13 +272,14 @@ public class TrustAgentWrapper {
alarmFilter.addDataScheme(mAlarmIntent.getScheme());
final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME);
alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL);
- mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null);
// Schedules a restart for when connecting times out. If the connection succeeds,
// the restart is canceled in mCallback's onConnected.
scheduleRestart();
mBound = context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, user);
- if (!mBound) {
+ if (mBound) {
+ mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null);
+ } else {
Log.e(TAG, "Can't bind to TrustAgent " + mName.flattenToShortString());
}
}
@@ -359,6 +361,8 @@ public class TrustAgentWrapper {
mSetTrustAgentFeaturesToken = new Binder();
mTrustAgentService.onConfigure(config, mSetTrustAgentFeaturesToken);
}
+ } else {
+ mTrustAgentService.onConfigure(Collections.EMPTY_LIST, null);
}
final long maxTimeToLock = dpm.getMaximumTimeToLock(null);
if (maxTimeToLock != mMaximumTimeToLock) {
@@ -377,7 +381,7 @@ public class TrustAgentWrapper {
}
if (mTrustDisabledByDpm != trustDisabled) {
mTrustDisabledByDpm = trustDisabled;
- mTrustManagerService.updateTrust(mUserId, false);
+ mTrustManagerService.updateTrust(mUserId, 0);
}
return trustDisabled;
}
@@ -404,6 +408,7 @@ public class TrustAgentWrapper {
mTrustManagerService.mArchive.logAgentStopped(mUserId, mName);
mContext.unbindService(mConnection);
mBound = false;
+ mContext.unregisterReceiver(mBroadcastReceiver);
mTrustAgentService = null;
mSetTrustAgentFeaturesToken = null;
mHandler.sendEmptyMessage(MSG_REVOKE_TRUST);
diff --git a/services/core/java/com/android/server/trust/TrustArchive.java b/services/core/java/com/android/server/trust/TrustArchive.java
index 7253716..fd63d48 100644
--- a/services/core/java/com/android/server/trust/TrustArchive.java
+++ b/services/core/java/com/android/server/trust/TrustArchive.java
@@ -19,6 +19,7 @@ package com.android.server.trust;
import android.content.ComponentName;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.service.trust.TrustAgentService;
import android.util.TimeUtils;
import java.io.PrintWriter;
@@ -48,20 +49,20 @@ public class TrustArchive {
// grantTrust
final String message;
final long duration;
- final boolean userInitiated;
+ final int flags;
// managingTrust
final boolean managingTrust;
private Event(int type, int userId, ComponentName agent, String message,
- long duration, boolean userInitiated, boolean managingTrust) {
+ long duration, int flags, boolean managingTrust) {
this.type = type;
this.userId = userId;
this.agent = agent;
this.elapsedTimestamp = SystemClock.elapsedRealtime();
this.message = message;
this.duration = duration;
- this.userInitiated = userInitiated;
+ this.flags = flags;
this.managingTrust = managingTrust;
}
}
@@ -69,33 +70,33 @@ public class TrustArchive {
ArrayDeque<Event> mEvents = new ArrayDeque<Event>();
public void logGrantTrust(int userId, ComponentName agent, String message,
- long duration, boolean userInitiated) {
+ long duration, int flags) {
addEvent(new Event(TYPE_GRANT_TRUST, userId, agent, message, duration,
- userInitiated, false));
+ flags, false));
}
public void logRevokeTrust(int userId, ComponentName agent) {
- addEvent(new Event(TYPE_REVOKE_TRUST, userId, agent, null, 0, false, false));
+ addEvent(new Event(TYPE_REVOKE_TRUST, userId, agent, null, 0, 0, false));
}
public void logTrustTimeout(int userId, ComponentName agent) {
- addEvent(new Event(TYPE_TRUST_TIMEOUT, userId, agent, null, 0, false, false));
+ addEvent(new Event(TYPE_TRUST_TIMEOUT, userId, agent, null, 0, 0, false));
}
public void logAgentDied(int userId, ComponentName agent) {
- addEvent(new Event(TYPE_AGENT_DIED, userId, agent, null, 0, false, false));
+ addEvent(new Event(TYPE_AGENT_DIED, userId, agent, null, 0, 0, false));
}
public void logAgentConnected(int userId, ComponentName agent) {
- addEvent(new Event(TYPE_AGENT_CONNECTED, userId, agent, null, 0, false, false));
+ addEvent(new Event(TYPE_AGENT_CONNECTED, userId, agent, null, 0, 0, false));
}
public void logAgentStopped(int userId, ComponentName agent) {
- addEvent(new Event(TYPE_AGENT_STOPPED, userId, agent, null, 0, false, false));
+ addEvent(new Event(TYPE_AGENT_STOPPED, userId, agent, null, 0, 0, false));
}
public void logManagingTrust(int userId, ComponentName agent, boolean managing) {
- addEvent(new Event(TYPE_MANAGING_TRUST, userId, agent, null, 0, false, managing));
+ addEvent(new Event(TYPE_MANAGING_TRUST, userId, agent, null, 0, 0, managing));
}
private void addEvent(Event e) {
@@ -129,8 +130,8 @@ public class TrustArchive {
}
switch (ev.type) {
case TYPE_GRANT_TRUST:
- writer.printf(", message=\"%s\", duration=%s, initiatedByUser=%d",
- ev.message, formatDuration(ev.duration), ev.userInitiated ? 1 : 0);
+ writer.printf(", message=\"%s\", duration=%s, flags=%s",
+ ev.message, formatDuration(ev.duration), dumpGrantFlags(ev.flags));
break;
case TYPE_MANAGING_TRUST:
writer.printf(", managingTrust=" + ev.managingTrust);
@@ -184,4 +185,20 @@ public class TrustArchive {
return "Unknown(" + type + ")";
}
}
+
+ private String dumpGrantFlags(int flags) {
+ StringBuilder sb = new StringBuilder();
+ if ((flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0) {
+ if (sb.length() != 0) sb.append('|');
+ sb.append("INITIATED_BY_USER");
+ }
+ if ((flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0) {
+ if (sb.length() != 0) sb.append('|');
+ sb.append("DISMISS_KEYGUARD");
+ }
+ if (sb.length() == 0) {
+ sb.append('0');
+ }
+ return sb.toString();
+ }
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index cd1c9a5..7d2fb43 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -179,11 +179,11 @@ public class TrustManagerService extends SystemService {
private void updateTrustAll() {
List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
for (UserInfo userInfo : userInfos) {
- updateTrust(userInfo.id, false);
+ updateTrust(userInfo.id, 0);
}
}
- public void updateTrust(int userId, boolean initiatedByUser) {
+ public void updateTrust(int userId, int flags) {
dispatchOnTrustManagedChanged(aggregateIsTrustManaged(userId), userId);
boolean trusted = aggregateIsTrusted(userId);
boolean changed;
@@ -191,7 +191,7 @@ public class TrustManagerService extends SystemService {
changed = mUserIsTrusted.get(userId) != trusted;
mUserIsTrusted.put(userId, trusted);
}
- dispatchOnTrustChanged(trusted, userId, initiatedByUser);
+ dispatchOnTrustChanged(trusted, userId, flags);
if (changed) {
refreshDeviceLockedForUser(userId);
}
@@ -281,7 +281,7 @@ public class TrustManagerService extends SystemService {
if (userId == UserHandle.USER_ALL) {
updateTrustAll();
} else {
- updateTrust(userId, false /* initiatedByUser */);
+ updateTrust(userId, 0);
}
}
}
@@ -394,7 +394,7 @@ public class TrustManagerService extends SystemService {
}
}
if (trustMayHaveChanged) {
- updateTrust(userId, false);
+ updateTrust(userId, 0);
}
refreshAgentList(userId);
}
@@ -587,11 +587,11 @@ public class TrustManagerService extends SystemService {
}
}
- private void dispatchOnTrustChanged(boolean enabled, int userId, boolean initiatedByUser) {
- if (!enabled) initiatedByUser = false;
+ private void dispatchOnTrustChanged(boolean enabled, int userId, int flags) {
+ if (!enabled) flags = 0;
for (int i = 0; i < mTrustListeners.size(); i++) {
try {
- mTrustListeners.get(i).onTrustChanged(enabled, userId, initiatedByUser);
+ mTrustListeners.get(i).onTrustChanged(enabled, userId, flags);
} catch (DeadObjectException e) {
Slog.d(TAG, "Removing dead TrustListener.");
mTrustListeners.remove(i);
@@ -691,6 +691,20 @@ public class TrustManagerService extends SystemService {
return isDeviceLockedInner(userId);
}
+ @Override
+ public boolean isDeviceSecure(int userId) throws RemoteException {
+ userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
+ false /* allowAll */, true /* requireFull */, "isDeviceSecure", null);
+ userId = resolveProfileParent(userId);
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ return new LockPatternUtils(mContext).isSecure(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private void enforceReportPermission() {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events");
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 50b2262..ac8ad30 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -55,11 +55,9 @@ import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.Surface;
-import com.android.internal.os.SomeArgs;
import com.android.server.SystemService;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
@@ -104,7 +102,6 @@ class TvInputHardwareManager implements TvInputHal.Callback {
};
private int mCurrentIndex = 0;
private int mCurrentMaxIndex = 0;
- private final boolean mUseMasterVolume;
// TODO: Should handle STANDBY case.
private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
@@ -119,8 +116,6 @@ class TvInputHardwareManager implements TvInputHal.Callback {
mContext = context;
mListener = listener;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mUseMasterVolume = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
mHal.init();
}
@@ -141,12 +136,10 @@ class TvInputHardwareManager implements TvInputHal.Callback {
} else {
Slog.w(TAG, "HdmiControlService is not available");
}
- if (!mUseMasterVolume) {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
- filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
- mContext.registerReceiver(mVolumeReceiver, filter);
- }
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
+ filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+ mContext.registerReceiver(mVolumeReceiver, filter);
updateVolume();
}
}
@@ -547,7 +540,7 @@ class TvInputHardwareManager implements TvInputHal.Callback {
}
private float getMediaStreamVolume() {
- return mUseMasterVolume ? 1.0f : ((float) mCurrentIndex / (float) mCurrentMaxIndex);
+ return (float) mCurrentIndex / (float) mCurrentMaxIndex;
}
private class Connection implements IBinder.DeathRecipient {
@@ -699,15 +692,14 @@ class TvInputHardwareManager implements TvInputHal.Callback {
private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) {
sinks.clear();
- ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
+ ArrayList<AudioDevicePort> devicePorts = new ArrayList<AudioDevicePort>();
if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
return;
}
int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
- for (AudioPort port : devicePorts) {
- AudioDevicePort devicePort = (AudioDevicePort) port;
- if ((devicePort.type() & sinkDevice) != 0) {
- sinks.add(devicePort);
+ for (AudioDevicePort port : devicePorts) {
+ if ((port.type() & sinkDevice) != 0) {
+ sinks.add(port);
}
}
}
@@ -716,14 +708,13 @@ class TvInputHardwareManager implements TvInputHal.Callback {
if (type == AudioManager.DEVICE_NONE) {
return null;
}
- ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
+ ArrayList<AudioDevicePort> devicePorts = new ArrayList<AudioDevicePort>();
if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
return null;
}
- for (AudioPort port : devicePorts) {
- AudioDevicePort devicePort = (AudioDevicePort) port;
- if (devicePort.type() == type && devicePort.address().equals(address)) {
- return devicePort;
+ for (AudioDevicePort port : devicePorts) {
+ if (port.type() == type && port.address().equals(address)) {
+ return port;
}
}
return null;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5375bfc..5972247 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -18,8 +18,6 @@ package com.android.server.tv;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
-import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
-import static android.media.tv.TvInputManager.INPUT_STATE_UNKNOWN;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
@@ -204,6 +202,14 @@ public final class TvInputManagerService extends SystemService {
}
@Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ // The input list needs to be updated in any cases, regardless of whether
+ // it happened to the whole package or a specific component. Returning true so that
+ // the update can be handled in {@link #onSomePackagesChanged}.
+ return true;
+ }
+
+ @Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
UserState userState = getUserStateLocked(getChangingUserId());
@@ -792,7 +798,7 @@ public final class TvInputManagerService extends SystemService {
synchronized (mLock) {
UserState userState = getUserStateLocked(resolvedUserId);
TvInputState state = userState.inputMap.get(inputId);
- return state == null ? INPUT_STATE_UNKNOWN : state.state;
+ return state == null ? INPUT_STATE_CONNECTED : state.state;
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1341,6 +1347,108 @@ public final class TvInputManagerService extends SystemService {
}
@Override
+ public void timeShiftPause(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "timeShiftPause");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .timeShiftPause();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in timeShiftPause", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void timeShiftResume(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "timeShiftResume");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .timeShiftResume();
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in timeShiftResume", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "timeShiftSeekTo");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .timeShiftSeekTo(timeMs);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in timeShiftSeekTo", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void timeShiftSetPlaybackRate(IBinder sessionToken, float rate, int audioMode,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "timeShiftSetPlaybackRate");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .timeShiftSetPlaybackRate(rate, audioMode);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in timeShiftSetPlaybackRate", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "timeShiftEnablePositionTracking");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .timeShiftEnablePositionTracking(enable);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -1798,7 +1906,7 @@ public final class TvInputManagerService extends SystemService {
for (TvInputState inputState : userState.inputMap.values()) {
if (inputState.info.getComponent().equals(component)
- && inputState.state != INPUT_STATE_DISCONNECTED) {
+ && inputState.state != INPUT_STATE_CONNECTED) {
notifyInputStateChangedLocked(userState, inputState.info.getId(),
inputState.state, null);
}
@@ -1847,13 +1955,6 @@ public final class TvInputManagerService extends SystemService {
serviceState.callback = null;
abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
-
- for (TvInputState inputState : userState.inputMap.values()) {
- if (inputState.info.getComponent().equals(component)) {
- notifyInputStateChangedLocked(userState, inputState.info.getId(),
- INPUT_STATE_DISCONNECTED, null);
- }
- }
}
}
}
@@ -2144,6 +2245,58 @@ public final class TvInputManagerService extends SystemService {
}
}
}
+
+ @Override
+ public void onTimeShiftStatusChanged(int status) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onTimeShiftStatusChanged()");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
+ }
+ }
+ }
+
+ @Override
+ public void onTimeShiftStartPositionChanged(long timeMs) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onTimeShiftStartPositionChanged()");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
+ }
+ }
+ }
+
+ @Override
+ public void onTimeShiftCurrentPositionChanged(long timeMs) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onTimeShiftCurrentPositionChanged()");
+ }
+ if (mSessionState.session == null || mSessionState.client == null) {
+ return;
+ }
+ try {
+ mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
+ mSessionState.seq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
+ }
+ }
+ }
}
private static final class WatchLogHandler extends Handler {
@@ -2346,9 +2499,6 @@ public final class TvInputManagerService extends SystemService {
}
private static class SessionNotFoundException extends IllegalArgumentException {
- public SessionNotFoundException() {
- }
-
public SessionNotFoundException(String name) {
super(name);
}
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 7f7aae3..8fc979c 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -16,29 +16,21 @@
package com.android.server.updates;
+import com.android.server.EventLogTags;
+
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.provider.Settings;
-import android.util.Base64;
import android.util.EventLog;
import android.util.Slog;
-import com.android.server.EventLogTags;
-
-import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
-import java.io.InputStream;
import java.io.IOException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
+import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.security.Signature;
import libcore.io.IoUtils;
import libcore.io.Streams;
@@ -47,13 +39,9 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
private static final String TAG = "ConfigUpdateInstallReceiver";
- private static final String EXTRA_CONTENT_PATH = "CONTENT_PATH";
private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH";
- private static final String EXTRA_SIGNATURE = "SIGNATURE";
private static final String EXTRA_VERSION_NUMBER = "VERSION";
- private static final String UPDATE_CERTIFICATE_KEY = "config_update_certificate";
-
protected final File updateDir;
protected final File updateContent;
protected final File updateVersion;
@@ -72,16 +60,12 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
@Override
public void run() {
try {
- // get the certificate from Settings.Secure
- X509Certificate cert = getCert(context.getContentResolver());
// get the content path from the extras
byte[] altContent = getAltContent(context, intent);
// get the version from the extras
int altVersion = getVersionFromIntent(intent);
// get the previous value from the extras
String altRequiredHash = getRequiredHashFromIntent(intent);
- // get the signature from the extras
- String altSig = getSignatureFromIntent(intent);
// get the version currently being used
int currentVersion = getCurrentVersion();
// get the hash of the currently used value
@@ -91,10 +75,6 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
} else if (!verifyPreviousHash(currentHash, altRequiredHash)) {
EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED,
"Current hash did not match required value");
- } else if (!verifySignature(altContent, altVersion, altRequiredHash, altSig,
- cert)) {
- EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED,
- "Signature did not verify");
} else {
// install the new content
Slog.i(TAG, "Found new update, installing...");
@@ -115,20 +95,6 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
}.start();
}
- private X509Certificate getCert(ContentResolver cr) {
- // get the cert from settings
- String cert = Settings.Secure.getString(cr, UPDATE_CERTIFICATE_KEY);
- // convert it into a real certificate
- try {
- byte[] derCert = Base64.decode(cert.getBytes(), Base64.DEFAULT);
- InputStream istream = new ByteArrayInputStream(derCert);
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- return (X509Certificate) cf.generateCertificate(istream);
- } catch (CertificateException e) {
- throw new IllegalStateException("Got malformed certificate from settings, ignoring");
- }
- }
-
private Uri getContentFromIntent(Intent i) {
Uri data = i.getData();
if (data == null) {
@@ -153,14 +119,6 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
return extraValue.trim();
}
- private String getSignatureFromIntent(Intent i) {
- String extraValue = i.getStringExtra(EXTRA_SIGNATURE);
- if (extraValue == null) {
- throw new IllegalStateException("Missing required signature, ignoring.");
- }
- return extraValue.trim();
- }
-
private int getCurrentVersion() throws NumberFormatException {
try {
String strVersion = IoUtils.readFileAsString(updateVersion.getCanonicalPath()).trim();
@@ -216,16 +174,6 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
return current.equals(required);
}
- 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);
- signer.update(Long.toString(version).getBytes());
- signer.update(requiredPrevious.getBytes());
- return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT));
- }
-
protected void writeUpdate(File dir, File file, byte[] content) throws IOException {
FileOutputStream out = null;
File tmp = null;
diff --git a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
index 24318df..4e53687 100644
--- a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
@@ -19,7 +19,6 @@ package com.android.server.updates;
import android.content.Context;
import android.content.Intent;
import android.os.SystemProperties;
-import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Base64;
@@ -48,39 +47,6 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver {
super("/data/security/bundle", "sepolicy_bundle", "metadata/", "version");
}
- private void backupContexts(File contexts) {
- new File(contexts, versionPath).renameTo(
- new File(contexts, versionPath + "_backup"));
-
- new File(contexts, macPermissionsPath).renameTo(
- new File(contexts, macPermissionsPath + "_backup"));
-
- 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"));
-
- new File(contexts, serviceContextsPath).renameTo(
- new File(contexts, serviceContextsPath + "_backup"));
- }
-
- private void copyUpdate(File contexts) {
- new File(updateDir, versionPath).renameTo(new File(contexts, versionPath));
- new File(updateDir, macPermissionsPath).renameTo(new File(contexts, macPermissionsPath));
- 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));
- new File(updateDir, serviceContextsPath).renameTo(new File(contexts, serviceContextsPath));
- }
-
private int readInt(BufferedInputStream reader) throws IOException {
int value = 0;
for (int i=0; i < 4; i++) {
@@ -108,17 +74,27 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver {
writeUpdate(updateDir, destination, Base64.decode(chunk, Base64.DEFAULT));
}
+ private void deleteRecursive(File fileOrDirectory) {
+ if (fileOrDirectory.isDirectory())
+ for (File child : fileOrDirectory.listFiles())
+ deleteRecursive(child);
+ fileOrDirectory.delete();
+ }
+
private void unpackBundle() throws IOException {
BufferedInputStream stream = new BufferedInputStream(new FileInputStream(updateContent));
+ File tmp = new File(updateDir.getParentFile(), "tmp");
try {
int[] chunkLengths = readChunkLengths(stream);
- installFile(new File(updateDir, versionPath), stream, chunkLengths[0]);
- installFile(new File(updateDir, macPermissionsPath), stream, chunkLengths[1]);
- installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[2]);
- installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[3]);
- installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[4]);
- installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[5]);
- installFile(new File(updateDir, serviceContextsPath), stream, chunkLengths[6]);
+ deleteRecursive(tmp);
+ tmp.mkdirs();
+ installFile(new File(tmp, versionPath), stream, chunkLengths[0]);
+ installFile(new File(tmp, macPermissionsPath), stream, chunkLengths[1]);
+ installFile(new File(tmp, seappContextsPath), stream, chunkLengths[2]);
+ installFile(new File(tmp, propertyContextsPath), stream, chunkLengths[3]);
+ installFile(new File(tmp, fileContextsPath), stream, chunkLengths[4]);
+ installFile(new File(tmp, sepolicyPath), stream, chunkLengths[5]);
+ installFile(new File(tmp, serviceContextsPath), stream, chunkLengths[6]);
} finally {
IoUtils.closeQuietly(stream);
}
@@ -126,22 +102,22 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver {
private void applyUpdate() throws IOException, ErrnoException {
Slog.i(TAG, "Applying SELinux policy");
- File contexts = new File(updateDir.getParentFile(), "contexts");
+ File backup = new File(updateDir.getParentFile(), "backup");
File current = new File(updateDir.getParentFile(), "current");
- File update = new File(updateDir.getParentFile(), "update");
File tmp = new File(updateDir.getParentFile(), "tmp");
if (current.exists()) {
- Os.symlink(updateDir.getPath(), update.getPath());
- Os.rename(update.getPath(), current.getPath());
- } else {
- Os.symlink(updateDir.getPath(), current.getPath());
+ deleteRecursive(backup);
+ Os.rename(current.getPath(), backup.getPath());
+ }
+ try {
+ Os.rename(tmp.getPath(), current.getPath());
+ SystemProperties.set("selinux.reload_policy", "1");
+ } catch (ErrnoException e) {
+ Slog.e(TAG, "Could not update selinux policy: ", e);
+ if (backup.exists()) {
+ Os.rename(backup.getPath(), current.getPath());
+ }
}
- contexts.mkdirs();
- backupContexts(contexts);
- copyUpdate(contexts);
- Os.symlink(contexts.getPath(), tmp.getPath());
- Os.rename(tmp.getPath(), current.getPath());
- SystemProperties.set("selinux.reload_policy", "1");
}
@Override
diff --git a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
deleted file mode 100644
index 2fe68f8..0000000
--- a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 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/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
new file mode 100644
index 0000000..b260e4e
--- /dev/null
+++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 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.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import libcore.tzdata.update.TzDataBundleInstaller;
+
+/**
+ * An install receiver responsible for installing timezone data updates.
+ */
+public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ private static final String TAG = "TZDataInstallReceiver";
+
+ private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
+ private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/";
+ private static final String UPDATE_METADATA_DIR_NAME = "metadata/";
+ private static final String UPDATE_VERSION_FILE_NAME = "version";
+ private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip";
+
+ private final TzDataBundleInstaller installer;
+
+ public TzDataInstallReceiver() {
+ super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME,
+ UPDATE_VERSION_FILE_NAME);
+ installer = new TzDataBundleInstaller(TAG, TZ_DATA_DIR);
+ }
+
+ @Override
+ protected void install(byte[] content, int version) throws IOException {
+ boolean valid = installer.install(content);
+ Slog.i(TAG, "Timezone data install valid for this device: " + valid);
+ // Even if !valid, we call super.install(). Only in the event of an exception should we
+ // not. If we didn't do this we could attempt to install repeatedly.
+ super.install(content, version);
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index bd2e923..54be380 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -20,6 +20,7 @@ import static android.os.ParcelFileDescriptor.*;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.IUserSwitchObserver;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
@@ -164,6 +165,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
final IWindowManager mIWindowManager;
final IPackageManager mIPackageManager;
final MyPackageMonitor mMonitor;
+ final AppOpsManager mAppOpsManager;
WallpaperData mLastWallpaper;
/**
@@ -478,6 +480,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
mIPackageManager = AppGlobals.getPackageManager();
+ mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mMonitor = new MyPackageMonitor();
mMonitor.register(context, null, UserHandle.ALL, true);
getWallpaperDir(UserHandle.USER_OWNER).mkdirs();
@@ -535,6 +538,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
@Override
public void onUserSwitchComplete(int newUserId) throws RemoteException {
}
+
+ @Override
+ public void onForegroundProfileSwitch(int newProfileId) {
+ // Ignore.
+ }
});
} catch (RemoteException e) {
// TODO Auto-generated catch block
@@ -613,8 +621,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
}
- public void clearWallpaper() {
+ public void clearWallpaper(String callingPackage) {
if (DEBUG) Slog.v(TAG, "clearWallpaper");
+ checkPermission(android.Manifest.permission.SET_WALLPAPER);
+ if (!isWallpaperSupported(callingPackage)) {
+ return;
+ }
synchronized (mLock) {
clearWallpaperLocked(false, UserHandle.getCallingUserId(), null);
}
@@ -622,6 +634,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
void clearWallpaperLocked(boolean defaultFailed, int userId, IRemoteCallback reply) {
WallpaperData wallpaper = mWallpaperMap.get(userId);
+ if (wallpaper == null) {
+ return;
+ }
File f = new File(getWallpaperDir(userId), WALLPAPER);
if (f.exists()) {
f.delete();
@@ -668,6 +683,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
Binder.restoreCallingIdentity(ident);
}
for (UserInfo user: users) {
+ // ignore managed profiles
+ if (user.isManagedProfile()) {
+ continue;
+ }
WallpaperData wd = mWallpaperMap.get(user.id);
if (wd == null) {
// User hasn't started yet, so load her settings to peek at the wallpaper
@@ -690,8 +709,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
return p;
}
- public void setDimensionHints(int width, int height) throws RemoteException {
+ public void setDimensionHints(int width, int height, String callingPackage)
+ throws RemoteException {
checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
+ if (!isWallpaperSupported(callingPackage)) {
+ return;
+ }
synchronized (mLock) {
int userId = UserHandle.getCallingUserId();
WallpaperData wallpaper = mWallpaperMap.get(userId);
@@ -733,19 +756,30 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
public int getWidthHint() throws RemoteException {
synchronized (mLock) {
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
- return wallpaper.width;
+ if (wallpaper != null) {
+ return wallpaper.width;
+ } else {
+ return 0;
+ }
}
}
public int getHeightHint() throws RemoteException {
synchronized (mLock) {
WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
- return wallpaper.height;
+ if (wallpaper != null) {
+ return wallpaper.height;
+ } else {
+ return 0;
+ }
}
}
- public void setDisplayPadding(Rect padding) {
+ public void setDisplayPadding(Rect padding, String callingPackage) {
checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
+ if (!isWallpaperSupported(callingPackage)) {
+ return;
+ }
synchronized (mLock) {
int userId = UserHandle.getCallingUserId();
WallpaperData wallpaper = mWallpaperMap.get(userId);
@@ -791,6 +825,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
wallpaperUserId = UserHandle.getUserId(callingUid);
}
WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId);
+ if (wallpaper == null) {
+ return null;
+ }
try {
if (outParams != null) {
outParams.putInt("width", wallpaper.width);
@@ -814,15 +851,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
WallpaperData wallpaper = mWallpaperMap.get(userId);
- if (wallpaper.connection != null) {
+ if (wallpaper != null && wallpaper.connection != null) {
return wallpaper.connection.mInfo;
}
return null;
}
}
- public ParcelFileDescriptor setWallpaper(String name) {
+ public ParcelFileDescriptor setWallpaper(String name, String callingPackage) {
checkPermission(android.Manifest.permission.SET_WALLPAPER);
+ if (!isWallpaperSupported(callingPackage)) {
+ return null;
+ }
synchronized (mLock) {
if (DEBUG) Slog.v(TAG, "setWallpaper");
int userId = UserHandle.getCallingUserId();
@@ -868,6 +908,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
return null;
}
+ public void setWallpaperComponentChecked(ComponentName name, String callingPackage) {
+ if (isWallpaperSupported(callingPackage)) {
+ setWallpaperComponent(name);
+ }
+ }
+
+ // ToDo: Remove this version of the function
public void setWallpaperComponent(ComponentName name) {
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
synchronized (mLock) {
@@ -1097,6 +1144,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
}
}
+ /**
+ * Certain user types do not support wallpapers (e.g. managed profiles). The check is
+ * implemented through through the OP_WRITE_WALLPAPER AppOp.
+ */
+ public boolean isWallpaperSupported(String callingPackage) {
+ return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_WRITE_WALLPAPER, Binder.getCallingUid(),
+ callingPackage) == AppOpsManager.MODE_ALLOWED;
+ }
+
private static JournaledFile makeJournaledFile(int userId) {
final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
return new JournaledFile(new File(base), new File(base + ".tmp"));
@@ -1135,6 +1191,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
out.endTag(null, "wp");
out.endDocument();
+ stream.flush();
+ FileUtils.sync(stream);
stream.close();
journal.commit();
} catch (IOException e) {
@@ -1172,7 +1230,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
private void loadSettingsLocked(int userId) {
if (DEBUG) Slog.v(TAG, "loadSettingsLocked");
-
+
JournaledFile journal = makeJournaledFile(userId);
FileInputStream stream = null;
File file = journal.chooseForRead();
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index d4c5f87..ac79b36 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -40,6 +40,8 @@ public class WebViewUpdateService extends SystemService {
private boolean mRelroReady32Bit = false;
private boolean mRelroReady64Bit = false;
+ private String oldWebViewPackageName = null;
+
private BroadcastReceiver mWebViewUpdatedReceiver;
public WebViewUpdateService(Context context) {
@@ -51,9 +53,22 @@ public class WebViewUpdateService extends SystemService {
mWebViewUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String webviewPackage = "package:" + WebViewFactory.getWebViewPackageName();
- if (webviewPackage.equals(intent.getDataString())) {
- onWebViewUpdateInstalled();
+
+ for (String packageName : WebViewFactory.getWebViewPackageNames()) {
+ String webviewPackage = "package:" + packageName;
+
+ if (webviewPackage.equals(intent.getDataString())) {
+ String usedPackageName =
+ WebViewFactory.findPreferredWebViewPackage().packageName;
+ // Only trigger update actions if the updated package is the one that
+ // will be used, or the one that was in use before the update.
+ if (packageName.equals(usedPackageName) ||
+ packageName.equals(oldWebViewPackageName)) {
+ onWebViewUpdateInstalled();
+ oldWebViewPackageName = usedPackageName;
+ }
+ return;
+ }
}
}
};
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 08754f9..91ce739 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -339,6 +339,7 @@ final class AccessibilityController {
case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
+ case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL:
case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
case WindowManager.LayoutParams.TYPE_PHONE:
@@ -397,8 +398,6 @@ final class AccessibilityController {
private final class MagnifiedViewport {
- private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5;
-
private final SparseArray<WindowState> mTempWindowStates =
new SparseArray<WindowState>();
@@ -411,6 +410,8 @@ final class AccessibilityController {
private final Region mMagnifiedBounds = new Region();
private final Region mOldMagnifiedBounds = new Region();
+ private final Path mCircularPath;
+
private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain();
private final WindowManager mWindowManager;
@@ -425,12 +426,22 @@ final class AccessibilityController {
public MagnifiedViewport() {
mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
- mBorderWidth = TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP,
- mContext.getResources().getDisplayMetrics());
+ mBorderWidth = mContext.getResources().getDimension(
+ com.android.internal.R.dimen.accessibility_magnification_indicator_width);
mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
mDrawBorderInset = (int) mBorderWidth / 2;
mWindow = new ViewportWindow(mContext);
+
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_windowIsRound)) {
+ mCircularPath = new Path();
+ mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+ final int centerXY = mTempPoint.x / 2;
+ mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
+ } else {
+ mCircularPath = null;
+ }
+
recomputeBoundsLocked();
}
@@ -459,6 +470,10 @@ final class AccessibilityController {
Region availableBounds = mTempRegion1;
availableBounds.set(0, 0, screenWidth, screenHeight);
+ if (mCircularPath != null) {
+ availableBounds.setPath(mCircularPath, availableBounds);
+ }
+
Region nonMagnifiedBounds = mTempRegion4;
nonMagnifiedBounds.set(0, 0, 0, 0);
@@ -606,9 +621,8 @@ final class AccessibilityController {
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) {
+ if (windowState.isOnScreen() &&
+ !windowState.mWinAnimator.mEnterAnimationPending) {
outWindows.put(windowState.mLayer, windowState);
}
}
@@ -656,7 +670,7 @@ final class AccessibilityController {
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
typedValue, true);
- final int borderColor = context.getResources().getColor(typedValue.resourceId);
+ final int borderColor = context.getColor(typedValue.resourceId);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mBorderWidth);
@@ -1237,7 +1251,6 @@ final class AccessibilityController {
&& windowType != WindowManager.LayoutParams.TYPE_DRAG
&& windowType != WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER
&& windowType != WindowManager.LayoutParams.TYPE_POINTER
- && windowType != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
&& windowType != WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY
&& windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
&& windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c0d54e1..9033c9c 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -22,6 +22,7 @@ import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
+import android.os.IBinder;
import android.os.IRemoteCallback;
import android.util.Slog;
import android.view.WindowManager;
@@ -30,15 +31,22 @@ import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.ClipRectAnimation;
+import android.view.animation.ClipRectLRAnimation;
+import android.view.animation.ClipRectTBAnimation;
import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
+import android.view.animation.TranslateXAnimation;
+import android.view.animation.TranslateYAnimation;
import com.android.internal.util.DumpUtils.Dump;
import com.android.server.AttributeCache;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import static android.view.WindowManagerInternal.AppTransitionListener;
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;
@@ -134,6 +142,7 @@ public class AppTransition implements Dump {
private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP = 5;
private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6;
private static final int NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE = 7;
+ private static final int NEXT_TRANSIT_TYPE_CLIP_REVEAL = 8;
private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
// These are the possible states for the enter/exit activities during a thumbnail transition
@@ -169,19 +178,26 @@ public class AppTransition implements Dump {
private final Interpolator mDecelerateInterpolator;
private final Interpolator mThumbnailFadeInInterpolator;
private final Interpolator mThumbnailFadeOutInterpolator;
- private final Interpolator mThumbnailFastOutSlowInInterpolator;
+ private final Interpolator mLinearOutSlowInInterpolator;
+ private final Interpolator mFastOutSlowInInterpolator;
+ private final LinearInterpolator mLinearInterpolator;
private int mCurrentUserId = 0;
+ private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
+
AppTransition(Context context, Handler h) {
mContext = context;
mH = h;
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.linear_out_slow_in);
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_slow_in);
+ mLinearInterpolator = new LinearInterpolator();
mConfigShortAnimTime = context.getResources().getInteger(
com.android.internal.R.integer.config_shortAnimTime);
mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.decelerate_cubic);
- mThumbnailFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
mThumbnailFadeInInterpolator = new Interpolator() {
@Override
public float getInterpolation(float input) {
@@ -276,12 +292,18 @@ public class AppTransition implements Dump {
void prepare() {
if (!isRunning()) {
mAppTransitionState = APP_STATE_IDLE;
+ notifyAppTransitionPendingLocked();
}
}
- void goodToGo() {
+ void goodToGo(AppWindowAnimator openingAppAnimator, AppWindowAnimator closingAppAnimator) {
mNextAppTransition = TRANSIT_UNSET;
mAppTransitionState = APP_STATE_RUNNING;
+ notifyAppTransitionStartingLocked(
+ openingAppAnimator != null ? openingAppAnimator.mAppToken.token : null,
+ closingAppAnimator != null ? closingAppAnimator.mAppToken.token : null,
+ openingAppAnimator != null ? openingAppAnimator.animation : null,
+ closingAppAnimator != null ? closingAppAnimator.animation : null);
}
void clear() {
@@ -294,6 +316,38 @@ public class AppTransition implements Dump {
setAppTransition(AppTransition.TRANSIT_UNSET);
clear();
setReady();
+ notifyAppTransitionCancelledLocked();
+ }
+
+ void registerListenerLocked(AppTransitionListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void notifyAppTransitionFinishedLocked(AppWindowAnimator animator) {
+ IBinder token = animator != null ? animator.mAppToken.token : null;
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onAppTransitionFinishedLocked(token);
+ }
+ }
+
+ private void notifyAppTransitionPendingLocked() {
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onAppTransitionPendingLocked();
+ }
+ }
+
+ private void notifyAppTransitionCancelledLocked() {
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onAppTransitionCancelledLocked();
+ }
+ }
+
+ private void notifyAppTransitionStartingLocked(IBinder openToken,
+ IBinder closeToken, Animation openAnimation, Animation closeAnimation) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onAppTransitionStartingLocked(openToken, closeToken, openAnimation,
+ closeAnimation);
+ }
}
private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) {
@@ -447,6 +501,86 @@ public class AppTransition implements Dump {
return a;
}
+ private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame) {
+ final Animation anim;
+ if (enter) {
+ // Reveal will expand and move faster in horizontal direction
+
+ // Start from upper left of start and move to final position
+ final int appWidth = appFrame.width();
+ final int appHeight = appFrame.height();
+
+ // Start from size of launch icon, expand to full width/height
+ Animation clipAnimLR = new ClipRectLRAnimation(
+ (appWidth - mNextAppTransitionStartWidth) / 2,
+ (appWidth + mNextAppTransitionStartWidth) / 2, 0, appWidth);
+ clipAnimLR.setInterpolator(mLinearOutSlowInInterpolator);
+ clipAnimLR.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+ Animation clipAnimTB = new ClipRectTBAnimation(
+ (appHeight - mNextAppTransitionStartHeight) / 2,
+ (appHeight + mNextAppTransitionStartHeight) / 2, 0, appHeight);
+ clipAnimTB.setInterpolator(mFastOutSlowInInterpolator);
+ clipAnimTB.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+
+ // Start from middle of launch icon area, move to 0, 0
+ int startMiddleX = mNextAppTransitionStartX +
+ (mNextAppTransitionStartWidth - appWidth) / 2 - appFrame.left;
+ int startMiddleY = mNextAppTransitionStartY +
+ (mNextAppTransitionStartHeight - appHeight) / 2 - appFrame.top;
+
+ TranslateXAnimation translateX = new TranslateXAnimation(
+ Animation.ABSOLUTE, startMiddleX, Animation.ABSOLUTE, 0);
+ translateX.setInterpolator(mLinearOutSlowInInterpolator);
+ translateX.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+ TranslateYAnimation translateY = new TranslateYAnimation(
+ Animation.ABSOLUTE, startMiddleY, Animation.ABSOLUTE, 0);
+ translateY.setInterpolator(mFastOutSlowInInterpolator);
+ translateY.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+
+ // Quick fade-in from icon to app window
+ final int alphaDuration = 100;
+ AlphaAnimation alpha = new AlphaAnimation(0.1f, 1);
+ alpha.setDuration(alphaDuration);
+ alpha.setInterpolator(mLinearInterpolator);
+
+ AnimationSet set = new AnimationSet(false);
+ set.addAnimation(clipAnimLR);
+ set.addAnimation(clipAnimTB);
+ set.addAnimation(translateX);
+ set.addAnimation(translateY);
+ set.addAnimation(alpha);
+ set.initialize(appWidth, appHeight, appWidth, appHeight);
+ anim = set;
+ } else {
+ final long duration;
+ switch (transit) {
+ case TRANSIT_ACTIVITY_OPEN:
+ case TRANSIT_ACTIVITY_CLOSE:
+ duration = mConfigShortAnimTime;
+ break;
+ default:
+ duration = DEFAULT_APP_TRANSITION_DURATION;
+ break;
+ }
+ 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.
+ anim = new AlphaAnimation(1, 0);
+ anim.setDetachWallpaper(true);
+ } else {
+ // For normal animations, the exiting element just holds in place.
+ anim = new AlphaAnimation(1, 1);
+ }
+ anim.setInterpolator(mDecelerateInterpolator);
+ anim.setDuration(duration);
+ anim.setFillAfter(true);
+ }
+ return anim;
+ }
+
/**
* Prepares the specified animation with a standard duration, interpolator, etc.
*/
@@ -522,14 +656,14 @@ public class AppTransition implements Dump {
Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
mNextAppTransitionStartX + (thumbWidth / 2f),
mNextAppTransitionStartY + (thumbHeight / 2f));
- scale.setInterpolator(mThumbnailFastOutSlowInInterpolator);
+ scale.setInterpolator(mFastOutSlowInInterpolator);
scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
Animation alpha = new AlphaAnimation(1, 0);
alpha.setInterpolator(mThumbnailFadeOutInterpolator);
alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
Animation translate = new TranslateAnimation(0, 0, 0, -unscaledStartY +
mNextAppTransitionInsets.top);
- translate.setInterpolator(mThumbnailFastOutSlowInInterpolator);
+ translate.setInterpolator(mFastOutSlowInInterpolator);
translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
// This AnimationSet uses the Interpolators assigned above.
@@ -543,14 +677,14 @@ public class AppTransition implements Dump {
Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f,
mNextAppTransitionStartX + (thumbWidth / 2f),
mNextAppTransitionStartY + (thumbHeight / 2f));
- scale.setInterpolator(mThumbnailFastOutSlowInInterpolator);
+ scale.setInterpolator(mFastOutSlowInInterpolator);
scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
Animation alpha = new AlphaAnimation(0f, 1f);
alpha.setInterpolator(mThumbnailFadeInInterpolator);
alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
Animation translate = new TranslateAnimation(0, 0, -unscaledStartY +
mNextAppTransitionInsets.top, 0);
- translate.setInterpolator(mThumbnailFastOutSlowInInterpolator);
+ translate.setInterpolator(mFastOutSlowInInterpolator);
translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
// This AnimationSet uses the Interpolators assigned above.
@@ -562,7 +696,7 @@ public class AppTransition implements Dump {
}
return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, 0,
- mThumbnailFastOutSlowInInterpolator);
+ mFastOutSlowInInterpolator);
}
/**
@@ -698,7 +832,7 @@ public class AppTransition implements Dump {
int duration = Math.max(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION,
THUMBNAIL_APP_TRANSITION_DURATION);
return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
- mThumbnailFastOutSlowInInterpolator);
+ mFastOutSlowInInterpolator);
}
/**
@@ -809,7 +943,7 @@ public class AppTransition implements Dump {
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets,
- boolean isFullScreen, boolean isVoiceInteraction) {
+ Rect appFrame, boolean isFullScreen, boolean isVoiceInteraction) {
Animation a;
if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
|| transit == TRANSIT_TASK_OPEN
@@ -845,6 +979,12 @@ public class AppTransition implements Dump {
"applyAnimation:"
+ " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE"
+ " transit=" + transit + " Callers=" + Debug.getCallers(3));
+ } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
+ a = createClipRevealAnimationLocked(transit, enter, appFrame);
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+ "applyAnimation:"
+ + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
+ + " 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,
@@ -987,6 +1127,19 @@ public class AppTransition implements Dump {
}
}
+ void overridePendingAppTransitionClipReveal(int startX, int startY,
+ int startWidth, int startHeight) {
+ if (isTransitionSet()) {
+ mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL;
+ 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()) {
@@ -1130,32 +1283,34 @@ public class AppTransition implements Dump {
}
@Override
- public void dump(PrintWriter pw) {
- pw.print(" " + this);
- pw.print(" mAppTransitionState="); pw.println(appStateToString());
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.println(this);
+ pw.print(prefix); pw.print("mAppTransitionState="); pw.println(appStateToString());
if (mNextAppTransitionType != NEXT_TRANSIT_TYPE_NONE) {
- pw.print(" mNextAppTransitionType="); pw.println(transitTypeToString());
+ pw.print(prefix); pw.print("mNextAppTransitionType=");
+ pw.println(transitTypeToString());
}
switch (mNextAppTransitionType) {
case NEXT_TRANSIT_TYPE_CUSTOM:
- pw.print(" mNextAppTransitionPackage=");
+ pw.print(prefix); pw.print("mNextAppTransitionPackage=");
pw.println(mNextAppTransitionPackage);
- pw.print(" mNextAppTransitionEnter=0x");
+ pw.print(prefix); pw.print("mNextAppTransitionEnter=0x");
pw.print(Integer.toHexString(mNextAppTransitionEnter));
pw.print(" mNextAppTransitionExit=0x");
pw.println(Integer.toHexString(mNextAppTransitionExit));
break;
case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
- pw.print(" mNextAppTransitionPackage=");
+ pw.print(prefix); pw.print("mNextAppTransitionPackage=");
pw.println(mNextAppTransitionPackage);
- pw.print(" mNextAppTransitionInPlace=0x");
+ pw.print(prefix); pw.print("mNextAppTransitionInPlace=0x");
pw.print(Integer.toHexString(mNextAppTransitionInPlace));
break;
case NEXT_TRANSIT_TYPE_SCALE_UP:
- pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX);
+ pw.print(prefix); pw.print("mNextAppTransitionStartX=");
+ pw.print(mNextAppTransitionStartX);
pw.print(" mNextAppTransitionStartY=");
pw.println(mNextAppTransitionStartY);
- pw.print(" mNextAppTransitionStartWidth=");
+ pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
pw.print(mNextAppTransitionStartWidth);
pw.print(" mNextAppTransitionStartHeight=");
pw.println(mNextAppTransitionStartHeight);
@@ -1164,22 +1319,23 @@ public class AppTransition implements Dump {
case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP:
case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN:
- pw.print(" mNextAppTransitionThumbnail=");
+ pw.print(prefix); pw.print("mNextAppTransitionThumbnail=");
pw.print(mNextAppTransitionThumbnail);
pw.print(" mNextAppTransitionStartX=");
pw.print(mNextAppTransitionStartX);
pw.print(" mNextAppTransitionStartY=");
pw.println(mNextAppTransitionStartY);
- pw.print(" mNextAppTransitionStartWidth=");
+ pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
pw.print(mNextAppTransitionStartWidth);
pw.print(" mNextAppTransitionStartHeight=");
pw.println(mNextAppTransitionStartHeight);
- pw.print(" mNextAppTransitionScaleUp="); pw.println(mNextAppTransitionScaleUp);
+ pw.print(prefix); pw.print("mNextAppTransitionScaleUp=");
+ pw.println(mNextAppTransitionScaleUp);
break;
}
if (mNextAppTransitionCallback != null) {
- pw.print(" mNextAppTransitionCallback=");
- pw.println(mNextAppTransitionCallback);
+ pw.print(prefix); pw.print("mNextAppTransitionCallback=");
+ pw.println(mNextAppTransitionCallback);
}
}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 5c81126..55ec9fc 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import android.graphics.Matrix;
-import android.os.RemoteException;
import android.util.Slog;
import android.util.TimeUtils;
import android.view.Display;
@@ -142,6 +141,10 @@ public class AppWindowAnimator {
}
}
+ public boolean isAnimating() {
+ return animation != null || mAppToken.inPendingTransaction;
+ }
+
public void clearThumbnail() {
if (thumbnail != null) {
thumbnail.destroy();
@@ -239,7 +242,7 @@ public class AppWindowAnimator {
}
// This must be called while inside a transaction.
- boolean stepAnimationLocked(long currentTime) {
+ boolean stepAnimationLocked(long currentTime, final int displayId) {
if (mService.okToDisplay()) {
// We will run animations as long as the display isn't frozen.
@@ -289,7 +292,7 @@ public class AppWindowAnimator {
}
mAnimator.setAppLayoutChanges(this, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM,
- "AppWindowToken");
+ "AppWindowToken", displayId);
clearAnimation();
animating = false;
@@ -312,23 +315,7 @@ public class AppWindowAnimator {
for (int i = 0; i < numAllAppWinAnimators; i++) {
mAllAppWinAnimators.get(i).finishExit();
}
- if (mAppToken.mLaunchTaskBehind) {
- try {
- mService.mActivityManager.notifyLaunchTaskBehindComplete(mAppToken.token);
- } catch (RemoteException e) {
- }
- mAppToken.mLaunchTaskBehind = false;
- } else {
- mAppToken.updateReportedVisibilityLocked();
- if (mAppToken.mEnteringAnimation) {
- mAppToken.mEnteringAnimation = false;
- try {
- mService.mActivityManager.notifyEnterAnimationComplete(mAppToken.token);
- } catch (RemoteException e) {
- }
- }
- }
-
+ mService.mAppTransition.notifyAppTransitionFinishedLocked(this);
return false;
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index da25c53..a210223 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -52,11 +52,11 @@ class AppWindowToken extends WindowToken {
final boolean voiceInteraction;
- int groupId = -1;
+ Task mTask;
boolean appFullscreen;
int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
boolean layoutConfigChanges;
- boolean showWhenLocked;
+ boolean showForAllUsers;
// The input dispatching timeout for this application token in nanoseconds.
long inputDispatchingTimeoutNanos;
@@ -107,7 +107,7 @@ class AppWindowToken extends WindowToken {
// Input application handle used by the input dispatcher.
final InputApplicationHandle mInputApplicationHandle;
- boolean mDeferRemoval;
+ boolean mIsExiting;
boolean mLaunchTaskBehind;
boolean mEnteringAnimation;
@@ -252,6 +252,21 @@ class AppWindowToken extends WindowToken {
return false;
}
+ void removeAppFromTaskLocked() {
+ mIsExiting = false;
+ removeAllWindows();
+
+ // Use local variable because removeAppToken will null out mTask.
+ final Task task = mTask;
+ if (task != null) {
+ if (!task.removeAppToken(this)) {
+ Slog.e(WindowManagerService.TAG, "removeAppFromTaskLocked: token=" + this
+ + " not found.");
+ }
+ task.mStack.mExitingAppTokens.remove(this);
+ }
+ }
+
@Override
void removeAllWindows() {
for (int winNdx = allAppWindows.size() - 1; winNdx >= 0;
@@ -266,8 +281,10 @@ class AppWindowToken extends WindowToken {
Slog.w(WindowManagerService.TAG, "removeAllWindows: removing win=" + win);
}
- win.mService.removeWindowLocked(win.mSession, win);
+ service.removeWindowLocked(win);
}
+ allAppWindows.clear();
+ windows.clear();
}
@Override
@@ -279,8 +296,8 @@ class AppWindowToken extends WindowToken {
if (allAppWindows.size() > 0) {
pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows);
}
- pw.print(prefix); pw.print("groupId="); pw.print(groupId);
- pw.print(" appFullscreen="); pw.print(appFullscreen);
+ pw.print(prefix); pw.print("task="); pw.println(mTask);
+ pw.print(prefix); pw.print(" appFullscreen="); pw.print(appFullscreen);
pw.print(" requestedOrientation="); pw.println(requestedOrientation);
pw.print(prefix); pw.print("hiddenRequested="); pw.print(hiddenRequested);
pw.print(" clientHidden="); pw.print(clientHidden);
@@ -304,11 +321,11 @@ class AppWindowToken extends WindowToken {
pw.print(prefix); pw.print("inPendingTransaction=");
pw.println(inPendingTransaction);
}
- if (startingData != null || removed || firstWindowDrawn || mDeferRemoval) {
+ if (startingData != null || removed || firstWindowDrawn || mIsExiting) {
pw.print(prefix); pw.print("startingData="); pw.print(startingData);
pw.print(" removed="); pw.print(removed);
pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
- pw.print(" mDeferRemoval="); pw.println(mDeferRemoval);
+ pw.print(" mIsExiting="); pw.println(mIsExiting);
}
if (startingWindow != null || startingView != null
|| startingDisplayed || startingMoved) {
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index 9fdfc47..7c2da2d 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -50,9 +50,10 @@ class CircularDisplayMask {
private int mRotation;
private boolean mVisible;
private boolean mDimensionsUnequal = false;
+ private int mMaskThickness;
public CircularDisplayMask(Display display, SurfaceSession session, int zOrder,
- int screenOffset) {
+ int screenOffset, int maskThickness) {
mScreenSize = new Point();
display.getSize(mScreenSize);
if (mScreenSize.x != mScreenSize.y) {
@@ -84,6 +85,7 @@ class CircularDisplayMask {
mPaint.setAntiAlias(true);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mScreenOffset = screenOffset;
+ mMaskThickness = maskThickness;
}
private void drawIfNeeded() {
@@ -121,8 +123,8 @@ class CircularDisplayMask {
int circleRadius = mScreenSize.x / 2;
c.drawColor(Color.BLACK);
- // The radius is reduced by 1 to provide an anti aliasing effect on the display edges.
- c.drawCircle(circleRadius, circleRadius, circleRadius - 1, mPaint);
+ // The radius is reduced by mMaskThickness to provide an anti aliasing effect on the display edges.
+ c.drawCircle(circleRadius, circleRadius, circleRadius - mMaskThickness, mPaint);
mSurface.unlockCanvasAndPost(c);
}
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index c09ea5c..e385be3 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -140,10 +140,9 @@ public class DimLayer {
}
/**
- * @param layer The new layer value.
- * @param inTransaction Whether the call is made within a surface transaction.
+ * NOTE: Must be called with Surface transaction open.
*/
- void adjustSurface(int layer, boolean inTransaction) {
+ private void adjustBounds() {
final int dw, dh;
final float xPos, yPos;
if (!mStack.isFullscreen()) {
@@ -163,29 +162,24 @@ public class DimLayer {
yPos = -1 * dh / 6;
}
- try {
- if (!inTransaction) {
- SurfaceControl.openTransaction();
- }
- mDimSurface.setPosition(xPos, yPos);
- mDimSurface.setSize(dw, dh);
- mDimSurface.setLayer(layer);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Failure setting size or layer", e);
- } finally {
- if (!inTransaction) {
- SurfaceControl.closeTransaction();
- }
- }
+ mDimSurface.setPosition(xPos, yPos);
+ mDimSurface.setSize(dw, dh);
+
mLastBounds.set(mBounds);
- mLayer = layer;
}
- // Assumes that surface transactions are currently closed.
+ /** @param bounds The new bounds to set */
void setBounds(Rect bounds) {
mBounds.set(bounds);
if (isDimming() && !mLastBounds.equals(bounds)) {
- adjustSurface(mLayer, false);
+ try {
+ SurfaceControl.openTransaction();
+ adjustBounds();
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure setting size", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
}
}
@@ -224,9 +218,10 @@ public class DimLayer {
return;
}
- if (!mLastBounds.equals(mBounds) || mLayer != layer) {
- adjustSurface(layer, true);
+ if (!mLastBounds.equals(mBounds)) {
+ adjustBounds();
}
+ setLayer(layer);
long curTime = SystemClock.uptimeMillis();
final boolean animating = isAnimating();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f57adaf..f914369 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -66,6 +66,7 @@ class DisplayContent {
int mBaseDisplayWidth = 0;
int mBaseDisplayHeight = 0;
int mBaseDisplayDensity = 0;
+ boolean mDisplayScalingDisabled;
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Display mDisplay;
@@ -241,22 +242,24 @@ class DisplayContent {
mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
}
}
+ if (mTapDetector != null) {
+ mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
+ }
}
- void switchUserStacks(int newUserId) {
+ void switchUserStacks() {
final WindowList windows = getWindowList();
for (int i = 0; i < windows.size(); i++) {
final WindowState win = windows.get(i);
if (win.isHiddenFromUserLocked()) {
- if (DEBUG_VISIBILITY) Slog.w(TAG, "user changing " + newUserId + " hiding "
- + win + ", attrs=" + win.mAttrs.type + ", belonging to "
- + win.mOwnerUid);
+ if (DEBUG_VISIBILITY) Slog.w(TAG, "user changing, hiding " + win
+ + ", attrs=" + win.mAttrs.type + ", belonging to " + win.mOwnerUid);
win.hideLw(false);
}
}
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
- mStacks.get(stackNdx).switchUser(newUserId);
+ mStacks.get(stackNdx).switchUser();
}
}
@@ -327,16 +330,10 @@ class DisplayContent {
AppTokenList tokens = task.mAppTokens;
for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
AppWindowToken wtoken = tokens.get(tokenNdx);
- if (wtoken.mDeferRemoval) {
- stack.mExitingAppTokens.remove(wtoken);
- wtoken.mDeferRemoval = false;
- mService.removeAppFromTaskLocked(wtoken);
+ if (wtoken.mIsExiting) {
+ wtoken.removeAppFromTaskLocked();
}
}
- if (task.mDeferRemoval) {
- task.mDeferRemoval = false;
- mService.removeTaskLocked(task);
- }
}
}
}
@@ -345,6 +342,12 @@ class DisplayContent {
}
}
+ static int deltaRotation(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
final String subPrefix = " " + prefix;
@@ -358,6 +361,9 @@ class DisplayContent {
pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight);
pw.print(" "); pw.print(mBaseDisplayDensity); pw.print("dpi");
}
+ if (mDisplayScalingDisabled) {
+ pw.println(" noscale");
+ }
pw.print(" cur=");
pw.print(mDisplayInfo.logicalWidth);
pw.print("x"); pw.print(mDisplayInfo.logicalHeight);
@@ -384,7 +390,7 @@ class DisplayContent {
ArrayList<Task> tasks = stack.getTasks();
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
final Task task = tasks.get(taskNdx);
- pw.print(" mTaskId="); pw.println(task.taskId);
+ pw.print(" mTaskId="); pw.println(task.mTaskId);
AppTokenList tokens = task.mAppTokens;
for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx, ++ndx) {
final AppWindowToken wtoken = tokens.get(tokenNdx);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index c6951bd..1a125d4 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -24,6 +24,7 @@ import com.android.server.wm.WindowManagerService.H;
import android.content.ClipData;
import android.content.ClipDescription;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.Region;
import android.os.IBinder;
import android.os.Message;
@@ -63,6 +64,7 @@ class DragState {
Display mDisplay;
private final Region mTmpRegion = new Region();
+ private final Rect mTmpRect = new Rect();
DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
int flags, IBinder localWin) {
@@ -411,6 +413,12 @@ class DragState {
continue;
}
+ child.getStackBounds(mTmpRect);
+ if (!mTmpRect.contains(x, y)) {
+ // outside of this window's activity stack == don't tell about drags
+ continue;
+ }
+
child.getTouchableRegion(mTmpRegion);
final int touchFlags = flags &
diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
index 62f2b48..4c8a6f9 100644
--- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -25,7 +25,6 @@ import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.util.Slog;
import android.view.Display;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
@@ -93,7 +92,9 @@ class EmulatorDisplayOverlay {
}
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC);
mSurfaceControl.setPosition(0, 0);
- mOverlay.setBounds(0, 0, mScreenSize.x, mScreenSize.y);
+ // Always draw the overlay with square dimensions
+ int size = Math.max(mScreenSize.x, mScreenSize.y);
+ mOverlay.setBounds(0, 0, size, size);
mOverlay.draw(c);
mSurface.unlockCanvasAndPost(c);
}
diff --git a/services/core/java/com/android/server/wm/FocusedStackFrame.java b/services/core/java/com/android/server/wm/FocusedStackFrame.java
index f1f5fe8..826fe97 100644
--- a/services/core/java/com/android/server/wm/FocusedStackFrame.java
+++ b/services/core/java/com/android/server/wm/FocusedStackFrame.java
@@ -16,14 +16,14 @@
package com.android.server.wm;
-import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
import static com.android.server.wm.WindowManagerService.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerService.SHOW_LIGHT_TRANSACTIONS;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.graphics.Region;
import android.util.Slog;
import android.view.Display;
import android.view.Surface.OutOfResourcesException;
@@ -35,14 +35,17 @@ import com.android.server.wm.WindowStateAnimator.SurfaceTrace;
class FocusedStackFrame {
private static final String TAG = "FocusedStackFrame";
- private static final int THICKNESS = 10;
+ private static final boolean DEBUG = false;
+ private static final int THICKNESS = 2;
private static final float ALPHA = 0.3f;
private final SurfaceControl mSurfaceControl;
private final Surface mSurface = new Surface();
+ private final Paint mInnerPaint = new Paint();
+ private final Paint mOuterPaint = new Paint();
+ private final Rect mBounds = new Rect();
private final Rect mLastBounds = new Rect();
- final Rect mBounds = new Rect();
- private final Rect mTmpDrawRect = new Rect();
+ private int mLayer = -1;
public FocusedStackFrame(Display display, SurfaceSession session) {
SurfaceControl ctrl = null;
@@ -60,83 +63,84 @@ class FocusedStackFrame {
} catch (OutOfResourcesException e) {
}
mSurfaceControl = ctrl;
+
+ mInnerPaint.setStyle(Paint.Style.STROKE);
+ mInnerPaint.setStrokeWidth(THICKNESS);
+ mInnerPaint.setColor(Color.WHITE);
+ mOuterPaint.setStyle(Paint.Style.STROKE);
+ mOuterPaint.setStrokeWidth(THICKNESS);
+ mOuterPaint.setColor(Color.BLACK);
}
- private void draw(Rect bounds, int color) {
- if (false && DEBUG_STACK) Slog.i(TAG, "draw: bounds=" + bounds.toShortString() +
- " color=" + Integer.toHexString(color));
- mTmpDrawRect.set(bounds);
+ private void draw() {
+ if (mLastBounds.isEmpty()) {
+ // Currently unset. Set it.
+ mLastBounds.set(mBounds);
+ }
+
+ if (DEBUG) Slog.i(TAG, "draw: mBounds=" + mBounds + " mLastBounds=" + mLastBounds);
+
Canvas c = null;
try {
- c = mSurface.lockCanvas(mTmpDrawRect);
+ c = mSurface.lockCanvas(mLastBounds);
} catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Unable to lock canvas", e);
} catch (Surface.OutOfResourcesException e) {
+ Slog.e(TAG, "Unable to lock canvas", e);
}
if (c == null) {
+ if (DEBUG) Slog.w(TAG, "Canvas is null...");
return;
}
- final int w = bounds.width();
- final int h = bounds.height();
-
- // Top
- mTmpDrawRect.set(0, 0, w, THICKNESS);
- c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
- c.drawColor(color);
- // Left (not including Top or Bottom stripe).
- mTmpDrawRect.set(0, THICKNESS, THICKNESS, h - THICKNESS);
- c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
- c.drawColor(color);
- // Right (not including Top or Bottom stripe).
- mTmpDrawRect.set(w - THICKNESS, THICKNESS, w, h - THICKNESS);
- c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
- c.drawColor(color);
- // Bottom
- mTmpDrawRect.set(0, h - THICKNESS, w, h);
- c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
- c.drawColor(color);
-
+ c.drawRect(0, 0, mBounds.width(), mBounds.height(), mOuterPaint);
+ c.drawRect(THICKNESS, THICKNESS, mBounds.width() - THICKNESS, mBounds.height() - THICKNESS,
+ mInnerPaint);
+ if (DEBUG) Slog.w(TAG, "c.width=" + c.getWidth() + " c.height=" + c.getHeight()
+ + " c.clip=" + c .getClipBounds());
mSurface.unlockCanvasAndPost(c);
+ mLastBounds.set(mBounds);
}
- private void positionSurface(Rect bounds) {
- if (false && DEBUG_STACK) Slog.i(TAG, "positionSurface: bounds=" + bounds.toShortString());
- mSurfaceControl.setSize(bounds.width(), bounds.height());
- mSurfaceControl.setPosition(bounds.left, bounds.top);
- }
-
- // Note: caller responsible for being inside
- // Surface.openTransaction() / closeTransaction()
- public void setVisibility(boolean on) {
- if (false && DEBUG_STACK) Slog.i(TAG, "setVisibility: on=" + on +
- " mLastBounds=" + mLastBounds.toShortString() +
- " mBounds=" + mBounds.toShortString());
+ private void setupSurface(boolean visible) {
if (mSurfaceControl == null) {
return;
}
- if (on) {
- if (!mLastBounds.equals(mBounds)) {
- // Erase the previous rectangle.
- positionSurface(mLastBounds);
- draw(mLastBounds, Color.TRANSPARENT);
- // Draw the latest rectangle.
- positionSurface(mBounds);
- draw(mBounds, Color.WHITE);
- // Update the history.
- mLastBounds.set(mBounds);
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setupSurface");
+ SurfaceControl.openTransaction();
+ try {
+ if (visible) {
+ mSurfaceControl.setPosition(mBounds.left, mBounds.top);
+ mSurfaceControl.setSize(mBounds.width(), mBounds.height());
+ mSurfaceControl.show();
+ } else {
+ mSurfaceControl.hide();
}
- mSurfaceControl.show();
- } else {
- mSurfaceControl.hide();
+ } finally {
+ SurfaceControl.closeTransaction();
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setupSurface");
}
}
- public void setBounds(TaskStack stack) {
- stack.getBounds(mBounds);
- if (false && DEBUG_STACK) Slog.i(TAG, "setBounds: bounds=" + mBounds);
+ void setVisibility(TaskStack stack) {
+ if (stack == null || stack.isFullscreen()) {
+ setupSurface(false);
+ } else {
+ stack.getBounds(mBounds);
+ setupSurface(true);
+ if (!mBounds.equals(mLastBounds)) {
+ draw();
+ }
+ }
}
- public void setLayer(int layer) {
- mSurfaceControl.setLayer(layer);
+ // Note: caller responsible for being inside
+ // Surface.openTransaction() / closeTransaction()
+ void setLayer(int layer) {
+ if (mLayer == layer) {
+ return;
+ }
+ mLayer = layer;
+ mSurfaceControl.setLayer(mLayer);
}
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 27ac32a..c24fcb3 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -78,7 +78,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
WindowState windowState = (WindowState) inputWindowHandle.windowState;
if (windowState != null) {
Slog.i(WindowManagerService.TAG, "WINDOW DIED " + windowState);
- mService.removeWindowLocked(windowState.mSession, windowState);
+ mService.removeWindowLocked(windowState);
}
}
}
@@ -148,7 +148,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
if (timeout >= 0) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
- return timeout;
+ return timeout * 1000000L; // nanoseconds
}
} catch (RemoteException ex) {
}
@@ -239,9 +239,6 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
// As an optimization, we could try to prune the list of windows but this turns
// out to be difficult because only the native code knows for sure which window
// currently has touch focus.
- final WindowStateAnimator universeBackground = mService.mAnimator.mUniverseBackground;
- final int aboveUniverseLayer = mService.mAnimator.mAboveUniverseLayer;
- boolean addedUniverse = false;
boolean disableWallpaperTouchEvents = false;
// If there's a drag in flight, provide a pseudowindow to catch drag input
@@ -299,20 +296,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
mService.mDragState.sendDragStartedIfNeededLw(child);
}
- if (universeBackground != null && !addedUniverse
- && child.mBaseLayer < aboveUniverseLayer && onDefaultDisplay) {
- final WindowState u = universeBackground.mWin;
- if (u.mInputChannel != null && u.mInputWindowHandle != null) {
- addInputWindowHandleLw(u.mInputWindowHandle, u, u.mAttrs.flags,
- u.mAttrs.type, true, u == mInputFocus, false);
- }
- addedUniverse = true;
- }
-
- if (child.mWinAnimator != universeBackground) {
- addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible,
- hasFocus, hasWallpaper);
- }
+ addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, hasFocus,
+ hasWallpaper);
}
}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index f79896b..7dd716e 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -291,12 +291,6 @@ class ScreenRotationAnimation {
return mSurfaceControl != null;
}
- static int deltaRotation(int oldRotation, int newRotation) {
- int delta = newRotation - oldRotation;
- if (delta < 0) delta += 4;
- return delta;
- }
-
private void setSnapshotTransformInTransaction(Matrix matrix, float alpha) {
if (mSurfaceControl != null) {
matrix.getValues(mTmpFloats);
@@ -352,7 +346,7 @@ class ScreenRotationAnimation {
// Compute the transformation matrix that must be applied
// to the snapshot to make it stay in the same original position
// with the current screen rotation.
- int delta = deltaRotation(rotation, Surface.ROTATION_0);
+ int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0);
createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
if (DEBUG_STATE) Slog.v(TAG, "**** ROTATION: " + delta);
@@ -391,7 +385,7 @@ class ScreenRotationAnimation {
boolean firstStart = false;
// Figure out how the screen has moved from the original rotation.
- int delta = deltaRotation(mCurRotation, mOriginalRotation);
+ int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation);
if (TWO_PHASE_ANIMATION && mFinishExitAnimation == null
&& (!dismissing || delta != Surface.ROTATION_0)) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index a4dfd8a..487483e 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -446,20 +446,6 @@ final class Session extends IWindowSession.Stub
mService.wallpaperCommandComplete(window, result);
}
- public void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
- float dsdx, float dtdx, float dsdy, float dtdy) {
- synchronized(mService.mWindowMap) {
- long ident = Binder.clearCallingIdentity();
- try {
- mService.setUniverseTransformLocked(
- mService.windowForClientLocked(this, window, true),
- alpha, offx, offy, dsdx, dtdx, dsdy, dtdy);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) {
synchronized(mService.mWindowMap) {
final long identity = Binder.clearCallingIdentity();
@@ -475,6 +461,16 @@ final class Session extends IWindowSession.Stub
return mService.getWindowId(window);
}
+ @Override
+ public void pokeDrawLock(IBinder window) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mService.pokeDrawLock(this, window);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
void windowAddedLocked() {
if (mSurfaceSession == null) {
if (WindowManagerService.localLOGV) Slog.v(
diff --git a/services/core/java/com/android/server/wm/StackTapPointerEventListener.java b/services/core/java/com/android/server/wm/StackTapPointerEventListener.java
index 8938358..1a85993 100644
--- a/services/core/java/com/android/server/wm/StackTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/StackTapPointerEventListener.java
@@ -31,7 +31,7 @@ public class StackTapPointerEventListener implements PointerEventListener {
private float mDownX;
private float mDownY;
private int mPointerId;
- final private Region mTouchExcludeRegion;
+ final private Region mTouchExcludeRegion = new Region();
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
@@ -39,7 +39,6 @@ public class StackTapPointerEventListener implements PointerEventListener {
DisplayContent displayContent) {
mService = service;
mDisplayContent = displayContent;
- mTouchExcludeRegion = displayContent.mTouchExcludeRegion;
DisplayInfo info = displayContent.getDisplayInfo();
mMotionSlop = (int)(info.logicalDensityDpi * TAP_MOTION_SLOP_INCHES);
}
@@ -58,8 +57,8 @@ public class StackTapPointerEventListener implements PointerEventListener {
int index = motionEvent.findPointerIndex(mPointerId);
if ((motionEvent.getEventTime() - motionEvent.getDownTime()) > TAP_TIMEOUT_MSEC
|| index < 0
- || (motionEvent.getX(index) - mDownX) > mMotionSlop
- || (motionEvent.getY(index) - mDownY) > mMotionSlop) {
+ || Math.abs(motionEvent.getX(index) - mDownX) > mMotionSlop
+ || Math.abs(motionEvent.getY(index) - mDownY) > mMotionSlop) {
mPointerId = -1;
}
}
@@ -72,12 +71,15 @@ public class StackTapPointerEventListener implements PointerEventListener {
if (mPointerId == motionEvent.getPointerId(index)) {
final int x = (int)motionEvent.getX(index);
final int y = (int)motionEvent.getY(index);
- if ((motionEvent.getEventTime() - motionEvent.getDownTime())
- < TAP_TIMEOUT_MSEC
- && (x - mDownX) < mMotionSlop && (y - mDownY) < mMotionSlop
- && !mTouchExcludeRegion.contains(x, y)) {
- mService.mH.obtainMessage(H.TAP_OUTSIDE_STACK, x, y,
- mDisplayContent).sendToTarget();
+ synchronized(this) {
+ if ((motionEvent.getEventTime() - motionEvent.getDownTime())
+ < TAP_TIMEOUT_MSEC
+ && Math.abs(x - mDownX) < mMotionSlop
+ && Math.abs(y - mDownY) < mMotionSlop
+ && !mTouchExcludeRegion.contains(x, y)) {
+ mService.mH.obtainMessage(H.TAP_OUTSIDE_STACK, x, y,
+ mDisplayContent).sendToTarget();
+ }
}
mPointerId = -1;
}
@@ -85,4 +87,10 @@ public class StackTapPointerEventListener implements PointerEventListener {
}
}
}
+
+ void setTouchExcludeRegion(Region newRegion) {
+ synchronized (this) {
+ mTouchExcludeRegion.set(newRegion);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b49b87c..0c3cf65 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -17,22 +17,25 @@
package com.android.server.wm;
import static com.android.server.wm.WindowManagerService.TAG;
+import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
import android.util.EventLog;
import android.util.Slog;
+import com.android.server.EventLogTags;
class Task {
TaskStack mStack;
final AppTokenList mAppTokens = new AppTokenList();
- final int taskId;
+ final int mTaskId;
final int mUserId;
boolean mDeferRemoval = false;
+ final WindowManagerService mService;
- Task(AppWindowToken wtoken, TaskStack stack, int userId) {
- taskId = wtoken.groupId;
- mAppTokens.add(wtoken);
+ Task(int taskId, TaskStack stack, int userId, WindowManagerService service) {
+ mTaskId = taskId;
mStack = stack;
mUserId = userId;
+ mService = service;
}
DisplayContent getDisplayContent() {
@@ -41,22 +44,60 @@ class Task {
void addAppToken(int addPos, AppWindowToken wtoken) {
final int lastPos = mAppTokens.size();
- for (int pos = 0; pos < lastPos && pos < addPos; ++pos) {
- if (mAppTokens.get(pos).removed) {
- // addPos assumes removed tokens are actually gone.
- ++addPos;
+ if (addPos >= lastPos) {
+ addPos = lastPos;
+ } else {
+ for (int pos = 0; pos < lastPos && pos < addPos; ++pos) {
+ if (mAppTokens.get(pos).removed) {
+ // addPos assumes removed tokens are actually gone.
+ ++addPos;
+ }
}
}
mAppTokens.add(addPos, wtoken);
+ wtoken.mTask = this;
mDeferRemoval = false;
}
+ void removeLocked() {
+ if (!mAppTokens.isEmpty() && mStack.isAnimating()) {
+ if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
+ mDeferRemoval = true;
+ return;
+ }
+ if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
+ EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeTask");
+ mDeferRemoval = false;
+ mStack.removeTask(this);
+ mService.mTaskIdToTask.delete(mTaskId);
+ }
+
+ void moveTaskToStack(TaskStack stack, boolean toTop) {
+ if (stack == mStack) {
+ return;
+ }
+ if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId
+ + " from stack=" + mStack);
+ EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
+ if (mStack != null) {
+ mStack.removeTask(this);
+ }
+ stack.addTask(this, toTop);
+ }
+
boolean removeAppToken(AppWindowToken wtoken) {
boolean removed = mAppTokens.remove(wtoken);
if (mAppTokens.size() == 0) {
- EventLog.writeEvent(com.android.server.EventLogTags.WM_TASK_REMOVED, taskId,
+ EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId,
"removeAppToken: last token");
+ if (mDeferRemoval) {
+ removeLocked();
+ }
}
+ wtoken.mTask = null;
+ /* Leave mTaskId for now, it might be useful for debug
+ wtoken.mTaskId = -1;
+ */
return removed;
}
@@ -66,8 +107,13 @@ class Task {
}
}
+ boolean showForAllUsers() {
+ final int tokensCount = mAppTokens.size();
+ return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
+ }
+
@Override
public String toString() {
- return "{taskId=" + taskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}";
+ return "{taskId=" + mTaskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}";
}
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 238c77e..7cdf8b2 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -19,11 +19,15 @@ package com.android.server.wm;
import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerService.TAG;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Debug;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
import android.util.TypedValue;
+import android.view.Surface;
+
import com.android.server.EventLogTags;
import java.io.PrintWriter;
@@ -49,6 +53,8 @@ public class TaskStack {
/** For comparison with DisplayContent bounds. */
private Rect mTmpRect = new Rect();
+ /** For handling display rotations. */
+ private Rect mTmpRect2 = new Rect();
/** Content limits relative to the DisplayContent this sits in. */
private Rect mBounds = new Rect();
@@ -78,9 +84,24 @@ public class TaskStack {
/** Detach this stack from its display when animation completes. */
boolean mDeferDetach;
+ // Contains configurations settings that are different from the global configuration due to
+ // stack specific operations. E.g. {@link #setBounds}.
+ Configuration mOverrideConfig;
+ // True if the stack was forced to fullscreen disregarding the override configuration.
+ private boolean mForceFullscreen;
+ // The {@link #mBounds} before the stack was forced to fullscreen. Will be restored as the
+ // stack bounds once the stack is no longer forced to fullscreen.
+ final private Rect mPreForceFullscreenBounds;
+
+ // Device rotation as of the last time {@link #mBounds} was set.
+ int mRotation;
+
TaskStack(WindowManagerService service, int stackId) {
mService = service;
mStackId = stackId;
+ mOverrideConfig = Configuration.EMPTY;
+ mForceFullscreen = false;
+ mPreForceFullscreenBounds = new Rect();
// TODO: remove bounds from log, they are always 0.
EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId, mBounds.left, mBounds.top,
mBounds.right, mBounds.bottom);
@@ -95,8 +116,6 @@ public class TaskStack {
}
void resizeWindows() {
- final boolean underStatusBar = mBounds.top == 0;
-
final ArrayList<WindowState> resizingWindows = mService.mResizingWindows;
for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens;
@@ -109,27 +128,40 @@ public class TaskStack {
"setBounds: Resizing " + win);
resizingWindows.add(win);
}
- win.mUnderStatusBar = underStatusBar;
}
}
}
}
+ /** Set the stack bounds. Passing in null sets the bounds to fullscreen. */
boolean setBounds(Rect bounds) {
boolean oldFullscreen = mFullscreen;
+ int rotation = Surface.ROTATION_0;
if (mDisplayContent != null) {
mDisplayContent.getLogicalDisplayRect(mTmpRect);
- mFullscreen = mTmpRect.equals(bounds);
+ rotation = mDisplayContent.getDisplayInfo().rotation;
+ if (bounds == null) {
+ bounds = mTmpRect;
+ mFullscreen = true;
+ } else {
+ bounds.intersect(mTmpRect); // ensure bounds are entirely within the display rect
+ mFullscreen = mTmpRect.equals(bounds);
+ }
}
- if (mBounds.equals(bounds) && oldFullscreen == mFullscreen) {
+ if (bounds == null) {
+ // Can't set to fullscreen if we don't have a display to get bounds from...
+ return false;
+ }
+ if (mBounds.equals(bounds) && oldFullscreen == mFullscreen && mRotation == rotation) {
return false;
}
mDimLayer.setBounds(bounds);
mAnimationBackgroundSurface.setBounds(bounds);
mBounds.set(bounds);
-
+ mRotation = rotation;
+ updateOverrideConfiguration();
return true;
}
@@ -137,10 +169,67 @@ public class TaskStack {
out.set(mBounds);
}
+ private void updateOverrideConfiguration() {
+ final Configuration serviceConfig = mService.mCurConfiguration;
+ if (mFullscreen) {
+ mOverrideConfig = Configuration.EMPTY;
+ return;
+ }
+
+ if (mOverrideConfig == Configuration.EMPTY) {
+ mOverrideConfig = new Configuration();
+ }
+
+ // TODO(multidisplay): Update Dp to that of display stack is on.
+ final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ mOverrideConfig.screenWidthDp =
+ Math.min((int)(mBounds.width() / density), serviceConfig.screenWidthDp);
+ mOverrideConfig.screenHeightDp =
+ Math.min((int)(mBounds.height() / density), serviceConfig.screenHeightDp);
+ mOverrideConfig.smallestScreenWidthDp =
+ Math.min(mOverrideConfig.screenWidthDp, mOverrideConfig.screenHeightDp);
+ mOverrideConfig.orientation =
+ (mOverrideConfig.screenWidthDp <= mOverrideConfig.screenHeightDp)
+ ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
+ }
+
void updateDisplayInfo() {
- if (mFullscreen && mDisplayContent != null) {
+ if (mFullscreen) {
+ setBounds(null);
+ } else if (mDisplayContent != null) {
+ final int newRotation = mDisplayContent.getDisplayInfo().rotation;
+ if (mRotation == newRotation) {
+ return;
+ }
+
+ // Device rotation changed. We don't want the stack to move around on the screen when
+ // this happens, so update the stack bounds so it stays in the same place.
+ final int rotationDelta = DisplayContent.deltaRotation(mRotation, newRotation);
mDisplayContent.getLogicalDisplayRect(mTmpRect);
- setBounds(mTmpRect);
+ switch (rotationDelta) {
+ case Surface.ROTATION_0:
+ mTmpRect2.set(mBounds);
+ break;
+ case Surface.ROTATION_90:
+ mTmpRect2.top = mTmpRect.bottom - mBounds.right;
+ mTmpRect2.left = mBounds.top;
+ mTmpRect2.right = mTmpRect2.left + mBounds.height();
+ mTmpRect2.bottom = mTmpRect2.top + mBounds.width();
+ break;
+ case Surface.ROTATION_180:
+ mTmpRect2.top = mTmpRect.bottom - mBounds.bottom;
+ mTmpRect2.left = mTmpRect.right - mBounds.right;
+ mTmpRect2.right = mTmpRect2.left + mBounds.width();
+ mTmpRect2.bottom = mTmpRect2.top + mBounds.height();
+ break;
+ case Surface.ROTATION_270:
+ mTmpRect2.top = mBounds.left;
+ mTmpRect2.left = mTmpRect.right - mBounds.bottom;
+ mTmpRect2.right = mTmpRect2.left + mBounds.height();
+ mTmpRect2.bottom = mTmpRect2.top + mBounds.width();
+ break;
+ }
+ setBounds(mTmpRect2);
}
}
@@ -148,6 +237,28 @@ public class TaskStack {
return mFullscreen;
}
+ /** Forces the stack to fullscreen if input is true, else un-forces the stack from fullscreen.
+ * Returns true if something happened.
+ */
+ boolean forceFullscreen(boolean forceFullscreen) {
+ if (mForceFullscreen == forceFullscreen) {
+ return false;
+ }
+ mForceFullscreen = forceFullscreen;
+ if (forceFullscreen) {
+ if (mFullscreen) {
+ return false;
+ }
+ mPreForceFullscreenBounds.set(mBounds);
+ return setBounds(null);
+ } else {
+ if (!mFullscreen || mPreForceFullscreenBounds.isEmpty()) {
+ return false;
+ }
+ return setBounds(mPreForceFullscreenBounds);
+ }
+ }
+
boolean isAnimating() {
for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens;
@@ -164,21 +275,28 @@ public class TaskStack {
return false;
}
+ void addTask(Task task, boolean toTop) {
+ addTask(task, toTop, task.showForAllUsers());
+ }
+
/**
* Put a Task in this stack. Used for adding and moving.
* @param task The task to add.
* @param toTop Whether to add it to the top or bottom.
+ * @param showForAllUsers Whether to show the task regardless of the current user.
*/
- void addTask(Task task, boolean toTop) {
+ void addTask(Task task, boolean toTop, boolean showForAllUsers) {
int stackNdx;
if (!toTop) {
stackNdx = 0;
} else {
stackNdx = mTasks.size();
- if (!mService.isCurrentProfileLocked(task.mUserId)) {
+ if (!showForAllUsers && !mService.isCurrentProfileLocked(task.mUserId)) {
// Place the task below all current user tasks.
while (--stackNdx >= 0) {
- if (!mService.isCurrentProfileLocked(mTasks.get(stackNdx).mUserId)) {
+ final Task tmpTask = mTasks.get(stackNdx);
+ if (!tmpTask.showForAllUsers()
+ || !mService.isCurrentProfileLocked(tmpTask.mUserId)) {
break;
}
}
@@ -191,8 +309,10 @@ public class TaskStack {
mTasks.add(stackNdx, task);
task.mStack = this;
- mDisplayContent.moveStack(this, true);
- EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.taskId, toTop ? 1 : 0, stackNdx);
+ if (toTop) {
+ mDisplayContent.moveStack(this, true);
+ }
+ EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.mTaskId, toTop ? 1 : 0, stackNdx);
}
void moveTaskToTop(Task task) {
@@ -222,6 +342,13 @@ public class TaskStack {
}
mDisplayContent.layoutNeeded = true;
}
+ for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) {
+ final AppWindowToken wtoken = mExitingAppTokens.get(appNdx);
+ if (wtoken.mTask == task) {
+ wtoken.mIsExiting = false;
+ mExitingAppTokens.remove(appNdx);
+ }
+ }
}
void attachDisplayContent(DisplayContent displayContent) {
@@ -244,7 +371,7 @@ public class TaskStack {
for (int appNdx = appWindowTokens.size() - 1; appNdx >= 0; --appNdx) {
final WindowList appWindows = appWindowTokens.get(appNdx).allAppWindows;
for (int winNdx = appWindows.size() - 1; winNdx >= 0; --winNdx) {
- mService.removeWindowInnerLocked(null, appWindows.get(winNdx));
+ mService.removeWindowInnerLocked(appWindows.get(winNdx));
doAnotherLayoutPass = true;
}
}
@@ -253,10 +380,8 @@ public class TaskStack {
mService.requestTraversalLocked();
}
- mAnimationBackgroundSurface.destroySurface();
- mAnimationBackgroundSurface = null;
- mDimLayer.destroySurface();
- mDimLayer = null;
+ close();
+
mDisplayContent = null;
}
@@ -336,18 +461,24 @@ public class TaskStack {
void startDimmingIfNeeded(WindowStateAnimator newWinAnimator) {
// Only set dim params on the highest dimmed layer.
- final WindowStateAnimator existingDimWinAnimator = 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)) {
+ if (newWinAnimator.mSurfaceShown && (mDimWinAnimator == null
+ || !mDimWinAnimator.mSurfaceShown
+ || mDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) {
mDimWinAnimator = newWinAnimator;
+ if (mDimWinAnimator.mWin.mAppToken == null
+ && !mFullscreen && mDisplayContent != null) {
+ // Dim should cover the entire screen for system windows.
+ mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDimLayer.setBounds(mTmpRect);
+ }
}
}
void stopDimmingIfNeeded() {
if (!mDimmingTag && isDimming()) {
mDimWinAnimator = null;
+ mDimLayer.setBounds(mBounds);
}
}
@@ -362,11 +493,11 @@ public class TaskStack {
}
}
- void switchUser(int userId) {
+ void switchUser() {
int top = mTasks.size();
for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
Task task = mTasks.get(taskNdx);
- if (mService.isCurrentProfileLocked(task.mUserId)) {
+ if (mService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) {
mTasks.remove(taskNdx);
mTasks.add(task);
--top;
@@ -375,8 +506,14 @@ public class TaskStack {
}
void close() {
- mDimLayer.mDimSurface.destroy();
- mAnimationBackgroundSurface.mDimSurface.destroy();
+ if (mAnimationBackgroundSurface != null) {
+ mAnimationBackgroundSurface.destroySurface();
+ mAnimationBackgroundSurface = null;
+ }
+ if (mDimLayer != null) {
+ mDimLayer.destroySurface();
+ mDimLayer = null;
+ }
}
public void dump(String prefix, PrintWriter pw) {
@@ -391,7 +528,7 @@ public class TaskStack {
}
if (mDimLayer.isDimming()) {
pw.print(prefix); pw.println("mDimLayer:");
- mDimLayer.printTo(prefix, pw);
+ mDimLayer.printTo(prefix + " ", pw);
pw.print(prefix); pw.print("mDimWinAnimator="); pw.println(mDimWinAnimator);
}
if (!mExitingAppTokens.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 64713d9..897b865 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -30,8 +30,6 @@ import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENT
import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_ACTION_PENDING;
import android.content.Context;
-import android.os.Debug;
-import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -41,6 +39,7 @@ import android.view.SurfaceControl;
import android.view.WindowManagerPolicy;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
+import android.view.Choreographer;
import com.android.server.wm.WindowManagerService.LayoutFields;
@@ -61,9 +60,13 @@ public class WindowAnimator {
final Context mContext;
final WindowManagerPolicy mPolicy;
+ /** Is any window animating? */
boolean mAnimating;
- final Runnable mAnimationRunnable;
+ /** Is any app window animating? */
+ boolean mAppWindowAnimating;
+
+ final Choreographer.FrameCallback mAnimationFrameCallback;
/** Time of current animation step. Reset on each iteration */
long mCurrentTime;
@@ -78,9 +81,6 @@ public class WindowAnimator {
* seen. If multiple windows satisfy this, use the lowest window. */
WindowState mWindowDetachedWallpaper = null;
- WindowStateAnimator mUniverseBackground = null;
- int mAboveUniverseLayer = 0;
-
int mBulkUpdateParams = 0;
Object mLastWindowFreezeSource;
@@ -118,12 +118,11 @@ public class WindowAnimator {
mContext = service.mContext;
mPolicy = service.mPolicy;
- mAnimationRunnable = new Runnable() {
- @Override
- public void run() {
+ mAnimationFrameCallback = new Choreographer.FrameCallback() {
+ public void doFrame(long frameTimeNs) {
synchronized (mService.mWindowMap) {
mService.mAnimationScheduled = false;
- animateLocked();
+ animateLocked(frameTimeNs);
}
}
};
@@ -149,35 +148,6 @@ public class WindowAnimator {
mDisplayContentsAnimators.delete(displayId);
}
- void hideWallpapersLocked(final WindowState w) {
- final WindowState wallpaperTarget = mService.mWallpaperTarget;
- final WindowState lowerWallpaperTarget = mService.mLowerWallpaperTarget;
- final ArrayList<WindowToken> wallpaperTokens = mService.mWallpaperTokens;
-
- if ((wallpaperTarget == w && lowerWallpaperTarget == null) || wallpaperTarget == null) {
- final int numTokens = wallpaperTokens.size();
- for (int i = numTokens - 1; i >= 0; i--) {
- final WindowToken token = wallpaperTokens.get(i);
- final int numWindows = token.windows.size();
- for (int j = numWindows - 1; j >= 0; j--) {
- final WindowState wallpaper = token.windows.get(j);
- final WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
- if (!winAnimator.mLastHidden) {
- winAnimator.hide();
- mService.dispatchWallpaperVisibility(wallpaper, false);
- setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
- WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
- }
- }
- if (WindowManagerService.DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG,
- "Hiding wallpaper " + token + " from " + w
- + " target=" + wallpaperTarget + " lower=" + lowerWallpaperTarget
- + "\n" + Debug.getCallers(5, " "));
- token.hidden = true;
- }
- }
- }
-
private void updateAppWindowsLocked(int displayId) {
ArrayList<TaskStack> stacks = mService.getDisplayContentLocked(displayId).getStacks();
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
@@ -189,13 +159,13 @@ public class WindowAnimator {
final AppWindowAnimator appAnimator = tokens.get(tokenNdx).mAppAnimator;
final boolean wasAnimating = appAnimator.animation != null
&& appAnimator.animation != AppWindowAnimator.sDummyAnimation;
- if (appAnimator.stepAnimationLocked(mCurrentTime)) {
- mAnimating = true;
+ if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
+ mAnimating = mAppWindowAnimating = true;
} else if (wasAnimating) {
// stopped animating, do one more pass through the layout
setAppLayoutChanges(appAnimator,
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
- "appToken " + appAnimator.mAppToken + " done");
+ "appToken " + appAnimator.mAppToken + " done", displayId);
if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
"updateWindowsApps...: done animating " + appAnimator.mAppToken);
}
@@ -208,12 +178,12 @@ public class WindowAnimator {
final AppWindowAnimator appAnimator = exitingAppTokens.get(i).mAppAnimator;
final boolean wasAnimating = appAnimator.animation != null
&& appAnimator.animation != AppWindowAnimator.sDummyAnimation;
- if (appAnimator.stepAnimationLocked(mCurrentTime)) {
- mAnimating = true;
+ if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) {
+ mAnimating = mAppWindowAnimating = true;
} else if (wasAnimating) {
// stopped animating, do one more pass through the layout
setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
- "exiting appToken " + appAnimator.mAppToken + " done");
+ "exiting appToken " + appAnimator.mAppToken + " done", displayId);
if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
"updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
}
@@ -605,11 +575,11 @@ public class WindowAnimator {
// This will set mOrientationChangeComplete and cause a pass through layout.
setAppLayoutChanges(appAnimator,
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
- "testTokenMayBeDrawnLocked: freezingScreen");
+ "testTokenMayBeDrawnLocked: freezingScreen", displayId);
} else {
setAppLayoutChanges(appAnimator,
WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM,
- "testTokenMayBeDrawnLocked");
+ "testTokenMayBeDrawnLocked", displayId);
// We can now show all of the drawn windows!
if (!mService.mOpeningApps.contains(wtoken)) {
@@ -624,15 +594,16 @@ public class WindowAnimator {
/** Locked on mService.mWindowMap. */
- private void animateLocked() {
+ private void animateLocked(long frameTimeNs) {
if (!mInitialized) {
return;
}
- mCurrentTime = SystemClock.uptimeMillis();
+ mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
boolean wasAnimating = mAnimating;
mAnimating = false;
+ mAppWindowAnimating = false;
if (WindowManagerService.DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
@@ -794,6 +765,7 @@ public class WindowAnimator {
} else if (dumpAll) {
pw.print(subPrefix); pw.println("no ScreenRotationAnimation ");
}
+ pw.println();
}
pw.println();
@@ -814,38 +786,36 @@ public class WindowAnimator {
pw.print(prefix); pw.print("mWindowDetachedWallpaper=");
pw.println(mWindowDetachedWallpaper);
}
- if (mUniverseBackground != null) {
- pw.print(prefix); pw.print("mUniverseBackground="); pw.print(mUniverseBackground);
- pw.print(" mAboveUniverseLayer="); pw.println(mAboveUniverseLayer);
- }
}
int getPendingLayoutChanges(final int displayId) {
if (displayId < 0) {
return 0;
}
- return mService.getDisplayContentLocked(displayId).pendingLayoutChanges;
+ final DisplayContent displayContent = mService.getDisplayContentLocked(displayId);
+ return (displayContent != null) ? displayContent.pendingLayoutChanges : 0;
}
void setPendingLayoutChanges(final int displayId, final int changes) {
- if (displayId >= 0) {
- mService.getDisplayContentLocked(displayId).pendingLayoutChanges |= changes;
+ if (displayId < 0) {
+ return;
+ }
+ final DisplayContent displayContent = mService.getDisplayContentLocked(displayId);
+ if (displayContent != null) {
+ displayContent.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(2);
+ void setAppLayoutChanges(final AppWindowAnimator appAnimator, final int changes, String reason,
+ final int displayId) {
WindowList windows = appAnimator.mAppToken.allAppWindows;
for (int i = windows.size() - 1; i >= 0; i--) {
- final int displayId = windows.get(i).getDisplayId();
- if (displayId >= 0 && displays.indexOfKey(displayId) < 0) {
+ if (displayId == windows.get(i).getDisplayId()) {
setPendingLayoutChanges(displayId, changes);
if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
- mService.debugLayoutRepeats(s, getPendingLayoutChanges(displayId));
+ mService.debugLayoutRepeats(reason, getPendingLayoutChanges(displayId));
}
- // Keep from processing this display again.
- displays.put(displayId, changes);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 089d897..c5bdbb0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
import static android.view.WindowManager.LayoutParams.*;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -28,9 +29,8 @@ import android.view.IWindowId;
import android.view.IWindowSessionCallback;
import android.view.WindowContentFrameStats;
+import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.app.IBatteryStats;
-import com.android.internal.policy.PolicyManager;
-import com.android.internal.policy.impl.PhoneWindowManager;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -39,12 +39,14 @@ import com.android.internal.view.WindowManagerPolicyThread;
import com.android.server.AttributeCache;
import com.android.server.DisplayThread;
import com.android.server.EventLogTags;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.am.BatteryStatsService;
import com.android.server.input.InputManagerService;
import com.android.server.power.ShutdownThread;
+import com.android.server.policy.PhoneWindowManager;
import android.Manifest;
import android.app.ActivityManagerNative;
@@ -65,11 +67,9 @@ import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
-import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
@@ -128,7 +128,6 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy;
@@ -137,7 +136,6 @@ import android.view.WindowManagerPolicy.FakeWindow;
import android.view.WindowManagerPolicy.PointerEventListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
import java.io.BufferedWriter;
import java.io.DataInputStream;
@@ -153,6 +151,7 @@ import java.io.StringWriter;
import java.net.Socket;
import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
@@ -181,7 +180,6 @@ public class WindowManagerService extends IWindowManager.Stub
static final boolean DEBUG_CONFIGURATION = false;
static final boolean DEBUG_APP_TRANSITIONS = false;
static final boolean DEBUG_STARTING_WINDOW = false;
- static final boolean DEBUG_REORDER = false;
static final boolean DEBUG_WALLPAPER = false;
static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
static final boolean DEBUG_DRAG = false;
@@ -194,6 +192,7 @@ public class WindowManagerService extends IWindowManager.Stub
static final boolean DEBUG_TASK_MOVEMENT = false;
static final boolean DEBUG_STACK = false;
static final boolean DEBUG_DISPLAY = false;
+ static final boolean DEBUG_POWER = false;
static final boolean SHOW_SURFACE_ALLOC = false;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
@@ -283,12 +282,6 @@ public class WindowManagerService extends IWindowManager.Stub
// The name of the boot animation service in init.rc.
private static final String BOOT_ANIMATION_SERVICE = "bootanim";
- /** Minimum value for attachStack and resizeStack weight value */
- public static final float STACK_WEIGHT_MIN = 0.2f;
-
- /** Maximum value for attachStack and resizeStack weight value */
- public static final float STACK_WEIGHT_MAX = 0.8f;
-
static final int UPDATE_FOCUS_NORMAL = 0;
static final int UPDATE_FOCUS_WILL_ASSIGN_LAYERS = 1;
static final int UPDATE_FOCUS_PLACING_SURFACES = 2;
@@ -338,12 +331,13 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean mHaveInputMethods;
final boolean mHasPermanentDpad;
+ final long mDrawLockTimeoutMillis;
final boolean mAllowBootMessages;
final boolean mLimitedAlphaCompositing;
- final WindowManagerPolicy mPolicy = PolicyManager.makeNewWindowManager();
+ final WindowManagerPolicy mPolicy = new PhoneWindowManager();
final IActivityManager mActivityManager;
@@ -419,7 +413,7 @@ public class WindowManagerService extends IWindowManager.Stub
* This is set when we have run out of memory, and will either be an empty
* list or contain windows that need to be force removed.
*/
- ArrayList<WindowState> mForceRemoves;
+ final ArrayList<WindowState> mForceRemoves = new ArrayList<>();
/**
* Windows that clients are waiting to have drawn.
@@ -504,10 +498,16 @@ public class WindowManagerService extends IWindowManager.Stub
int mLastDisplayFreezeDuration = 0;
Object mLastFinishedFreezeSource = null;
boolean mWaitingForConfig = false;
- boolean mWindowsFreezingScreen = false;
+
+ final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
+ final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
+ final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
+ private int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
+
boolean mClientFreezingScreen = false;
int mAppsFreezingScreen = 0;
int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ int mLastKeyguardForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
int mLayoutSeq = 0;
@@ -555,6 +555,10 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState mInputMethodWindow = null;
final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<WindowState>();
+ /** Temporary list for comparison. Always clear this after use so we don't end up with
+ * orphaned windows references */
+ final ArrayList<WindowState> mTmpWindows = new ArrayList<>();
+
boolean mHardKeyboardAvailable;
boolean mShowImeWithHardKeyboard;
OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
@@ -584,7 +588,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>();
+ private final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>();
// If non-null, this is the currently visible window that is associated
// with the wallpaper.
@@ -613,6 +617,19 @@ public class WindowManagerService extends IWindowManager.Stub
static final long WALLPAPER_TIMEOUT_RECOVERY = 10000;
boolean mAnimateWallpaperWithTarget;
+ // We give a wallpaper up to 500ms to finish drawing before playing app transitions.
+ static final long WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION = 500;
+ static final int WALLPAPER_DRAW_NORMAL = 0;
+ static final int WALLPAPER_DRAW_PENDING = 1;
+ static final int WALLPAPER_DRAW_TIMEOUT = 2;
+ int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
+
+ // Set to the wallpaper window we would like to hide once the transition animations are done.
+ // This is useful in cases where we don't want the wallpaper to be hidden when the close app
+ // is a wallpaper target and is done animating out, but the opening app isn't a wallpaper
+ // target and isn't done animating in.
+ WindowState mDeferredHideWallpaper = null;
+
AppWindowToken mFocusedApp = null;
PowerManager mPowerManager;
@@ -687,17 +704,22 @@ public class WindowManagerService extends IWindowManager.Stub
final WindowAnimator mAnimator;
- SparseArray<Task> mTaskIdToTask = new SparseArray<Task>();
+ SparseArray<Task> mTaskIdToTask = new SparseArray<>();
/** All of the TaskStacks in the window manager, unordered. For an ordered list call
* DisplayContent.getStacks(). */
- SparseArray<TaskStack> mStackIdToStack = new SparseArray<TaskStack>();
+ SparseArray<TaskStack> mStackIdToStack = new SparseArray<>();
private final PointerEventDispatcher mPointerEventDispatcher;
private WindowContentFrameStats mTempWindowRenderStats;
final class DragInputEventReceiver extends InputEventReceiver {
+ // Set, if stylus button was down at the start of the drag.
+ private boolean mStylusButtonDownAtStart;
+ // Indicates the first event to check for button state.
+ private boolean mIsStartEvent = true;
+
public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@@ -713,6 +735,18 @@ public class WindowManagerService extends IWindowManager.Stub
boolean endDrag = false;
final float newX = motionEvent.getRawX();
final float newY = motionEvent.getRawY();
+ final boolean isStylusButtonDown =
+ (motionEvent.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS)
+ && (motionEvent.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0;
+
+ if (mIsStartEvent) {
+ if (isStylusButtonDown) {
+ // First event and the button was down, check for the button being
+ // lifted in the future, if that happens we'll drop the item.
+ mStylusButtonDownAtStart = true;
+ }
+ mIsStartEvent = false;
+ }
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN: {
@@ -722,9 +756,17 @@ public class WindowManagerService extends IWindowManager.Stub
} break;
case MotionEvent.ACTION_MOVE: {
- synchronized (mWindowMap) {
- // move the surface and tell the involved window(s) where we are
- mDragState.notifyMoveLw(newX, newY);
+ if (mStylusButtonDownAtStart && !isStylusButtonDown) {
+ if (DEBUG_DRAG) Slog.d(TAG, "Button no longer pressed; dropping at "
+ + newX + "," + newY);
+ synchronized (mWindowMap) {
+ endDrag = mDragState.notifyDropLw(newX, newY);
+ }
+ } else {
+ synchronized (mWindowMap) {
+ // move the surface and tell the involved window(s) where we are
+ mDragState.notifyMoveLw(newX, newY);
+ }
}
} break;
@@ -748,6 +790,8 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
mDragState.endDragLw();
}
+ mStylusButtonDownAtStart = false;
+ mIsStartEvent = true;
}
handled = true;
@@ -786,6 +830,35 @@ public class WindowManagerService extends IWindowManager.Stub
// For example, when this flag is true, there will be no wallpaper service.
final boolean mOnlyCore;
+ /** Listener to notify activity manager about app transitions. */
+ private final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
+ = new WindowManagerInternal.AppTransitionListener() {
+
+ @Override
+ public void onAppTransitionFinishedLocked(IBinder token) {
+ AppWindowToken atoken = findAppWindowToken(token);
+ if (atoken == null) {
+ return;
+ }
+ if (atoken.mLaunchTaskBehind) {
+ try {
+ mActivityManager.notifyLaunchTaskBehindComplete(atoken.token);
+ } catch (RemoteException e) {
+ }
+ atoken.mLaunchTaskBehind = false;
+ } else {
+ atoken.updateReportedVisibilityLocked();
+ if (atoken.mEnteringAnimation) {
+ atoken.mEnteringAnimation = false;
+ try {
+ mActivityManager.notifyEnterAnimationComplete(atoken.token);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+ };
+
public static WindowManagerService main(final Context context,
final InputManagerService im,
final boolean haveInputMethods, final boolean showBootMsgs,
@@ -808,9 +881,6 @@ public class WindowManagerService extends IWindowManager.Stub
WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
- mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer()
- * TYPE_LAYER_MULTIPLIER
- + TYPE_LAYER_OFFSET;
}
}, 0);
}
@@ -827,6 +897,8 @@ public class WindowManagerService extends IWindowManager.Stub
com.android.internal.R.bool.config_hasPermanentDpad);
mInTouchMode = context.getResources().getBoolean(
com.android.internal.R.bool.config_defaultInTouchMode);
+ mDrawLockTimeoutMillis = context.getResources().getInteger(
+ com.android.internal.R.integer.config_drawLockTimeoutMillis);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mDisplaySettings = new DisplaySettings();
@@ -865,6 +937,7 @@ public class WindowManagerService extends IWindowManager.Stub
mScreenFrozenLock.setReferenceCounted(false);
mAppTransition = new AppTransition(context, mH);
+ mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
mActivityManager = ActivityManagerNative.getDefault();
mBatteryStats = BatteryStatsService.getService();
@@ -1699,7 +1772,7 @@ public class WindowManagerService extends IWindowManager.Stub
return true;
}
- final boolean isWallpaperVisible(WindowState wallpaperTarget) {
+ private boolean isWallpaperVisible(WindowState wallpaperTarget) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
+ (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
+ " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
@@ -1713,10 +1786,42 @@ public class WindowManagerService extends IWindowManager.Stub
|| mLowerWallpaperTarget != null;
}
- static final int ADJUST_WALLPAPER_LAYERS_CHANGED = 1<<1;
- static final int ADJUST_WALLPAPER_VISIBILITY_CHANGED = 1<<2;
+ void hideWallpapersLocked(final WindowState winGoingAway) {
+ if (mWallpaperTarget != null
+ && (mWallpaperTarget != winGoingAway || mLowerWallpaperTarget != null)) {
+ return;
+ }
+ if (mAppTransition.isRunning()) {
+ // Defer hiding the wallpaper when app transition is running until the animations
+ // are done.
+ mDeferredHideWallpaper = winGoingAway;
+ return;
+ }
+
+ final boolean wasDeferred = (mDeferredHideWallpaper == winGoingAway);
+ for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
+ final WindowToken token = mWallpaperTokens.get(i);
+ for (int j = token.windows.size() - 1; j >= 0; j--) {
+ final WindowState wallpaper = token.windows.get(j);
+ final WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
+ if (!winAnimator.mLastHidden || wasDeferred) {
+ winAnimator.hide();
+ dispatchWallpaperVisibility(wallpaper, false);
+ final DisplayContent displayContent = wallpaper.getDisplayContent();
+ if (displayContent != null) {
+ displayContent.pendingLayoutChanges |=
+ WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+ }
+ }
+ }
+ if (DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG, "Hiding wallpaper " + token
+ + " from " + winGoingAway + " target=" + mWallpaperTarget + " lower="
+ + mLowerWallpaperTarget + "\n" + Debug.getCallers(5, " "));
+ token.hidden = true;
+ }
+ }
- int adjustWallpaperWindowsLocked() {
+ boolean adjustWallpaperWindowsLocked() {
mInnerFields.mWallpaperMayChange = false;
boolean targetChanged = false;
@@ -1895,7 +2000,7 @@ public class WindowManagerService extends IWindowManager.Stub
// AND any starting window associated with it, AND below the
// maximum layer the policy allows for wallpapers.
while (foundI > 0) {
- WindowState wb = windows.get(foundI-1);
+ WindowState wb = windows.get(foundI - 1);
if (wb.mBaseLayer < maxLayer &&
wb.mAttachedWindow != foundW &&
(foundW.mAttachedWindow == null ||
@@ -1921,7 +2026,7 @@ public class WindowManagerService extends IWindowManager.Stub
} else {
// Okay i is the position immediately above the wallpaper. Look at
// what is below it for later.
- foundW = foundI > 0 ? windows.get(foundI-1) : null;
+ foundW = foundI > 0 ? windows.get(foundI - 1) : null;
}
if (visible) {
@@ -1943,44 +2048,37 @@ 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--;
- WindowToken token = mWallpaperTokens.get(curTokenIndex);
+ boolean changed = false;
+ for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+ WindowToken token = mWallpaperTokens.get(curTokenNdx);
if (token.hidden == visible) {
if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
"Wallpaper token " + token + " hidden=" + !visible);
- changed |= ADJUST_WALLPAPER_VISIBILITY_CHANGED;
token.hidden = !visible;
- // Need to do a layout to ensure the wallpaper now has the
- // correct size.
+ // Need to do a layout to ensure the wallpaper now has the correct size.
getDefaultDisplayContentLocked().layoutNeeded = true;
}
- int curWallpaperIndex = token.windows.size();
- while (curWallpaperIndex > 0) {
- curWallpaperIndex--;
- WindowState wallpaper = token.windows.get(curWallpaperIndex);
+ final WindowList tokenWindows = token.windows;
+ for (int wallpaperNdx = tokenWindows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
+ WindowState wallpaper = tokenWindows.get(wallpaperNdx);
if (visible) {
updateWallpaperOffsetLocked(wallpaper, dw, dh, false);
}
- // First, make sure the client has the current visibility
- // state.
+ // First, make sure the client has the current visibility state.
dispatchWallpaperVisibility(wallpaper, visible);
- wallpaper.mWinAnimator.mAnimLayer = wallpaper.mLayer + mWallpaperAnimLayerAdjustment;
+ wallpaper.mWinAnimator.mAnimLayer =
+ wallpaper.mLayer + mWallpaperAnimLayerAdjustment;
if (DEBUG_LAYERS || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "adjustWallpaper win "
+ wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer);
- // First, if this window is at the current index, then all
- // is well.
+ // First, if this window is at the current index, then all is well.
if (wallpaper == foundW) {
foundI--;
- foundW = foundI > 0
- ? windows.get(foundI-1) : null;
+ foundW = foundI > 0 ? windows.get(foundI - 1) : null;
continue;
}
@@ -2016,52 +2114,24 @@ public class WindowManagerService extends IWindowManager.Stub
windows.add(insertionIndex, wallpaper);
mWindowsChanged = true;
- changed |= ADJUST_WALLPAPER_LAYERS_CHANGED;
- }
- }
-
- /*
- final TaskStack targetStack =
- mWallpaperTarget == null ? null : mWallpaperTarget.getStack();
- if ((changed & ADJUST_WALLPAPER_LAYERS_CHANGED) != 0 &&
- targetStack != null && !targetStack.isHomeStack()) {
- // If the wallpaper target is not on the home stack then make sure that all windows
- // from other non-home stacks are above the wallpaper.
- for (i = foundI - 1; i >= 0; --i) {
- WindowState win = windows.get(i);
- if (!win.isVisibleLw()) {
- continue;
- }
- final TaskStack winStack = win.getStack();
- if (winStack != null && !winStack.isHomeStack() && winStack != targetStack) {
- windows.remove(i);
- windows.add(foundI + 1, win);
- }
+ changed = true;
}
}
- */
- if (targetChanged && DEBUG_WALLPAPER_LIGHT) {
- Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget
- + " lower=" + mLowerWallpaperTarget + " upper="
- + mUpperWallpaperTarget);
- }
+ if (targetChanged && DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target="
+ + mWallpaperTarget + " lower=" + mLowerWallpaperTarget + " upper="
+ + mUpperWallpaperTarget);
return changed;
}
void setWallpaperAnimLayerAdjustmentLocked(int adj) {
- if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG,
- "Setting wallpaper layer adj to " + adj);
+ if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, "Setting wallpaper layer adj to " + adj);
mWallpaperAnimLayerAdjustment = adj;
- int curTokenIndex = mWallpaperTokens.size();
- while (curTokenIndex > 0) {
- curTokenIndex--;
- WindowToken token = mWallpaperTokens.get(curTokenIndex);
- int curWallpaperIndex = token.windows.size();
- while (curWallpaperIndex > 0) {
- curWallpaperIndex--;
- WindowState wallpaper = token.windows.get(curWallpaperIndex);
+ for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+ WindowList windows = mWallpaperTokens.get(curTokenNdx).windows;
+ for (int wallpaperNdx = windows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
+ WindowState wallpaper = windows.get(wallpaperNdx);
wallpaper.mWinAnimator.mAnimLayer = wallpaper.mLayer + adj;
if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, "setWallpaper win "
+ wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer);
@@ -2195,14 +2265,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- int curTokenIndex = mWallpaperTokens.size();
- while (curTokenIndex > 0) {
- curTokenIndex--;
- WindowToken token = mWallpaperTokens.get(curTokenIndex);
- int curWallpaperIndex = token.windows.size();
- while (curWallpaperIndex > 0) {
- curWallpaperIndex--;
- WindowState wallpaper = token.windows.get(curWallpaperIndex);
+ for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+ WindowList windows = mWallpaperTokens.get(curTokenNdx).windows;
+ for (int wallpaperNdx = windows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
+ WindowState wallpaper = windows.get(wallpaperNdx);
if (updateWallpaperOffsetLocked(wallpaper, dw, dh, sync)) {
WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
winAnimator.computeShownFrameLocked();
@@ -2217,12 +2283,15 @@ public class WindowManagerService extends IWindowManager.Stub
}
/**
- * Check wallpaper for visiblity change and notify window if so.
+ * Check wallpaper for visibility change and notify window if so.
* @param wallpaper The wallpaper to test and notify.
* @param visible Current visibility.
*/
void dispatchWallpaperVisibility(final WindowState wallpaper, final boolean visible) {
- if (wallpaper.mWallpaperVisible != visible) {
+ // Only send notification if the visibility actually changed and we are not trying to hide
+ // the wallpaper when we are deferring hiding of the wallpaper.
+ if (wallpaper.mWallpaperVisible != visible
+ && (mDeferredHideWallpaper == null || visible)) {
wallpaper.mWallpaperVisible = visible;
try {
if (DEBUG_VISIBILITY || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
@@ -2234,7 +2303,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- void updateWallpaperVisibilityLocked() {
+ private void updateWallpaperVisibilityLocked() {
final boolean visible = isWallpaperVisible(mWallpaperTarget);
final DisplayContent displayContent = mWallpaperTarget.getDisplayContent();
if (displayContent == null) {
@@ -2244,21 +2313,18 @@ public class WindowManagerService extends IWindowManager.Stub
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
- int curTokenIndex = mWallpaperTokens.size();
- while (curTokenIndex > 0) {
- curTokenIndex--;
- WindowToken token = mWallpaperTokens.get(curTokenIndex);
+ for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+ WindowToken token = mWallpaperTokens.get(curTokenNdx);
if (token.hidden == visible) {
token.hidden = !visible;
// Need to do a layout to ensure the wallpaper now has the
// correct size.
- getDefaultDisplayContentLocked().layoutNeeded = true;
+ displayContent.layoutNeeded = true;
}
- int curWallpaperIndex = token.windows.size();
- while (curWallpaperIndex > 0) {
- curWallpaperIndex--;
- WindowState wallpaper = token.windows.get(curWallpaperIndex);
+ final WindowList windows = token.windows;
+ for (int wallpaperNdx = windows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
+ WindowState wallpaper = windows.get(wallpaperNdx);
if (visible) {
updateWallpaperOffsetLocked(wallpaper, dw, dh, false);
}
@@ -2594,11 +2660,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (win == null) {
return;
}
- removeWindowLocked(session, win);
+ removeWindowLocked(win);
}
}
- public void removeWindowLocked(Session session, WindowState win) {
+ void removeWindowLocked(WindowState win) {
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win);
}
@@ -2672,7 +2738,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- removeWindowInnerLocked(session, win);
+ removeWindowInnerLocked(win);
// Removing a visible window will effect the computed orientation
// So just update orientation if needed.
if (wasVisible && updateOrientationFromAppTokensLocked(false)) {
@@ -2682,7 +2748,7 @@ public class WindowManagerService extends IWindowManager.Stub
Binder.restoreCallingIdentity(origId);
}
- void removeWindowInnerLocked(Session session, WindowState win) {
+ void removeWindowInnerLocked(WindowState win) {
if (win.mRemoved) {
// Nothing to do.
return;
@@ -2692,7 +2758,7 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState cwin = win.mChildWindows.get(i);
Slog.w(TAG, "Force-removing child win " + cwin + " from container "
+ win);
- removeWindowInnerLocked(cwin.mSession, cwin);
+ removeWindowInnerLocked(cwin);
}
win.mRemoved = true;
@@ -2916,14 +2982,10 @@ public class WindowManagerService extends IWindowManager.Stub
if (window == mWallpaperTarget || window == mLowerWallpaperTarget
|| window == mUpperWallpaperTarget) {
boolean doWait = sync;
- int curTokenIndex = mWallpaperTokens.size();
- while (curTokenIndex > 0) {
- curTokenIndex--;
- WindowToken token = mWallpaperTokens.get(curTokenIndex);
- int curWallpaperIndex = token.windows.size();
- while (curWallpaperIndex > 0) {
- curWallpaperIndex--;
- WindowState wallpaper = token.windows.get(curWallpaperIndex);
+ for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+ final WindowList windows = mWallpaperTokens.get(curTokenNdx).windows;
+ for (int wallpaperNdx = windows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
+ WindowState wallpaper = windows.get(wallpaperNdx);
try {
wallpaper.mClient.dispatchWallpaperCommand(action,
x, y, z, extras, sync);
@@ -2942,37 +3004,6 @@ public class WindowManagerService extends IWindowManager.Stub
return null;
}
- public void setUniverseTransformLocked(WindowState window, float alpha,
- float offx, float offy, float dsdx, float dtdx, float dsdy, float dtdy) {
- Transformation transform = window.mWinAnimator.mUniverseTransform;
- transform.setAlpha(alpha);
- Matrix matrix = transform.getMatrix();
- matrix.getValues(mTmpFloats);
- mTmpFloats[Matrix.MTRANS_X] = offx;
- mTmpFloats[Matrix.MTRANS_Y] = offy;
- mTmpFloats[Matrix.MSCALE_X] = dsdx;
- mTmpFloats[Matrix.MSKEW_Y] = dtdx;
- mTmpFloats[Matrix.MSKEW_X] = dsdy;
- mTmpFloats[Matrix.MSCALE_Y] = dtdy;
- matrix.setValues(mTmpFloats);
- final DisplayContent displayContent = window.getDisplayContent();
- if (displayContent == null) {
- return;
- }
-
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final RectF dispRect = new RectF(0, 0,
- displayInfo.logicalWidth, displayInfo.logicalHeight);
- matrix.mapRect(dispRect);
- window.mGivenTouchableRegion.set(0, 0,
- displayInfo.logicalWidth, displayInfo.logicalHeight);
- window.mGivenTouchableRegion.op((int)dispRect.left, (int)dispRect.top,
- (int)dispRect.right, (int)dispRect.bottom, Region.Op.DIFFERENCE);
- window.mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
- displayContent.layoutNeeded = true;
- performLayoutAndPlaceSurfacesLocked();
- }
-
public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) {
synchronized (mWindowMap) {
if (mAccessibilityController != null) {
@@ -2992,6 +3023,15 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ public void pokeDrawLock(Session session, IBinder token) {
+ synchronized (mWindowMap) {
+ WindowState window = windowForClientLocked(session, token, false);
+ if (window != null) {
+ window.pokeDrawLockLw(mDrawLockTimeoutMillis);
+ }
+ }
+ }
+
public int relayoutWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, int flags,
@@ -3119,6 +3159,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
winAnimator.mEnteringAnimation = true;
if (toBeDisplayed) {
+ if ((win.mAttrs.softInputMode & SOFT_INPUT_MASK_ADJUST)
+ == SOFT_INPUT_ADJUST_RESIZE) {
+ win.mLayoutNeeded = true;
+ }
if (win.isDrawnLw() && okToDisplay()) {
winAnimator.applyEnterAnimationLocked();
}
@@ -3384,6 +3428,7 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState win = atoken.findMainWindow();
Rect containingFrame = new Rect(0, 0, width, height);
Rect contentInsets = new Rect();
+ Rect appFrame = new Rect(0, 0, width, height);
boolean isFullScreen = true;
if (win != null) {
if (win.mContainingFrame != null) {
@@ -3392,6 +3437,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (win.mContentInsets != null) {
contentInsets.set(win.mContentInsets);
}
+ if (win.mFrame != null) {
+ appFrame.set(win.mFrame);
+ }
isFullScreen =
((win.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) ==
SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) ||
@@ -3405,8 +3453,8 @@ public class WindowManagerService extends IWindowManager.Stub
enter = false;
}
Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
- mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen,
- isVoiceInteraction);
+ mCurConfiguration.orientation, containingFrame, contentInsets, appFrame,
+ isFullScreen, isVoiceInteraction);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -3592,13 +3640,13 @@ public class WindowManagerService extends IWindowManager.Stub
false /*updateInputWindows*/);
}
- if (delayed) {
- if (displayContent != null) {
- displayContent.mExitingTokens.add(wtoken);
- }
+ if (delayed && displayContent != null) {
+ displayContent.mExitingTokens.add(wtoken);
} else if (wtoken.windowType == TYPE_WALLPAPER) {
mWallpaperTokens.remove(wtoken);
}
+ } else if (wtoken.windowType == TYPE_WALLPAPER) {
+ mWallpaperTokens.remove(wtoken);
}
mInputMonitor.updateInputWindowsLw(true /*force*/);
@@ -3609,23 +3657,23 @@ public class WindowManagerService extends IWindowManager.Stub
Binder.restoreCallingIdentity(origId);
}
- private Task createTask(int taskId, int stackId, int userId, AppWindowToken atoken) {
- if (DEBUG_STACK) Slog.i(TAG, "createTask: taskId=" + taskId + " stackId=" + stackId
+ private Task createTaskLocked(int taskId, int stackId, int userId, AppWindowToken atoken) {
+ if (DEBUG_STACK) Slog.i(TAG, "createTaskLocked: taskId=" + taskId + " stackId=" + stackId
+ " atoken=" + atoken);
final TaskStack stack = mStackIdToStack.get(stackId);
if (stack == null) {
throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
}
EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
- Task task = new Task(atoken, stack, userId);
+ Task task = new Task(taskId, stack, userId, this);
mTaskIdToTask.put(taskId, task);
- stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */);
+ stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */, atoken.showForAllUsers);
return task;
}
@Override
public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
- int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
+ int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int userId,
int configChanges, boolean voiceInteraction, boolean launchTaskBehind) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addAppToken()")) {
@@ -3654,9 +3702,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
atoken = new AppWindowToken(this, token, voiceInteraction);
atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
- atoken.groupId = taskId;
atoken.appFullscreen = fullscreen;
- atoken.showWhenLocked = showWhenLocked;
+ atoken.showForAllUsers = showForAllUsers;
atoken.requestedOrientation = requestedOrientation;
atoken.layoutConfigChanges = (configChanges &
(ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
@@ -3666,10 +3713,9 @@ public class WindowManagerService extends IWindowManager.Stub
Task task = mTaskIdToTask.get(taskId);
if (task == null) {
- createTask(taskId, stackId, userId, atoken);
- } else {
- task.addAppToken(addPos, atoken);
+ task = createTaskLocked(taskId, stackId, userId, atoken);
}
+ task.addAppToken(addPos, atoken);
mTokenMap.put(token.asBinder(), atoken);
@@ -3682,68 +3728,92 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void setAppGroupId(IBinder token, int groupId) {
+ public void setAppTask(IBinder token, int taskId) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
- "setAppGroupId()")) {
+ "setAppTask()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
}
synchronized(mWindowMap) {
final AppWindowToken atoken = findAppWindowToken(token);
if (atoken == null) {
- Slog.w(TAG, "Attempted to set group id of non-existing app token: " + token);
+ Slog.w(TAG, "Attempted to set task id of non-existing app token: " + token);
return;
}
- final Task oldTask = mTaskIdToTask.get(atoken.groupId);
+ final Task oldTask = atoken.mTask;
oldTask.removeAppToken(atoken);
- atoken.groupId = groupId;
- Task newTask = mTaskIdToTask.get(groupId);
+ Task newTask = mTaskIdToTask.get(taskId);
if (newTask == null) {
- newTask = createTask(groupId, oldTask.mStack.mStackId, oldTask.mUserId, atoken);
- } else {
- newTask.mAppTokens.add(atoken);
+ newTask =
+ createTaskLocked(taskId, oldTask.mStack.mStackId, oldTask.mUserId, atoken);
}
+ newTask.addAppToken(Integer.MAX_VALUE /* at top */, atoken);
}
}
- public int getOrientationFromWindowsLocked() {
- if (mDisplayFrozen || mOpeningApps.size() > 0 || mClosingApps.size() > 0) {
- // If the display is frozen, some activities may be in the middle
- // of restarting, and thus have removed their old window. If the
- // window has the flag to hide the lock screen, then the lock screen
- // can re-appear and inflict its own orientation on us. Keep the
- // orientation stable until this all settles down.
- return mLastWindowForcedOrientation;
- }
-
- // TODO(multidisplay): Change to the correct display.
- final WindowList windows = getDefaultWindowListLocked();
- int pos = windows.size() - 1;
- while (pos >= 0) {
- WindowState win = windows.get(pos);
- pos--;
- if (win.mAppToken != null) {
- // We hit an application window. so the orientation will be determined by the
- // app window. No point in continuing further.
- return (mLastWindowForcedOrientation=ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- }
- if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) {
- continue;
+ public int getOrientationLocked() {
+ if (mDisplayFrozen) {
+ if (mLastWindowForcedOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Display is frozen, return "
+ + mLastWindowForcedOrientation);
+ // If the display is frozen, some activities may be in the middle
+ // of restarting, and thus have removed their old window. If the
+ // window has the flag to hide the lock screen, then the lock screen
+ // can re-appear and inflict its own orientation on us. Keep the
+ // orientation stable until this all settles down.
+ return mLastWindowForcedOrientation;
}
- int req = win.mAttrs.screenOrientation;
- if((req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) ||
- (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND)){
- continue;
+ } else {
+ // TODO(multidisplay): Change to the correct display.
+ final WindowList windows = getDefaultWindowListLocked();
+ for (int pos = windows.size() - 1; pos >= 0; --pos) {
+ WindowState win = windows.get(pos);
+ if (win.mAppToken != null) {
+ // We hit an application window. so the orientation will be determined by the
+ // app window. No point in continuing further.
+ break;
+ }
+ if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) {
+ continue;
+ }
+ int req = win.mAttrs.screenOrientation;
+ if((req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) ||
+ (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND)){
+ continue;
+ }
+
+ if (DEBUG_ORIENTATION) Slog.v(TAG, win + " forcing orientation to " + req);
+ if (mPolicy.isKeyguardHostWindow(win.mAttrs)) {
+ mLastKeyguardForcedOrientation = req;
+ }
+ return (mLastWindowForcedOrientation = req);
}
+ mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
- if (DEBUG_ORIENTATION) Slog.v(TAG, win + " forcing orientation to " + req);
- return (mLastWindowForcedOrientation=req);
+ if (mPolicy.isKeyguardLocked()) {
+ // The screen is locked and no top system window is requesting an orientation.
+ // Return either the orientation of the show-when-locked app (if there is any) or
+ // the orientation of the keyguard. No point in searching from the rest of apps.
+ WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw();
+ AppWindowToken appShowWhenLocked = winShowWhenLocked == null ?
+ null : winShowWhenLocked.mAppToken;
+ if (appShowWhenLocked != null) {
+ int req = appShowWhenLocked.requestedOrientation;
+ if (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+ req = mLastKeyguardForcedOrientation;
+ }
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + appShowWhenLocked
+ + " -- show when locked, return " + req);
+ return req;
+ }
+ if (DEBUG_ORIENTATION) Slog.v(TAG,
+ "No one is requesting an orientation when the screen is locked");
+ return mLastKeyguardForcedOrientation;
+ }
}
- return (mLastWindowForcedOrientation=ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- }
- public int getOrientationFromAppTokensLocked() {
+ // Top system windows are not requesting an orientation. Start searching from apps.
int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
boolean findingBehind = false;
boolean lastFullscreen = false;
@@ -3798,14 +3868,12 @@ public class WindowManagerService extends IWindowManager.Stub
// to use the orientation behind it, then just take whatever
// orientation it has and ignores whatever is under it.
lastFullscreen = atoken.appFullscreen;
- if (lastFullscreen
- && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+ if (lastFullscreen && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken
+ " -- full screen, return " + or);
return or;
}
- // If this application has requested an explicit orientation,
- // then use it.
+ // If this application has requested an explicit orientation, then use it.
if (or != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
&& or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken
@@ -3815,8 +3883,11 @@ public class WindowManagerService extends IWindowManager.Stub
findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND);
}
}
- if (DEBUG_ORIENTATION) Slog.v(TAG, "No app is requesting an orientation");
- return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "No app is requesting an orientation, return "
+ + mForcedAppOrientation);
+ // The next app has not been requested to be visible, so we keep the current orientation
+ // to prevent freezing/unfreezing the display too early.
+ return mForcedAppOrientation;
}
@Override
@@ -3841,6 +3912,9 @@ public class WindowManagerService extends IWindowManager.Stub
private Configuration updateOrientationFromAppTokensLocked(
Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
+ if (!mDisplayReady) {
+ return null;
+ }
Configuration config = null;
if (updateOrientationFromAppTokensLocked(false)) {
@@ -3859,20 +3933,19 @@ public class WindowManagerService extends IWindowManager.Stub
// the value of the previous configuration.
mTempConfiguration.setToDefaults();
mTempConfiguration.fontScale = currentConfig.fontScale;
- if (computeScreenConfigurationLocked(mTempConfiguration)) {
- if (currentConfig.diff(mTempConfiguration) != 0) {
- mWaitingForConfig = true;
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
- displayContent.layoutNeeded = true;
- int anim[] = new int[2];
- if (displayContent.isDimming()) {
- anim[0] = anim[1] = 0;
- } else {
- mPolicy.selectRotationAnimationLw(anim);
- }
- startFreezingDisplayLocked(false, anim[0], anim[1]);
- config = new Configuration(mTempConfiguration);
+ computeScreenConfigurationLocked(mTempConfiguration);
+ if (currentConfig.diff(mTempConfiguration) != 0) {
+ mWaitingForConfig = true;
+ final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ displayContent.layoutNeeded = true;
+ int anim[] = new int[2];
+ if (displayContent.isDimming()) {
+ anim[0] = anim[1] = 0;
+ } else {
+ mPolicy.selectRotationAnimationLw(anim);
}
+ startFreezingDisplayLocked(false, anim[0], anim[1]);
+ config = new Configuration(mTempConfiguration);
}
}
@@ -3896,11 +3969,7 @@ public class WindowManagerService extends IWindowManager.Stub
boolean updateOrientationFromAppTokensLocked(boolean inTransaction) {
long ident = Binder.clearCallingIdentity();
try {
- int req = getOrientationFromWindowsLocked();
- if (req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
- req = getOrientationFromAppTokensLocked();
- }
-
+ int req = getOrientationLocked();
if (req != mForcedAppOrientation) {
mForcedAppOrientation = req;
//send a message to Policy indicating orientation change to take
@@ -3987,7 +4056,7 @@ public class WindowManagerService extends IWindowManager.Stub
void setFocusedStackFrame() {
final TaskStack stack;
if (mFocusedApp != null) {
- Task task = mTaskIdToTask.get(mFocusedApp.groupId);
+ final Task task = mFocusedApp.mTask;
stack = task.mStack;
final DisplayContent displayContent = task.getDisplayContent();
if (displayContent != null) {
@@ -3996,20 +4065,7 @@ public class WindowManagerService extends IWindowManager.Stub
} else {
stack = null;
}
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setFocusedStackFrame");
- SurfaceControl.openTransaction();
- try {
- if (stack == null) {
- mFocusedStackFrame.setVisibility(false);
- } else {
- mFocusedStackFrame.setBounds(stack);
- final boolean multipleStacks = !stack.isFullscreen();
- mFocusedStackFrame.setVisibility(multipleStacks);
- }
- } finally {
- SurfaceControl.closeTransaction();
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setFocusedStackFrame");
- }
+ mFocusedStackFrame.setVisibility(stack);
}
@Override
@@ -4037,6 +4093,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (changed) {
mFocusedApp = newFocus;
mInputMonitor.setFocusedAppLw(newFocus);
+ setFocusedStackFrame();
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setFocusedApp");
+ SurfaceControl.openTransaction();
+ try {
+ setFocusedStackLayer();
+ } finally {
+ SurfaceControl.closeTransaction();
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setFocusedApp");
+ }
}
if (moveFocusNow && changed) {
@@ -4079,6 +4144,8 @@ public class WindowManagerService extends IWindowManager.Stub
mAppTransition.prepare();
mStartingIconInTransition = false;
mSkipAppTransitionAnimation = false;
+ }
+ if (mAppTransition.isTransitionSet()) {
mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, 5000);
}
@@ -4109,6 +4176,15 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public void overridePendingAppTransitionClipReveal(int startX, int startY,
+ int startWidth, int startHeight) {
+ synchronized(mWindowMap) {
+ mAppTransition.overridePendingAppTransitionClipReveal(startX, startY, startWidth,
+ startHeight);
+ }
+ }
+
+ @Override
public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX,
int startY, IRemoteCallback startedCallback, boolean scaleUp) {
synchronized(mWindowMap) {
@@ -4142,8 +4218,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
synchronized(mWindowMap) {
- if (DEBUG_APP_TRANSITIONS) Slog.w(TAG, "Execute app transition: " + mAppTransition,
- new RuntimeException("here").fillInStackTrace());
+ if (DEBUG_APP_TRANSITIONS) Slog.w(TAG, "Execute app transition: " + mAppTransition
+ + " Callers=" + Debug.getCallers(5));
if (mAppTransition.isTransitionSet()) {
mAppTransition.setReady();
final long origId = Binder.clearCallingIdentity();
@@ -4334,8 +4410,13 @@ public class WindowManagerService extends IWindowManager.Stub
+ " ShowWallpaper="
+ ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowShowWallpaper, false));
- if (ent.array.getBoolean(
- com.android.internal.R.styleable.Window_windowIsTranslucent, false)) {
+ final boolean windowIsTranslucentDefined = ent.array.hasValue(
+ com.android.internal.R.styleable.Window_windowIsTranslucent);
+ final boolean windowIsTranslucent = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+ final boolean windowSwipeToDismiss = ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowSwipeToDismiss, false);
+ if (windowIsTranslucent || (!windowIsTranslucentDefined && windowSwipeToDismiss)) {
return;
}
if (ent.array.getBoolean(
@@ -4574,22 +4655,26 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG, "setAppVisibility(" +
token + ", visible=" + visible + "): " + mAppTransition +
" hidden=" + wtoken.hidden + " hiddenRequested=" +
- wtoken.hiddenRequested, HIDE_STACK_CRAWLS ?
- null : new RuntimeException("here").fillInStackTrace());
+ wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6));
+
+ mOpeningApps.remove(wtoken);
+ mClosingApps.remove(wtoken);
+ wtoken.waitingToShow = wtoken.waitingToHide = false;
+ wtoken.hiddenRequested = !visible;
+
+ mOpeningApps.remove(wtoken);
+ mClosingApps.remove(wtoken);
+ wtoken.waitingToShow = wtoken.waitingToHide = false;
+ wtoken.hiddenRequested = !visible;
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
if (okToDisplay() && mAppTransition.isTransitionSet()) {
- wtoken.hiddenRequested = !visible;
-
if (!wtoken.startingDisplayed) {
if (DEBUG_APP_TRANSITIONS) Slog.v(
TAG, "Setting dummy animation on: " + wtoken);
wtoken.mAppAnimator.setDummyAnimation();
}
- mOpeningApps.remove(wtoken);
- mClosingApps.remove(wtoken);
- wtoken.waitingToShow = wtoken.waitingToHide = false;
wtoken.inPendingTransaction = true;
if (visible) {
mOpeningApps.add(wtoken);
@@ -4644,6 +4729,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
final long origId = Binder.clearCallingIdentity();
+ wtoken.inPendingTransaction = false;
setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET,
true, wtoken.voiceInteraction);
wtoken.updateReportedVisibilityLocked();
@@ -4662,7 +4748,8 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState w = wtoken.allAppWindows.get(i);
if (w.mAppFreezing) {
w.mAppFreezing = false;
- if (w.mHasSurface && !w.mOrientationChanging) {
+ if (w.mHasSurface && !w.mOrientationChanging
+ && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "set mOrientationChanging of " + w);
w.mOrientationChanging = true;
mInnerFields.mOrientationChangeComplete = false;
@@ -4711,7 +4798,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (mAppsFreezingScreen == 1) {
startFreezingDisplayLocked(false, 0, 0);
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 5000);
+ mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
}
}
final int N = wtoken.allAppWindows.size();
@@ -4766,17 +4853,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- void removeAppFromTaskLocked(AppWindowToken wtoken) {
- wtoken.removeAllWindows();
-
- final Task task = mTaskIdToTask.get(wtoken.groupId);
- if (task != null) {
- if (!task.removeAppToken(wtoken)) {
- Slog.e(TAG, "removeAppFromTaskLocked: token=" + wtoken + " not found.");
- }
- }
- }
-
@Override
public void removeAppToken(IBinder token) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
@@ -4811,20 +4887,20 @@ public class WindowManagerService extends IWindowManager.Stub
+ " animating=" + wtoken.mAppAnimator.animating);
if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "removeAppToken: "
+ wtoken + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
- final TaskStack stack = mTaskIdToTask.get(wtoken.groupId).mStack;
+ final TaskStack stack = wtoken.mTask.mStack;
if (delayed && !wtoken.allAppWindows.isEmpty()) {
// set the token aside because it has an active animation to be finished
if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
"removeAppToken make exiting: " + wtoken);
stack.mExitingAppTokens.add(wtoken);
- wtoken.mDeferRemoval = true;
+ wtoken.mIsExiting = true;
} else {
// Make sure there is no animation running on this token,
// so any windows associated with it will be removed as
// soon as their animations are complete
wtoken.mAppAnimator.clearAnimation();
wtoken.mAppAnimator.animating = false;
- removeAppFromTaskLocked(wtoken);
+ wtoken.removeAppFromTaskLocked();
}
wtoken.removed = true;
@@ -4867,28 +4943,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private boolean tmpRemoveAppWindowsLocked(WindowToken token) {
- WindowList windows = token.windows;
- final int NW = windows.size();
- if (NW > 0) {
- mWindowsChanged = true;
- }
- for (int i = 0; i < NW; i++) {
- WindowState win = windows.get(i);
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing app window " + win);
- win.getWindowList().remove(win);
- int j = win.mChildWindows.size();
- while (j > 0) {
- j--;
- WindowState cwin = win.mChildWindows.get(j);
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG,
- "Tmp removing child window " + cwin);
- cwin.getWindowList().remove(cwin);
- }
- }
- return NW > 0;
- }
-
void dumpAppTokensLocked() {
final int numStacks = mStackIdToStack.size();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
@@ -4898,7 +4952,7 @@ public class WindowManagerService extends IWindowManager.Stub
final int numTasks = tasks.size();
for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
final Task task = tasks.get(taskNdx);
- Slog.v(TAG, " Task #" + task.taskId + " activities from bottom to top:");
+ Slog.v(TAG, " Task #" + task.mTaskId + " activities from bottom to top:");
AppTokenList tokens = task.mAppTokens;
final int numTokens = tokens.size();
for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
@@ -4920,90 +4974,20 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private int findAppWindowInsertionPointLocked(AppWindowToken target) {
- final int taskId = target.groupId;
- Task targetTask = mTaskIdToTask.get(taskId);
- if (targetTask == null) {
- Slog.w(TAG, "findAppWindowInsertionPointLocked: no Task for " + target + " taskId="
- + taskId);
- return 0;
- }
- DisplayContent displayContent = targetTask.getDisplayContent();
- if (displayContent == null) {
- Slog.w(TAG, "findAppWindowInsertionPointLocked: no DisplayContent for " + target);
- return 0;
- }
- final WindowList windows = displayContent.getWindowList();
- final int NW = windows.size();
-
- boolean found = false;
- final ArrayList<Task> tasks = displayContent.getTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = tasks.get(taskNdx);
- if (!found && task.taskId != taskId) {
- continue;
- }
- AppTokenList tokens = task.mAppTokens;
- for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- final AppWindowToken wtoken = tokens.get(tokenNdx);
- if (!found && wtoken == target) {
- found = true;
- }
- if (found) {
- // Find the first app token below the new position that has
- // a window displayed.
- if (DEBUG_REORDER) Slog.v(TAG, "Looking for lower windows in " + wtoken.token);
- if (wtoken.sendingToBottom) {
- if (DEBUG_REORDER) Slog.v(TAG, "Skipping token -- currently sending to bottom");
- continue;
- }
- for (int i = wtoken.windows.size() - 1; i >= 0; --i) {
- WindowState win = wtoken.windows.get(i);
- for (int j = win.mChildWindows.size() - 1; j >= 0; --j) {
- WindowState cwin = win.mChildWindows.get(j);
- if (cwin.mSubLayer >= 0) {
- for (int pos = NW - 1; pos >= 0; pos--) {
- if (windows.get(pos) == cwin) {
- if (DEBUG_REORDER) Slog.v(TAG,
- "Found child win @" + (pos + 1));
- return pos + 1;
- }
- }
- }
- }
- for (int pos = NW - 1; pos >= 0; pos--) {
- if (windows.get(pos) == win) {
- if (DEBUG_REORDER) Slog.v(TAG, "Found win @" + (pos + 1));
- return pos + 1;
- }
- }
- }
- }
- }
- }
- // Never put an app window underneath wallpaper.
- for (int pos = NW - 1; pos >= 0; pos--) {
- if (windows.get(pos).mIsWallpaper) {
- if (DEBUG_REORDER) Slog.v(TAG, "Found wallpaper @" + pos);
- return pos + 1;
- }
- }
- return 0;
- }
-
private final int reAddWindowLocked(int index, WindowState win) {
final WindowList windows = win.getWindowList();
+ // Adding child windows relies on mChildWindows being ordered by mSubLayer.
final int NCW = win.mChildWindows.size();
- boolean added = false;
+ boolean winAdded = false;
for (int j=0; j<NCW; j++) {
WindowState cwin = win.mChildWindows.get(j);
- if (!added && cwin.mSubLayer >= 0) {
+ if (!winAdded && cwin.mSubLayer >= 0) {
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding child window at "
+ index + ": " + cwin);
win.mRebuilding = false;
windows.add(index, win);
index++;
- added = true;
+ winAdded = true;
}
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at "
+ index + ": " + cwin);
@@ -5011,7 +4995,7 @@ public class WindowManagerService extends IWindowManager.Stub
windows.add(index, cwin);
index++;
}
- if (!added) {
+ if (!winAdded) {
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at "
+ index + ": " + win);
win.mRebuilding = false;
@@ -5036,41 +5020,40 @@ public class WindowManagerService extends IWindowManager.Stub
return index;
}
- void tmpRemoveTaskWindowsLocked(Task task) {
- AppTokenList tokens = task.mAppTokens;
- for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
- tmpRemoveAppWindowsLocked(tokens.get(tokenNdx));
- }
- }
void moveStackWindowsLocked(DisplayContent displayContent) {
- // First remove all of the windows from the list.
- final ArrayList<Task> tasks = displayContent.getTasks();
- final int numTasks = tasks.size();
- for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
- tmpRemoveTaskWindowsLocked(tasks.get(taskNdx));
- }
+ final WindowList windows = displayContent.getWindowList();
+ mTmpWindows.addAll(windows);
- // And now add them back at the correct place.
- // Where to start adding?
- for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
- AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
- final int numTokens = tokens.size();
- if (numTokens == 0) {
- continue;
- }
- int pos = findAppWindowInsertionPointLocked(tokens.get(0));
- for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
- final AppWindowToken wtoken = tokens.get(tokenNdx);
- if (wtoken != null) {
- final int newPos = reAddAppWindowsLocked(displayContent, pos, wtoken);
- if (newPos != pos) {
- displayContent.layoutNeeded = true;
- }
- pos = newPos;
- }
+ rebuildAppWindowListLocked(displayContent);
+
+ // Set displayContent.layoutNeeded if window order changed.
+ final int tmpSize = mTmpWindows.size();
+ final int winSize = windows.size();
+ int tmpNdx = 0, winNdx = 0;
+ while (tmpNdx < tmpSize && winNdx < winSize) {
+ // Skip over all exiting windows, they've been moved out of order.
+ WindowState tmp;
+ do {
+ tmp = mTmpWindows.get(tmpNdx++);
+ } while (tmpNdx < tmpSize && tmp.mAppToken != null && tmp.mAppToken.mIsExiting);
+
+ WindowState win;
+ do {
+ win = windows.get(winNdx++);
+ } while (winNdx < winSize && win.mAppToken != null && win.mAppToken.mIsExiting);
+
+ if (tmp != win) {
+ // Window order changed.
+ displayContent.layoutNeeded = true;
+ break;
}
}
+ if (tmpNdx != winNdx) {
+ // One list was different from the other.
+ displayContent.layoutNeeded = true;
+ }
+ mTmpWindows.clear();
if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
false /*updateInputWindows*/)) {
@@ -5193,30 +5176,6 @@ public class WindowManagerService extends IWindowManager.Stub
mStackIdToStack.remove(stackId);
}
- void removeTaskLocked(Task task) {
- final int taskId = task.taskId;
- final TaskStack stack = task.mStack;
- if (!task.mAppTokens.isEmpty() && stack.isAnimating()) {
- if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + taskId);
- task.mDeferRemoval = true;
- return;
- }
- if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + taskId);
- EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, taskId, "removeTask");
- task.mDeferRemoval = false;
- stack.removeTask(task);
- mTaskIdToTask.delete(task.taskId);
-
- final ArrayList<AppWindowToken> exitingApps = stack.mExitingAppTokens;
- for (int appNdx = exitingApps.size() - 1; appNdx >= 0; --appNdx) {
- final AppWindowToken wtoken = exitingApps.get(appNdx);
- if (wtoken.groupId == taskId) {
- wtoken.mDeferRemoval = false;
- exitingApps.remove(appNdx);
- }
- }
- }
-
public void removeTask(int taskId) {
synchronized (mWindowMap) {
Task task = mTaskIdToTask.get(taskId);
@@ -5224,7 +5183,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_STACK) Slog.i(TAG, "removeTask: could not find taskId=" + taskId);
return;
}
- removeTaskLocked(task);
+ task.removeLocked();
}
}
@@ -5234,6 +5193,7 @@ public class WindowManagerService extends IWindowManager.Stub
+ " to " + (toTop ? "top" : "bottom"));
Task task = mTaskIdToTask.get(taskId);
if (task == null) {
+ if (DEBUG_STACK) Slog.i(TAG, "addTask: could not find taskId=" + taskId);
return;
}
TaskStack stack = mStackIdToStack.get(stackId);
@@ -5244,7 +5204,33 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- public void resizeStack(int stackId, Rect bounds) {
+ public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+ synchronized (mWindowMap) {
+ if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: moving taskId=" + taskId
+ + " to stackId=" + stackId + " at " + (toTop ? "top" : "bottom"));
+ Task task = mTaskIdToTask.get(taskId);
+ if (task == null) {
+ if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find taskId=" + taskId);
+ return;
+ }
+ TaskStack stack = mStackIdToStack.get(stackId);
+ if (stack == null) {
+ if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find stackId=" + stackId);
+ return;
+ }
+ task.moveTaskToStack(stack, toTop);
+ final DisplayContent displayContent = stack.getDisplayContent();
+ displayContent.layoutNeeded = true;
+ performLayoutAndPlaceSurfacesLocked();
+ }
+ }
+
+ /**
+ * Re-sizes the specified stack and its containing windows.
+ * Returns a {@link Configuration} object that contains configurations settings
+ * that should be overridden due to the operation.
+ */
+ public Configuration resizeStack(int stackId, Rect bounds) {
synchronized (mWindowMap) {
final TaskStack stack = mStackIdToStack.get(stackId);
if (stack == null) {
@@ -5256,6 +5242,7 @@ public class WindowManagerService extends IWindowManager.Stub
stack.getDisplayContent().layoutNeeded = true;
performLayoutAndPlaceSurfacesLocked();
}
+ return new Configuration(stack.mOverrideConfig);
}
}
@@ -5268,6 +5255,44 @@ public class WindowManagerService extends IWindowManager.Stub
bounds.setEmpty();
}
+ /** Returns the id of an application (non-home stack) stack that match the input bounds.
+ * -1 if no stack matches.*/
+ public int getStackIdWithBounds(Rect bounds) {
+ Rect stackBounds = new Rect();
+ synchronized (mWindowMap) {
+ for (int i = mStackIdToStack.size() - 1; i >= 0; --i) {
+ TaskStack stack = mStackIdToStack.valueAt(i);
+ if (stack.mStackId != HOME_STACK_ID) {
+ stack.getBounds(stackBounds);
+ if (stackBounds.equals(bounds)) {
+ return stack.mStackId;
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ /** Forces the stack to fullscreen if input is true, else un-forces the stack from fullscreen.
+ * Returns a {@link Configuration} object that contains configurations settings
+ * that should be overridden due to the operation.
+ */
+ public Configuration forceStackToFullscreen(int stackId, boolean forceFullscreen) {
+ synchronized (mWindowMap) {
+ final TaskStack stack = mStackIdToStack.get(stackId);
+ if (stack == null) {
+ throw new IllegalArgumentException("resizeStack: stackId " + stackId
+ + " not found.");
+ }
+ if (stack.forceFullscreen(forceFullscreen)) {
+ stack.resizeWindows();
+ stack.getDisplayContent().layoutNeeded = true;
+ performLayoutAndPlaceSurfacesLocked();
+ }
+ return new Configuration(stack.mOverrideConfig);
+ }
+ }
+
// -------------------------------------------------------------
// Misc IWindowSession methods
// -------------------------------------------------------------
@@ -5628,7 +5653,7 @@ public class WindowManagerService extends IWindowManager.Stub
final int numDisplays = mDisplayContents.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
- displayContent.switchUserStacks(newUserId);
+ displayContent.switchUserStacks();
rebuildAppWindowListLocked(displayContent);
}
performLayoutAndPlaceSurfacesLocked();
@@ -5926,13 +5951,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (mCircularDisplayMask == null) {
int screenOffset = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.circular_display_mask_offset);
+ int maskThickness = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.circular_display_mask_thickness);
mCircularDisplayMask = new CircularDisplayMask(
getDefaultDisplayContentLocked().getDisplay(),
mFxSession,
mPolicy.windowTypeToLayerLw(
WindowManager.LayoutParams.TYPE_POINTER)
- * TYPE_LAYER_MULTIPLIER + 10, screenOffset);
+ * TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness);
}
mCircularDisplayMask.setVisibility(true);
} else if (mCircularDisplayMask != null) {
@@ -6055,26 +6082,57 @@ 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.
+ */
+ @Override
+ public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) {
+ if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER,
+ "requestAssistScreenshot()")) {
+ throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+ }
+
+ FgThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ Bitmap bm = screenshotApplicationsInner(null, Display.DEFAULT_DISPLAY, -1, -1,
+ true);
+ try {
+ receiver.send(bm);
+ } catch (RemoteException e) {
+ }
+ }
+ });
+
+ return true;
+ }
+
+ /**
+ * 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
- * @param force565 if true the returned bitmap will be RGB_565, otherwise it
- * will be the same config as the surface
*/
@Override
- public Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
- int height, boolean force565) {
+ public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height) {
if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER,
"screenshotApplications()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
+ return screenshotApplicationsInner(appToken, displayId, width, height, false);
+ }
- final DisplayContent displayContent = getDisplayContentLocked(displayId);
- if (displayContent == null) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
- + ": returning null. No Display for displayId=" + displayId);
- return null;
+ Bitmap screenshotApplicationsInner(IBinder appToken, int displayId, int width, int height,
+ boolean includeFullDisplay) {
+ final DisplayContent displayContent;
+ synchronized(mWindowMap) {
+ displayContent = getDisplayContentLocked(displayId);
+ if (displayContent == null) {
+ if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
+ + ": returning null. No Display for displayId=" + displayId);
+ return null;
+ }
}
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
int dw = displayInfo.logicalWidth;
@@ -6091,9 +6149,6 @@ public class WindowManagerService extends IWindowManager.Stub
final Rect frame = new Rect();
final Rect stackBounds = new Rect();
- float scale = 0;
- int rot = Surface.ROTATION_0;
-
boolean screenshotReady;
int minLayer;
if (appToken == null) {
@@ -6174,7 +6229,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
// Don't include wallpaper in bounds calculation
- if (!ws.mIsWallpaper) {
+ if (!includeFullDisplay && !ws.mIsWallpaper) {
final Rect wf = ws.mFrame;
final Rect cr = ws.mContentInsets;
int left = wf.left + cr.left;
@@ -6187,9 +6242,13 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (ws.mAppToken != null && ws.mAppToken.token == appToken &&
- ws.isDisplayedLw()) {
+ ws.isDisplayedLw() && winAnim.mSurfaceShown) {
screenshotReady = true;
}
+
+ if (ws.isFullscreen(dw, dh) && ws.isOpaqueDrawn()){
+ break;
+ }
}
if (appToken != null && appWin == null) {
@@ -6224,8 +6283,21 @@ public class WindowManagerService extends IWindowManager.Stub
return null;
}
- // Constrain frame to the screen size.
- frame.intersect(0, 0, dw, dh);
+ if (!includeFullDisplay) {
+ // Constrain frame to the screen size.
+ frame.intersect(0, 0, dw, dh);
+ } else {
+ // Caller just wants entire display.
+ frame.set(0, 0, dw, dh);
+ }
+
+
+ if (width < 0) {
+ width = frame.width();
+ }
+ if (height < 0) {
+ height = frame.height();
+ }
// Tell surface flinger what part of the image to crop. Take the top
// right part of the application, and crop the larger dimension to fit.
@@ -6239,7 +6311,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
// The screenshot API does not apply the current screen rotation.
- rot = getDefaultDisplayContentLocked().getDisplay().getRotation();
+ int rot = getDefaultDisplayContentLocked().getDisplay().getRotation();
if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90;
@@ -6485,7 +6557,7 @@ public class WindowManagerService extends IWindowManager.Stub
mAltOrientation = altOrientation;
mPolicy.setRotationLw(mRotation);
- mWindowsFreezingScreen = true;
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
mWaitingForConfig = true;
@@ -6502,13 +6574,12 @@ public class WindowManagerService extends IWindowManager.Stub
screenRotationAnimation =
mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
- // We need to update our screen size information to match the new
- // rotation. Note that this is redundant with the later call to
- // sendNewConfiguration() that must be called after this function
- // returns... however we need to do the screen size part of that
- // before then so we have the correct size to use when initializing
- // the rotation animation for the new rotation.
- computeScreenConfigurationLocked(null);
+ // We need to update our screen size information to match the new rotation. If the rotation
+ // has actually changed then this method will return true and, according to the comment at
+ // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
+ // By updating the Display info here it will be available to
+ // computeScreenConfigurationLocked later.
+ updateDisplayAndOrientationLocked();
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (!inTransaction) {
@@ -7057,9 +7128,11 @@ public class WindowManagerService extends IWindowManager.Stub
public Configuration computeNewConfiguration() {
synchronized (mWindowMap) {
+ if (!mDisplayReady) {
+ return null;
+ }
Configuration config = computeNewConfigurationLocked();
- if (config == null && mWaitingForConfig) {
- // Nothing changed but we are waiting for something... stop that!
+ if (mWaitingForConfig) {
mWaitingForConfig = false;
mLastFinishedFreezeSource = "new-config";
performLayoutAndPlaceSurfacesLocked();
@@ -7071,9 +7144,7 @@ public class WindowManagerService extends IWindowManager.Stub
Configuration computeNewConfigurationLocked() {
Configuration config = new Configuration();
config.fontScale = 0;
- if (!computeScreenConfigurationLocked(config)) {
- return null;
- }
+ computeScreenConfigurationLocked(config);
return config;
}
@@ -7180,11 +7251,8 @@ public class WindowManagerService extends IWindowManager.Stub
return sw;
}
- boolean computeScreenConfigurationLocked(Configuration config) {
- if (!mDisplayReady) {
- return false;
- }
-
+ /** Do not call if mDisplayReady == false */
+ DisplayInfo updateDisplayAndOrientationLocked() {
// TODO(multidisplay): For now, apply Configuration to main screen only.
final DisplayContent displayContent = getDefaultDisplayContentLocked();
@@ -7214,11 +7282,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- if (config != null) {
- config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT :
- Configuration.ORIENTATION_LANDSCAPE;
- }
-
// Update application display metrics.
final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
@@ -7233,96 +7296,113 @@ public class WindowManagerService extends IWindowManager.Stub
displayInfo.getLogicalMetrics(mRealDisplayMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
displayInfo.getAppMetrics(mDisplayMetrics);
+ if (displayContent.mDisplayScalingDisabled) {
+ displayInfo.flags |= Display.FLAG_SCALING_DISABLED;
+ } else {
+ displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
+ }
+
mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
displayContent.getDisplayId(), displayInfo);
+
+ displayContent.mBaseDisplayRect.set(0, 0, dw, dh);
}
if (false) {
Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
}
- final DisplayMetrics dm = mDisplayMetrics;
- mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm,
+ mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
mCompatDisplayMetrics);
+ return displayInfo;
+ }
- if (config != null) {
- config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation)
- / dm.density);
- config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation)
- / dm.density);
- computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, dm.density, config);
-
- config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
- config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
- config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh);
- config.densityDpi = displayContent.mBaseDisplayDensity;
-
- // Update the configuration based on available input devices, lid switch,
- // and platform configuration.
- config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
- config.keyboard = Configuration.KEYBOARD_NOKEYS;
- config.navigation = Configuration.NAVIGATION_NONAV;
-
- int keyboardPresence = 0;
- int navigationPresence = 0;
- final InputDevice[] devices = mInputManager.getInputDevices();
- final int len = devices.length;
- for (int i = 0; i < len; i++) {
- InputDevice device = devices[i];
- if (!device.isVirtual()) {
- final int sources = device.getSources();
- final int presenceFlag = device.isExternal() ?
- WindowManagerPolicy.PRESENCE_EXTERNAL :
- WindowManagerPolicy.PRESENCE_INTERNAL;
-
- if (mIsTouchDevice) {
- if ((sources & InputDevice.SOURCE_TOUCHSCREEN) ==
- InputDevice.SOURCE_TOUCHSCREEN) {
- config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
- }
- } else {
- config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
- }
+ /** Do not call if mDisplayReady == false */
+ void computeScreenConfigurationLocked(Configuration config) {
+ final DisplayInfo displayInfo = updateDisplayAndOrientationLocked();
- if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
- config.navigation = Configuration.NAVIGATION_TRACKBALL;
- navigationPresence |= presenceFlag;
- } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
- && config.navigation == Configuration.NAVIGATION_NONAV) {
- config.navigation = Configuration.NAVIGATION_DPAD;
- navigationPresence |= presenceFlag;
+ final int dw = displayInfo.logicalWidth;
+ final int dh = displayInfo.logicalHeight;
+ config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT :
+ Configuration.ORIENTATION_LANDSCAPE;
+ config.screenWidthDp =
+ (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation) / mDisplayMetrics.density);
+ config.screenHeightDp =
+ (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation) / mDisplayMetrics.density);
+ final boolean rotated = (mRotation == Surface.ROTATION_90
+ || mRotation == Surface.ROTATION_270);
+ computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, mDisplayMetrics.density,
+ config);
+
+ config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
+ config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
+ config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated,
+ mDisplayMetrics, dw, dh);
+ config.densityDpi = displayInfo.logicalDensityDpi;
+
+ // Update the configuration based on available input devices, lid switch,
+ // and platform configuration.
+ config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
+ config.keyboard = Configuration.KEYBOARD_NOKEYS;
+ config.navigation = Configuration.NAVIGATION_NONAV;
+
+ int keyboardPresence = 0;
+ int navigationPresence = 0;
+ final InputDevice[] devices = mInputManager.getInputDevices();
+ final int len = devices.length;
+ for (int i = 0; i < len; i++) {
+ InputDevice device = devices[i];
+ if (!device.isVirtual()) {
+ final int sources = device.getSources();
+ final int presenceFlag = device.isExternal() ?
+ WindowManagerPolicy.PRESENCE_EXTERNAL :
+ WindowManagerPolicy.PRESENCE_INTERNAL;
+
+ if (mIsTouchDevice) {
+ if ((sources & InputDevice.SOURCE_TOUCHSCREEN) ==
+ InputDevice.SOURCE_TOUCHSCREEN) {
+ config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
}
+ } else {
+ config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
+ }
- if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
- config.keyboard = Configuration.KEYBOARD_QWERTY;
- keyboardPresence |= presenceFlag;
- }
+ if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
+ config.navigation = Configuration.NAVIGATION_TRACKBALL;
+ navigationPresence |= presenceFlag;
+ } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
+ && config.navigation == Configuration.NAVIGATION_NONAV) {
+ config.navigation = Configuration.NAVIGATION_DPAD;
+ navigationPresence |= presenceFlag;
}
- }
- if (config.navigation == Configuration.NAVIGATION_NONAV && mHasPermanentDpad) {
- config.navigation = Configuration.NAVIGATION_DPAD;
- navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL;
+ if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
+ config.keyboard = Configuration.KEYBOARD_QWERTY;
+ keyboardPresence |= presenceFlag;
+ }
}
+ }
- // Determine whether a hard keyboard is available and enabled.
- boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
- if (hardKeyboardAvailable != mHardKeyboardAvailable) {
- mHardKeyboardAvailable = hardKeyboardAvailable;
- mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
- mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
- }
- if (mShowImeWithHardKeyboard) {
- config.keyboard = Configuration.KEYBOARD_NOKEYS;
- }
+ if (config.navigation == Configuration.NAVIGATION_NONAV && mHasPermanentDpad) {
+ config.navigation = Configuration.NAVIGATION_DPAD;
+ navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL;
+ }
- // Let the policy update hidden states.
- config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
- config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
- mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
+ // Determine whether a hard keyboard is available and enabled.
+ boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
+ if (hardKeyboardAvailable != mHardKeyboardAvailable) {
+ mHardKeyboardAvailable = hardKeyboardAvailable;
+ mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+ mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+ }
+ if (mShowImeWithHardKeyboard) {
+ config.keyboard = Configuration.KEYBOARD_NOKEYS;
}
- return true;
+ // Let the policy update hidden states.
+ config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
+ config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
+ config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
+ mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
}
public boolean isHardKeyboardAvailable() {
@@ -7393,7 +7473,7 @@ public class WindowManagerService extends IWindowManager.Stub
outSurface.copyFrom(surface);
final IBinder winBinder = window.asBinder();
token = new Binder();
- mDragState = new DragState(this, token, surface, /*flags*/ 0, winBinder);
+ mDragState = new DragState(this, token, surface, flags, winBinder);
token = mDragState.mToken = new Binder();
// 5 second timeout for this window to actually begin the drag
@@ -7521,7 +7601,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized(mWindowMap) {
final DisplayContent displayContent = getDefaultDisplayContentLocked();
- readForcedDisplaySizeAndDensityLocked(displayContent);
+ readForcedDisplayPropertiesLocked(displayContent);
mDisplayReady = true;
}
@@ -7618,6 +7698,7 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int CHECK_IF_BOOT_ANIMATION_FINISHED = 37;
public static final int RESET_ANR_MESSAGE = 38;
+ public static final int WALLPAPER_DRAW_PENDING_TIMEOUT = 39;
@Override
public void handleMessage(Message msg) {
@@ -7853,6 +7934,7 @@ public class WindowManagerService extends IWindowManager.Stub
// TODO(multidisplay): Can non-default displays rotate?
synchronized (mWindowMap) {
Slog.w(TAG, "Window freeze timeout expired.");
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
final WindowList windows = getDefaultWindowListLocked();
int i = windows.size();
while (i > 0) {
@@ -7872,8 +7954,12 @@ public class WindowManagerService extends IWindowManager.Stub
case APP_TRANSITION_TIMEOUT: {
synchronized (mWindowMap) {
- if (mAppTransition.isTransitionSet()) {
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** APP TRANSITION TIMEOUT");
+ if (mAppTransition.isTransitionSet() || !mOpeningApps.isEmpty()
+ || !mClosingApps.isEmpty()) {
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** APP TRANSITION TIMEOUT."
+ + " isTransitionSet()=" + mAppTransition.isTransitionSet()
+ + " mOpeningApps.size()=" + mOpeningApps.size()
+ + " mClosingApps.size()=" + mClosingApps.size());
mAppTransition.setTimeout();
performLayoutAndPlaceSurfacesLocked();
}
@@ -7920,6 +8006,7 @@ public class WindowManagerService extends IWindowManager.Stub
case APP_FREEZE_TIMEOUT: {
synchronized (mWindowMap) {
Slog.w(TAG, "App freeze timeout expired.");
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
final int numStacks = mStackIdToStack.size();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
@@ -8062,16 +8149,16 @@ public class WindowManagerService extends IWindowManager.Stub
break;
case TAP_OUTSIDE_STACK: {
-// int stackId;
-// synchronized (mWindowMap) {
-// stackId = ((DisplayContent)msg.obj).stackIdFromPoint(msg.arg1, msg.arg2);
-// }
-// if (stackId >= 0) {
-// try {
-// mActivityManager.setFocusedStack(stackId);
-// } catch (RemoteException e) {
-// }
-// }
+ int stackId;
+ synchronized (mWindowMap) {
+ stackId = ((DisplayContent)msg.obj).stackIdFromPoint(msg.arg1, msg.arg2);
+ }
+ if (stackId >= 0) {
+ try {
+ mActivityManager.setFocusedStack(stackId);
+ } catch (RemoteException e) {
+ }
+ }
}
break;
case NOTIFY_ACTIVITY_DRAWN:
@@ -8134,6 +8221,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
break;
+ case WALLPAPER_DRAW_PENDING_TIMEOUT: {
+ synchronized (mWindowMap) {
+ if (mWallpaperDrawState == WALLPAPER_DRAW_PENDING) {
+ mWallpaperDrawState = WALLPAPER_DRAW_TIMEOUT;
+ if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
+ "*** WALLPAPER DRAW TIMEOUT");
+ performLayoutAndPlaceSurfacesLocked();
+ }
+ }
+ }
+ break;
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG, "handleMessage: exit");
@@ -8276,7 +8374,47 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private void readForcedDisplaySizeAndDensityLocked(final DisplayContent displayContent) {
+ @Override
+ public void setForcedDisplayScalingMode(int displayId, int mode) {
+ 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");
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized(mWindowMap) {
+ final DisplayContent displayContent = getDisplayContentLocked(displayId);
+ if (displayContent != null) {
+ if (mode < 0 || mode > 1) {
+ mode = 0;
+ }
+ setForcedDisplayScalingModeLocked(displayContent, mode);
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DISPLAY_SCALING_FORCE, mode);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void setForcedDisplayScalingModeLocked(DisplayContent displayContent,
+ int mode) {
+ Slog.i(TAG, "Using display scaling mode: " + (mode == 0 ? "auto" : "off"));
+
+ synchronized(displayContent.mDisplaySizeLock) {
+ displayContent.mDisplayScalingDisabled = (mode != 0);
+ }
+ reconfigureDisplayLocked(displayContent);
+ }
+
+ private void readForcedDisplayPropertiesLocked(final DisplayContent displayContent) {
+ // Display size.
String sizeStr = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.DISPLAY_SIZE_FORCED);
if (sizeStr == null || sizeStr.length() == 0) {
@@ -8301,6 +8439,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
}
+
+ // Display density.
String densityStr = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.DISPLAY_DENSITY_FORCED);
if (densityStr == null || densityStr.length() == 0) {
@@ -8319,6 +8459,16 @@ public class WindowManagerService extends IWindowManager.Stub
} catch (NumberFormatException ex) {
}
}
+
+ // Display scaling mode.
+ int mode = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DISPLAY_SCALING_FORCE, 0);
+ if (mode != 0) {
+ synchronized(displayContent.mDisplaySizeLock) {
+ Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED");
+ displayContent.mDisplayScalingDisabled = true;
+ }
+ }
}
// displayContent must not be null
@@ -8451,17 +8601,17 @@ public class WindowManagerService extends IWindowManager.Stub
// displayContent must not be null
private void reconfigureDisplayLocked(DisplayContent displayContent) {
// TODO: Multidisplay: for now only use with default display.
+ if (!mDisplayReady) {
+ return;
+ }
configureDisplayPolicyLocked(displayContent);
displayContent.layoutNeeded = true;
boolean configChanged = updateOrientationFromAppTokensLocked(false);
mTempConfiguration.setToDefaults();
mTempConfiguration.fontScale = mCurConfiguration.fontScale;
- if (computeScreenConfigurationLocked(mTempConfiguration)) {
- if (mCurConfiguration.diff(mTempConfiguration) != 0) {
- configChanged = true;
- }
- }
+ computeScreenConfigurationLocked(mTempConfiguration);
+ configChanged |= mCurConfiguration.diff(mTempConfiguration) != 0;
if (configChanged) {
mWaitingForConfig = true;
@@ -8587,7 +8737,7 @@ public class WindowManagerService extends IWindowManager.Stub
numRemoved++;
continue;
} else if (lastBelow == i-1) {
- if (w.mAttrs.type == TYPE_WALLPAPER || w.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
+ if (w.mAttrs.type == TYPE_WALLPAPER) {
lastBelow = i;
}
}
@@ -8622,7 +8772,7 @@ public class WindowManagerService extends IWindowManager.Stub
final int numTokens = tokens.size();
for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
final AppWindowToken wtoken = tokens.get(tokenNdx);
- if (wtoken.mDeferRemoval) {
+ if (wtoken.mIsExiting) {
continue;
}
i = reAddAppWindowsLocked(displayContent, i, wtoken);
@@ -8632,6 +8782,7 @@ public class WindowManagerService extends IWindowManager.Stub
i -= lastBelow;
if (i != numRemoved) {
+ displayContent.layoutNeeded = true;
Slog.w(TAG, "On display=" + displayContent.getDisplayId() + " Rebuild removed " +
numRemoved + " windows but added " + i,
new RuntimeException("here").fillInStackTrace());
@@ -8652,6 +8803,7 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.w(TAG, "Final window list:");
dumpWindowsLocked();
}
+ Arrays.fill(mRebuildTmp, null);
}
private final void assignLayersLocked(WindowList windows) {
@@ -8760,29 +8912,24 @@ 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;
- // Wait a little bit for things to settle down, and off we go.
- for (int i=0; i<mForceRemoves.size(); i++) {
- WindowState ws = mForceRemoves.get(i);
- Slog.i(TAG, "Force removing: " + ws);
- removeWindowInnerLocked(ws.mSession, ws);
- }
- mForceRemoves = null;
- Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
- Object tmp = new Object();
- synchronized (tmp) {
- try {
- tmp.wait(250);
- } catch (InterruptedException e) {
- }
+ boolean recoveringMemory = false;
+ if (!mForceRemoves.isEmpty()) {
+ recoveringMemory = true;
+ // Wait a little bit for things to settle down, and off we go.
+ while (!mForceRemoves.isEmpty()) {
+ WindowState ws = mForceRemoves.remove(0);
+ Slog.i(TAG, "Force removing: " + ws);
+ removeWindowInnerLocked(ws);
+ }
+ Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
+ Object tmp = new Object();
+ synchronized (tmp) {
+ try {
+ tmp.wait(250);
+ } catch (InterruptedException e) {
}
}
- } catch (RuntimeException e) {
- Slog.wtf(TAG, "Unhandled exception while force removing for memory", e);
}
try {
@@ -8840,8 +8987,6 @@ public class WindowManagerService extends IWindowManager.Stub
+ displayContent.layoutNeeded + " dw=" + dw + " dh=" + dh);
}
- WindowStateAnimator universeBackground = null;
-
mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation);
if (isDefaultDisplay) {
// Not needed on non-default displays.
@@ -8898,8 +9043,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (!gone || !win.mHaveFrame || win.mLayoutNeeded
|| ((win.isConfigChanged() || win.setInsetsChanged()) &&
((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
- win.mAppToken != null && win.mAppToken.layoutConfigChanges))
- || win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
+ (win.mHasSurface && win.mAppToken != null &&
+ win.mAppToken.layoutConfigChanges)))) {
if (!win.mLayoutAttached) {
if (initial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
@@ -8923,16 +9068,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (topAttached < 0) topAttached = i;
}
}
- if (win.mViewVisibility == View.VISIBLE
- && win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND
- && universeBackground == null) {
- universeBackground = win.mWinAnimator;
- }
- }
-
- if (mAnimator.mUniverseBackground != universeBackground) {
- mFocusMayChange = true;
- mAnimator.mUniverseBackground = universeBackground;
}
boolean attachedBehindDream = false;
@@ -8993,13 +9128,13 @@ public class WindowManagerService extends IWindowManager.Stub
// If the screen is currently frozen or off, then keep
// it frozen/off until this window draws at its new
// orientation.
- if (!okToDisplay()) {
+ if (!okToDisplay() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
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;
+ if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
// XXX should probably keep timeout from
// when we first froze the display.
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
@@ -9023,10 +9158,7 @@ public class WindowManagerService extends IWindowManager.Stub
"Checking " + NN + " opening apps (frozen="
+ mDisplayFrozen + " timeout="
+ 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.
+ if (!mAppTransition.isTimeout()) {
for (i=0; i<NN && goodToGo; i++) {
AppWindowToken wtoken = mOpeningApps.valueAt(i);
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
@@ -9039,6 +9171,40 @@ public class WindowManagerService extends IWindowManager.Stub
goodToGo = false;
}
}
+
+ if (goodToGo && isWallpaperVisible(mWallpaperTarget)) {
+ boolean wallpaperGoodToGo = true;
+ for (int curTokenIndex = mWallpaperTokens.size() - 1;
+ curTokenIndex >= 0 && wallpaperGoodToGo; curTokenIndex--) {
+ WindowToken token = mWallpaperTokens.get(curTokenIndex);
+ for (int curWallpaperIndex = token.windows.size() - 1; curWallpaperIndex >= 0;
+ curWallpaperIndex--) {
+ WindowState wallpaper = token.windows.get(curWallpaperIndex);
+ if (wallpaper.mWallpaperVisible && !wallpaper.isDrawnLw()) {
+ // We've told this wallpaper to be visible, but it is not drawn yet
+ wallpaperGoodToGo = false;
+ if (mWallpaperDrawState != WALLPAPER_DRAW_TIMEOUT) {
+ // wait for this wallpaper until it is drawn or timeout
+ goodToGo = false;
+ }
+ if (mWallpaperDrawState == WALLPAPER_DRAW_NORMAL) {
+ mWallpaperDrawState = WALLPAPER_DRAW_PENDING;
+ mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
+ mH.sendEmptyMessageDelayed(H.WALLPAPER_DRAW_PENDING_TIMEOUT,
+ WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
+ }
+ if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
+ "Wallpaper should be visible but has not been drawn yet. " +
+ "mWallpaperDrawState=" + mWallpaperDrawState);
+ break;
+ }
+ }
+ }
+ if (wallpaperGoodToGo) {
+ mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
+ mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
+ }
+ }
}
if (goodToGo) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
@@ -9046,7 +9212,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (mSkipAppTransitionAnimation) {
transit = AppTransition.TRANSIT_UNSET;
}
- mAppTransition.goodToGo();
mStartingIconInTransition = false;
mSkipAppTransitionAnimation = false;
@@ -9199,6 +9364,7 @@ public class WindowManagerService extends IWindowManager.Stub
for (int j = 0; j < N; j++) {
appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
}
+ mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
}
}
@@ -9221,6 +9387,7 @@ public class WindowManagerService extends IWindowManager.Stub
appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
}
mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+ mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
if (animLp != null) {
int layer = -1;
@@ -9257,6 +9424,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (wtoken.startingWindow != null && !wtoken.startingWindow.mExiting) {
scheduleRemoveStartingWindowLocked(wtoken);
}
+ mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
if (animLp != null) {
int layer = -1;
@@ -9341,6 +9509,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ mAppTransition.goodToGo(openingAppAnimator, closingAppAnimator);
mAppTransition.postAnimationCallback();
mAppTransition.clear();
@@ -9374,6 +9543,12 @@ public class WindowManagerService extends IWindowManager.Stub
int changes = 0;
mAppTransition.setIdle();
+
+ if (mDeferredHideWallpaper != null) {
+ hideWallpapersLocked(mDeferredHideWallpaper);
+ mDeferredHideWallpaper = null;
+ }
+
// Restore window app tokens to the ActivityManager views
ArrayList<TaskStack> stacks = getDefaultDisplayContentLocked().getStacks();
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
@@ -9470,14 +9645,12 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
- *
- * @param w WindowState this method is applied to.
- * @param currentTime The time which animations use for calculating transitions.
+ * @param w WindowState this method is applied to.
* @param innerDw Width of app window.
* @param innerDh Height of app window.
*/
- private void handleNotObscuredLocked(final WindowState w, final long currentTime,
- final int innerDw, final int innerDh) {
+ private void handleNotObscuredLocked(final WindowState w,
+ final int innerDw, final int innerDh) {
final WindowManager.LayoutParams attrs = w.mAttrs;
final int attrFlags = attrs.flags;
final boolean canBeSeen = w.isDisplayedLw();
@@ -9596,8 +9769,6 @@ public class WindowManagerService extends IWindowManager.Stub
+ Debug.getCallers(3));
}
- final long currentTime = SystemClock.uptimeMillis();
-
int i;
boolean updateInputWindowsNeeded = false;
@@ -9688,8 +9859,7 @@ public class WindowManagerService extends IWindowManager.Stub
if ((displayContent.pendingLayoutChanges &
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0 &&
- (adjustWallpaperWindowsLocked() &
- ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
+ adjustWallpaperWindowsLocked()) {
assignLayersLocked(windows);
displayContent.layoutNeeded = true;
}
@@ -9720,15 +9890,12 @@ public class WindowManagerService extends IWindowManager.Stub
// it is animating.
displayContent.pendingLayoutChanges = 0;
- if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("loop number "
- + mLayoutRepeatCount, displayContent.pendingLayoutChanges);
-
if (isDefaultDisplay) {
mPolicy.beginPostLayoutPolicyLw(dw, dh);
for (i = windows.size() - 1; i >= 0; i--) {
WindowState w = windows.get(i);
if (w.mHasSurface) {
- mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.mAttachedWindow);
+ mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs);
}
}
displayContent.pendingLayoutChanges |= mPolicy.finishPostLayoutPolicyLw();
@@ -9757,7 +9924,7 @@ public class WindowManagerService extends IWindowManager.Stub
// Update effect.
w.mObscured = mInnerFields.mObscured;
if (!mInnerFields.mObscured) {
- handleNotObscuredLocked(w, currentTime, innerDw, innerDh);
+ handleNotObscuredLocked(w, innerDw, innerDh);
}
if (stack != null && !stack.testDimmingTag()) {
@@ -9806,7 +9973,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (w.mHasSurface && !w.isHiddenFromUserLocked()) {
// Take care of the window being ready to display.
final boolean committed =
- winAnimator.commitFinishDrawingLocked(currentTime);
+ winAnimator.commitFinishDrawingLocked();
if (isDefaultDisplay && committed) {
if (w.mAttrs.type == TYPE_DREAM) {
// HACK: When a dream is shown, it may at that
@@ -9931,7 +10098,7 @@ public class WindowManagerService extends IWindowManager.Stub
defaultDisplay.pendingLayoutChanges);
}
- if (!mAnimator.mAnimating && mAppTransition.isRunning()) {
+ if (!mAnimator.mAppWindowAnimating && 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
@@ -9994,8 +10161,8 @@ public class WindowManagerService extends IWindowManager.Stub
"With display frozen, orientationChangeComplete="
+ mInnerFields.mOrientationChangeComplete);
if (mInnerFields.mOrientationChangeComplete) {
- if (mWindowsFreezingScreen) {
- mWindowsFreezingScreen = false;
+ if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
+ mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
mLastFinishedFreezeSource = mInnerFields.mLastWindowFreezeSource;
mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
}
@@ -10044,7 +10211,7 @@ public class WindowManagerService extends IWindowManager.Stub
for (i = exitingAppTokens.size() - 1; i >= 0; i--) {
AppWindowToken token = exitingAppTokens.get(i);
if (!token.hasVisible && !mClosingApps.contains(token) &&
- (!token.mDeferRemoval || token.allAppWindows.isEmpty())) {
+ (!token.mIsExiting || token.allAppWindows.isEmpty())) {
// Make sure there is no animation running on this token,
// so any windows associated with it will be removed as
// soon as their animations are complete
@@ -10052,12 +10219,7 @@ public class WindowManagerService extends IWindowManager.Stub
token.mAppAnimator.animating = false;
if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
"performLayout: App token exiting now removed" + token);
- removeAppFromTaskLocked(token);
- exitingAppTokens.remove(i);
- final Task task = mTaskIdToTask.get(token.groupId);
- if (task != null && task.mDeferRemoval && task.mAppTokens.isEmpty()) {
- removeTaskLocked(task);
- }
+ token.removeAppFromTaskLocked();
}
}
}
@@ -10110,7 +10272,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (mAllowTheaterModeWakeFromLayout
|| Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0) == 0) {
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!");
+ if (DEBUG_VISIBILITY || DEBUG_POWER) {
+ Slog.v(TAG, "Turning screen on after layout!");
+ }
mPowerManager.wakeUp(SystemClock.uptimeMillis());
}
mTurnOnScreen = false;
@@ -10141,7 +10305,7 @@ public class WindowManagerService extends IWindowManager.Stub
DisplayContentList displayList = new DisplayContentList();
for (i = 0; i < N; i++) {
WindowState w = mPendingRemoveTmp[i];
- removeWindowInnerLocked(w.mSession, w);
+ removeWindowInnerLocked(w);
final DisplayContent displayContent = w.getDisplayContent();
if (displayContent != null && !displayList.contains(displayContent)) {
displayList.add(displayContent);
@@ -10244,8 +10408,7 @@ public class WindowManagerService extends IWindowManager.Stub
void scheduleAnimationLocked() {
if (!mAnimationScheduled) {
mAnimationScheduled = true;
- mChoreographer.postCallback(
- Choreographer.CALLBACK_ANIMATION, mAnimator.mAnimationRunnable, null);
+ mChoreographer.postFrameCallback(mAnimator.mAnimationFrameCallback);
}
}
@@ -10281,7 +10444,7 @@ public class WindowManagerService extends IWindowManager.Stub
} else {
mInnerFields.mOrientationChangeComplete = true;
mInnerFields.mLastWindowFreezeSource = mAnimator.mLastWindowFreezeSource;
- if (mWindowsFreezingScreen) {
+ if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
doRequest = true;
}
}
@@ -10318,10 +10481,6 @@ public class WindowManagerService extends IWindowManager.Stub
EventLog.writeEvent(EventLogTags.WM_NO_SURFACE_MEMORY, winAnimator.mWin.toString(),
winAnimator.mSession.mPid, operation);
- if (mForceRemoves == null) {
- mForceRemoves = new ArrayList<WindowState>();
- }
-
long callingIdentity = Binder.clearCallingIdentity();
try {
// There was some problem... first, do a sanity check of the
@@ -10481,11 +10640,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
private WindowState computeFocusedWindowLocked() {
- if (mAnimator.mUniverseBackground != null
- && mAnimator.mUniverseBackground.mWin.canReceiveKeys()) {
- return mAnimator.mUniverseBackground.mWin;
- }
-
final int displayCount = mDisplayContents.size();
for (int i = 0; i < displayCount; i++) {
final DisplayContent displayContent = mDisplayContents.valueAt(i);
@@ -10508,6 +10662,10 @@ public class WindowManagerService extends IWindowManager.Stub
+ ", flags=" + win.mAttrs.flags
+ ", canReceive=" + win.canReceiveKeys());
+ if (!win.canReceiveKeys()) {
+ continue;
+ }
+
AppWindowToken wtoken = win.mAppToken;
// If this window's application has been removed, just skip it.
@@ -10517,10 +10675,6 @@ public class WindowManagerService extends IWindowManager.Stub
continue;
}
- if (!win.canReceiveKeys()) {
- continue;
- }
-
// Descend through all of the app tokens and find the first that either matches
// win.mAppToken (return win) or mFocusedApp (return null).
if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING &&
@@ -10626,13 +10780,15 @@ public class WindowManagerService extends IWindowManager.Stub
return;
}
- if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen
- || mClientFreezingScreen) {
+ if (mWaitingForConfig || mAppsFreezingScreen > 0
+ || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
+ || mClientFreezingScreen || !mOpeningApps.isEmpty()) {
if (DEBUG_ORIENTATION) Slog.d(TAG,
"stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig
+ ", mAppsFreezingScreen=" + mAppsFreezingScreen
+ ", mWindowsFreezingScreen=" + mWindowsFreezingScreen
- + ", mClientFreezingScreen=" + mClientFreezingScreen);
+ + ", mClientFreezingScreen=" + mClientFreezingScreen
+ + ", mOpeningApps.size()=" + mOpeningApps.size());
return;
}
@@ -10674,15 +10830,13 @@ public class WindowManagerService extends IWindowManager.Stub
scheduleAnimationLocked();
} else {
screenRotationAnimation.kill();
- screenRotationAnimation = null;
- mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
+ mAnimator.setScreenRotationAnimationLocked(displayId, null);
updateRotation = true;
}
} else {
if (screenRotationAnimation != null) {
screenRotationAnimation.kill();
- screenRotationAnimation = null;
- mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
+ mAnimator.setScreenRotationAnimationLocked(displayId, null);
}
updateRotation = true;
}
@@ -10949,7 +11103,7 @@ public class WindowManagerService extends IWindowManager.Stub
void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)");
- if (mTokenMap.size() > 0) {
+ if (!mTokenMap.isEmpty()) {
pw.println(" All tokens:");
Iterator<WindowToken> it = mTokenMap.values().iterator();
while (it.hasNext()) {
@@ -10963,7 +11117,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
}
- if (mWallpaperTokens.size() > 0) {
+ if (!mWallpaperTokens.isEmpty()) {
pw.println();
pw.println(" Wallpaper tokens:");
for (int i=mWallpaperTokens.size()-1; i>=0; i--) {
@@ -10978,7 +11132,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
}
- if (mFinishedStarting.size() > 0) {
+ if (!mFinishedStarting.isEmpty()) {
pw.println();
pw.println(" Finishing start of application tokens:");
for (int i=mFinishedStarting.size()-1; i>=0; i--) {
@@ -10993,7 +11147,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
}
- if (mOpeningApps.size() > 0 || mClosingApps.size() > 0) {
+ if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) {
pw.println();
if (mOpeningApps.size() > 0) {
pw.print(" mOpeningApps="); pw.println(mOpeningApps);
@@ -11228,7 +11382,7 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition);
pw.print(" mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation);
pw.println(" mLayoutToAnim:");
- mAppTransition.dump(pw);
+ mAppTransition.dump(pw, " ");
}
}
@@ -11784,5 +11938,12 @@ public class WindowManagerService extends IWindowManager.Stub
WindowManagerService.this.removeWindowToken(token);
}
}
+
+ @Override
+ public void registerAppTransitionListener(AppTransitionListener listener) {
+ synchronized (mWindowMap) {
+ mAppTransition.registerListenerLocked(listener);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 021a6e4..ec70879 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,30 +16,34 @@
package com.android.server.wm;
-import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
-import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
-import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
-import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
-import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
-
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
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_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
+import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
+import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerService.DEBUG_POWER;
+import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
+import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
import android.app.AppOpsManager;
import android.os.Debug;
+import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.SystemClock;
+import android.os.WorkSource;
import android.util.TimeUtils;
import android.view.Display;
import android.view.IWindowFocusObserver;
import android.view.IWindowId;
+
import com.android.server.input.InputWindowHandle;
import android.content.Context;
@@ -130,7 +134,8 @@ final class WindowState implements WindowManagerPolicy.WindowState {
int mLayoutSeq = -1;
- Configuration mConfiguration = null;
+ private Configuration mConfiguration = Configuration.EMPTY;
+ private Configuration mOverrideConfig = Configuration.EMPTY;
// Sticky answer to isConfigChanged(), remains true until new Configuration is assigned.
// Used only on {@link #TYPE_KEYGUARD}.
private boolean mConfigHasChanged;
@@ -231,7 +236,8 @@ final class WindowState implements WindowManagerPolicy.WindowState {
final Rect mParentFrame = new Rect();
- // The entire screen area of the device.
+ // The entire screen area of the {@link TaskStack} this window is in. Usually equal to the
+ // screen area of the device.
final Rect mDisplayFrame = new Rect();
// The region of the display frame that the display type supports displaying content on. This
@@ -341,9 +347,14 @@ final class WindowState implements WindowManagerPolicy.WindowState {
/** When true this window can be displayed on screens owther than mOwnerUid's */
private boolean mShowToOwnerOnly;
- /** When true this window is at the top of the screen and should be layed out to extend under
- * the status bar */
- boolean mUnderStatusBar = true;
+ /**
+ * Wake lock for drawing.
+ * Even though it's slightly more expensive to do so, we will use a separate wake lock
+ * for each app that is requesting to draw while dozing so that we can accurately track
+ * who is preventing the system from suspending.
+ * This lock is only acquired on first use.
+ */
+ PowerManager.WakeLock mDrawLock;
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
@@ -407,28 +418,26 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mAttachedWindow = attachedWindow;
if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow);
- int children_size = mAttachedWindow.mChildWindows.size();
- if (children_size == 0) {
- mAttachedWindow.mChildWindows.add(this);
+ final WindowList childWindows = mAttachedWindow.mChildWindows;
+ final int numChildWindows = childWindows.size();
+ if (numChildWindows == 0) {
+ childWindows.add(this);
} else {
- for (int i = 0; i < children_size; i++) {
- WindowState child = (WindowState)mAttachedWindow.mChildWindows.get(i);
- if (this.mSubLayer < child.mSubLayer) {
- mAttachedWindow.mChildWindows.add(i, this);
+ boolean added = false;
+ for (int i = 0; i < numChildWindows; i++) {
+ final int childSubLayer = childWindows.get(i).mSubLayer;
+ if (mSubLayer < childSubLayer
+ || (mSubLayer == childSubLayer && childSubLayer < 0)) {
+ // We insert the child window into the list ordered by the sub-layer. For
+ // same sub-layers, the negative one should go below others; the positive
+ // one should go above others.
+ childWindows.add(i, this);
+ added = true;
break;
- } else if (this.mSubLayer > child.mSubLayer) {
- continue;
- }
-
- if (this.mBaseLayer <= child.mBaseLayer) {
- mAttachedWindow.mChildWindows.add(i, this);
- break;
- } else {
- continue;
}
}
- if (children_size == mAttachedWindow.mChildWindows.size()) {
- mAttachedWindow.mChildWindows.add(this);
+ if (!added) {
+ childWindows.add(this);
}
}
@@ -470,6 +479,12 @@ final class WindowState implements WindowManagerPolicy.WindowState {
if (mAppToken != null) {
final DisplayContent appDisplay = getDisplayContent();
mNotOnAppsDisplay = displayContent != appDisplay;
+
+ if (mAppToken.showForAllUsers) {
+ // Windows for apps that can show for all users should also show when the
+ // device is locked.
+ mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED;
+ }
}
mWinAnimator = new WindowStateAnimator(this);
@@ -508,18 +523,25 @@ final class WindowState implements WindowManagerPolicy.WindowState {
public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf) {
mHaveFrame = true;
- TaskStack stack = mAppToken != null ? getStack() : null;
- if (stack != null && !stack.isFullscreen()) {
- getStackBounds(stack, mContainingFrame);
- if (mUnderStatusBar) {
- mContainingFrame.top = pf.top;
+ final TaskStack stack = mAppToken != null ? getStack() : null;
+ final boolean nonFullscreenStack = stack != null && !stack.isFullscreen();
+ if (nonFullscreenStack) {
+ stack.getBounds(mContainingFrame);
+ final WindowState imeWin = mService.mInputMethodWindow;
+ if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this
+ && mContainingFrame.bottom > cf.bottom) {
+ // IME is up and obscuring this window. Adjust the window position so it is visible.
+ mContainingFrame.top -= mContainingFrame.bottom - cf.bottom;
}
+ // Make sure the containing frame is within the content frame so we don't layout
+ // resized window under screen decorations.
+ mContainingFrame.intersect(cf);
+ mDisplayFrame.set(mContainingFrame);
} else {
mContainingFrame.set(pf);
+ mDisplayFrame.set(df);
}
- mDisplayFrame.set(df);
-
final int pw = mContainingFrame.width();
final int ph = mContainingFrame.height();
@@ -577,9 +599,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
final int fw = mFrame.width();
final int fh = mFrame.height();
- //System.out.println("In: w=" + w + " h=" + h + " container=" +
- // container + " x=" + mAttrs.x + " y=" + mAttrs.y);
-
float x, y;
if (mEnforceSizeCompat) {
x = mAttrs.x * mGlobalScale;
@@ -589,14 +608,19 @@ final class WindowState implements WindowManagerPolicy.WindowState {
y = mAttrs.y;
}
+ if (nonFullscreenStack) {
+ // Make sure window fits in containing frame since it is in a non-fullscreen stack as
+ // required by {@link Gravity#apply} call.
+ w = Math.min(w, pw);
+ h = Math.min(h, ph);
+ }
+
Gravity.apply(mAttrs.gravity, w, h, mContainingFrame,
(int) (x + mAttrs.horizontalMargin * pw),
(int) (y + mAttrs.verticalMargin * ph), mFrame);
- //System.out.println("Out: " + mFrame);
-
- // Now make sure the window fits in the overall display.
- Gravity.applyDisplay(mAttrs.gravity, df, mFrame);
+ // Now make sure the window fits in the overall display frame.
+ Gravity.applyDisplay(mAttrs.gravity, mDisplayFrame, mFrame);
// Make sure the content and visible frames are inside of the
// final window frame.
@@ -794,14 +818,14 @@ final class WindowState implements WindowManagerPolicy.WindowState {
TaskStack getStack() {
AppWindowToken wtoken = mAppToken == null ? mService.mFocusedApp : mAppToken;
if (wtoken != null) {
- Task task = mService.mTaskIdToTask.get(wtoken.groupId);
+ Task task = wtoken.mTask;
if (task != null) {
if (task.mStack != null) {
return task.mStack;
}
Slog.e(TAG, "getStack: mStack null for task=" + task);
} else {
- Slog.e(TAG, "getStack: " + this + " couldn't find taskId=" + wtoken.groupId
+ Slog.e(TAG, "getStack: " + this + " couldn't find task for " + wtoken
+ " Callers=" + Debug.getCallers(4));
}
}
@@ -809,10 +833,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
void getStackBounds(Rect bounds) {
- getStackBounds(getStack(), bounds);
- }
-
- private void getStackBounds(TaskStack stack, Rect bounds) {
+ final TaskStack stack = getStack();
if (stack != null) {
stack.getBounds(bounds);
return;
@@ -1078,9 +1099,13 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
boolean isConfigChanged() {
- boolean configChanged = mConfiguration != mService.mCurConfiguration
- && (mConfiguration == null
- || (mConfiguration.diff(mService.mCurConfiguration) != 0));
+ final TaskStack stack = getStack();
+ final Configuration overrideConfig =
+ (stack != null) ? stack.mOverrideConfig : Configuration.EMPTY;
+ final Configuration serviceConfig = mService.mCurConfiguration;
+ boolean configChanged =
+ (mConfiguration != serviceConfig && mConfiguration.diff(serviceConfig) != 0)
+ || (mOverrideConfig != overrideConfig && !mOverrideConfig.equals(overrideConfig));
if ((mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
// Retain configuration changed status until resetConfiguration called.
@@ -1109,8 +1134,10 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
}
- void setConfiguration(final Configuration newConfig) {
+ private void setConfiguration(
+ final Configuration newConfig, final Configuration newOverrideConfig) {
mConfiguration = newConfig;
+ mOverrideConfig = newOverrideConfig;
mConfigHasChanged = false;
}
@@ -1142,10 +1169,10 @@ final class WindowState implements WindowManagerPolicy.WindowState {
WindowState win = mService.windowForClientLocked(mSession, mClient, false);
Slog.i(TAG, "WIN DEATH: " + win);
if (win != null) {
- mService.removeWindowLocked(mSession, win);
+ mService.removeWindowLocked(win);
} else if (mHasSurface) {
Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
- mService.removeWindowLocked(mSession, WindowState.this);
+ mService.removeWindowLocked(WindowState.this);
}
}
} catch (IllegalArgumentException ex) {
@@ -1272,6 +1299,33 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
}
+ public void pokeDrawLockLw(long timeout) {
+ if (isVisibleOrAdding()) {
+ if (mDrawLock == null) {
+ // We want the tag name to be somewhat stable so that it is easier to correlate
+ // in wake lock statistics. So in particular, we don't want to include the
+ // window's hash code as in toString().
+ CharSequence tag = mAttrs.getTitle();
+ if (tag == null) {
+ tag = mAttrs.packageName;
+ }
+ mDrawLock = mService.mPowerManager.newWakeLock(
+ PowerManager.DRAW_WAKE_LOCK, "Window:" + tag);
+ mDrawLock.setReferenceCounted(false);
+ mDrawLock.setWorkSource(new WorkSource(mOwnerUid, mAttrs.packageName));
+ }
+ // Each call to acquire resets the timeout.
+ if (DEBUG_POWER) {
+ Slog.d(TAG, "pokeDrawLock: poking draw lock on behalf of visible window owned by "
+ + mAttrs.packageName);
+ }
+ mDrawLock.acquire(timeout);
+ } else if (DEBUG_POWER) {
+ Slog.d(TAG, "pokeDrawLock: suppressed draw lock request for invisible window "
+ + "owned by " + mAttrs.packageName);
+ }
+ }
+
@Override
public boolean isAlive() {
return mClient.asBinder().isBinderAlive();
@@ -1291,6 +1345,15 @@ final class WindowState implements WindowManagerPolicy.WindowState {
return displayContent.isDefaultDisplay;
}
+ @Override
+ public boolean isDimming() {
+ TaskStack stack = getStack();
+ if (stack == null) {
+ return false;
+ }
+ return stack.isDimming(mWinAnimator);
+ }
+
public void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
mShowToOwnerOnly = showToOwnerOnly;
}
@@ -1302,7 +1365,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
win = win.mAttachedWindow;
}
if (win.mAttrs.type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
- && win.mAppToken != null && win.mAppToken.showWhenLocked) {
+ && win.mAppToken != null && win.mAppToken.showForAllUsers) {
// Save some cycles by not calling getDisplayInfo unless it is an application
// window intended for all users.
final DisplayContent displayContent = win.getDisplayContent();
@@ -1386,12 +1449,15 @@ final class WindowState implements WindowManagerPolicy.WindowState {
if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, "Reporting new frame to " + this
+ ": " + mCompatFrame);
boolean configChanged = isConfigChanged();
+ final TaskStack stack = getStack();
+ final Configuration overrideConfig =
+ (stack != null) ? stack.mOverrideConfig : Configuration.EMPTY;
if ((DEBUG_RESIZE || DEBUG_ORIENTATION || DEBUG_CONFIGURATION) && configChanged) {
Slog.i(TAG, "Sending new config to window " + this + ": "
- + mWinAnimator.mSurfaceW + "x" + mWinAnimator.mSurfaceH
- + " / " + mService.mCurConfiguration);
+ + mWinAnimator.mSurfaceW + "x" + mWinAnimator.mSurfaceH + " / config="
+ + mService.mCurConfiguration + " overrideConfig=" + overrideConfig);
}
- setConfiguration(mService.mCurConfiguration);
+ setConfiguration(mService.mCurConfiguration, overrideConfig);
if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING)
Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING");
@@ -1436,6 +1502,11 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mOrientationChanging = false;
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mService.mDisplayFreezeTime);
+ // We are assuming the hosting process is dead or in a zombie state.
+ Slog.w(TAG, "Failed to report 'resized' to the client of " + this
+ + ", removing this window.");
+ mService.mPendingRemove.add(this);
+ mService.requestTraversalLocked();
}
}
@@ -1463,7 +1534,11 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+ final TaskStack stack = getStack();
pw.print(prefix); pw.print("mDisplayId="); pw.print(getDisplayId());
+ if (stack != null) {
+ pw.print(" stackId="); pw.print(stack.mStackId);
+ }
pw.print(" mSession="); pw.print(mSession);
pw.print(" mClient="); pw.println(mClient.asBinder());
pw.print(prefix); pw.print("mOwnerUid="); pw.print(mOwnerUid);
@@ -1547,6 +1622,9 @@ final class WindowState implements WindowManagerPolicy.WindowState {
pw.print(prefix); pw.print("touchable region="); pw.println(region);
}
pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration);
+ if (mOverrideConfig != Configuration.EMPTY) {
+ pw.print(prefix); pw.print("mOverrideConfig="); pw.println(mOverrideConfig);
+ }
}
pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
pw.print(" mShownFrame="); mShownFrame.printShortString(pw);
@@ -1557,6 +1635,10 @@ final class WindowState implements WindowManagerPolicy.WindowState {
pw.println();
pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw);
pw.print(" last="); mLastSystemDecorRect.printShortString(pw);
+ if (mWinAnimator.mHasClipRect) {
+ pw.print(" mLastClipRect=");
+ mWinAnimator.mLastClipRect.printShortString(pw);
+ }
pw.println();
}
if (mEnforceSizeCompat) {
@@ -1626,6 +1708,9 @@ final class WindowState implements WindowManagerPolicy.WindowState {
pw.print(" mWallpaperDisplayOffsetY=");
pw.println(mWallpaperDisplayOffsetY);
}
+ if (mDrawLock != null) {
+ pw.println("mDrawLock=" + mDrawLock);
+ }
}
String makeInputChannelName() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index c2d8004..d6726c1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -53,21 +53,16 @@ import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerPolicy;
import android.view.WindowManager.LayoutParams;
-import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;
-import com.android.internal.R;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
import java.util.ArrayList;
-class WinAnimatorList extends ArrayList<WindowStateAnimator> {
-}
-
/**
* Keep track of animations and surface operations for a single WindowState.
**/
@@ -85,10 +80,6 @@ class WindowStateAnimator {
final Context mContext;
final boolean mIsWallpaper;
- // If this is a universe background window, this is the transformation
- // it is applying to the rest of the universe.
- final Transformation mUniverseTransform = new Transformation();
-
// Currently running animation.
boolean mAnimating;
boolean mLocalAnimating;
@@ -189,7 +180,7 @@ class WindowStateAnimator {
int mAttrType;
- public WindowStateAnimator(final WindowState win) {
+ WindowStateAnimator(final WindowState win) {
final WindowManagerService service = win.mService;
mService = service;
@@ -247,9 +238,7 @@ class WindowStateAnimator {
boolean isAnimating() {
return mAnimation != null
|| (mAttachedWinAnimator != null && mAttachedWinAnimator.mAnimation != null)
- || (mAppAnimator != null &&
- (mAppAnimator.animation != null
- || mAppAnimator.mAppToken.inPendingTransaction));
+ || (mAppAnimator != null && mAppAnimator.isAnimating());
}
/** Is the window animating the DummyAnimation? */
@@ -486,7 +475,7 @@ class WindowStateAnimator {
mService.mPendingRemove.add(mWin);
mWin.mRemoveOnExit = false;
}
- mAnimator.hideWallpapersLocked(mWin);
+ mService.hideWallpapersLocked(mWin);
}
void hide() {
@@ -527,7 +516,7 @@ class WindowStateAnimator {
}
// This must be called while inside a transaction.
- boolean commitFinishDrawingLocked(long currentTime) {
+ boolean commitFinishDrawingLocked() {
if (DEBUG_STARTING_WINDOW &&
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState="
@@ -542,9 +531,9 @@ class WindowStateAnimator {
mDrawState = READY_TO_SHOW;
final AppWindowToken atoken = mWin.mAppToken;
if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
- performShowLocked();
+ return performShowLocked();
}
- return true;
+ return false;
}
static class SurfaceTrace extends SurfaceControl {
@@ -984,7 +973,7 @@ class WindowStateAnimator {
}
mSurfaceControl.destroy();
}
- mAnimator.hideWallpapersLocked(mWin);
+ mService.hideWallpapersLocked(mWin);
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying Window " + this
+ " surface " + mSurfaceControl + " session " + mSession
@@ -1010,7 +999,7 @@ class WindowStateAnimator {
WindowManagerService.logSurface(mWin, "DESTROY PENDING", e);
}
mPendingDestroySurface.destroy();
- mAnimator.hideWallpapersLocked(mWin);
+ mService.hideWallpapersLocked(mWin);
}
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying Window "
@@ -1096,9 +1085,6 @@ class WindowStateAnimator {
if (appTransformation != null) {
tmpMatrix.postConcat(appTransformation.getMatrix());
}
- if (mAnimator.mUniverseBackground != null) {
- tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix());
- }
if (screenAnimation) {
tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
}
@@ -1164,9 +1150,6 @@ class WindowStateAnimator {
mHasClipRect = true;
}
}
- if (mAnimator.mUniverseBackground != null) {
- mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha();
- }
if (screenAnimation) {
mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
}
@@ -1192,15 +1175,12 @@ class WindowStateAnimator {
TAG, "computeShownFrameLocked: " + this +
" not attached, mAlpha=" + mAlpha);
- final boolean applyUniverseTransformation = (mAnimator.mUniverseBackground != null
- && mWin.mAttrs.type != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
- && mWin.mBaseLayer < mAnimator.mAboveUniverseLayer);
MagnificationSpec spec = null;
//TODO (multidisplay): Magnification is supported only for the default display.
if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) {
spec = mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
}
- if (applyUniverseTransformation || spec != null) {
+ if (spec != null) {
final Rect frame = mWin.mFrame;
final float tmpFloats[] = mService.mTmpFloats;
final Matrix tmpMatrix = mWin.mTmpMatrix;
@@ -1208,10 +1188,6 @@ class WindowStateAnimator {
tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale);
tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
- if (applyUniverseTransformation) {
- tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix());
- }
-
if (spec != null && !spec.isNop()) {
tmpMatrix.postScale(spec.scale, spec.scale);
tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
@@ -1231,9 +1207,6 @@ class WindowStateAnimator {
mWin.mShownFrame.set(x, y, x + w, y + h);
mShownAlpha = mAlpha;
- if (applyUniverseTransformation) {
- mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha();
- }
} else {
mWin.mShownFrame.set(mWin.mFrame);
if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
@@ -1248,7 +1221,7 @@ class WindowStateAnimator {
}
}
- void applyDecorRect(final Rect decorRect) {
+ private void applyDecorRect(final Rect decorRect) {
final WindowState w = mWin;
final int width = w.mFrame.width();
final int height = w.mFrame.height();
@@ -1301,17 +1274,9 @@ class WindowStateAnimator {
displayInfo.logicalHeight - w.mCompatFrame.top);
} else if (w.mLayer >= mService.mSystemDecorLayer) {
// Above the decor layer is easy, just use the entire window.
- // Unless we have a universe background... in which case all the
- // windows need to be cropped by the screen, so they don't cover
- // the universe background.
- if (mAnimator.mUniverseBackground == null) {
- w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
- } else {
- applyDecorRect(mService.mScreenRect);
- }
- } else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
- || w.mDecorFrame.isEmpty()) {
- // The universe background isn't cropped, nor windows without policy decor.
+ w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
+ } else if (w.mDecorFrame.isEmpty()) {
+ // Windows without policy decor aren't cropped.
w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
} else if (w.mAttrs.type == LayoutParams.TYPE_WALLPAPER && mAnimator.mAnimating) {
// If we're animating, the wallpaper crop should only be updated at the end of the
@@ -1436,6 +1401,9 @@ class WindowStateAnimator {
if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
"SIZE " + width + "x" + height, null);
mSurfaceControl.setSize(width, height);
+ mSurfaceControl.setMatrix(
+ mDsDx * w.mHScale, mDtDx * w.mVScale,
+ mDsDy * w.mHScale, mDtDy * w.mVScale);
mAnimator.setPendingLayoutChanges(w.getDisplayId(),
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
if ((w.mAttrs.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) {
@@ -1482,7 +1450,7 @@ class WindowStateAnimator {
hide();
} else if (w.mAttachedHidden || !w.isOnScreen()) {
hide();
- mAnimator.hideWallpapersLocked(w);
+ mService.hideWallpapersLocked(w);
// If we are waiting for this window to handle an
// orientation change, well, it is hidden, so
@@ -1654,13 +1622,8 @@ class WindowStateAnimator {
}
if (DEBUG_VISIBILITY || (DEBUG_STARTING_WINDOW &&
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING)) {
- RuntimeException e = null;
- if (!WindowManagerService.HIDE_STACK_CRAWLS) {
- e = new RuntimeException();
- e.fillInStackTrace();
- }
Slog.v(TAG, "performShow on " + this
- + ": mDrawState=" + mDrawState + " readyForDisplay="
+ + ": mDrawState=" + drawStateToString() + " readyForDisplay="
+ mWin.isReadyForDisplayIgnoringKeyguard()
+ " starting=" + (mWin.mAttrs.type == TYPE_APPLICATION_STARTING)
+ " during animation: policyVis=" + mWin.mPolicyVisibility
@@ -1671,7 +1634,8 @@ class WindowStateAnimator {
+ (mWin.mAppToken != null ? mWin.mAppToken.hidden : false)
+ " animating=" + mAnimating
+ " tok animating="
- + (mAppAnimator != null ? mAppAnimator.animating : false), e);
+ + (mAppAnimator != null ? mAppAnimator.animating : false) + " Callers="
+ + Debug.getCallers(3));
}
if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {
if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
@@ -1932,11 +1896,6 @@ class WindowStateAnimator {
pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized);
pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred);
}
- if (mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND) {
- pw.print(prefix); pw.print("mUniverseTransform=");
- mUniverseTransform.printShortString(pw);
- pw.println();
- }
if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) {
pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha);
pw.print(" mAlpha="); pw.print(mAlpha);
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 1a672e6..b303505 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -83,8 +83,9 @@ class WindowToken {
WindowState win = windows.get(winNdx);
if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) Slog.w(WindowManagerService.TAG,
"removeAllWindows: removing win=" + win);
- win.mService.removeWindowLocked(win.mSession, win);
+ win.mService.removeWindowLocked(win);
}
+ windows.clear();
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index d81cdd9..a5546cf 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -2,7 +2,7 @@
# files
LOCAL_REL_DIR := core/jni
-LOCAL_CFLAGS += -Wno-unused-parameter
+LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter
LOCAL_SRC_FILES += \
$(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \
@@ -10,6 +10,7 @@ LOCAL_SRC_FILES += \
$(LOCAL_REL_DIR)/com_android_server_AssetAtlasService.cpp \
$(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
$(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_fingerprint_FingerprintService.cpp \
$(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecController.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputApplicationHandle.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputManagerService.cpp \
@@ -22,23 +23,24 @@ LOCAL_SRC_FILES += \
$(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
$(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
$(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
$(LOCAL_REL_DIR)/onload.cpp
-include external/stlport/libstlport.mk
-
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
frameworks/base/services \
frameworks/base/libs \
+ frameworks/base/libs/hwui \
frameworks/base/core/jni \
frameworks/native/services \
libcore/include \
libcore/include/libsuspend \
- $(call include-path-for, libhardware)/hardware \
- $(call include-path-for, libhardware_legacy)/hardware_legacy \
+ system/security/keystore/include \
+ $(call include-path-for, libhardware)/hardware \
+ $(call include-path-for, libhardware_legacy)/hardware_legacy \
LOCAL_SHARED_LIBRARIES += \
libandroid_runtime \
@@ -48,6 +50,7 @@ LOCAL_SHARED_LIBRARIES += \
liblog \
libhardware \
libhardware_legacy \
+ libkeystore_binder \
libnativehelper \
libutils \
libui \
diff --git a/services/core/jni/com_android_server_AlarmManagerService.cpp b/services/core/jni/com_android_server_AlarmManagerService.cpp
index a58b00bce..3fd0f84 100644
--- a/services/core/jni/com_android_server_AlarmManagerService.cpp
+++ b/services/core/jni/com_android_server_AlarmManagerService.cpp
@@ -21,7 +21,9 @@
#include "jni.h"
#include <utils/Log.h>
#include <utils/misc.h>
+#include <utils/String8.h>
+#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
@@ -80,8 +82,8 @@ public:
class AlarmImplTimerFd : public AlarmImpl
{
public:
- AlarmImplTimerFd(int fds[N_ANDROID_TIMERFDS], int epollfd) :
- AlarmImpl(fds, N_ANDROID_TIMERFDS), epollfd(epollfd) { }
+ AlarmImplTimerFd(int fds[N_ANDROID_TIMERFDS], int epollfd, int rtc_id) :
+ AlarmImpl(fds, N_ANDROID_TIMERFDS), epollfd(epollfd), rtc_id(rtc_id) { }
~AlarmImplTimerFd();
int set(int type, struct timespec *ts);
@@ -90,6 +92,7 @@ public:
private:
int epollfd;
+ int rtc_id;
};
AlarmImpl::AlarmImpl(int *fds_, size_t n_fds) : fds(new int[n_fds]),
@@ -170,9 +173,16 @@ int AlarmImplTimerFd::setTime(struct timeval *tv)
return -1;
}
- fd = open("/dev/rtc0", O_RDWR);
+ if (rtc_id < 0) {
+ ALOGV("Not setting RTC because wall clock RTC was not found");
+ errno = ENODEV;
+ return -1;
+ }
+
+ android::String8 rtc_dev = String8::format("/dev/rtc%d", rtc_id);
+ fd = open(rtc_dev.string(), O_RDWR);
if (fd < 0) {
- ALOGV("Unable to open RTC driver: %s\n", strerror(errno));
+ ALOGV("Unable to open %s: %s\n", rtc_dev.string(), strerror(errno));
return res;
}
@@ -283,6 +293,66 @@ static jlong init_alarm_driver()
return reinterpret_cast<jlong>(ret);
}
+static const char rtc_sysfs[] = "/sys/class/rtc";
+
+static bool rtc_is_hctosys(unsigned int rtc_id)
+{
+ android::String8 hctosys_path = String8::format("%s/rtc%u/hctosys",
+ rtc_sysfs, rtc_id);
+
+ FILE *file = fopen(hctosys_path.string(), "re");
+ if (!file) {
+ ALOGE("failed to open %s: %s", hctosys_path.string(), strerror(errno));
+ return false;
+ }
+
+ unsigned int hctosys;
+ bool ret = false;
+ int err = fscanf(file, "%u", &hctosys);
+ if (err == EOF)
+ ALOGE("failed to read from %s: %s", hctosys_path.string(),
+ strerror(errno));
+ else if (err == 0)
+ ALOGE("%s did not have expected contents", hctosys_path.string());
+ else
+ ret = hctosys;
+
+ fclose(file);
+ return ret;
+}
+
+static int wall_clock_rtc()
+{
+ DIR *dir = opendir(rtc_sysfs);
+ if (!dir) {
+ ALOGE("failed to open %s: %s", rtc_sysfs, strerror(errno));
+ return -1;
+ }
+
+ struct dirent *dirent;
+ while (errno = 0, dirent = readdir(dir)) {
+ unsigned int rtc_id;
+ int matched = sscanf(dirent->d_name, "rtc%u", &rtc_id);
+
+ if (matched < 0)
+ break;
+ else if (matched != 1)
+ continue;
+
+ if (rtc_is_hctosys(rtc_id)) {
+ ALOGV("found wall clock RTC %u", rtc_id);
+ return rtc_id;
+ }
+ }
+
+ if (errno == 0)
+ ALOGW("no wall clock RTC found");
+ else
+ ALOGE("failed to enumerate RTCs: %s", strerror(errno));
+
+ return -1;
+}
+
static jlong init_timerfd()
{
int epollfd;
@@ -290,7 +360,7 @@ static jlong init_timerfd()
epollfd = epoll_create(N_ANDROID_TIMERFDS);
if (epollfd < 0) {
- ALOGV("epoll_create(%u) failed: %s", N_ANDROID_TIMERFDS,
+ ALOGV("epoll_create(%zu) failed: %s", N_ANDROID_TIMERFDS,
strerror(errno));
return 0;
}
@@ -308,7 +378,7 @@ static jlong init_timerfd()
}
}
- AlarmImpl *ret = new AlarmImplTimerFd(fds, epollfd);
+ AlarmImpl *ret = new AlarmImplTimerFd(fds, epollfd, wall_clock_rtc());
for (size_t i = 0; i < N_ANDROID_TIMERFDS; i++) {
epoll_event event;
diff --git a/services/core/jni/com_android_server_AssetAtlasService.cpp b/services/core/jni/com_android_server_AssetAtlasService.cpp
index 3696e24..ad1d0f5 100644
--- a/services/core/jni/com_android_server_AssetAtlasService.cpp
+++ b/services/core/jni/com_android_server_AssetAtlasService.cpp
@@ -29,8 +29,12 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
+// Disable warnings for Skia.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
#include <SkCanvas.h>
#include <SkBitmap.h>
+#pragma GCC diagnostic pop
namespace android {
@@ -43,40 +47,9 @@ namespace android {
#define FENCE_TIMEOUT 2000000000
// ----------------------------------------------------------------------------
-// JNI Helpers
-// ----------------------------------------------------------------------------
-
-static struct {
- jmethodID setNativeBitmap;
-} gCanvasClassInfo;
-
-#define INVOKEV(object, method, ...) \
- env->CallVoidMethod(object, method, __VA_ARGS__)
-
-// ----------------------------------------------------------------------------
// Canvas management
// ----------------------------------------------------------------------------
-static jlong com_android_server_AssetAtlasService_acquireCanvas(JNIEnv* env, jobject,
- jobject canvas, jint width, jint height) {
-
- SkBitmap* bitmap = new SkBitmap;
- bitmap->allocN32Pixels(width, height);
- bitmap->eraseColor(0);
- INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(bitmap));
-
- return reinterpret_cast<jlong>(bitmap);
-}
-
-static void com_android_server_AssetAtlasService_releaseCanvas(JNIEnv* env, jobject,
- jobject canvas, jlong bitmapHandle) {
-
- SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
- INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0);
-
- delete bitmap;
-}
-
#define CLEANUP_GL_AND_RETURN(result) \
if (fence != EGL_NO_SYNC_KHR) eglDestroySyncKHR(display, fence); \
if (image) eglDestroyImageKHR(display, image); \
@@ -89,9 +62,11 @@ static void com_android_server_AssetAtlasService_releaseCanvas(JNIEnv* env, jobj
return result;
static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject,
- jobject graphicBuffer, jlong bitmapHandle) {
+ jobject graphicBuffer, jobject bitmapHandle) {
+
+ SkBitmap& bitmap = *GraphicsJNI::getSkBitmap(env, bitmapHandle);
+ SkAutoLockPixels alp(bitmap);
- SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
// The goal of this method is to copy the bitmap into the GraphicBuffer
// using the GPU to swizzle the texture content
sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer));
@@ -182,9 +157,9 @@ static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject
}
// Upload the content of the bitmap in the GraphicBuffer
- glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap->width(), bitmap->height(),
- GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels());
+ glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap.bytesPerPixel());
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(),
+ GL_RGBA, GL_UNSIGNED_BYTE, bitmap.getPixels());
if (glGetError() != GL_NO_ERROR) {
ALOGW("Could not upload to texture");
CLEANUP_GL_AND_RETURN(JNI_FALSE);
@@ -229,20 +204,11 @@ static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject
const char* const kClassPathName = "com/android/server/AssetAtlasService";
static JNINativeMethod gMethods[] = {
- { "nAcquireAtlasCanvas", "(Landroid/graphics/Canvas;II)J",
- (void*) com_android_server_AssetAtlasService_acquireCanvas },
- { "nReleaseAtlasCanvas", "(Landroid/graphics/Canvas;J)V",
- (void*) com_android_server_AssetAtlasService_releaseCanvas },
- { "nUploadAtlas", "(Landroid/view/GraphicBuffer;J)Z",
+ { "nUploadAtlas", "(Landroid/view/GraphicBuffer;Landroid/graphics/Bitmap;)Z",
(void*) com_android_server_AssetAtlasService_upload },
};
int register_android_server_AssetAtlasService(JNIEnv* env) {
- jclass clazz;
-
- FIND_CLASS(clazz, "android/graphics/Canvas");
- GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V");
-
return jniRegisterNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/services/core/jni/com_android_server_ConsumerIrService.cpp b/services/core/jni/com_android_server_ConsumerIrService.cpp
index 3a50ff7..f5121cd 100644
--- a/services/core/jni/com_android_server_ConsumerIrService.cpp
+++ b/services/core/jni/com_android_server_ConsumerIrService.cpp
@@ -29,7 +29,7 @@
namespace android {
-static jlong halOpen(JNIEnv *env, jobject obj) {
+static jlong halOpen(JNIEnv* /* env */, jobject /* obj */) {
hw_module_t const* module;
consumerir_device_t *dev;
int err;
@@ -50,7 +50,7 @@ static jlong halOpen(JNIEnv *env, jobject obj) {
return reinterpret_cast<jlong>(dev);
}
-static jint halTransmit(JNIEnv *env, jobject obj, jlong halObject,
+static jint halTransmit(JNIEnv *env, jobject /* obj */, jlong halObject,
jint carrierFrequency, jintArray pattern) {
int ret;
@@ -66,7 +66,7 @@ static jint halTransmit(JNIEnv *env, jobject obj, jlong halObject,
return reinterpret_cast<jint>(ret);
}
-static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject obj,
+static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject /* obj */,
jlong halObject) {
consumerir_device_t *dev = reinterpret_cast<consumerir_device_t*>(halObject);
consumerir_freq_range_t *ranges;
diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp
index b889b78..d48d159 100644
--- a/services/core/jni/com_android_server_SerialService.cpp
+++ b/services/core/jni/com_android_server_SerialService.cpp
@@ -34,7 +34,7 @@ static struct parcel_file_descriptor_offsets_t
jmethodID mConstructor;
} gParcelFileDescriptorOffsets;
-static jobject android_server_SerialService_open(JNIEnv *env, jobject thiz, jstring path)
+static jobject android_server_SerialService_open(JNIEnv *env, jobject /* thiz */, jstring path)
{
const char *pathStr = env->GetStringUTFChars(path, NULL);
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 0625544..c50d63c 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -25,7 +25,7 @@
namespace android {
-static void android_server_SystemServer_nativeInit(JNIEnv* env, jobject clazz) {
+static void android_server_SystemServer_nativeInit(JNIEnv* /* env */, jobject /* clazz */) {
char propBuf[PROPERTY_VALUE_MAX];
property_get("system_init.startsensorservice", propBuf, "1");
if (strcmp(propBuf, "1") == 0) {
diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp
index 3551733..a1bff9d 100644
--- a/services/core/jni/com_android_server_UsbDeviceManager.cpp
+++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp
@@ -41,20 +41,12 @@ static struct parcel_file_descriptor_offsets_t
jmethodID mConstructor;
} gParcelFileDescriptorOffsets;
-static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
- if (env->ExceptionCheck()) {
- ALOGE("An exception was thrown by callback '%s'.", methodName);
- LOGE_EX(env);
- env->ExceptionClear();
- }
-}
-
static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strArray, int index)
{
char buffer[256];
buffer[0] = 0;
- int length = ioctl(fd, cmd, buffer);
+ ioctl(fd, cmd, buffer);
if (buffer[0]) {
jstring obj = env->NewStringUTF(buffer);
env->SetObjectArrayElement(strArray, index, obj);
@@ -63,7 +55,8 @@ static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strA
}
-static jobjectArray android_server_UsbDeviceManager_getAccessoryStrings(JNIEnv *env, jobject thiz)
+static jobjectArray android_server_UsbDeviceManager_getAccessoryStrings(JNIEnv *env,
+ jobject /* thiz */)
{
int fd = open(DRIVER_NAME, O_RDWR);
if (fd < 0) {
@@ -85,7 +78,7 @@ out:
return strArray;
}
-static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobject thiz)
+static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobject /* thiz */)
{
int fd = open(DRIVER_NAME, O_RDWR);
if (fd < 0) {
@@ -100,7 +93,8 @@ static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobjec
gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
}
-static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv *env, jobject thiz)
+static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv* /* env */,
+ jobject /* thiz */)
{
int fd = open(DRIVER_NAME, O_RDWR);
if (fd < 0) {
@@ -112,7 +106,7 @@ static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv *env, jo
return (result == 1);
}
-static jint android_server_UsbDeviceManager_getAudioMode(JNIEnv *env, jobject thiz)
+static jint android_server_UsbDeviceManager_getAudioMode(JNIEnv* /* env */, jobject /* thiz */)
{
int fd = open(DRIVER_NAME, O_RDWR);
if (fd < 0) {
diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp
index 32c3f95..ee50ff9 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -148,7 +148,7 @@ static int usb_device_removed(const char *devname, void* client_data) {
return 0;
}
-static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv *env, jobject thiz)
+static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv* /* env */, jobject thiz)
{
struct usb_host_context* context = usb_host_init();
if (!context) {
@@ -159,7 +159,8 @@ static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv *env, jobject
usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)thiz);
}
-static jobject android_server_UsbHostManager_openDevice(JNIEnv *env, jobject thiz, jstring deviceName)
+static jobject android_server_UsbHostManager_openDevice(JNIEnv *env, jobject /* thiz */,
+ jstring deviceName)
{
const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL);
struct usb_device* device = usb_device_open(deviceNameStr);
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp
new file mode 100644
index 0000000..cb70144
--- /dev/null
+++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp
@@ -0,0 +1,132 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "UsbMidiDeviceJNI"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <asm/byteorder.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sound/asound.h>
+
+namespace android
+{
+
+static jclass sFileDescriptorClass;
+
+static jint
+android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */,
+ jint card, jint device)
+{
+ char path[100];
+
+ snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
+ int fd = open(path, O_RDWR);
+ if (fd < 0) {
+ ALOGE("could not open %s", path);
+ return 0;
+ }
+
+ struct snd_rawmidi_info info;
+ memset(&info, 0, sizeof(info));
+ info.device = device;
+ int ret = ioctl(fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, &info);
+ close(fd);
+
+ if (ret < 0) {
+ ALOGE("SNDRV_CTL_IOCTL_RAWMIDI_INFO failed, errno: %d path: %s", errno, path);
+ return -1;
+ }
+
+ ALOGD("subdevices_count: %d", info.subdevices_count);
+ return info.subdevices_count;
+}
+
+static jobjectArray
+android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device,
+ jint subdevice_count)
+{
+ char path[100];
+
+ snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);
+
+ jobjectArray fds = env->NewObjectArray(subdevice_count, sFileDescriptorClass, NULL);
+ if (!fds) {
+ return NULL;
+ }
+
+ // to support multiple subdevices we open the same file multiple times
+ for (int i = 0; i < subdevice_count; i++) {
+ int fd = open(path, O_RDWR);
+ if (fd < 0) {
+ ALOGE("open failed on %s for index %d", path, i);
+ return NULL;
+ }
+
+ jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
+ env->SetObjectArrayElement(fds, i, fileDescriptor);
+ env->DeleteLocalRef(fileDescriptor);
+ }
+
+ return fds;
+}
+
+static void
+android_server_UsbMidiDevice_close(JNIEnv *env, jobject /* thiz */, jobjectArray fds)
+{
+ int count = env->GetArrayLength(fds);
+ for (int i = 0; i < count; i++) {
+ jobject fd = env->GetObjectArrayElement(fds, i);
+ close(jniGetFDFromFileDescriptor(env, fd));
+ }
+}
+
+static JNINativeMethod method_table[] = {
+ { "nativeGetSubdeviceCount", "(II)I", (void*)android_server_UsbMidiDevice_get_subdevice_count },
+ { "nativeOpen", "(III)[Ljava/io/FileDescriptor;", (void*)android_server_UsbMidiDevice_open },
+ { "nativeClose", "([Ljava/io/FileDescriptor;)V", (void*)android_server_UsbMidiDevice_close },
+};
+
+int register_android_server_UsbMidiDevice(JNIEnv *env)
+{
+ jclass clazz = env->FindClass("java/io/FileDescriptor");
+ if (clazz == NULL) {
+ ALOGE("Can't find java/io/FileDescriptor");
+ return -1;
+ }
+ sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);;
+
+ clazz = env->FindClass("com/android/server/usb/UsbMidiDevice");
+ if (clazz == NULL) {
+ ALOGE("Can't find com/android/server/usb/UsbMidiDevice");
+ return -1;
+ }
+
+ return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice",
+ method_table, NELEM(method_table));
+}
+
+};
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 2b3f74a..fb1166b 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -29,18 +29,18 @@
namespace android
{
-static jboolean vibratorExists(JNIEnv *env, jobject clazz)
+static jboolean vibratorExists(JNIEnv* /* env */, jobject /* clazz */)
{
return vibrator_exists() > 0 ? JNI_TRUE : JNI_FALSE;
}
-static void vibratorOn(JNIEnv *env, jobject clazz, jlong timeout_ms)
+static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
{
// ALOGI("vibratorOn\n");
vibrator_on(timeout_ms);
}
-static void vibratorOff(JNIEnv *env, jobject clazz)
+static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
{
// ALOGI("vibratorOff\n");
vibrator_off();
diff --git a/services/core/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp
index 2a16dfe..7faeb49 100644
--- a/services/core/jni/com_android_server_connectivity_Vpn.cpp
+++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp
@@ -226,12 +226,12 @@ static bool modifyAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAdd
jniThrowNullPointerException(env, "address");
} else {
if (add) {
- if (error = ifc_add_address(name, address, jPrefixLength)) {
+ if ((error = ifc_add_address(name, address, jPrefixLength)) != 0) {
ALOGE("Cannot add address %s/%d on interface %s (%s)", address, jPrefixLength, name,
strerror(-error));
}
} else {
- if (error = ifc_del_address(name, address, jPrefixLength)) {
+ if ((error = ifc_del_address(name, address, jPrefixLength)) != 0) {
ALOGE("Cannot del address %s/%d on interface %s (%s)", address, jPrefixLength, name,
strerror(-error));
}
@@ -258,7 +258,7 @@ static void throwException(JNIEnv *env, int error, const char *message)
}
}
-static jint create(JNIEnv *env, jobject thiz, jint mtu)
+static jint create(JNIEnv *env, jobject /* thiz */, jint mtu)
{
int tun = create_interface(mtu);
if (tun < 0) {
@@ -268,7 +268,7 @@ static jint create(JNIEnv *env, jobject thiz, jint mtu)
return tun;
}
-static jstring getName(JNIEnv *env, jobject thiz, jint tun)
+static jstring getName(JNIEnv *env, jobject /* thiz */, jint tun)
{
char name[IFNAMSIZ];
if (get_interface_name(name, tun) < 0) {
@@ -278,7 +278,7 @@ static jstring getName(JNIEnv *env, jobject thiz, jint tun)
return env->NewStringUTF(name);
}
-static jint setAddresses(JNIEnv *env, jobject thiz, jstring jName,
+static jint setAddresses(JNIEnv *env, jobject /* thiz */, jstring jName,
jstring jAddresses)
{
const char *name = NULL;
@@ -311,7 +311,7 @@ error:
return count;
}
-static void reset(JNIEnv *env, jobject thiz, jstring jName)
+static void reset(JNIEnv *env, jobject /* thiz */, jstring jName)
{
const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL;
if (!name) {
@@ -324,7 +324,7 @@ static void reset(JNIEnv *env, jobject thiz, jstring jName)
env->ReleaseStringUTFChars(jName, name);
}
-static jint check(JNIEnv *env, jobject thiz, jstring jName)
+static jint check(JNIEnv *env, jobject /* thiz */, jstring jName)
{
const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL;
if (!name) {
diff --git a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp
new file mode 100644
index 0000000..7dbfaf6
--- /dev/null
+++ b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Fingerprint-JNI"
+
+#include "JNIHelp.h"
+#include <inttypes.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <android_os_MessageQueue.h>
+#include <binder/IServiceManager.h>
+#include <utils/String16.h>
+#include <utils/Looper.h>
+#include <keystore/IKeystoreService.h>
+#include <keystore/keystore.h> // for error code
+
+#include <hardware/hardware.h>
+#include <hardware/fingerprint.h>
+#include <hardware/hw_auth_token.h>
+
+#include <utils/Log.h>
+#include "core_jni_helpers.h"
+
+
+namespace android {
+
+static const uint16_t kVersion = HARDWARE_MODULE_API_VERSION(2, 0);
+
+static const char* FINGERPRINT_SERVICE = "com/android/server/fingerprint/FingerprintService";
+static struct {
+ jclass clazz;
+ jmethodID notify;
+} gFingerprintServiceClassInfo;
+
+static struct {
+ fingerprint_module_t const* module;
+ fingerprint_device_t *device;
+} gContext;
+
+static sp<Looper> gLooper;
+static jobject gCallback;
+
+class CallbackHandler : public MessageHandler {
+ int type;
+ int arg1, arg2, arg3;
+public:
+ CallbackHandler(int type, int arg1, int arg2, int arg3)
+ : type(type), arg1(arg1), arg2(arg2), arg3(arg3) { }
+
+ virtual void handleMessage(const Message& message) {
+ //ALOG(LOG_VERBOSE, LOG_TAG, "hal_notify(msg=%d, arg1=%d, arg2=%d)\n", msg.type, arg1, arg2);
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(gCallback, gFingerprintServiceClassInfo.notify, type, arg1, arg2, arg3);
+ }
+};
+
+static void notifyKeystore(uint8_t *auth_token, size_t auth_token_length) {
+ if (auth_token != NULL && auth_token_length > 0) {
+ // TODO: cache service?
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("android.security.keystore"));
+ sp<IKeystoreService> service = interface_cast<IKeystoreService>(binder);
+ if (service != NULL) {
+ status_t ret = service->addAuthToken(auth_token, auth_token_length);
+ if (ret != ResponseCode::NO_ERROR) {
+ ALOGE("Falure sending auth token to KeyStore: %d", ret);
+ }
+ } else {
+ ALOGE("Unable to communicate with KeyStore");
+ }
+ }
+}
+
+// Called by the HAL to notify us of fingerprint events
+static void hal_notify_callback(fingerprint_msg_t msg) {
+ uint32_t arg1 = 0;
+ uint32_t arg2 = 0;
+ uint32_t arg3 = 0;
+ switch (msg.type) {
+ case FINGERPRINT_ERROR:
+ arg1 = msg.data.error;
+ break;
+ case FINGERPRINT_ACQUIRED:
+ arg1 = msg.data.acquired.acquired_info;
+ break;
+ case FINGERPRINT_AUTHENTICATED:
+ arg1 = msg.data.authenticated.finger.fid;
+ arg2 = msg.data.authenticated.finger.gid;
+ if (arg1 != 0) {
+ notifyKeystore(reinterpret_cast<uint8_t *>(&msg.data.authenticated.hat),
+ sizeof(msg.data.authenticated.hat));
+ }
+ break;
+ case FINGERPRINT_TEMPLATE_ENROLLING:
+ arg1 = msg.data.enroll.finger.fid;
+ arg2 = msg.data.enroll.finger.gid;
+ arg3 = msg.data.enroll.samples_remaining;
+ break;
+ case FINGERPRINT_TEMPLATE_REMOVED:
+ arg1 = msg.data.removed.finger.fid;
+ arg2 = msg.data.removed.finger.gid;
+ break;
+ default:
+ ALOGE("fingerprint: invalid msg: %d", msg.type);
+ return;
+ }
+ // This call potentially comes in on a thread not owned by us. Hand it off to our
+ // looper so it runs on our thread when calling back to FingerprintService.
+ // CallbackHandler object is reference-counted, so no cleanup necessary.
+ gLooper->sendMessage(new CallbackHandler(msg.type, arg1, arg2, arg3), Message());
+}
+
+static void nativeInit(JNIEnv *env, jobject clazz, jobject mQueue, jobject callbackObj) {
+ ALOG(LOG_VERBOSE, LOG_TAG, "nativeInit()\n");
+ gCallback = MakeGlobalRefOrDie(env, callbackObj);
+ gLooper = android_os_MessageQueue_getMessageQueue(env, mQueue)->getLooper();
+}
+
+static jint nativeEnroll(JNIEnv* env, jobject clazz, jbyteArray token, jint groupId, jint timeout) {
+ ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll(gid=%d, timeout=%d)\n", groupId, timeout);
+ const int tokenSize = env->GetArrayLength(token);
+ jbyte* tokenData = env->GetByteArrayElements(token, 0);
+ if (tokenSize != sizeof(hw_auth_token_t)) {
+ ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll() : invalid token size %d\n", tokenSize);
+ return -1;
+ }
+ int ret = gContext.device->enroll(gContext.device,
+ reinterpret_cast<const hw_auth_token_t*>(tokenData), groupId, timeout);
+ env->ReleaseByteArrayElements(token, tokenData, 0);
+ return reinterpret_cast<jint>(ret);
+}
+
+static jlong nativePreEnroll(JNIEnv* env, jobject clazz) {
+ uint64_t ret = gContext.device->pre_enroll(gContext.device);
+ // ALOG(LOG_VERBOSE, LOG_TAG, "nativePreEnroll(), result = %llx", ret);
+ return reinterpret_cast<jlong>((int64_t)ret);
+}
+
+static jint nativeStopEnrollment(JNIEnv* env, jobject clazz) {
+ ALOG(LOG_VERBOSE, LOG_TAG, "nativeStopEnrollment()\n");
+ int ret = gContext.device->cancel(gContext.device);
+ return reinterpret_cast<jint>(ret);
+}
+
+static jint nativeAuthenticate(JNIEnv* env, jobject clazz, jlong sessionId, jint groupId) {
+ ALOG(LOG_VERBOSE, LOG_TAG, "nativeAuthenticate(sid=%" PRId64 ", gid=%d)\n", sessionId, groupId);
+ int ret = gContext.device->authenticate(gContext.device, sessionId, groupId);
+ return reinterpret_cast<jint>(ret);
+}
+
+static jint nativeStopAuthentication(JNIEnv* env, jobject clazz) {
+ ALOG(LOG_VERBOSE, LOG_TAG, "nativeStopAuthentication()\n");
+ int ret = gContext.device->cancel(gContext.device);
+ return reinterpret_cast<jint>(ret);
+}
+
+static jint nativeRemove(JNIEnv* env, jobject clazz, jint fingerId, jint groupId) {
+ ALOG(LOG_VERBOSE, LOG_TAG, "nativeRemove(fid=%d, gid=%d)\n", fingerId, groupId);
+ fingerprint_finger_id_t finger;
+ finger.fid = fingerId;
+ finger.gid = groupId;
+ int ret = gContext.device->remove(gContext.device, finger);
+ return reinterpret_cast<jint>(ret);
+}
+
+static jlong nativeGetAuthenticatorId(JNIEnv *, jobject clazz) {
+ return gContext.device->get_authenticator_id(gContext.device);
+}
+
+static jint nativeOpenHal(JNIEnv* env, jobject clazz) {
+ ALOG(LOG_VERBOSE, LOG_TAG, "nativeOpenHal()\n");
+ int err;
+ const hw_module_t *hw_module = NULL;
+ if (0 != (err = hw_get_module(FINGERPRINT_HARDWARE_MODULE_ID, &hw_module))) {
+ ALOGE("Can't open fingerprint HW Module, error: %d", err);
+ return 0;
+ }
+ if (NULL == hw_module) {
+ ALOGE("No valid fingerprint module");
+ return 0;
+ }
+
+ gContext.module = reinterpret_cast<const fingerprint_module_t*>(hw_module);
+
+ if (gContext.module->common.methods->open == NULL) {
+ ALOGE("No valid open method");
+ return 0;
+ }
+
+ hw_device_t *device = NULL;
+
+ if (0 != (err = gContext.module->common.methods->open(hw_module, NULL, &device))) {
+ ALOGE("Can't open fingerprint methods, error: %d", err);
+ return 0;
+ }
+
+ if (kVersion != device->version) {
+ ALOGE("Wrong fp version. Expected %d, got %d", kVersion, device->version);
+ // return 0; // FIXME
+ }
+
+ gContext.device = reinterpret_cast<fingerprint_device_t*>(device);
+ err = gContext.device->set_notify(gContext.device, hal_notify_callback);
+ if (err < 0) {
+ ALOGE("Failed in call to set_notify(), err=%d", err);
+ return 0;
+ }
+
+ // Sanity check - remove
+ if (gContext.device->notify != hal_notify_callback) {
+ ALOGE("NOTIFY not set properly: %p != %p", gContext.device->notify, hal_notify_callback);
+ }
+
+ ALOG(LOG_VERBOSE, LOG_TAG, "fingerprint HAL successfully initialized");
+ return reinterpret_cast<jlong>(gContext.device);
+}
+
+static jint nativeCloseHal(JNIEnv* env, jobject clazz) {
+ return -ENOSYS; // TODO
+}
+
+
+// ----------------------------------------------------------------------------
+
+
+// TODO: clean up void methods
+static const JNINativeMethod g_methods[] = {
+ { "nativeAuthenticate", "(JI)I", (void*)nativeAuthenticate },
+ { "nativeStopAuthentication", "()I", (void*)nativeStopAuthentication },
+ { "nativeEnroll", "([BII)I", (void*)nativeEnroll },
+ { "nativePreEnroll", "()J", (void*)nativePreEnroll },
+ { "nativeStopEnrollment", "()I", (void*)nativeStopEnrollment },
+ { "nativeRemove", "(II)I", (void*)nativeRemove },
+ { "nativeGetAuthenticatorId", "()J", (void*)nativeGetAuthenticatorId },
+ { "nativeOpenHal", "()I", (void*)nativeOpenHal },
+ { "nativeCloseHal", "()I", (void*)nativeCloseHal },
+ { "nativeInit","(Landroid/os/MessageQueue;"
+ "Lcom/android/server/fingerprint/FingerprintService;)V", (void*)nativeInit }
+};
+
+int register_android_server_fingerprint_FingerprintService(JNIEnv* env) {
+ jclass clazz = FindClassOrDie(env, FINGERPRINT_SERVICE);
+ gFingerprintServiceClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gFingerprintServiceClassInfo.notify =
+ GetMethodIDOrDie(env, gFingerprintServiceClassInfo.clazz,"notify", "(IIII)V");
+ int result = RegisterMethodsOrDie(env, FINGERPRINT_SERVICE, g_methods, NELEM(g_methods));
+ ALOG(LOG_VERBOSE, LOG_TAG, "FingerprintManager JNI ready.\n");
+ return result;
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
index a35af91..f2d0f06 100644
--- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
+++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
@@ -408,6 +408,7 @@ static JNINativeMethod sMethods[] = {
int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+ (void)res; // Don't scream about unused variable in the LOG_NDEBUG case
return 0;
}
diff --git a/services/core/jni/com_android_server_input_InputApplicationHandle.cpp b/services/core/jni/com_android_server_input_InputApplicationHandle.cpp
index f943d16..11388d8 100644
--- a/services/core/jni/com_android_server_input_InputApplicationHandle.cpp
+++ b/services/core/jni/com_android_server_input_InputApplicationHandle.cpp
@@ -137,6 +137,7 @@ static JNINativeMethod gInputApplicationHandleMethods[] = {
int register_android_server_InputApplicationHandle(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "com/android/server/input/InputApplicationHandle",
gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods));
+ (void) res; // Faked use when LOG_NDEBUG.
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
jclass clazz;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index cddca92..f3edbd1 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -350,14 +350,14 @@ void NativeInputManager::setDisplayViewport(bool external, const DisplayViewport
}
}
-status_t NativeInputManager::registerInputChannel(JNIEnv* env,
+status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
const sp<InputChannel>& inputChannel,
const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
return mInputManager->getDispatcher()->registerInputChannel(
inputChannel, inputWindowHandle, monitor);
}
-status_t NativeInputManager::unregisterInputChannel(JNIEnv* env,
+status_t NativeInputManager::unregisterInputChannel(JNIEnv* /* env */,
const sp<InputChannel>& inputChannel) {
return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel);
}
@@ -428,7 +428,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
} // release lock
}
-sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32_t deviceId) {
+sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32_t /* deviceId */) {
AutoMutex _l(mLock);
sp<PointerController> controller = mLocked.pointerController.promote();
@@ -548,7 +548,7 @@ String8 NativeInputManager::getDeviceAlias(const InputDeviceIdentifier& identifi
}
void NativeInputManager::notifySwitch(nsecs_t when,
- uint32_t switchValues, uint32_t switchMask, uint32_t policyFlags) {
+ uint32_t switchValues, uint32_t switchMask, uint32_t /* policyFlags */) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("notifySwitch - when=%lld, switchValues=0x%08x, switchMask=0x%08x, policyFlags=0x%x",
when, switchValues, switchMask, policyFlags);
@@ -752,7 +752,7 @@ void NativeInputManager::setInteractive(bool interactive) {
void NativeInputManager::reloadCalibration() {
mInputManager->getReader()->requestRefreshConfiguration(
- InputReaderConfiguration::TOUCH_AFFINE_TRANSFORMATION);
+ InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION);
}
TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
@@ -1006,7 +1006,7 @@ void NativeInputManager::loadPointerResources(PointerResources* outResources) {
// ----------------------------------------------------------------------------
-static jlong nativeInit(JNIEnv* env, jclass clazz,
+static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
@@ -1020,7 +1020,7 @@ static jlong nativeInit(JNIEnv* env, jclass clazz,
return reinterpret_cast<jlong>(im);
}
-static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) {
+static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_t result = im->getInputManager()->start();
@@ -1029,8 +1029,8 @@ static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) {
}
}
-static void nativeSetDisplayViewport(JNIEnv* env, jclass clazz, jlong ptr, jboolean external,
- jint displayId, jint orientation,
+static void nativeSetDisplayViewport(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
+ jboolean external, jint displayId, jint orientation,
jint logicalLeft, jint logicalTop, jint logicalRight, jint logicalBottom,
jint physicalLeft, jint physicalTop, jint physicalRight, jint physicalBottom,
jint deviceWidth, jint deviceHeight) {
@@ -1052,7 +1052,7 @@ static void nativeSetDisplayViewport(JNIEnv* env, jclass clazz, jlong ptr, jbool
im->setDisplayViewport(external, v);
}
-static jint nativeGetScanCodeState(JNIEnv* env, jclass clazz,
+static jint nativeGetScanCodeState(JNIEnv* /* env */, jclass /* clazz */,
jlong ptr, jint deviceId, jint sourceMask, jint scanCode) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1060,7 +1060,7 @@ static jint nativeGetScanCodeState(JNIEnv* env, jclass clazz,
deviceId, uint32_t(sourceMask), scanCode);
}
-static jint nativeGetKeyCodeState(JNIEnv* env, jclass clazz,
+static jint nativeGetKeyCodeState(JNIEnv* /* env */, jclass /* clazz */,
jlong ptr, jint deviceId, jint sourceMask, jint keyCode) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1068,7 +1068,7 @@ static jint nativeGetKeyCodeState(JNIEnv* env, jclass clazz,
deviceId, uint32_t(sourceMask), keyCode);
}
-static jint nativeGetSwitchState(JNIEnv* env, jclass clazz,
+static jint nativeGetSwitchState(JNIEnv* /* env */, jclass /* clazz */,
jlong ptr, jint deviceId, jint sourceMask, jint sw) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1076,7 +1076,7 @@ static jint nativeGetSwitchState(JNIEnv* env, jclass clazz,
deviceId, uint32_t(sourceMask), sw);
}
-static jboolean nativeHasKeys(JNIEnv* env, jclass clazz,
+static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */,
jlong ptr, jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1106,7 +1106,7 @@ static void throwInputChannelNotInitialized(JNIEnv* env) {
}
static void handleInputChannelDisposed(JNIEnv* env,
- jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) {
+ jobject /* inputChannelObj */, const sp<InputChannel>& inputChannel, void* data) {
NativeInputManager* im = static_cast<NativeInputManager*>(data);
ALOGW("Input channel object '%s' was disposed without first being unregistered with "
@@ -1114,7 +1114,7 @@ static void handleInputChannelDisposed(JNIEnv* env,
im->unregisterInputChannel(env, inputChannel);
}
-static void nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
+static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1143,7 +1143,7 @@ static void nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
}
}
-static void nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
+static void nativeUnregisterInputChannel(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobject inputChannelObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1164,14 +1164,14 @@ static void nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
}
}
-static void nativeSetInputFilterEnabled(JNIEnv* env, jclass clazz,
+static void nativeSetInputFilterEnabled(JNIEnv* /* env */, jclass /* clazz */,
jlong ptr, jboolean enabled) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->getInputManager()->getDispatcher()->setInputFilterEnabled(enabled);
}
-static jint nativeInjectInputEvent(JNIEnv* env, jclass clazz,
+static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobject inputEventObj, jint displayId, jint injectorPid, jint injectorUid,
jint syncMode, jint timeoutMillis, jint policyFlags) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1203,36 +1203,36 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass clazz,
}
}
-static void nativeSetInputWindows(JNIEnv* env, jclass clazz,
+static void nativeSetInputWindows(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobjectArray windowHandleObjArray) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->setInputWindows(env, windowHandleObjArray);
}
-static void nativeSetFocusedApplication(JNIEnv* env, jclass clazz,
+static void nativeSetFocusedApplication(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobject applicationHandleObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->setFocusedApplication(env, applicationHandleObj);
}
-static void nativeSetInputDispatchMode(JNIEnv* env,
- jclass clazz, jlong ptr, jboolean enabled, jboolean frozen) {
+static void nativeSetInputDispatchMode(JNIEnv* /* env */,
+ jclass /* clazz */, jlong ptr, jboolean enabled, jboolean frozen) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->setInputDispatchMode(enabled, frozen);
}
-static void nativeSetSystemUiVisibility(JNIEnv* env,
- jclass clazz, jlong ptr, jint visibility) {
+static void nativeSetSystemUiVisibility(JNIEnv* /* env */,
+ jclass /* clazz */, jlong ptr, jint visibility) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->setSystemUiVisibility(visibility);
}
static jboolean nativeTransferTouchFocus(JNIEnv* env,
- jclass clazz, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
+ jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
sp<InputChannel> fromChannel =
@@ -1252,15 +1252,15 @@ static jboolean nativeTransferTouchFocus(JNIEnv* env,
}
}
-static void nativeSetPointerSpeed(JNIEnv* env,
- jclass clazz, jlong ptr, jint speed) {
+static void nativeSetPointerSpeed(JNIEnv* /* env */,
+ jclass /* clazz */, jlong ptr, jint speed) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->setPointerSpeed(speed);
}
-static void nativeSetShowTouches(JNIEnv* env,
- jclass clazz, jlong ptr, jboolean enabled) {
+static void nativeSetShowTouches(JNIEnv* /* env */,
+ jclass /* clazz */, jlong ptr, jboolean enabled) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->setShowTouches(enabled);
@@ -1279,7 +1279,7 @@ static void nativeReloadCalibration(JNIEnv* env, jclass clazz, jlong ptr) {
}
static void nativeVibrate(JNIEnv* env,
- jclass clazz, jlong ptr, jint deviceId, jlongArray patternObj,
+ jclass /* clazz */, jlong ptr, jint deviceId, jlongArray patternObj,
jint repeat, jint token) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1303,30 +1303,30 @@ static void nativeVibrate(JNIEnv* env,
im->getInputManager()->getReader()->vibrate(deviceId, pattern, patternSize, repeat, token);
}
-static void nativeCancelVibrate(JNIEnv* env,
- jclass clazz, jlong ptr, jint deviceId, jint token) {
+static void nativeCancelVibrate(JNIEnv* /* env */,
+ jclass /* clazz */, jlong ptr, jint deviceId, jint token) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->getInputManager()->getReader()->cancelVibrate(deviceId, token);
}
-static void nativeReloadKeyboardLayouts(JNIEnv* env,
- jclass clazz, jlong ptr) {
+static void nativeReloadKeyboardLayouts(JNIEnv* /* env */,
+ jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->getInputManager()->getReader()->requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS);
}
-static void nativeReloadDeviceAliases(JNIEnv* env,
- jclass clazz, jlong ptr) {
+static void nativeReloadDeviceAliases(JNIEnv* /* env */,
+ jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->getInputManager()->getReader()->requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_DEVICE_ALIAS);
}
-static jstring nativeDump(JNIEnv* env, jclass clazz, jlong ptr) {
+static jstring nativeDump(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
String8 dump;
@@ -1334,7 +1334,7 @@ static jstring nativeDump(JNIEnv* env, jclass clazz, jlong ptr) {
return env->NewStringUTF(dump.string());
}
-static void nativeMonitor(JNIEnv* env, jclass clazz, jlong ptr) {
+static void nativeMonitor(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->getInputManager()->getReader()->monitor();
@@ -1416,6 +1416,7 @@ static JNINativeMethod gInputManagerMethods[] = {
int register_android_server_InputManager(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "com/android/server/input/InputManagerService",
gInputManagerMethods, NELEM(gInputManagerMethods));
+ (void) res; // Faked use when LOG_NDEBUG.
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
// Callbacks
diff --git a/services/core/jni/com_android_server_input_InputWindowHandle.cpp b/services/core/jni/com_android_server_input_InputWindowHandle.cpp
index 46ec1f4..01c51cf 100644
--- a/services/core/jni/com_android_server_input_InputWindowHandle.cpp
+++ b/services/core/jni/com_android_server_input_InputWindowHandle.cpp
@@ -227,6 +227,7 @@ static JNINativeMethod gInputWindowHandleMethods[] = {
int register_android_server_InputWindowHandle(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "com/android/server/input/InputWindowHandle",
gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods));
+ (void) res; // Faked use when LOG_NDEBUG.
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
jclass clazz;
diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp
index d51e044..b2b2783 100644
--- a/services/core/jni/com_android_server_lights_LightsService.cpp
+++ b/services/core/jni/com_android_server_lights_LightsService.cpp
@@ -60,7 +60,7 @@ static light_device_t* get_device(hw_module_t* module, char const* name)
}
}
-static jlong init_native(JNIEnv *env, jobject clazz)
+static jlong init_native(JNIEnv* /* env */, jobject /* clazz */)
{
int err;
hw_module_t* module;
@@ -93,7 +93,7 @@ static jlong init_native(JNIEnv *env, jobject clazz)
return (jlong)devices;
}
-static void finalize_native(JNIEnv *env, jobject clazz, jlong ptr)
+static void finalize_native(JNIEnv* /* env */, jobject /* clazz */, jlong ptr)
{
Devices* devices = (Devices*)ptr;
if (devices == NULL) {
@@ -103,7 +103,7 @@ static void finalize_native(JNIEnv *env, jobject clazz, jlong ptr)
free(devices);
}
-static void setLight_native(JNIEnv *env, jobject clazz, jlong ptr,
+static void setLight_native(JNIEnv* /* env */, jobject /* clazz */, jlong ptr,
jint light, jint colorARGB, jint flashMode, jint onMS, jint offMS, jint brightnessMode)
{
Devices* devices = (Devices*)ptr;
diff --git a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp b/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp
index 37a3eaa..2ca5f5a 100644
--- a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp
+++ b/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp
@@ -31,14 +31,18 @@ static jobject sCallbacksObj = NULL;
static JNIEnv *sCallbackEnv = NULL;
static hw_device_t* sHardwareDevice = NULL;
+static jmethodID sSetVersion = NULL;
static jmethodID sOnLocationReport = NULL;
static jmethodID sOnDataReport = NULL;
+static jmethodID sOnBatchingCapabilities = NULL;
+static jmethodID sOnBatchingStatus = NULL;
static jmethodID sOnGeofenceTransition = NULL;
static jmethodID sOnGeofenceMonitorStatus = NULL;
static jmethodID sOnGeofenceAdd = NULL;
static jmethodID sOnGeofenceRemove = NULL;
static jmethodID sOnGeofencePause = NULL;
static jmethodID sOnGeofenceResume = NULL;
+static jmethodID sOnGeofencingCapabilities = NULL;
static const FlpLocationInterface* sFlpInterface = NULL;
static const FlpDiagnosticInterface* sFlpDiagnosticInterface = NULL;
@@ -85,6 +89,32 @@ static bool IsValidCallbackThread() {
return true;
}
+static void BatchingCapabilitiesCallback(int32_t capabilities) {
+ if(!IsValidCallbackThread()) {
+ return;
+ }
+
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj,
+ sOnBatchingCapabilities,
+ capabilities
+ );
+ CheckExceptions(sCallbackEnv, __FUNCTION__);
+}
+
+static void BatchingStatusCallback(int32_t status) {
+ if(!IsValidCallbackThread()) {
+ return;
+ }
+
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj,
+ sOnBatchingStatus,
+ status
+ );
+ CheckExceptions(sCallbackEnv, __FUNCTION__);
+}
+
static int SetThreadEvent(ThreadEvent event) {
JavaVM* javaVm = AndroidRuntime::getJavaVM();
@@ -112,6 +142,14 @@ static int SetThreadEvent(ThreadEvent event) {
}
ALOGV("Callback thread attached: %p", sCallbackEnv);
+
+ // Send the version to the upper layer.
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj,
+ sSetVersion,
+ sFlpInterface->size == sizeof(FlpLocationInterface) ? 2 : 1
+ );
+ CheckExceptions(sCallbackEnv, __FUNCTION__);
break;
}
case DISASSOCIATE_JVM:
@@ -147,6 +185,10 @@ static void ClassInit(JNIEnv* env, jclass clazz) {
sFlpInterface = NULL;
// get references to the Java provider methods
+ sSetVersion = env->GetMethodID(
+ clazz,
+ "setVersion",
+ "(I)V");
sOnLocationReport = env->GetMethodID(
clazz,
"onLocationReport",
@@ -156,6 +198,18 @@ static void ClassInit(JNIEnv* env, jclass clazz) {
"onDataReport",
"(Ljava/lang/String;)V"
);
+ sOnBatchingCapabilities = env->GetMethodID(
+ clazz,
+ "onBatchingCapabilities",
+ "(I)V");
+ sOnBatchingStatus = env->GetMethodID(
+ clazz,
+ "onBatchingStatus",
+ "(I)V");
+ sOnGeofencingCapabilities = env->GetMethodID(
+ clazz,
+ "onGeofencingCapabilities",
+ "(I)V");
sOnGeofenceTransition = env->GetMethodID(
clazz,
"onGeofenceTransition",
@@ -297,6 +351,14 @@ static void TranslateFromObject(
getSourcesToUse
);
+ jmethodID getSmallestDisplacementMeters = env->GetMethodID(
+ batchOptionsClass,
+ "getSmallestDisplacementMeters",
+ "()F"
+ );
+ batchOptions.smallest_displacement_meters
+ = env->CallFloatMethod(batchOptionsObject, getSmallestDisplacementMeters);
+
jmethodID getFlags = env->GetMethodID(batchOptionsClass, "getFlags", "()I");
batchOptions.flags = env->CallIntMethod(batchOptionsObject, getFlags);
@@ -526,7 +588,9 @@ FlpCallbacks sFlpCallbacks = {
LocationCallback,
AcquireWakelock,
ReleaseWakelock,
- SetThreadEvent
+ SetThreadEvent,
+ BatchingCapabilitiesCallback,
+ BatchingStatusCallback
};
static void ReportData(char* data, int length) {
@@ -662,6 +726,19 @@ static void GeofenceResumeCallback(int32_t geofenceId, int32_t result) {
CheckExceptions(sCallbackEnv, __FUNCTION__);
}
+static void GeofencingCapabilitiesCallback(int32_t capabilities) {
+ if(!IsValidCallbackThread()) {
+ return;
+ }
+
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj,
+ sOnGeofencingCapabilities,
+ capabilities
+ );
+ CheckExceptions(sCallbackEnv, __FUNCTION__);
+}
+
FlpGeofenceCallbacks sFlpGeofenceCallbacks = {
sizeof(FlpGeofenceCallbacks),
GeofenceTransitionCallback,
@@ -670,7 +747,8 @@ FlpGeofenceCallbacks sFlpGeofenceCallbacks = {
GeofenceRemoveCallback,
GeofencePauseCallback,
GeofenceResumeCallback,
- SetThreadEvent
+ SetThreadEvent,
+ GeofencingCapabilitiesCallback
};
/*
@@ -698,14 +776,14 @@ static void Init(JNIEnv* env, jobject obj) {
// TODO: inject any device context if when needed
}
-static jboolean IsSupported(JNIEnv* env, jclass clazz) {
+static jboolean IsSupported(JNIEnv* /* env */, jclass /* clazz */) {
if (sFlpInterface == NULL) {
return JNI_FALSE;
}
return JNI_TRUE;
}
-static jint GetBatchSize(JNIEnv* env, jobject object) {
+static jint GetBatchSize(JNIEnv* env, jobject /* object */) {
if(sFlpInterface == NULL) {
ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
}
@@ -715,7 +793,7 @@ static jint GetBatchSize(JNIEnv* env, jobject object) {
static void StartBatching(
JNIEnv* env,
- jobject object,
+ jobject /* object */,
jint id,
jobject optionsObject) {
if(sFlpInterface == NULL || optionsObject == NULL) {
@@ -730,7 +808,7 @@ static void StartBatching(
static void UpdateBatchingOptions(
JNIEnv* env,
- jobject object,
+ jobject /* object */,
jint id,
jobject optionsObject) {
if(sFlpInterface == NULL || optionsObject == NULL) {
@@ -743,7 +821,7 @@ static void UpdateBatchingOptions(
ThrowOnError(env, result, __FUNCTION__);
}
-static void StopBatching(JNIEnv* env, jobject object, jint id) {
+static void StopBatching(JNIEnv* env, jobject /* object */, jint id) {
if(sFlpInterface == NULL) {
ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
}
@@ -751,7 +829,7 @@ static void StopBatching(JNIEnv* env, jobject object, jint id) {
sFlpInterface->stop_batching(id);
}
-static void Cleanup(JNIEnv* env, jobject object) {
+static void Cleanup(JNIEnv* env, jobject /* object */) {
if(sFlpInterface == NULL) {
ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
}
@@ -774,7 +852,7 @@ static void Cleanup(JNIEnv* env, jobject object) {
}
}
-static void GetBatchedLocation(JNIEnv* env, jobject object, jint lastNLocations) {
+static void GetBatchedLocation(JNIEnv* env, jobject /* object */, jint lastNLocations) {
if(sFlpInterface == NULL) {
ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
}
@@ -782,7 +860,15 @@ static void GetBatchedLocation(JNIEnv* env, jobject object, jint lastNLocations)
sFlpInterface->get_batched_location(lastNLocations);
}
-static void InjectLocation(JNIEnv* env, jobject object, jobject locationObject) {
+static void FlushBatchedLocations(JNIEnv* env, jobject /* object */) {
+ if(sFlpInterface == NULL) {
+ ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+ }
+
+ sFlpInterface->flush_batched_locations();
+}
+
+static void InjectLocation(JNIEnv* env, jobject /* object */, jobject locationObject) {
if(locationObject == NULL) {
ALOGE("Invalid location for injection: %p", locationObject);
ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
@@ -806,7 +892,7 @@ static jboolean IsDiagnosticSupported() {
return sFlpDiagnosticInterface != NULL;
}
-static void InjectDiagnosticData(JNIEnv* env, jobject object, jstring stringData) {
+static void InjectDiagnosticData(JNIEnv* env, jobject /* object */, jstring stringData) {
if(stringData == NULL) {
ALOGE("Invalid diagnostic data for injection: %p", stringData);
ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
@@ -830,7 +916,7 @@ static jboolean IsDeviceContextSupported() {
return sFlpDeviceContextInterface != NULL;
}
-static void InjectDeviceContext(JNIEnv* env, jobject object, jint enabledMask) {
+static void InjectDeviceContext(JNIEnv* env, jobject /* object */, jint enabledMask) {
if(sFlpDeviceContextInterface == NULL) {
ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
}
@@ -845,7 +931,7 @@ static jboolean IsGeofencingSupported() {
static void AddGeofences(
JNIEnv* env,
- jobject object,
+ jobject /* object */,
jobjectArray geofenceRequestsArray) {
if(geofenceRequestsArray == NULL) {
ALOGE("Invalid Geofences to add: %p", geofenceRequestsArray);
@@ -885,7 +971,7 @@ static void AddGeofences(
}
}
-static void PauseGeofence(JNIEnv* env, jobject object, jint geofenceId) {
+static void PauseGeofence(JNIEnv* env, jobject /* object */, jint geofenceId) {
if(sFlpGeofencingInterface == NULL) {
ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
}
@@ -895,7 +981,7 @@ static void PauseGeofence(JNIEnv* env, jobject object, jint geofenceId) {
static void ResumeGeofence(
JNIEnv* env,
- jobject object,
+ jobject /* object */,
jint geofenceId,
jint monitorTransitions) {
if(sFlpGeofencingInterface == NULL) {
@@ -907,7 +993,7 @@ static void ResumeGeofence(
static void ModifyGeofenceOption(
JNIEnv* env,
- jobject object,
+ jobject /* object */,
jint geofenceId,
jint lastTransition,
jint monitorTransitions,
@@ -931,7 +1017,7 @@ static void ModifyGeofenceOption(
static void RemoveGeofences(
JNIEnv* env,
- jobject object,
+ jobject /* object */,
jintArray geofenceIdsArray) {
if(sFlpGeofencingInterface == NULL) {
ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
@@ -964,6 +1050,9 @@ static JNINativeMethod sMethods[] = {
{"nativeRequestBatchedLocation",
"(I)V",
reinterpret_cast<void*>(GetBatchedLocation)},
+ {"nativeFlushBatchedLocations",
+ "()V",
+ reinterpret_cast<void*>(FlushBatchedLocations)},
{"nativeInjectLocation",
"(Landroid/location/Location;)V",
reinterpret_cast<void*>(InjectLocation)},
diff --git a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
index 8183321..3804e1d 100644
--- a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -214,7 +214,7 @@ static void agps_status_callback(AGpsStatus* agps_status)
size_t status_size = agps_status->size;
if (status_size == sizeof(AGpsStatus_v3)) {
- ALOGV("AGpsStatus is V3: %d", status_size);
+ ALOGV("AGpsStatus is V3: %zd", status_size);
switch (agps_status->addr.ss_family)
{
case AF_INET:
@@ -256,7 +256,7 @@ static void agps_status_callback(AGpsStatus* agps_status)
break;
}
} else if (status_size >= sizeof(AGpsStatus_v2)) {
- ALOGV("AGpsStatus is V2+: %d", status_size);
+ ALOGV("AGpsStatus is V2+: %zd", status_size);
// for back-compatibility reasons we check in v2 that the data structure size is greater or
// equal to the declared size in gps.h
uint32_t ipaddr = agps_status->ipaddr;
@@ -266,12 +266,12 @@ static void agps_status_callback(AGpsStatus* agps_status)
isSupported = true;
}
} else if (status_size >= sizeof(AGpsStatus_v1)) {
- ALOGV("AGpsStatus is V1+: %d", status_size);
+ ALOGV("AGpsStatus is V1+: %zd", status_size);
// because we have to check for >= with regards to v2, we also need to relax the check here
// and only make sure that the size is at least what we expect
isSupported = true;
} else {
- ALOGE("Invalid size of AGpsStatus found: %d.", status_size);
+ ALOGE("Invalid size of AGpsStatus found: %zd.", status_size);
}
if (isSupported) {
@@ -509,12 +509,22 @@ static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env,
}
}
-static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass clazz) {
- if (sGpsInterface != NULL) {
- return JNI_TRUE;
- } else {
- return JNI_FALSE;
- }
+static jboolean android_location_GpsLocationProvider_is_supported(
+ JNIEnv* /* env */, jclass /* clazz */)
+{
+ return (sGpsInterface != NULL) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean android_location_GpsLocationProvider_is_agps_ril_supported(
+ JNIEnv* /* env */, jclass /* clazz */)
+{
+ return (sAGpsRilInterface != NULL) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean android_location_gpsLocationProvider_is_gnss_configuration_supported(
+ JNIEnv* /* env */, jclass /* jclazz */)
+{
+ return (sGnssConfigurationInterface != NULL) ? JNI_TRUE : JNI_FALSE;
}
static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj)
@@ -543,14 +553,15 @@ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject o
return JNI_TRUE;
}
-static void android_location_GpsLocationProvider_cleanup(JNIEnv* env, jobject obj)
+static void android_location_GpsLocationProvider_cleanup(JNIEnv* /* env */, jobject /* obj */)
{
if (sGpsInterface)
sGpsInterface->cleanup();
}
-static jboolean android_location_GpsLocationProvider_set_position_mode(JNIEnv* env, jobject obj,
- jint mode, jint recurrence, jint min_interval, jint preferred_accuracy, jint preferred_time)
+static jboolean android_location_GpsLocationProvider_set_position_mode(JNIEnv* /* env */,
+ jobject /* obj */, jint mode, jint recurrence, jint min_interval, jint preferred_accuracy,
+ jint preferred_time)
{
if (sGpsInterface) {
if (sGpsInterface->set_position_mode(mode, recurrence, min_interval, preferred_accuracy,
@@ -564,7 +575,7 @@ static jboolean android_location_GpsLocationProvider_set_position_mode(JNIEnv* e
return JNI_FALSE;
}
-static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject obj)
+static jboolean android_location_GpsLocationProvider_start(JNIEnv* /* env */, jobject /* obj */)
{
if (sGpsInterface) {
if (sGpsInterface->start() == 0) {
@@ -577,7 +588,7 @@ static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject
return JNI_FALSE;
}
-static jboolean android_location_GpsLocationProvider_stop(JNIEnv* env, jobject obj)
+static jboolean android_location_GpsLocationProvider_stop(JNIEnv* /* env */, jobject /* obj */)
{
if (sGpsInterface) {
if (sGpsInterface->stop() == 0) {
@@ -590,13 +601,15 @@ static jboolean android_location_GpsLocationProvider_stop(JNIEnv* env, jobject o
return JNI_FALSE;
}
-static void android_location_GpsLocationProvider_delete_aiding_data(JNIEnv* env, jobject obj, jint flags)
+static void android_location_GpsLocationProvider_delete_aiding_data(JNIEnv* /* env */,
+ jobject /* obj */,
+ jint flags)
{
if (sGpsInterface)
sGpsInterface->delete_aiding_data(flags);
}
-static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, jobject obj,
+static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, jobject /* obj */,
jintArray prnArray, jfloatArray snrArray, jfloatArray elevArray, jfloatArray azumArray,
jintArray maskArray)
{
@@ -627,8 +640,8 @@ static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, job
return (jint) num_svs;
}
-static void android_location_GpsLocationProvider_agps_set_reference_location_cellid(JNIEnv* env,
- jobject obj, jint type, jint mcc, jint mnc, jint lac, jint cid)
+static void android_location_GpsLocationProvider_agps_set_reference_location_cellid(
+ JNIEnv* /* env */, jobject /* obj */, jint type, jint mcc, jint mnc, jint lac, jint cid)
{
AGpsRefLocation location;
@@ -655,7 +668,7 @@ static void android_location_GpsLocationProvider_agps_set_reference_location_cel
}
static void android_location_GpsLocationProvider_agps_send_ni_message(JNIEnv* env,
- jobject obj, jbyteArray ni_msg, jint size)
+ jobject /* obj */, jbyteArray ni_msg, jint size)
{
size_t sz;
@@ -671,8 +684,8 @@ static void android_location_GpsLocationProvider_agps_send_ni_message(JNIEnv* en
env->ReleaseByteArrayElements(ni_msg,b,0);
}
-static void android_location_GpsLocationProvider_agps_set_id(JNIEnv *env,
- jobject obj, jint type, jstring setid_string)
+static void android_location_GpsLocationProvider_agps_set_id(JNIEnv *env, jobject /* obj */,
+ jint type, jstring setid_string)
{
if (!sAGpsRilInterface) {
ALOGE("no AGPS RIL interface in agps_set_id");
@@ -684,7 +697,7 @@ static void android_location_GpsLocationProvider_agps_set_id(JNIEnv *env,
env->ReleaseStringUTFChars(setid_string, setid);
}
-static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj,
+static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject /* obj */,
jbyteArray nmeaArray, jint buffer_size)
{
// this should only be called from within a call to reportNmea
@@ -697,30 +710,27 @@ static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject
return (jint) length;
}
-static void android_location_GpsLocationProvider_inject_time(JNIEnv* env, jobject obj,
+static void android_location_GpsLocationProvider_inject_time(JNIEnv* /* env */, jobject /* obj */,
jlong time, jlong timeReference, jint uncertainty)
{
if (sGpsInterface)
sGpsInterface->inject_time(time, timeReference, uncertainty);
}
-static void android_location_GpsLocationProvider_inject_location(JNIEnv* env, jobject obj,
- jdouble latitude, jdouble longitude, jfloat accuracy)
+static void android_location_GpsLocationProvider_inject_location(JNIEnv* /* env */,
+ jobject /* obj */, jdouble latitude, jdouble longitude, jfloat accuracy)
{
if (sGpsInterface)
sGpsInterface->inject_location(latitude, longitude, accuracy);
}
-static jboolean android_location_GpsLocationProvider_supports_xtra(JNIEnv* env, jobject obj)
+static jboolean android_location_GpsLocationProvider_supports_xtra(
+ JNIEnv* /* env */, jobject /* obj */)
{
- if (sGpsXtraInterface != NULL) {
- return JNI_TRUE;
- } else {
- return JNI_FALSE;
- }
+ return (sGpsXtraInterface != NULL) ? JNI_TRUE : JNI_FALSE;
}
-static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject obj,
+static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject /* obj */,
jbyteArray data, jint length)
{
if (!sGpsXtraInterface) {
@@ -734,7 +744,7 @@ static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, j
}
static void android_location_GpsLocationProvider_agps_data_conn_open(
- JNIEnv* env, jobject obj, jstring apn, jint apnIpType)
+ JNIEnv* env, jobject /* obj */, jstring apn, jint apnIpType)
{
if (!sAGpsInterface) {
ALOGE("no AGPS interface in agps_data_conn_open");
@@ -753,13 +763,14 @@ static void android_location_GpsLocationProvider_agps_data_conn_open(
} else if (interface_size == sizeof(AGpsInterface_v1)) {
sAGpsInterface->data_conn_open(apnStr);
} else {
- ALOGE("Invalid size of AGpsInterface found: %d.", interface_size);
+ ALOGE("Invalid size of AGpsInterface found: %zd.", interface_size);
}
env->ReleaseStringUTFChars(apn, apnStr);
}
-static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* env, jobject obj)
+static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* /* env */,
+ jobject /* obj */)
{
if (!sAGpsInterface) {
ALOGE("no AGPS interface in agps_data_conn_closed");
@@ -768,7 +779,8 @@ static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* e
sAGpsInterface->data_conn_closed();
}
-static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* env, jobject obj)
+static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* /* env */,
+ jobject /* obj */)
{
if (!sAGpsInterface) {
ALOGE("no AGPS interface in agps_data_conn_failed");
@@ -777,7 +789,7 @@ static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* e
sAGpsInterface->data_conn_failed();
}
-static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jobject obj,
+static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jobject /* obj */,
jint type, jstring hostname, jint port)
{
if (!sAGpsInterface) {
@@ -789,8 +801,8 @@ static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jo
env->ReleaseStringUTFChars(hostname, c_hostname);
}
-static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
- jint notifId, jint response)
+static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* /* env */,
+ jobject /* obj */, jint notifId, jint response)
{
if (!sGpsNiInterface) {
ALOGE("no NI interface in send_ni_response");
@@ -800,8 +812,8 @@ static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, j
sGpsNiInterface->respond(notifId, response);
}
-static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj)
-{
+static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env,
+ jobject /* obj */) {
jstring result = NULL;
if (sGpsDebugInterface) {
const size_t maxLength = 2047;
@@ -814,7 +826,7 @@ static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* e
return result;
}
-static void android_location_GpsLocationProvider_update_network_state(JNIEnv* env, jobject obj,
+static void android_location_GpsLocationProvider_update_network_state(JNIEnv* env, jobject /* obj */,
jboolean connected, jint type, jboolean roaming, jboolean available, jstring extraInfo, jstring apn)
{
@@ -837,16 +849,14 @@ 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_is_geofence_supported(
+ JNIEnv* /* env */, jobject /* obj */)
+{
+ return (sGpsGeofencingInterface != NULL) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean android_location_GpsLocationProvider_add_geofence(JNIEnv* env, jobject obj,
- jint geofence_id, jdouble latitude, jdouble longitude, jdouble radius,
+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) {
@@ -860,8 +870,8 @@ static jboolean android_location_GpsLocationProvider_add_geofence(JNIEnv* env, j
return JNI_FALSE;
}
-static jboolean android_location_GpsLocationProvider_remove_geofence(JNIEnv* env, jobject obj,
- jint geofence_id) {
+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;
@@ -871,8 +881,8 @@ static jboolean android_location_GpsLocationProvider_remove_geofence(JNIEnv* env
return JNI_FALSE;
}
-static jboolean android_location_GpsLocationProvider_pause_geofence(JNIEnv* env, jobject obj,
- jint geofence_id) {
+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;
@@ -882,8 +892,8 @@ static jboolean android_location_GpsLocationProvider_pause_geofence(JNIEnv* env,
return JNI_FALSE;
}
-static jboolean android_location_GpsLocationProvider_resume_geofence(JNIEnv* env, jobject obj,
- jint geofence_id, jint monitor_transition) {
+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;
@@ -1253,7 +1263,7 @@ static void measurement_callback(GpsData* data) {
env->DeleteLocalRef(gpsMeasurementsEventClass);
env->DeleteLocalRef(gpsMeasurementsEvent);
} else {
- ALOGE("Invalid GpsData size found in gps_measurement_callback, size=%d", data->size);
+ ALOGE("Invalid GpsData size found in gps_measurement_callback, size=%zd", data->size);
}
}
@@ -1304,7 +1314,7 @@ static jobject translate_gps_navigation_message(JNIEnv* env, GpsNavigationMessag
size_t dataLength = message->data_length;
uint8_t* data = message->data;
if (dataLength == 0 || data == NULL) {
- ALOGE("Invalid Navigation Message found: data=%p, length=%d", data, dataLength);
+ ALOGE("Invalid Navigation Message found: data=%p, length=%zd", data, dataLength);
return NULL;
}
@@ -1363,7 +1373,7 @@ static void navigation_message_callback(GpsNavigationMessage* message) {
env->DeleteLocalRef(navigationMessageEventClass);
env->DeleteLocalRef(navigationMessageEvent);
} else {
- ALOGE("Invalid GpsNavigationMessage size found: %d", message->size);
+ ALOGE("Invalid GpsNavigationMessage size found: %zd", message->size);
}
}
@@ -1428,6 +1438,10 @@ static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
{"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported},
+ {"native_is_agps_ril_supported", "()Z",
+ (void*)android_location_GpsLocationProvider_is_agps_ril_supported},
+ {"native_is_gnss_configuration_supported", "()Z",
+ (void*)android_location_gpsLocationProvider_is_gnss_configuration_supported},
{"native_init", "()Z", (void*)android_location_GpsLocationProvider_init},
{"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup},
{"native_set_position_mode",
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 33e0bd7..6dcdd9d 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -112,17 +112,17 @@ static void nativeInit(JNIEnv* env, jobject obj) {
}
}
-static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) {
+static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
ScopedUtfChars name(env, nameStr);
acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str());
}
-static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) {
+static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
ScopedUtfChars name(env, nameStr);
release_wake_lock(name.c_str());
}
-static void nativeSetInteractive(JNIEnv *env, jclass clazz, jboolean enable) {
+static void nativeSetInteractive(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
if (gPowerModule) {
if (enable) {
ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(true) while turning screen on");
@@ -134,7 +134,7 @@ static void nativeSetInteractive(JNIEnv *env, jclass clazz, jboolean enable) {
}
}
-static void nativeSetAutoSuspend(JNIEnv *env, jclass clazz, jboolean enable) {
+static void nativeSetAutoSuspend(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
if (enable) {
ALOGD_IF_SLOW(100, "Excessive delay in autosuspend_enable() while turning screen off");
autosuspend_enable();
@@ -189,6 +189,7 @@ static JNINativeMethod gPowerManagerServiceMethods[] = {
int register_android_server_PowerManagerService(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "com/android/server/power/PowerManagerService",
gPowerManagerServiceMethods, NELEM(gPowerManagerServiceMethods));
+ (void) res; // Faked use when LOG_NDEBUG.
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
// Callbacks
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index dcb5199..507bc9c 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -688,6 +688,7 @@ int register_android_server_tv_TvInputHal(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "com/android/server/tv/TvInputHal",
gTvInputHalMethods, NELEM(gTvInputHalMethods));
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+ (void)res; // Don't complain about unused variable in the LOG_NDEBUG case
jclass clazz;
FIND_CLASS(clazz, "com/android/server/tv/TvInputHal");
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 7b2e408..7db7414 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -32,6 +32,7 @@ int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
+int register_android_server_UsbMidiDevice(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
@@ -46,7 +47,7 @@ int register_android_server_Watchdog(JNIEnv* env);
using namespace android;
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
@@ -65,6 +66,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
register_android_server_UsbDeviceManager(env);
+ register_android_server_UsbMidiDevice(env);
register_android_server_UsbHostManager(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
index 9fd0e09..28ffc57 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
@@ -22,6 +22,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.AtomicFile;
import android.util.Slog;
@@ -39,23 +40,27 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.util.HashMap;
+import java.util.Map;
import java.util.Set;
/**
* Stores and restores state for the Device and Profile owners. By definition there can be
* only one device owner, but there may be a profile owner for each user.
*/
-public class DeviceOwner {
+class DeviceOwner {
private static final String TAG = "DevicePolicyManagerService";
private static final String DEVICE_OWNER_XML = "device_owner.xml";
private static final String TAG_DEVICE_OWNER = "device-owner";
+ private static final String TAG_DEVICE_INITIALIZER = "device-initializer";
private static final String TAG_PROFILE_OWNER = "profile-owner";
private static final String ATTR_NAME = "name";
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_COMPONENT_NAME = "component";
private static final String ATTR_USERID = "userId";
+ private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy";
private AtomicFile fileForWriting;
@@ -66,9 +71,15 @@ public class DeviceOwner {
// Internal state for the device owner package.
private OwnerInfo mDeviceOwner;
+ // Internal state for the device initializer package.
+ private OwnerInfo mDeviceInitializer;
+
// Internal state for the profile owner packages.
private final HashMap<Integer, OwnerInfo> mProfileOwners = new HashMap<Integer, OwnerInfo>();
+ // Local system update policy controllable by device owner.
+ private PersistableBundle mSystemUpdatePolicy;
+
// Private default constructor.
private DeviceOwner() {
}
@@ -102,12 +113,11 @@ public class DeviceOwner {
}
/**
- * @deprecated Use a component name instead of package name
- * Creates an instance of the device owner object with the profile owner set.
+ * Creates an instance of the device owner object with the device initializer set.
*/
- static DeviceOwner createWithProfileOwner(String packageName, String ownerName, int userId) {
+ static DeviceOwner createWithDeviceInitializer(ComponentName admin, String ownerName) {
DeviceOwner owner = new DeviceOwner();
- owner.mProfileOwners.put(userId, new OwnerInfo(ownerName, packageName));
+ owner.mDeviceInitializer = new OwnerInfo(ownerName, admin);
return owner;
}
@@ -136,11 +146,28 @@ public class DeviceOwner {
mDeviceOwner = null;
}
- /**
- * @deprecated
- */
- void setProfileOwner(String packageName, String ownerName, int userId) {
- mProfileOwners.put(userId, new OwnerInfo(ownerName, packageName));
+ ComponentName getDeviceInitializerComponent() {
+ return mDeviceInitializer.admin;
+ }
+
+ String getDeviceInitializerPackageName() {
+ return mDeviceInitializer != null ? mDeviceInitializer.packageName : null;
+ }
+
+ String getDeviceInitializerName() {
+ return mDeviceInitializer != null ? mDeviceInitializer.name : null;
+ }
+
+ void setDeviceInitializer(ComponentName admin, String ownerName) {
+ mDeviceInitializer = new OwnerInfo(ownerName, admin);
+ }
+
+ void clearDeviceInitializer() {
+ mDeviceInitializer = null;
+ }
+
+ boolean hasDeviceInitializer() {
+ return mDeviceInitializer != null;
}
void setProfileOwner(ComponentName admin, String ownerName, int userId) {
@@ -151,16 +178,6 @@ public class DeviceOwner {
mProfileOwners.remove(userId);
}
- /**
- * @deprecated Use getProfileOwnerComponent
- * @param userId
- * @return
- */
- String getProfileOwnerPackageName(int userId) {
- OwnerInfo profileOwner = mProfileOwners.get(userId);
- return profileOwner != null ? profileOwner.packageName : null;
- }
-
ComponentName getProfileOwnerComponent(int userId) {
OwnerInfo profileOwner = mProfileOwners.get(userId);
return profileOwner != null ? profileOwner.admin : null;
@@ -175,6 +192,18 @@ public class DeviceOwner {
return mProfileOwners.keySet();
}
+ PersistableBundle getSystemUpdatePolicy() {
+ return mSystemUpdatePolicy;
+ }
+
+ void setSystemUpdatePolicy(PersistableBundle systemUpdatePolicy) {
+ mSystemUpdatePolicy = systemUpdatePolicy;
+ }
+
+ void clearSystemUpdatePolicy() {
+ mSystemUpdatePolicy = null;
+ }
+
boolean hasDeviceOwner() {
return mDeviceOwner != null;
}
@@ -207,6 +236,7 @@ public class DeviceOwner {
return false;
}
+ @VisibleForTesting
void readOwnerFile() {
try {
InputStream input = openRead();
@@ -223,6 +253,20 @@ public class DeviceOwner {
String name = parser.getAttributeValue(null, ATTR_NAME);
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
mDeviceOwner = new OwnerInfo(name, packageName);
+ } else if (tag.equals(TAG_DEVICE_INITIALIZER)) {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+ String initializerComponentStr =
+ parser.getAttributeValue(null, ATTR_COMPONENT_NAME);
+ ComponentName admin =
+ ComponentName.unflattenFromString(initializerComponentStr);
+ if (admin != null) {
+ mDeviceInitializer = new OwnerInfo(name, admin);
+ } else {
+ mDeviceInitializer = new OwnerInfo(name, packageName);
+ Slog.e(TAG, "Error parsing device-owner file. Bad component name " +
+ initializerComponentStr);
+ }
} else if (tag.equals(TAG_PROFILE_OWNER)) {
String profileOwnerPackageName = parser.getAttributeValue(null, ATTR_PACKAGE);
String profileOwnerName = parser.getAttributeValue(null, ATTR_NAME);
@@ -246,6 +290,8 @@ public class DeviceOwner {
profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName);
}
mProfileOwners.put(userId, profileOwnerInfo);
+ } else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) {
+ mSystemUpdatePolicy = PersistableBundle.restoreFromXml(parser);
} else {
throw new XmlPullParserException(
"Unexpected tag in device owner file: " + tag);
@@ -259,6 +305,7 @@ public class DeviceOwner {
}
}
+ @VisibleForTesting
void writeOwnerFile() {
synchronized (this) {
writeOwnerFileLocked();
@@ -282,6 +329,20 @@ public class DeviceOwner {
out.endTag(null, TAG_DEVICE_OWNER);
}
+ // Write device initializer tag
+ if (mDeviceInitializer != null) {
+ out.startTag(null, TAG_DEVICE_INITIALIZER);
+ out.attribute(null, ATTR_PACKAGE, mDeviceInitializer.packageName);
+ if (mDeviceInitializer.name != null) {
+ out.attribute(null, ATTR_NAME, mDeviceInitializer.name);
+ }
+ if (mDeviceInitializer.admin != null) {
+ out.attribute(
+ null, ATTR_COMPONENT_NAME, mDeviceInitializer.admin.flattenToString());
+ }
+ out.endTag(null, TAG_DEVICE_INITIALIZER);
+ }
+
// Write profile owner tags
if (mProfileOwners.size() > 0) {
for (HashMap.Entry<Integer, OwnerInfo> owner : mProfileOwners.entrySet()) {
@@ -296,6 +357,17 @@ public class DeviceOwner {
out.endTag(null, TAG_PROFILE_OWNER);
}
}
+
+ // Write system update policy tag
+ if (mSystemUpdatePolicy != null) {
+ out.startTag(null, TAG_SYSTEM_UPDATE_POLICY);
+ try {
+ mSystemUpdatePolicy.saveToXml(out);
+ } catch (XmlPullParserException e) {
+ Slog.e(TAG, "Failed to save system update policy", e);
+ }
+ out.endTag(null, TAG_SYSTEM_UPDATE_POLICY);
+ }
out.endDocument();
out.flush();
finishWrite(outputStream);
@@ -329,10 +401,10 @@ public class DeviceOwner {
}
}
- static class OwnerInfo {
- public String name;
- public String packageName;
- public ComponentName admin;
+ private static class OwnerInfo {
+ public final String name;
+ public final String packageName;
+ public final ComponentName admin;
public OwnerInfo(String name, String packageName) {
this.name = name;
@@ -345,5 +417,23 @@ public class DeviceOwner {
this.admin = admin;
this.packageName = admin.getPackageName();
}
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "admin=" + admin);
+ pw.println(prefix + "name=" + name);
+ pw.println();
+ }
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ if (mDeviceOwner != null) {
+ pw.println(prefix + "Device Owner: ");
+ mDeviceOwner.dump(prefix + " ", pw);
+ }
+ if (mProfileOwners != null) {
+ for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) {
+ pw.println(prefix + "Profile Owner (User " + entry.getKey() + "): ");
+ entry.getValue().dump(prefix + " ", pw);
+ }
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ec1258c..6fc3103 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -17,10 +17,12 @@
package com.android.server.devicepolicy;
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
import static android.content.pm.PackageManager.GET_UNINSTALLED_PACKAGES;
+import android.Manifest.permission;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accounts.AccountManager;
import android.app.Activity;
@@ -31,6 +33,7 @@ import android.app.IActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.StatusBarManager;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
@@ -43,13 +46,16 @@ import android.content.ContentResolver;
import android.content.Context;
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.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.graphics.Bitmap;
import android.hardware.usb.UsbManager;
import android.media.AudioManager;
import android.media.IAudioService;
@@ -75,8 +81,11 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.ContactsContract.QuickContact;
+import android.provider.ContactsInternal;
import android.provider.Settings;
import android.security.Credentials;
+import android.security.IKeyChainAliasCallback;
import android.security.IKeyChainService;
import android.security.KeyChain;
import android.security.KeyChain.KeyChainConnection;
@@ -96,8 +105,10 @@ import android.view.IWindowManager;
import com.android.internal.R;
import com.android.internal.os.storage.ExternalStorageFormatter;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
@@ -126,6 +137,7 @@ import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@@ -141,9 +153,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final String LOG_TAG = "DevicePolicyManagerService";
+ private static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
+
private static final String DEVICE_POLICIES_XML = "device_policies.xml";
- private static final String LOCK_TASK_COMPONENTS_XML = "lock-task-component";
+ private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
+
+ private static final String TAG_STATUS_BAR = "statusbar";
+
+ private static final String ATTR_ENABLED = "enabled";
+
+ private static final String DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML =
+ "do-not-ask-credentials-on-boot";
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
@@ -161,6 +182,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
private static final String ATTR_SETUP_COMPLETE = "setup-complete";
+ private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer";
+
+ private static final int STATUS_BAR_DISABLE_MASK =
+ StatusBarManager.DISABLE_EXPAND |
+ StatusBarManager.DISABLE_NOTIFICATION_ICONS |
+ StatusBarManager.DISABLE_NOTIFICATION_ALERTS |
+ StatusBarManager.DISABLE_SEARCH;
+
private static final Set<String> DEVICE_OWNER_USER_RESTRICTIONS;
static {
DEVICE_OWNER_USER_RESTRICTIONS = new HashSet();
@@ -174,6 +203,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_UNMUTE_MICROPHONE);
DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_ADJUST_VOLUME);
DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SMS);
+ DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SAFE_BOOT);
+ }
+
+ // The following user restrictions cannot be changed by any active admin, including device
+ // owner and profile owner.
+ private static final Set<String> IMMUTABLE_USER_RESTRICTIONS;
+ static {
+ IMMUTABLE_USER_RESTRICTIONS = new HashSet();
+ IMMUTABLE_USER_RESTRICTIONS.add(UserManager.DISALLOW_WALLPAPER);
}
private static final Set<String> SECURE_SETTINGS_WHITELIST;
@@ -193,14 +231,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.ADB_ENABLED);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME_ZONE);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.BLUETOOTH_ON);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.DATA_ROAMING);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.MODE_RINGER);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.NETWORK_PREFERENCE);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.USB_MASS_STORAGE_ENABLED);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.WIFI_ON);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.WIFI_SLEEP_POLICY);
+ GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN);
}
final Context mContext;
@@ -218,6 +255,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Stores and loads state on device and profile owners.
private DeviceOwner mDeviceOwner;
+ private final Binder mToken = new Binder();
+
/**
* Whether or not device admin feature is supported. If it isn't return defaults for all
* public methods.
@@ -261,24 +300,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
long mLastMaximumTimeToLock = -1;
boolean mUserSetupComplete = false;
- final HashMap<ComponentName, ActiveAdmin> mAdminMap
- = new HashMap<ComponentName, ActiveAdmin>();
- final ArrayList<ActiveAdmin> mAdminList
- = new ArrayList<ActiveAdmin>();
- final ArrayList<ComponentName> mRemovingAdmins
- = new ArrayList<ComponentName>();
+ final HashMap<ComponentName, ActiveAdmin> mAdminMap = new HashMap<>();
+ final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
+ final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
// This is the list of component allowed to start lock task mode.
- final List<String> mLockTaskPackages = new ArrayList<String>();
+ List<String> mLockTaskPackages = new ArrayList<>();
+
+ boolean mStatusBarEnabledState = true;
ComponentName mRestrictionsProvider;
+ String mDelegatedCertInstallerPackage;
+
+ boolean doNotAskCredentialsOnBoot = false;
+
public DevicePolicyData(int userHandle) {
mUserHandle = userHandle;
}
}
- final SparseArray<DevicePolicyData> mUserData = new SparseArray<DevicePolicyData>();
+ final SparseArray<DevicePolicyData> mUserData = new SparseArray<>();
Handler mHandler = new Handler();
@@ -329,6 +371,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
private static final String TAG_DISABLE_CAMERA = "disable-camera";
private static final String TAG_DISABLE_CALLER_ID = "disable-caller-id";
+ private static final String TAG_DISABLE_BLUETOOTH_CONTACT_SHARING
+ = "disable-bt-contacts-sharing";
private static final String TAG_DISABLE_SCREEN_CAPTURE = "disable-screen-capture";
private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management";
private static final String TAG_REQUIRE_AUTO_TIME = "require_auto_time";
@@ -410,6 +454,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
boolean encryptionRequested = false;
boolean disableCamera = false;
boolean disableCallerId = false;
+ boolean disableBluetoothContactSharing = true;
boolean disableScreenCapture = false; // Can only be set by a device/profile owner.
boolean requireAutoTime = false; // Can only be set by a device owner.
@@ -551,6 +596,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
out.attribute(null, ATTR_VALUE, Boolean.toString(disableCallerId));
out.endTag(null, TAG_DISABLE_CALLER_ID);
}
+ if (disableBluetoothContactSharing) {
+ out.startTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING);
+ out.attribute(null, ATTR_VALUE,
+ Boolean.toString(disableBluetoothContactSharing));
+ out.endTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING);
+ }
if (disableScreenCapture) {
out.startTag(null, TAG_DISABLE_SCREEN_CAPTURE);
out.attribute(null, ATTR_VALUE, Boolean.toString(disableScreenCapture));
@@ -696,6 +747,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
} else if (TAG_DISABLE_CALLER_ID.equals(tag)) {
disableCallerId = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_DISABLE_BLUETOOTH_CONTACT_SHARING.equals(tag)) {
+ disableBluetoothContactSharing = Boolean.parseBoolean(parser
+ .getAttributeValue(null, ATTR_VALUE));
} else if (TAG_DISABLE_SCREEN_CAPTURE.equals(tag)) {
disableScreenCapture = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
@@ -886,6 +940,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
pw.println(disableCamera);
pw.print(prefix); pw.print("disableCallerId=");
pw.println(disableCallerId);
+ pw.print(prefix); pw.print("disableBluetoothContactSharing=");
+ pw.println(disableBluetoothContactSharing);
pw.print(prefix); pw.print("disableScreenCapture=");
pw.println(disableScreenCapture);
pw.print(prefix); pw.print("requireAutoTime=");
@@ -935,6 +991,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
syncDeviceCapabilitiesLocked(policy);
saveSettingsLocked(policy.mUserHandle);
}
+
+ if (policy.mDelegatedCertInstallerPackage != null &&
+ (packageName == null
+ || packageName.equals(policy.mDelegatedCertInstallerPackage))) {
+ try {
+ // Check if delegated cert installer package is removed.
+ if (pm.getPackageInfo(
+ policy.mDelegatedCertInstallerPackage, 0, userHandle) == null) {
+ policy.mDelegatedCertInstallerPackage = null;
+ saveSettingsLocked(policy.mUserHandle);
+ }
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ }
+ }
}
}
@@ -1035,6 +1106,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
void loadDeviceOwner() {
synchronized (this) {
mDeviceOwner = DeviceOwner.load();
+ updateDeviceOwnerLocked();
}
}
@@ -1108,68 +1180,85 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
throws SecurityException {
final int callingUid = Binder.getCallingUid();
- final int userHandle = UserHandle.getUserId(callingUid);
- final DevicePolicyData policy = getUserData(userHandle);
- List<ActiveAdmin> candidates = new ArrayList<ActiveAdmin>();
+ ActiveAdmin result = getActiveAdminWithPolicyForUidLocked(who, reqPolicy, callingUid);
+ if (result != null) {
+ return result;
+ }
+
+ if (who != null) {
+ final int userId = UserHandle.getUserId(callingUid);
+ final DevicePolicyData policy = getUserData(userId);
+ ActiveAdmin admin = policy.mAdminMap.get(who);
+ if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
+ throw new SecurityException("Admin " + admin.info.getComponent()
+ + " does not own the device");
+ }
+ if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
+ throw new SecurityException("Admin " + admin.info.getComponent()
+ + " does not own the profile");
+ }
+ throw new SecurityException("Admin " + admin.info.getComponent()
+ + " did not specify uses-policy for: "
+ + admin.info.getTagForPolicy(reqPolicy));
+ } else {
+ throw new SecurityException("No active admin owned by uid "
+ + Binder.getCallingUid() + " for policy #" + reqPolicy);
+ }
+ }
- // Build a list of admins for this uid matching the given ComponentName
+ private ActiveAdmin getActiveAdminWithPolicyForUidLocked(ComponentName who, int reqPolicy,
+ int uid) {
+ // Try to find an admin which can use reqPolicy
+ final int userId = UserHandle.getUserId(uid);
+ final DevicePolicyData policy = getUserData(userId);
if (who != null) {
ActiveAdmin admin = policy.mAdminMap.get(who);
if (admin == null) {
throw new SecurityException("No active admin " + who);
}
- if (admin.getUid() != callingUid) {
+ if (admin.getUid() != uid) {
throw new SecurityException("Admin " + who + " is not owned by uid "
+ Binder.getCallingUid());
}
- candidates.add(admin);
+ if (isActiveAdminWithPolicyForUserLocked(admin, reqPolicy, userId)) {
+ return admin;
+ }
} else {
for (ActiveAdmin admin : policy.mAdminList) {
- if (admin.getUid() == callingUid) {
- candidates.add(admin);
+ if (admin.getUid() == uid && isActiveAdminWithPolicyForUserLocked(admin, reqPolicy,
+ userId)) {
+ return admin;
}
}
}
- // Try to find an admin which can use reqPolicy
- for (ActiveAdmin admin : candidates) {
- boolean ownsDevice = isDeviceOwner(admin.info.getPackageName());
- boolean ownsProfile = (getProfileOwner(userHandle) != null
- && getProfileOwner(userHandle).getPackageName()
- .equals(admin.info.getPackageName()));
+ return null;
+ }
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
- if (ownsDevice) {
- return admin;
- }
- } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
- if (ownsDevice || ownsProfile) {
- return admin;
- }
- } else {
- if (admin.info.usesPolicy(reqPolicy)) {
- return admin;
- }
- }
- }
+ private boolean isActiveAdminWithPolicyForUserLocked(ActiveAdmin admin, int reqPolicy,
+ int userId) {
+ boolean ownsDevice = isDeviceOwner(admin.info.getPackageName());
+ boolean ownsProfile = (getProfileOwner(userId) != null
+ && getProfileOwner(userId).getPackageName()
+ .equals(admin.info.getPackageName()));
+ boolean ownsInitialization = isDeviceInitializer(admin.info.getPackageName())
+ && !hasUserSetupCompleted(userId);
- if (who != null) {
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
- throw new SecurityException("Admin " + candidates.get(0).info.getComponent()
- + " does not own the device");
+ if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
+ if (ownsDevice || (userId == UserHandle.USER_OWNER && ownsInitialization)) {
+ return true;
}
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
- throw new SecurityException("Admin " + candidates.get(0).info.getComponent()
- + " does not own the profile");
+ } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
+ if (ownsDevice || ownsProfile || ownsInitialization) {
+ return true;
}
- throw new SecurityException("Admin " + candidates.get(0).info.getComponent()
- + " did not specify uses-policy for: "
- + candidates.get(0).info.getTagForPolicy(reqPolicy));
} else {
- throw new SecurityException("No active admin owned by uid "
- + Binder.getCallingUid() + " for policy #" + reqPolicy);
+ if (admin.info.usesPolicy(reqPolicy)) {
+ return true;
+ }
}
+ return false;
}
void sendAdminCommandLocked(ActiveAdmin admin, String action) {
@@ -1317,6 +1406,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
out.attribute(null, ATTR_SETUP_COMPLETE,
Boolean.toString(true));
}
+ if (policy.mDelegatedCertInstallerPackage != null) {
+ out.attribute(null, ATTR_DELEGATED_CERT_INSTALLER,
+ policy.mDelegatedCertInstallerPackage);
+ }
+
final int N = policy.mAdminList.size();
for (int i=0; i<N; i++) {
@@ -1360,9 +1454,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
for (int i=0; i<policy.mLockTaskPackages.size(); i++) {
String component = policy.mLockTaskPackages.get(i);
- out.startTag(null, LOCK_TASK_COMPONENTS_XML);
+ out.startTag(null, TAG_LOCK_TASK_COMPONENTS);
out.attribute(null, "name", component);
- out.endTag(null, LOCK_TASK_COMPONENTS_XML);
+ out.endTag(null, TAG_LOCK_TASK_COMPONENTS);
+ }
+
+ if (!policy.mStatusBarEnabledState) {
+ out.startTag(null, TAG_STATUS_BAR);
+ out.attribute(null, ATTR_ENABLED, Boolean.toString(policy.mStatusBarEnabledState));
+ out.endTag(null, TAG_STATUS_BAR);
+ }
+
+ if (policy.doNotAskCredentialsOnBoot) {
+ out.startTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML);
+ out.endTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML);
}
out.endTag(null, "policies");
@@ -1424,6 +1529,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (userSetupComplete != null && Boolean.toString(true).equals(userSetupComplete)) {
policy.mUserSetupComplete = true;
}
+ policy.mDelegatedCertInstallerPackage = parser.getAttributeValue(null,
+ ATTR_DELEGATED_CERT_INSTALLER);
type = parser.next();
int outerDepth = parser.getDepth();
@@ -1481,9 +1588,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
policy.mActivePasswordNonLetter = Integer.parseInt(
parser.getAttributeValue(null, "nonletter"));
XmlUtils.skipCurrentTag(parser);
- } else if (LOCK_TASK_COMPONENTS_XML.equals(tag)) {
+ } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
XmlUtils.skipCurrentTag(parser);
+ } else if (TAG_STATUS_BAR.equals(tag)) {
+ policy.mStatusBarEnabledState = Boolean.parseBoolean(
+ parser.getAttributeValue(null, ATTR_ENABLED));
+ XmlUtils.skipCurrentTag(parser);
+ } else if (DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML.equals(tag)) {
+ policy.doNotAskCredentialsOnBoot = true;
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -1517,25 +1630,59 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// sufficiently what is currently set. Note that this is only
// a sanity check in case the two get out of sync; this should
// never normally happen.
- LockPatternUtils utils = new LockPatternUtils(mContext);
- if (utils.getActivePasswordQuality() < policy.mActivePasswordQuality) {
- Slog.w(LOG_TAG, "Active password quality 0x"
- + Integer.toHexString(policy.mActivePasswordQuality)
- + " does not match actual quality 0x"
- + Integer.toHexString(utils.getActivePasswordQuality()));
- policy.mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- policy.mActivePasswordLength = 0;
- policy.mActivePasswordUpperCase = 0;
- policy.mActivePasswordLowerCase = 0;
- policy.mActivePasswordLetters = 0;
- policy.mActivePasswordNumeric = 0;
- policy.mActivePasswordSymbols = 0;
- policy.mActivePasswordNonLetter = 0;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ LockPatternUtils utils = new LockPatternUtils(mContext);
+ if (utils.getActivePasswordQuality(userHandle) < policy.mActivePasswordQuality) {
+ Slog.w(LOG_TAG, "Active password quality 0x"
+ + Integer.toHexString(policy.mActivePasswordQuality)
+ + " does not match actual quality 0x"
+ + Integer.toHexString(utils.getActivePasswordQuality(userHandle)));
+ policy.mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ policy.mActivePasswordLength = 0;
+ policy.mActivePasswordUpperCase = 0;
+ policy.mActivePasswordLowerCase = 0;
+ policy.mActivePasswordLetters = 0;
+ policy.mActivePasswordNumeric = 0;
+ policy.mActivePasswordSymbols = 0;
+ policy.mActivePasswordNonLetter = 0;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
validatePasswordOwnerLocked(policy);
syncDeviceCapabilitiesLocked(policy);
updateMaximumTimeToLockLocked(policy);
+ addDeviceInitializerToLockTaskPackagesLocked(userHandle);
+ updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
+ if (!policy.mStatusBarEnabledState) {
+ setStatusBarEnabledStateInternal(policy.mStatusBarEnabledState, userHandle);
+ }
+ }
+
+ private void updateLockTaskPackagesLocked(List<String> packages, int userId) {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ long ident = Binder.clearCallingIdentity();
+ try {
+ am.updateLockTaskPackages(userId, packages.toArray(new String[packages.size()]));
+ } catch (RemoteException e) {
+ // Not gonna happen.
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void updateDeviceOwnerLocked() {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ long ident = Binder.clearCallingIdentity();
+ try {
+ am.updateDeviceOwner(getDeviceOwner());
+ } catch (RemoteException e) {
+ // Not gonna happen.
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
static void validateQualityConstant(int quality) {
@@ -1578,15 +1725,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
void syncDeviceCapabilitiesLocked(DevicePolicyData policy) {
// Ensure the status of the camera is synced down to the system. Interested native services
// should monitor this value and act accordingly.
- boolean systemState = SystemProperties.getBoolean(SYSTEM_PROP_DISABLE_CAMERA, false);
+ String cameraPropertyForUser = SYSTEM_PROP_DISABLE_CAMERA_PREFIX + policy.mUserHandle;
+ boolean systemState = SystemProperties.getBoolean(cameraPropertyForUser, false);
boolean cameraDisabled = getCameraDisabled(null, policy.mUserHandle);
if (cameraDisabled != systemState) {
long token = Binder.clearCallingIdentity();
try {
String value = cameraDisabled ? "1" : "0";
if (DBG) Slog.v(LOG_TAG, "Change in camera state ["
- + SYSTEM_PROP_DISABLE_CAMERA + "] = " + value);
- SystemProperties.set(SYSTEM_PROP_DISABLE_CAMERA, value);
+ + cameraPropertyForUser + "] = " + value);
+ SystemProperties.set(cameraPropertyForUser, value);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1613,7 +1761,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
updateScreenCaptureDisabledInWindowManager(userHandle,
getScreenCaptureDisabled(null, userHandle));
}
-
}
private void cleanUpOldUsers() {
@@ -1748,7 +1895,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
.setContentIntent(notifyIntent)
.setPriority(Notification.PRIORITY_HIGH)
.setShowWhen(false)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color))
.build();
@@ -1897,7 +2044,7 @@ 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.
+ // Active device owners must remain active admins.
if (isDeviceOwner(adminReceiver.getPackageName())) {
return;
}
@@ -1913,17 +2060,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordQuality(ComponentName who, int quality, int userHandle) {
+ public void setPasswordQuality(ComponentName who, int quality) {
if (!mHasFeature) {
return;
}
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
validateQualityConstant(quality);
- enforceCrossUserPermission(userHandle);
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.passwordQuality != quality) {
@@ -1962,15 +2107,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordMinimumLength(ComponentName who, int length, int userHandle) {
+ public void setPasswordMinimumLength(ComponentName who, int length) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.minimumPasswordLength != length) {
@@ -2009,15 +2152,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordHistoryLength(ComponentName who, int length, int userHandle) {
+ public void setPasswordHistoryLength(ComponentName who, int length) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.passwordHistoryLength != length) {
@@ -2056,18 +2197,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordExpirationTimeout(ComponentName who, long timeout, int userHandle) {
+ public void setPasswordExpirationTimeout(ComponentName who, long timeout) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkArgumentNonnegative(timeout, "Timeout must be >= 0 ms");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
- if (timeout < 0) {
- throw new IllegalArgumentException("Timeout must be >= 0 ms");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD);
// Calling this API automatically bumps the expiration date
@@ -2225,15 +2362,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordMinimumUpperCase(ComponentName who, int length, int userHandle) {
+ public void setPasswordMinimumUpperCase(ComponentName who, int length) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.minimumPasswordUpperCase != length) {
@@ -2272,12 +2407,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordMinimumLowerCase(ComponentName who, int length, int userHandle) {
- enforceCrossUserPermission(userHandle);
+ public void setPasswordMinimumLowerCase(ComponentName who, int length) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.minimumPasswordLowerCase != length) {
@@ -2316,15 +2449,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordMinimumLetters(ComponentName who, int length, int userHandle) {
+ public void setPasswordMinimumLetters(ComponentName who, int length) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.minimumPasswordLetters != length) {
@@ -2354,6 +2485,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int N = policy.mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin admin = policy.mAdminList.get(i);
+ if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) {
+ continue;
+ }
if (length < admin.minimumPasswordLetters) {
length = admin.minimumPasswordLetters;
}
@@ -2363,15 +2497,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordMinimumNumeric(ComponentName who, int length, int userHandle) {
+ public void setPasswordMinimumNumeric(ComponentName who, int length) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.minimumPasswordNumeric != length) {
@@ -2401,6 +2533,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int N = policy.mAdminList.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = policy.mAdminList.get(i);
+ if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) {
+ continue;
+ }
if (length < admin.minimumPasswordNumeric) {
length = admin.minimumPasswordNumeric;
}
@@ -2410,15 +2545,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordMinimumSymbols(ComponentName who, int length, int userHandle) {
+ public void setPasswordMinimumSymbols(ComponentName who, int length) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.minimumPasswordSymbols != length) {
@@ -2448,6 +2581,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int N = policy.mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin admin = policy.mAdminList.get(i);
+ if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) {
+ continue;
+ }
if (length < admin.minimumPasswordSymbols) {
length = admin.minimumPasswordSymbols;
}
@@ -2457,15 +2593,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setPasswordMinimumNonLetter(ComponentName who, int length, int userHandle) {
+ public void setPasswordMinimumNonLetter(ComponentName who, int length) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (ap.minimumPasswordNonLetter != length) {
@@ -2495,6 +2629,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int N = policy.mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin admin = policy.mAdminList.get(i);
+ if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) {
+ continue;
+ }
if (length < admin.minimumPasswordNonLetter) {
length = admin.minimumPasswordNonLetter;
}
@@ -2521,8 +2658,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(null,
- DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
if (policy.mActivePasswordQuality < getPasswordQuality(null, userHandle)
|| policy.mActivePasswordLength < getPasswordMinimumLength(null, userHandle)) {
return false;
@@ -2555,15 +2691,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, int userHandle) {
+ public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
getActiveAdminForCallerLocked(who,
@@ -2631,11 +2765,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return strictestAdmin;
}
- public boolean resetPassword(String passwordOrNull, int flags, int userHandle) {
+ public boolean resetPassword(String passwordOrNull, int flags) {
if (!mHasFeature) {
return false;
}
- enforceCrossUserPermission(userHandle);
+ final int userHandle = UserHandle.getCallingUserId();
enforceNotManagedProfile(userHandle, "reset the password");
String password = passwordOrNull != null ? passwordOrNull : "";
@@ -2738,15 +2872,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
+ boolean callerIsDeviceOwnerAdmin = isCallerDeviceOwnerOrInitializer(callingUid);
+ boolean doNotAskCredentialsOnBoot =
+ (flags & DevicePolicyManager.DO_NOT_ASK_CREDENTIALS_ON_BOOT) != 0;
+ if (callerIsDeviceOwnerAdmin && doNotAskCredentialsOnBoot) {
+ setDoNotAskCredentialsOnBoot();
+ }
+
// Don't do this with the lock held, because it is going to call
// back in to the service.
long ident = Binder.clearCallingIdentity();
try {
LockPatternUtils utils = new LockPatternUtils(mContext);
if (!TextUtils.isEmpty(password)) {
- utils.saveLockPassword(password, quality, false, userHandle);
+ utils.saveLockPassword(password, null, quality, userHandle);
} else {
- utils.clearLock(false, userHandle);
+ utils.clearLock(userHandle);
}
boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
if (requireEntry) {
@@ -2766,15 +2907,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return true;
}
- public void setMaximumTimeToLock(ComponentName who, long timeMs, int userHandle) {
+ private void setDoNotAskCredentialsOnBoot() {
+ synchronized (this) {
+ DevicePolicyData policyData = getUserData(UserHandle.USER_OWNER);
+ if (!policyData.doNotAskCredentialsOnBoot) {
+ policyData.doNotAskCredentialsOnBoot = true;
+ saveSettingsLocked(UserHandle.USER_OWNER);
+ }
+ }
+ }
+
+ public boolean getDoNotAskCredentialsOnBoot() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT, null);
+ synchronized (this) {
+ DevicePolicyData policyData = getUserData(UserHandle.USER_OWNER);
+ return policyData.doNotAskCredentialsOnBoot;
+ }
+ }
+
+ public void setMaximumTimeToLock(ComponentName who, long timeMs) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
if (ap.maximumTimeToUnlock != timeMs) {
@@ -2877,7 +3035,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void enforceCanManageCaCerts(ComponentName who) {
if (who == null) {
- mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
+ if (!isCallerDelegatedCertInstaller()) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
+ }
} else {
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -2885,6 +3045,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private boolean isCallerDelegatedCertInstaller() {
+ final int callingUid = Binder.getCallingUid();
+ final int userHandle = UserHandle.getUserId(callingUid);
+ synchronized (this) {
+ final DevicePolicyData policy = getUserData(userHandle);
+ if (policy.mDelegatedCertInstallerPackage == null) {
+ return false;
+ }
+
+ try {
+ int uid = mContext.getPackageManager().getPackageUid(
+ policy.mDelegatedCertInstallerPackage, userHandle);
+ return uid == callingUid;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+ }
+
@Override
public boolean installCaCert(ComponentName admin, byte[] certBuffer) throws RemoteException {
enforceCanManageCaCerts(admin);
@@ -2954,10 +3133,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, String alias) {
if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
- synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (!isCallerDelegatedCertInstaller()) {
+ throw new SecurityException("who == null, but caller is not cert installer");
+ }
+ } else {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ }
}
final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
final long id = Binder.clearCallingIdentity();
@@ -2980,7 +3162,88 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
+ @Override
+ public void choosePrivateKeyAlias(final int uid, final String host, int port, final String url,
+ final String alias, final IBinder response) {
+ // Caller UID needs to be trusted, so we restrict this method to SYSTEM_UID callers.
+ if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) {
+ return;
+ }
+
+ final UserHandle caller = Binder.getCallingUserHandle();
+ final ComponentName profileOwner = getProfileOwner(caller.getIdentifier());
+
+ if (profileOwner == null) {
+ sendPrivateKeyAliasResponse(null, response);
+ return;
+ }
+
+ Intent intent = new Intent(DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS);
+ intent.setComponent(profileOwner);
+ intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, uid);
+ intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_HOST, host);
+ intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_PORT, port);
+ intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URL, url);
+ intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS, alias);
+ intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE, response);
+
+ final long id = Binder.clearCallingIdentity();
+ try {
+ mContext.sendOrderedBroadcastAsUser(intent, caller, null, new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String chosenAlias = getResultData();
+ sendPrivateKeyAliasResponse(chosenAlias, response);
+ }
+ }, null, Activity.RESULT_OK, null, null);
+ } finally {
+ Binder.restoreCallingIdentity(id);
+ }
+ }
+
+ private void sendPrivateKeyAliasResponse(final String alias, final IBinder responseBinder) {
+ final IKeyChainAliasCallback keyChainAliasResponse =
+ IKeyChainAliasCallback.Stub.asInterface(responseBinder);
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ try {
+ keyChainAliasResponse.alias(alias);
+ } catch (Exception e) {
+ // Catch everything (not just RemoteException): caller could throw a
+ // RuntimeException back across processes.
+ Log.e(LOG_TAG, "error while responding to callback", e);
+ }
+ return null;
+ }
+ }.execute();
+ }
+
+ @Override
+ public void setCertInstallerPackage(ComponentName who, String installerPackage)
+ throws SecurityException {
+ int userHandle = UserHandle.getCallingUserId();
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ DevicePolicyData policy = getUserData(userHandle);
+ policy.mDelegatedCertInstallerPackage = installerPackage;
+ saveSettingsLocked(userHandle);
+ }
+ }
+
+ @Override
+ public String getCertInstallerPackage(ComponentName who) throws SecurityException {
+ int userHandle = UserHandle.getCallingUserId();
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ DevicePolicyData policy = getUserData(userHandle);
+ return policy.mDelegatedCertInstallerPackage;
+ }
+ }
+
private void wipeDataLocked(boolean wipeExtRequested, String reason) {
+ // TODO: wipe all public volumes on device
+
// If the SD card is encrypted and non-removable, we have to force a wipe.
boolean forceExtWipe = !Environment.isExternalStorageRemovable() && isExtStorageEncrypted();
@@ -3024,8 +3287,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
long ident = Binder.clearCallingIdentity();
try {
if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
+ boolean ownsInitialization = isDeviceInitializer(admin.info.getPackageName())
+ && !hasUserSetupCompleted(userHandle);
if (userHandle != UserHandle.USER_OWNER
- || !isDeviceOwner(admin.info.getPackageName())) {
+ || !(isDeviceOwner(admin.info.getPackageName())
+ || ownsInitialization)) {
throw new SecurityException(
"Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
}
@@ -3235,15 +3501,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
public ComponentName setGlobalProxy(ComponentName who, String proxySpec,
- String exclusionList, int userHandle) {
+ String exclusionList) {
if (!mHasFeature) {
return null;
}
- enforceCrossUserPermission(userHandle);
synchronized(this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
+ Preconditions.checkNotNull(who, "ComponentName is null");
// Only check if owner has set global proxy. We don't allow other users to set it.
DevicePolicyData policy = getUserData(UserHandle.USER_OWNER);
@@ -3265,7 +3528,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// If the user is not the owner, don't set the global proxy. Fail silently.
if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
Slog.w(LOG_TAG, "Only the owner is allowed to set the global proxy. User "
- + userHandle + " is not permitted.");
+ + UserHandle.getCallingUserId() + " is not permitted.");
return null;
}
if (proxySpec == null) {
@@ -3375,16 +3638,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* Set the storage encryption request for a single admin. Returns the new total request
* status (for all admins).
*/
- public int setStorageEncryption(ComponentName who, boolean encrypt, int userHandle) {
+ public int setStorageEncryption(ComponentName who, boolean encrypt) {
if (!mHasFeature) {
return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
// Check for permissions
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
// Only owner can set storage encryption
if (userHandle != UserHandle.USER_OWNER
|| UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
@@ -3479,8 +3740,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Hook to low-levels: Reporting the current status of encryption.
- * @return A value such as {@link DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED} or
- * {@link DevicePolicyManager#ENCRYPTION_STATUS_INACTIVE} or
+ * @return A value such as {@link DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED},
+ * {@link DevicePolicyManager#ENCRYPTION_STATUS_INACTIVE},
+ * {@link DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY}, or
* {@link DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE}.
*/
private int getEncryptionStatus() {
@@ -3490,7 +3752,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
try {
return LockPatternUtils.isDeviceEncrypted()
? DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE
- : DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE;
+ : DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -3511,15 +3773,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Set whether the screen capture is disabled for the user managed by the specified admin.
*/
- public void setScreenCaptureDisabled(ComponentName who, int userHandle, boolean disabled) {
+ public void setScreenCaptureDisabled(ComponentName who, boolean disabled) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
if (ap.disableScreenCapture != disabled) {
@@ -3570,15 +3830,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Set whether auto time is required by the specified admin (must be device owner).
*/
- public void setAutoTimeRequired(ComponentName who, int userHandle, boolean required) {
+ public void setAutoTimeRequired(ComponentName who, boolean required) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin admin = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
if (admin.requireAutoTime != required) {
@@ -3614,22 +3872,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* The system property used to share the state of the camera. The native camera service
- * is expected to read this property and act accordingly.
+ * is expected to read this property and act accordingly. The userId should be appended
+ * to this key.
*/
- public static final String SYSTEM_PROP_DISABLE_CAMERA = "sys.secpolicy.camera.disabled";
+ public static final String SYSTEM_PROP_DISABLE_CAMERA_PREFIX = "sys.secpolicy.camera.off_";
/**
* Disables all device cameras according to the specified admin.
*/
- public void setCameraDisabled(ComponentName who, boolean disabled, int userHandle) {
+ public void setCameraDisabled(ComponentName who, boolean disabled) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
if (ap.disableCamera != disabled) {
@@ -3670,16 +3927,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Selectively disable keyguard features.
*/
- public void setKeyguardDisabledFeatures(ComponentName who, int which, int userHandle) {
+ public void setKeyguardDisabledFeatures(ComponentName who, int which) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
enforceNotManagedProfile(userHandle, "disable keyguard features");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
if (ap.disabledKeyguardFeatures != which) {
@@ -3728,15 +3983,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ " for device owner");
}
synchronized (this) {
- if (!allowedToSetDeviceOwnerOnDevice()) {
- throw new IllegalStateException(
- "Trying to set device owner but device is already provisioned.");
- }
-
- if (mDeviceOwner != null && mDeviceOwner.hasDeviceOwner()) {
- throw new IllegalStateException(
- "Trying to set device owner but device owner is already set.");
- }
+ enforceCanSetDeviceOwner();
// Shutting down backup manager service permanently.
long ident = Binder.clearCallingIdentity();
@@ -3753,14 +4000,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (mDeviceOwner == null) {
// Device owner is not set and does not exist, set it.
mDeviceOwner = DeviceOwner.createWithDeviceOwner(packageName, ownerName);
- mDeviceOwner.writeOwnerFile();
- return true;
} else {
- // Device owner is not set but a profile owner exists, update Device owner state.
+ // Device owner state already exists, update it.
mDeviceOwner.setDeviceOwner(packageName, ownerName);
- mDeviceOwner.writeOwnerFile();
- return true;
}
+ mDeviceOwner.writeOwnerFile();
+ updateDeviceOwnerLocked();
+ return true;
}
}
@@ -3823,9 +4069,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void clearDeviceOwner(String packageName) {
- if (packageName == null) {
- throw new NullPointerException("packageName is null");
- }
+ Preconditions.checkNotNull(packageName, "packageName is null");
try {
int uid = mContext.getPackageManager().getPackageUid(packageName, 0);
if (uid != Binder.getCallingUid()) {
@@ -3844,6 +4088,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (mDeviceOwner != null) {
mDeviceOwner.clearDeviceOwner();
mDeviceOwner.writeOwnerFile();
+ updateDeviceOwnerLocked();
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -3852,47 +4097,143 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
- public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
+ public boolean setDeviceInitializer(ComponentName who, ComponentName initializer,
+ String ownerName) {
if (!mHasFeature) {
return false;
}
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+ if (initializer == null || !DeviceOwner.isInstalled(
+ initializer.getPackageName(), mContext.getPackageManager())) {
+ throw new IllegalArgumentException("Invalid component name " + initializer
+ + " for device initializer");
+ }
+ synchronized (this) {
+ enforceCanSetDeviceInitializer(who);
- UserInfo info = mUserManager.getUserInfo(userHandle);
- if (info == null) {
- // User doesn't exist.
- throw new IllegalArgumentException(
- "Attempted to set profile owner for invalid userId: " + userHandle);
+ if (mDeviceOwner != null && mDeviceOwner.hasDeviceInitializer()) {
+ throw new IllegalStateException(
+ "Trying to set device initializer but device initializer is already set.");
+ }
+
+ if (mDeviceOwner == null) {
+ // Device owner state does not exist, create it.
+ mDeviceOwner = DeviceOwner.createWithDeviceInitializer(initializer, ownerName);
+ } else {
+ // Device owner already exists, update it.
+ mDeviceOwner.setDeviceInitializer(initializer, ownerName);
+ }
+
+ addDeviceInitializerToLockTaskPackagesLocked(UserHandle.USER_OWNER);
+ mDeviceOwner.writeOwnerFile();
+ return true;
}
- if (info.isGuest()) {
- throw new IllegalStateException("Cannot set a profile owner on a guest");
+ }
+
+ private void enforceCanSetDeviceInitializer(ComponentName who) {
+ if (who == null) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_DEVICE_ADMINS, null);
+ if (hasUserSetupCompleted(UserHandle.USER_OWNER)) {
+ throw new IllegalStateException(
+ "Trying to set device initializer but device is already provisioned.");
+ }
+ } else {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+ }
+
+ @Override
+ public boolean isDeviceInitializer(String packageName) {
+ if (!mHasFeature) {
+ return false;
+ }
+ synchronized (this) {
+ return mDeviceOwner != null
+ && mDeviceOwner.hasDeviceInitializer()
+ && mDeviceOwner.getDeviceInitializerPackageName().equals(packageName);
+ }
+ }
+
+ @Override
+ public String getDeviceInitializer() {
+ if (!mHasFeature) {
+ return null;
+ }
+ synchronized (this) {
+ if (mDeviceOwner != null && mDeviceOwner.hasDeviceInitializer()) {
+ return mDeviceOwner.getDeviceInitializerPackageName();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public ComponentName getDeviceInitializerComponent() {
+ if (!mHasFeature) {
+ return null;
+ }
+ synchronized (this) {
+ if (mDeviceOwner != null && mDeviceOwner.hasDeviceInitializer()) {
+ return mDeviceOwner.getDeviceInitializerComponent();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void clearDeviceInitializer(ComponentName who) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who, UserHandle.getCallingUserId());
+
+ if (admin.getUid() != Binder.getCallingUid()) {
+ throw new SecurityException("Admin " + who + " is not owned by uid "
+ + Binder.getCallingUid());
+ }
+
+ if (!isDeviceInitializer(admin.info.getPackageName())
+ && !isDeviceOwner(admin.info.getPackageName())) {
+ throw new SecurityException(
+ "clearDeviceInitializer can only be called by the device initializer/owner");
+ }
+ synchronized (this) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mDeviceOwner != null) {
+ mDeviceOwner.clearDeviceInitializer();
+ mDeviceOwner.writeOwnerFile();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
+ }
+ @Override
+ public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
+ if (!mHasFeature) {
+ return false;
+ }
if (who == null
|| !DeviceOwner.isInstalledForUser(who.getPackageName(), userHandle)) {
throw new IllegalArgumentException("Component " + who
+ " not installed for userId:" + userHandle);
}
synchronized (this) {
- // Only SYSTEM_UID can override the userSetupComplete
- if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID
- && hasUserSetupCompleted(userHandle)) {
- throw new IllegalStateException(
- "Trying to set profile owner but user is already set-up.");
- }
-
+ enforceCanSetProfileOwner(userHandle);
if (mDeviceOwner == null) {
// Device owner state does not exist, create it.
mDeviceOwner = DeviceOwner.createWithProfileOwner(who, ownerName,
userHandle);
- mDeviceOwner.writeOwnerFile();
- return true;
} else {
- // Device owner already exists, update it.
+ // Device owner state already exists, update it.
mDeviceOwner.setProfileOwner(who, ownerName, userHandle);
- mDeviceOwner.writeOwnerFile();
- return true;
}
+ mDeviceOwner.writeOwnerFile();
+ return true;
}
}
@@ -3924,7 +4265,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Bundle userRestrictions = mUserManager.getUserRestrictions();
mUserManager.setUserRestrictions(new Bundle(), userHandle);
if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) {
- audioManager.setMasterMute(false);
+ audioManager.setMasterMute(false, 0);
}
if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) {
audioManager.setMicrophoneMute(false);
@@ -3946,16 +4287,58 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
- public void setProfileEnabled(ComponentName who) {
+ public boolean setUserEnabled(ComponentName who) {
if (!mHasFeature) {
- return;
+ return false;
}
- final int userHandle = UserHandle.getCallingUserId();
synchronized (this) {
- // Check for permissions
if (who == null) {
throw new NullPointerException("ComponentName is null");
}
+ int userId = UserHandle.getCallingUserId();
+
+ ActiveAdmin activeAdmin =
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (!isDeviceInitializer(activeAdmin.info.getPackageName())) {
+ throw new SecurityException(
+ "This method can only be called by device initializers");
+ }
+
+ long id = Binder.clearCallingIdentity();
+ try {
+ if (!isDeviceOwner(activeAdmin.info.getPackageName())) {
+ IPackageManager ipm = AppGlobals.getPackageManager();
+ ipm.setComponentEnabledSetting(who,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP, userId);
+
+ removeActiveAdmin(who, userId);
+ }
+
+ if (userId == UserHandle.USER_OWNER) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+ }
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 1, userId);
+ } catch (RemoteException e) {
+ Log.i(LOG_TAG, "Can't talk to package manager", e);
+ return false;
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ return true;
+ }
+ }
+
+ @Override
+ public void setProfileEnabled(ComponentName who) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ final int userHandle = UserHandle.getCallingUserId();
+ synchronized (this) {
// Check if this is the profile owner who is calling
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
int userId = UserHandle.getCallingUserId();
@@ -3977,12 +4360,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setProfileName(ComponentName who, String profileName) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
int userId = UserHandle.getCallingUserId();
-
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
-
// Check if this is the profile owner (includes device owner).
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -4044,15 +4423,76 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
/**
- * Device owner can only be set on an unprovisioned device, unless it was initiated by "adb", in
- * which case we allow it if no account is associated with the device.
+ * The profile owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS
+ * permission.
+ * The profile owner can only be set before the user setup phase has completed,
+ * except for:
+ * - SYSTEM_UID
+ * - adb if there are not accounts.
*/
- private boolean allowedToSetDeviceOwnerOnDevice() {
- int callingId = Binder.getCallingUid();
- if (callingId == Process.SHELL_UID || callingId == Process.ROOT_UID) {
- return AccountManager.get(mContext).getAccounts().length == 0;
- } else {
- return !hasUserSetupCompleted(UserHandle.USER_OWNER);
+ private void enforceCanSetProfileOwner(int userHandle) {
+ UserInfo info = mUserManager.getUserInfo(userHandle);
+ if (info == null) {
+ // User doesn't exist.
+ throw new IllegalArgumentException(
+ "Attempted to set profile owner for invalid userId: " + userHandle);
+ }
+ if (info.isGuest()) {
+ throw new IllegalStateException("Cannot set a profile owner on a guest");
+ }
+ if (getProfileOwner(userHandle) != null) {
+ throw new IllegalStateException("Trying to set the profile owner, but profile owner "
+ + "is already set.");
+ }
+ int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+ if (hasUserSetupCompleted(userHandle) &&
+ AccountManager.get(mContext).getAccountsAsUser(userHandle).length > 0) {
+ throw new IllegalStateException("Not allowed to set the profile owner because "
+ + "there are already some accounts on the profile");
+ }
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
+ if (hasUserSetupCompleted(userHandle)
+ && UserHandle.getAppId(callingUid) != Process.SYSTEM_UID) {
+ throw new IllegalStateException("Cannot set the profile owner on a user which is "
+ + "already set-up");
+ }
+ }
+
+ /**
+ * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS
+ * permission.
+ * The device owner can only be set before the setup phase of the primary user has completed,
+ * except for adb if no accounts or additional users are present on the device.
+ */
+ private void enforceCanSetDeviceOwner() {
+ if (mDeviceOwner != null && mDeviceOwner.hasDeviceOwner()) {
+ throw new IllegalStateException("Trying to set the device owner, but device owner "
+ + "is already set.");
+ }
+ int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+ if (!hasUserSetupCompleted(UserHandle.USER_OWNER)) {
+ return;
+ }
+ if (mUserManager.getUserCount() > 1) {
+ throw new IllegalStateException("Not allowed to set the device owner because there "
+ + "are already several users on the device");
+ }
+ if (AccountManager.get(mContext).getAccounts().length > 0) {
+ throw new IllegalStateException("Not allowed to set the device owner because there "
+ + "are already some accounts on the device");
+ }
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
+ if (hasUserSetupCompleted(UserHandle.USER_OWNER)) {
+ throw new IllegalStateException("Cannot set the device owner if the device is "
+ + "already set-up");
}
}
@@ -4130,7 +4570,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (this) {
p.println("Current Device Policy Manager state:");
-
+ if (mDeviceOwner != null) {
+ mDeviceOwner.dump(" ", pw);
+ }
int userCount = mUserData.size();
for (int u = 0; u < userCount; u++) {
DevicePolicyData policy = getUserData(mUserData.keyAt(u));
@@ -4158,12 +4600,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void addPersistentPreferredActivity(ComponentName who, IntentFilter filter,
ComponentName activity) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
final int userHandle = UserHandle.getCallingUserId();
-
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
IPackageManager pm = AppGlobals.getPackageManager();
@@ -4180,12 +4619,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void clearPackagePersistentPreferredActivities(ComponentName who, String packageName) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
final int userHandle = UserHandle.getCallingUserId();
-
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
IPackageManager pm = AppGlobals.getPackageManager();
@@ -4202,12 +4638,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setApplicationRestrictions(ComponentName who, String packageName, Bundle settings) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
-
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long id = Binder.clearCallingIdentity();
@@ -4220,19 +4653,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent,
- PersistableBundle args, int userHandle) {
+ PersistableBundle args) {
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ Preconditions.checkNotNull(admin, "admin is null");
+ Preconditions.checkNotNull(agent, "agent is null");
+ final int userHandle = UserHandle.getCallingUserId();
enforceNotManagedProfile(userHandle, "set trust agent configuration");
synchronized (this) {
- if (admin == null) {
- throw new NullPointerException("admin is null");
- }
- if (agent == null) {
- throw new NullPointerException("agent is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
ap.trustAgentInfos.put(agent.flattenToString(), new TrustAgentInfo(args));
@@ -4246,10 +4675,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return null;
}
+ Preconditions.checkNotNull(agent, "agent null");
enforceCrossUserPermission(userHandle);
- if (agent == null) {
- throw new NullPointerException("agent is null");
- }
synchronized (this) {
final String componentName = agent.flattenToString();
@@ -4302,10 +4729,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
int userHandle = UserHandle.getCallingUserId();
@@ -4327,23 +4752,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
int callingUserId = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
IPackageManager pm = AppGlobals.getPackageManager();
long id = Binder.clearCallingIdentity();
try {
if ((flags & DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED) != 0) {
- pm.addCrossProfileIntentFilter(filter, who.getPackageName(),
- mContext.getUserId(), callingUserId, UserHandle.USER_OWNER, 0);
+ pm.addCrossProfileIntentFilter(filter, who.getPackageName(), callingUserId,
+ UserHandle.USER_OWNER, 0);
}
if ((flags & DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT) != 0) {
pm.addCrossProfileIntentFilter(filter, who.getPackageName(),
- mContext.getUserId(), UserHandle.USER_OWNER, callingUserId, 0);
+ UserHandle.USER_OWNER, callingUserId, 0);
}
} catch (RemoteException re) {
// Shouldn't happen
@@ -4354,21 +4777,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
public void clearCrossProfileIntentFilters(ComponentName who) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
int callingUserId = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
IPackageManager pm = AppGlobals.getPackageManager();
long id = Binder.clearCallingIdentity();
try {
- pm.clearCrossProfileIntentFilters(callingUserId, who.getPackageName(),
- callingUserId);
+ // Removing those that go from the managed profile to the primary user.
+ pm.clearCrossProfileIntentFilters(callingUserId, who.getPackageName());
+ // And those that go from the primary user to the managed profile.
// If we want to support multiple managed profiles, we will have to only remove
// those that have callingUserId as their target.
- pm.clearCrossProfileIntentFilters(UserHandle.USER_OWNER, who.getPackageName(),
- callingUserId);
+ pm.clearCrossProfileIntentFilters(UserHandle.USER_OWNER, who.getPackageName());
} catch (RemoteException re) {
// Shouldn't happen
} finally {
@@ -4399,8 +4820,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
try {
ApplicationInfo applicationInfo = pm.getApplicationInfo(enabledPackage,
PackageManager.GET_UNINSTALLED_PACKAGES, userIdToCheck);
- systemService = (applicationInfo.flags
- & ApplicationInfo.FLAG_SYSTEM) != 0;
+ systemService = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
} catch (RemoteException e) {
Log.i(LOG_TAG, "Can't talk to package managed", e);
}
@@ -4429,9 +4849,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return false;
}
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
+ Preconditions.checkNotNull(who, "ComponentName is null");
if (packageList != null) {
int userId = UserHandle.getCallingUserId();
@@ -4476,10 +4894,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return null;
}
-
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
ActiveAdmin admin = getActiveAdminForCallerLocked(who,
@@ -4534,15 +4949,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
IPackageManager pm = AppGlobals.getPackageManager();
if (installedServices != null) {
for (AccessibilityServiceInfo service : installedServices) {
- String packageName = service.getResolveInfo().serviceInfo.packageName;
- try {
- ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES, userId);
- if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- result.add(packageName);
- }
- } catch (RemoteException e) {
- Log.i(LOG_TAG, "Accessibility service in missing package", e);
+ ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+ ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ result.add(serviceInfo.packageName);
}
}
}
@@ -4589,9 +4999,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return false;
}
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
+ Preconditions.checkNotNull(who, "ComponentName is null");
// TODO When InputMethodManager supports per user calls remove
// this restriction.
@@ -4634,10 +5042,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return null;
}
-
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
ActiveAdmin admin = getActiveAdminForCallerLocked(who,
@@ -4693,16 +5098,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
IPackageManager pm = AppGlobals.getPackageManager();
if (imes != null) {
for (InputMethodInfo ime : imes) {
- String packageName = ime.getPackageName();
- try {
- ApplicationInfo applicationInfo = pm.getApplicationInfo(
- packageName, PackageManager.GET_UNINSTALLED_PACKAGES,
- userId);
- if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- result.add(packageName);
- }
- } catch (RemoteException e) {
- Log.i(LOG_TAG, "Input method for missing package", e);
+ ServiceInfo serviceInfo = ime.getServiceInfo();
+ ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ result.add(serviceInfo.packageName);
}
}
}
@@ -4716,10 +5115,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public UserHandle createUser(ComponentName who, String name) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
long id = Binder.clearCallingIdentity();
@@ -4748,20 +5145,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final IPackageManager ipm = AppGlobals.getPackageManager();
IActivityManager activityManager = ActivityManagerNative.getDefault();
+ final int userHandle = user.getIdentifier();
try {
// Install the profile owner if not present.
- if (!ipm.isPackageAvailable(profileOwnerPkg, user.getIdentifier())) {
- ipm.installExistingPackageAsUser(profileOwnerPkg, user.getIdentifier());
+ if (!ipm.isPackageAvailable(profileOwnerPkg, userHandle)) {
+ ipm.installExistingPackageAsUser(profileOwnerPkg, userHandle);
}
// Start user in background.
- activityManager.startUserInBackground(user.getIdentifier());
+ activityManager.startUserInBackground(userHandle);
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Failed to make remote calls for configureUser", e);
}
- setActiveAdmin(profileOwnerComponent, true, user.getIdentifier(), adminExtras);
- setProfileOwner(profileOwnerComponent, ownerName, user.getIdentifier());
+ setActiveAdmin(profileOwnerComponent, true, userHandle, adminExtras);
+ setProfileOwner(profileOwnerComponent, ownerName, userHandle);
return user;
} finally {
restoreCallingIdentity(id);
@@ -4770,10 +5168,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean removeUser(ComponentName who, UserHandle userHandle) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
long id = Binder.clearCallingIdentity();
@@ -4787,10 +5183,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean switchUser(ComponentName who, UserHandle userHandle) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
long id = Binder.clearCallingIdentity();
@@ -4811,17 +5205,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public Bundle getApplicationRestrictions(ComponentName who, String packageName) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long id = Binder.clearCallingIdentity();
try {
- return mUserManager.getApplicationRestrictions(packageName, userHandle);
+ Bundle bundle = mUserManager.getApplicationRestrictions(packageName, userHandle);
+ // if no restrictions were saved, mUserManager.getApplicationRestrictions
+ // returns null, but DPM method should return an empty Bundle as per JavaDoc
+ return bundle != null ? bundle : Bundle.EMPTY;
} finally {
restoreCallingIdentity(id);
}
@@ -4830,12 +5225,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setUserRestriction(ComponentName who, String key, boolean enabled) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
final UserHandle user = new UserHandle(UserHandle.getCallingUserId());
final int userHandle = user.getIdentifier();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin activeAdmin =
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
boolean isDeviceOwner = isDeviceOwner(activeAdmin.info.getPackageName());
@@ -4843,6 +5236,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
&& DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
throw new SecurityException("Profile owners cannot set user restriction " + key);
}
+ if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
+ throw new SecurityException("User restriction " + key + " cannot be changed");
+ }
boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
IAudioService iAudioService = null;
@@ -4857,7 +5253,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
iAudioService.setMicrophoneMute(true, who.getPackageName());
} else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- iAudioService.setMasterMute(true, 0, who.getPackageName(), null);
+ iAudioService.setMasterMute(true, 0, who.getPackageName());
}
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
@@ -4922,7 +5318,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
iAudioService.setMicrophoneMute(false, who.getPackageName());
} else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- iAudioService.setMasterMute(false, 0, who.getPackageName(), null);
+ iAudioService.setMasterMute(false, 0, who.getPackageName());
}
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
@@ -4935,11 +5331,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean setApplicationHidden(ComponentName who, String packageName,
boolean hidden) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
int callingUserId = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long id = Binder.clearCallingIdentity();
@@ -4958,11 +5352,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean isApplicationHidden(ComponentName who, String packageName) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
int callingUserId = UserHandle.getCallingUserId();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long id = Binder.clearCallingIdentity();
@@ -4981,11 +5373,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void enableSystemApp(ComponentName who, String packageName) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
-
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -5026,11 +5415,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public int enableSystemAppWithIntent(ComponentName who, Intent intent) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
-
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -5058,15 +5444,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (activitiesToEnable != null) {
for (ResolveInfo info : activitiesToEnable) {
if (info.activityInfo != null) {
-
- if (!isSystemApp(pm, info.activityInfo.packageName, primaryUser.id)) {
- throw new IllegalArgumentException(
- "Only system apps can be enabled this way.");
+ String packageName = info.activityInfo.packageName;
+ if (isSystemApp(pm, packageName, primaryUser.id)) {
+ numberOfAppsInstalled++;
+ pm.installExistingPackageAsUser(packageName, userId);
+ } else {
+ Slog.d(LOG_TAG, "Not enabling " + packageName + " since is not a"
+ + " system app");
}
-
-
- numberOfAppsInstalled++;
- pm.installExistingPackageAsUser(info.activityInfo.packageName, userId);
}
}
}
@@ -5085,7 +5470,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throws RemoteException {
ApplicationInfo appInfo = pm.getApplicationInfo(packageName, GET_UNINSTALLED_PACKAGES,
userId);
- return (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) > 0;
+ if (appInfo == null) {
+ throw new IllegalArgumentException("The application " + packageName +
+ " is not present on this device");
+ }
+ return (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
@Override
@@ -5094,10 +5483,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin ap = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
if (disabled) {
@@ -5135,12 +5522,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setUninstallBlocked(ComponentName who, String packageName,
boolean uninstallBlocked) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
final int userId = UserHandle.getCallingUserId();
-
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
long id = Binder.clearCallingIdentity();
@@ -5187,10 +5571,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin admin = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
if (admin.disableCallerId != disabled) {
@@ -5205,12 +5587,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return false;
}
-
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
-
ActiveAdmin admin = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
return admin.disableCallerId;
@@ -5227,50 +5605,143 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ @Override
+ public void startManagedQuickContact(String actualLookupKey, long actualContactId,
+ Intent originalIntent) {
+ final Intent intent = QuickContact.rebuildManagedQuickContactsIntent(
+ actualLookupKey, actualContactId, originalIntent);
+ final int callingUserId = UserHandle.getCallingUserId();
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ final int managedUserId = getManagedUserId(callingUserId);
+ if (managedUserId < 0) {
+ return;
+ }
+ if (getCrossProfileCallerIdDisabledForUser(managedUserId)) {
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG,
+ "Cross-profile contacts access disabled for user " + managedUserId);
+ }
+ return;
+ }
+ ContactsInternal.startQuickContactWithErrorToastForUser(
+ mContext, intent, new UserHandle(managedUserId));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * @return the user ID of the managed user that is linked to the current user, if any.
+ * Otherwise -1.
+ */
+ public int getManagedUserId(int callingUserId) {
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "getManagedUserId: callingUserId=" + callingUserId);
+ }
+
+ for (UserInfo ui : mUserManager.getProfiles(callingUserId)) {
+ if (ui.id == callingUserId || !ui.isManagedProfile()) {
+ continue; // Caller user self, or not a managed profile. Skip.
+ }
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "Managed user=" + ui.id);
+ }
+ return ui.id;
+ }
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "Managed user not found.");
+ }
+ return -1;
+ }
+
+ @Override
+ public void setBluetoothContactSharingDisabled(ComponentName who, boolean disabled) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (admin.disableBluetoothContactSharing != disabled) {
+ admin.disableBluetoothContactSharing = disabled;
+ saveSettingsLocked(UserHandle.getCallingUserId());
+ }
+ }
+ }
+
+ @Override
+ public boolean getBluetoothContactSharingDisabled(ComponentName who) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ return admin.disableBluetoothContactSharing;
+ }
+ }
+
+ @Override
+ public boolean getBluetoothContactSharingDisabledForUser(int userId) {
+ // TODO: Should there be a check to make sure this relationship is
+ // within a profile group?
+ // enforceSystemProcess("getCrossProfileCallerIdDisabled can only be called by system");
+ synchronized (this) {
+ ActiveAdmin admin = getProfileOwnerAdmin(userId);
+ return (admin != null) ? admin.disableBluetoothContactSharing : false;
+ }
+ }
+
/**
* Sets which packages may enter lock task mode.
*
* This function can only be called by the device owner.
- * @param components The list of components allowed to enter lock task mode.
+ * @param packages The list of packages allowed to enter lock task mode.
*/
- public void setLockTaskPackages(ComponentName who, String[] packages) throws SecurityException {
+ public void setLockTaskPackages(ComponentName who, String[] packages)
+ throws SecurityException {
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
int userHandle = Binder.getCallingUserHandle().getIdentifier();
- DevicePolicyData policy = getUserData(userHandle);
- policy.mLockTaskPackages.clear();
- if (packages != null) {
- for (int j = 0; j < packages.length; j++) {
- String pkg = packages[j];
- policy.mLockTaskPackages.add(pkg);
- }
- }
-
- // Store the settings persistently.
- saveSettingsLocked(userHandle);
+ setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
}
}
+ private void setLockTaskPackagesLocked(int userHandle, List<String> packages) {
+ DevicePolicyData policy = getUserData(userHandle);
+ policy.mLockTaskPackages = packages;
+
+ // Store the settings persistently.
+ saveSettingsLocked(userHandle);
+ updateLockTaskPackagesLocked(packages, userHandle);
+ }
+
/**
* This function returns the list of components allowed to start the task lock mode.
*/
public String[] getLockTaskPackages(ComponentName who) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
int userHandle = Binder.getCallingUserHandle().getIdentifier();
- DevicePolicyData policy = getUserData(userHandle);
- return policy.mLockTaskPackages.toArray(new String[0]);
+ final List<String> packages = getLockTaskPackagesLocked(userHandle);
+ return packages.toArray(new String[packages.size()]);
}
}
+ private List<String> getLockTaskPackagesLocked(int userHandle) {
+ final DevicePolicyData policy = getUserData(userHandle);
+ return policy.mLockTaskPackages;
+ }
+
/**
* This function lets the caller know whether the given package is allowed to start the
* lock task mode.
@@ -5323,16 +5794,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setGlobalSetting(ComponentName who, String setting, String value) {
final ContentResolver contentResolver = mContext.getContentResolver();
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
if (!GLOBAL_SETTINGS_WHITELIST.contains(setting)) {
- throw new SecurityException(String.format(
- "Permission denial: device owners cannot update %1$s", setting));
+ // BLUETOOTH_ON and WIFI_ON used to be supported but not any more. We do not want to
+ // throw a SecurityException not to break apps.
+ if (!Settings.Global.BLUETOOTH_ON.equals(setting)
+ && !Settings.Global.WIFI_ON.equals(setting)) {
+ throw new SecurityException(String.format(
+ "Permission denial: device owners cannot update %1$s", setting));
+ }
+ }
+
+ if (Settings.Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) {
+ // ignore if it contradicts an existing policy
+ long timeMs = getMaximumTimeToLock(who, UserHandle.getCallingUserId());
+ if (timeMs > 0 && timeMs < Integer.MAX_VALUE) {
+ return;
+ }
}
long id = Binder.clearCallingIdentity();
@@ -5346,13 +5828,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setSecureSetting(ComponentName who, String setting, String value) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
int callingUserId = UserHandle.getCallingUserId();
final ContentResolver contentResolver = mContext.getContentResolver();
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
ActiveAdmin activeAdmin =
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -5377,18 +5857,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setMasterVolumeMuted(ComponentName who, boolean on) {
- final ContentResolver contentResolver = mContext.getContentResolver();
-
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
IAudioService iAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
- try{
- iAudioService.setMasterMute(on, 0, who.getPackageName(), null);
+ try {
+ iAudioService.setMasterMute(on, 0, who.getPackageName());
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed to setMasterMute", re);
}
@@ -5397,12 +5873,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean isMasterVolumeMuted(ComponentName who) {
- final ContentResolver contentResolver = mContext.getContentResolver();
-
+ Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (this) {
- if (who == null) {
- throw new NullPointerException("ComponentName is null");
- }
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
AudioManager audioManager =
@@ -5411,6 +5883,108 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ @Override
+ public void setUserIcon(ComponentName who, Bitmap icon) {
+ synchronized (this) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+ int userId = UserHandle.getCallingUserId();
+ long id = Binder.clearCallingIdentity();
+ try {
+ mUserManager.setUserIcon(userId, icon);
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+ }
+
+ @Override
+ public void sendDeviceInitializerStatus(int statusCode, String description) {
+ synchronized (this) {
+ String packageName = getDeviceInitializer();
+ if (packageName == null) {
+ throw new SecurityException("No device initializers");
+ }
+ UserHandle callingUser = Binder.getCallingUserHandle();
+ int deviceInitializerUid = -1;
+ try {
+ deviceInitializerUid = mContext.getPackageManager().getPackageUid(
+ packageName, callingUser.getIdentifier());
+ } catch (NameNotFoundException e) {
+ throw new SecurityException(e);
+ }
+ if (Binder.getCallingUid() != deviceInitializerUid) {
+ throw new SecurityException("Caller must be a device initializer");
+ }
+ long id = Binder.clearCallingIdentity();
+ try {
+ Intent intent = new Intent(
+ DevicePolicyManager.ACTION_SEND_DEVICE_INITIALIZER_STATUS);
+ intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_INITIALIZER_STATUS_CODE,
+ statusCode);
+ intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_INITIALIZER_STATUS_DESCRIPTION,
+ description);
+ mContext.sendBroadcastAsUser(intent, callingUser,
+ android.Manifest.permission.RECEIVE_DEVICE_INITIALIZER_STATUS);
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+ }
+
+ @Override
+ public boolean setKeyguardEnabledState(ComponentName who, boolean enabled) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+ final int userId = UserHandle.getCallingUserId();
+ LockPatternUtils utils = new LockPatternUtils(mContext);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ // disallow disabling the keyguard if a password is currently set
+ if (!enabled && utils.isSecure(userId)) {
+ return false;
+ }
+ utils.setLockScreenDisabled(!enabled, userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return true;
+ }
+
+ @Override
+ public void setStatusBarEnabledState(ComponentName who, boolean enabled) {
+ int userId = UserHandle.getCallingUserId();
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ DevicePolicyData policy = getUserData(userId);
+ if (policy.mStatusBarEnabledState != enabled) {
+ policy.mStatusBarEnabledState = enabled;
+ setStatusBarEnabledStateInternal(enabled, userId);
+ saveSettingsLocked(userId);
+ }
+ }
+ }
+
+ private void setStatusBarEnabledStateInternal(boolean enabled, int userId) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
+ ServiceManager.checkService(Context.STATUS_BAR_SERVICE));
+ if (statusBarService != null) {
+ int flags = enabled ? StatusBarManager.DISABLE_NONE : STATUS_BAR_DISABLE_MASK;
+ statusBarService.disableForUser(flags, mToken, mContext.getPackageName(), userId);
+ }
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Failed to disable the status bar", e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* We need to update the internal state of whether a user has completed setup once. After
* that, we ignore any changes that reset the Settings.Secure.USER_SETUP_COMPLETE changes
@@ -5431,6 +6005,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!policy.mUserSetupComplete) {
policy.mUserSetupComplete = true;
synchronized (this) {
+ // The DeviceInitializer was whitelisted but now should be removed.
+ removeDeviceInitializerFromLockTaskPackages(userHandle);
saveSettingsLocked(userHandle);
}
}
@@ -5438,6 +6014,35 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private void addDeviceInitializerToLockTaskPackagesLocked(int userHandle) {
+ if (hasUserSetupCompleted(userHandle)) {
+ return;
+ }
+
+ final String deviceInitializerPackage = getDeviceInitializer();
+ if (deviceInitializerPackage == null) {
+ return;
+ }
+
+ final List<String> packages = getLockTaskPackagesLocked(userHandle);
+ if (!packages.contains(deviceInitializerPackage)) {
+ packages.add(deviceInitializerPackage);
+ setLockTaskPackagesLocked(userHandle, packages);
+ }
+ }
+
+ private void removeDeviceInitializerFromLockTaskPackages(int userHandle) {
+ final String deviceInitializerPackage = getDeviceInitializer();
+ if (deviceInitializerPackage == null) {
+ return;
+ }
+
+ List<String> packages = getLockTaskPackagesLocked(userHandle);
+ if (packages.remove(deviceInitializerPackage)) {
+ setLockTaskPackagesLocked(userHandle, packages);
+ }
+ }
+
private class SetupContentObserver extends ContentObserver {
private final Uri mUserSetupComplete = Settings.Secure.getUriFor(
@@ -5498,6 +6103,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ @Override
+ public boolean isActiveAdminWithPolicy(int uid, int reqPolicy) {
+ final int userId = UserHandle.getUserId(uid);
+ synchronized(DevicePolicyManagerService.this) {
+ return getActiveAdminWithPolicyForUidLocked(null, reqPolicy, uid) != null;
+ }
+ }
+
private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
synchronized (DevicePolicyManagerService.this) {
@@ -5510,4 +6123,92 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
+
+ /**
+ * Returns true if specified admin is allowed to limit passwords and has a
+ * {@code passwordQuality} of at least {@code minPasswordQuality}
+ */
+ private static boolean isLimitPasswordAllowed(ActiveAdmin admin, int minPasswordQuality) {
+ if (admin.passwordQuality < minPasswordQuality) {
+ return false;
+ }
+ return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ }
+
+ @Override
+ public void setSystemUpdatePolicy(ComponentName who, PersistableBundle policy) {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ if (policy == null) {
+ mDeviceOwner.clearSystemUpdatePolicy();
+ } else {
+ mDeviceOwner.setSystemUpdatePolicy(policy);
+ }
+ mDeviceOwner.writeOwnerFile();
+ }
+ mContext.sendBroadcastAsUser(
+ new Intent(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED),
+ UserHandle.OWNER);
+ }
+
+ @Override
+ public PersistableBundle getSystemUpdatePolicy() {
+ synchronized (this) {
+ return mDeviceOwner.getSystemUpdatePolicy();
+ }
+ }
+
+ /**
+ * Checks if the caller of the method is the device owner app or device initialization app.
+ *
+ * @param callerUid UID of the caller.
+ * @return true if the caller is the device owner app or device initializer.
+ */
+ private boolean isCallerDeviceOwnerOrInitializer(int callerUid) {
+ String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid);
+ for (String pkg : pkgs) {
+ if (isDeviceOwner(pkg) || isDeviceInitializer(pkg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void notifyPendingSystemUpdate(long updateReceivedTime) {
+ mContext.enforceCallingOrSelfPermission(permission.NOTIFY_PENDING_SYSTEM_UPDATE,
+ "Only the system update service can broadcast update information");
+
+ if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
+ Slog.w(LOG_TAG, "Only the system update service in the primary user" +
+ "can broadcast update information.");
+ return;
+ }
+ Intent intent = new Intent(DeviceAdminReceiver.ACTION_NOTIFY_PENDING_SYSTEM_UPDATE);
+ intent.putExtra(DeviceAdminReceiver.EXTRA_SYSTEM_UPDATE_RECEIVED_TIME,
+ updateReceivedTime);
+
+ synchronized (this) {
+ String deviceOwnerPackage = getDeviceOwner();
+ if (deviceOwnerPackage == null) {
+ return;
+ }
+
+ try {
+ ActivityInfo[] receivers = mContext.getPackageManager().getPackageInfo(
+ deviceOwnerPackage, PackageManager.GET_RECEIVERS).receivers;
+ if (receivers != null) {
+ for (int i = 0; i < receivers.length; i++) {
+ if (permission.BIND_DEVICE_ADMIN.equals(receivers[i].permission)) {
+ intent.setComponent(new ComponentName(deviceOwnerPackage,
+ receivers[i].name));
+ mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot find device owner package", e);
+ }
+ }
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0705fbd..593853c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -26,16 +26,11 @@ import android.content.ComponentName;
import android.content.ContentResolver;
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.media.tv.TvInputManager;
import android.os.Build;
import android.os.Environment;
import android.os.FactoryTest;
-import android.os.Handler;
-import android.os.IBinder;
import android.os.IPowerManager;
import android.os.Looper;
import android.os.RemoteException;
@@ -44,22 +39,21 @@ import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.service.dreams.DreamService;
+import android.os.storage.IMountService;
import android.util.DisplayMetrics;
import android.util.EventLog;
-import android.util.Log;
import android.util.Slog;
import android.view.WindowManager;
import android.webkit.WebViewFactory;
import com.android.internal.R;
import com.android.internal.os.BinderInternal;
-import com.android.internal.os.Zygote;
import com.android.internal.os.SamplingProfilerIntegration;
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.audio.AudioService;
+import com.android.server.camera.CameraService;
import com.android.server.clipboard.ClipboardService;
import com.android.server.content.ContentService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -69,7 +63,6 @@ import com.android.server.fingerprint.FingerprintService;
import com.android.server.hdmi.HdmiControlService;
import com.android.server.input.InputManagerService;
import com.android.server.job.JobSchedulerService;
-import com.android.server.lights.LightsManager;
import com.android.server.lights.LightsService;
import com.android.server.media.MediaRouterService;
import com.android.server.media.MediaSessionService;
@@ -83,6 +76,7 @@ import com.android.server.pm.Installer;
import com.android.server.pm.LauncherAppsService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerService;
+import com.android.server.power.DeviceIdleController;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
import com.android.server.restrictions.RestrictionsManagerService;
@@ -131,6 +125,8 @@ public final class SystemServer {
"com.android.server.print.PrintManagerService";
private static final String USB_SERVICE_CLASS =
"com.android.server.usb.UsbService$Lifecycle";
+ private static final String MIDI_SERVICE_CLASS =
+ "com.android.server.midi.MidiService$Lifecycle";
private static final String WIFI_SERVICE_CLASS =
"com.android.server.wifi.WifiService";
private static final String WIFI_P2P_SERVICE_CLASS =
@@ -139,6 +135,8 @@ public final class SystemServer {
"com.android.server.ethernet.EthernetService";
private static final String JOB_SCHEDULER_SERVICE_CLASS =
"com.android.server.job.JobSchedulerService";
+ private static final String MOUNT_SERVICE_CLASS =
+ "com.android.server.MountService$Lifecycle";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
private final int mFactoryTestMode;
@@ -327,6 +325,9 @@ public final class SystemServer {
// initialize power management features.
mActivityManagerService.initPowerManagement();
+ // Manages LEDs and display backlight so we need it to bring up the display.
+ mSystemServiceManager.startService(LightsService.class);
+
// Display manager is needed to provide display metrics before package manager
// starts up.
mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class);
@@ -365,9 +366,6 @@ public final class SystemServer {
* Starts some essential services that are not tangled up in the bootstrap process.
*/
private void startCoreServices() {
- // Manages LEDs and display backlight.
- mSystemServiceManager.startService(LightsService.class);
-
// Tracks the battery level. Requires LightService.
mSystemServiceManager.startService(BatteryService.class);
@@ -392,7 +390,7 @@ public final class SystemServer {
ContentService contentService = null;
VibratorService vibrator = null;
IAlarmManager alarm = null;
- MountService mountService = null;
+ IMountService mountService = null;
NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
@@ -410,11 +408,11 @@ public final class SystemServer {
ConsumerIrService consumerIr = null;
AudioService audioService = null;
MmsServiceBroker mmsService = null;
+ EntropyMixer entropyMixer = null;
+ CameraService cameraService = null;
boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
- boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false);
boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
- boolean disableTelephony = SystemProperties.getBoolean("config.disable_telephony", false);
boolean disableLocation = SystemProperties.getBoolean("config.disable_location", false);
boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false);
boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false);
@@ -436,10 +434,13 @@ public final class SystemServer {
ServiceManager.addService("telephony.registry", telephonyRegistry);
Slog.i(TAG, "Entropy Mixer");
- ServiceManager.addService("entropy", new EntropyMixer(context));
+ entropyMixer = new EntropyMixer(context);
mContentResolver = context.getContentResolver();
+ Slog.i(TAG, "Camera Service");
+ mSystemServiceManager.startService(CameraService.class);
+
// The AccountManager must come before the ContentService
try {
// TODO: seems like this should be disable-able, but req'd by ContentService
@@ -526,23 +527,20 @@ public final class SystemServer {
// Bring up services needed for UI.
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
- //if (!disableNonCoreServices) { // TODO: View depends on these; mock them?
- if (true) {
- try {
- Slog.i(TAG, "Input Method Service");
- imm = new InputMethodManagerService(context, wm);
- ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
- } catch (Throwable e) {
- reportWtf("starting Input Manager Service", e);
- }
+ try {
+ Slog.i(TAG, "Input Method Service");
+ imm = new InputMethodManagerService(context, wm);
+ ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
+ } catch (Throwable e) {
+ reportWtf("starting Input Manager Service", e);
+ }
- try {
- Slog.i(TAG, "Accessibility Manager");
- ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,
- new AccessibilityManagerService(context));
- } catch (Throwable e) {
- reportWtf("starting Accessibility Manager", e);
- }
+ try {
+ Slog.i(TAG, "Accessibility Manager");
+ ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,
+ new AccessibilityManagerService(context));
+ } catch (Throwable e) {
+ reportWtf("starting Accessibility Manager", e);
}
}
@@ -560,15 +558,19 @@ public final class SystemServer {
* NotificationManagerService is dependant on MountService,
* (for media / usb notifications) so we must start MountService first.
*/
- Slog.i(TAG, "Mount Service");
- mountService = new MountService(context);
- ServiceManager.addService("mount", mountService);
+ mSystemServiceManager.startService(MOUNT_SERVICE_CLASS);
+ mountService = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
} catch (Throwable e) {
reportWtf("starting Mount Service", e);
}
}
}
+ // We start this here so that we update our configuration to set watch or television
+ // as appropriate.
+ mSystemServiceManager.startService(UiModeManagerService.class);
+
try {
mPackageManagerService.performBootDexOpt();
} catch (Throwable e) {
@@ -719,7 +721,10 @@ public final class SystemServer {
* first before continuing.
*/
if (mountService != null && !mOnlyCore) {
- mountService.waitForAsecScan();
+ try {
+ mountService.waitForAsecScan();
+ } catch (RemoteException ignored) {
+ }
}
try {
@@ -790,32 +795,33 @@ public final class SystemServer {
}
}
- if (!disableMedia && !"0".equals(SystemProperties.get("system_init.startaudioservice"))) {
- try {
- Slog.i(TAG, "Audio Service");
- audioService = new AudioService(context);
- ServiceManager.addService(Context.AUDIO_SERVICE, audioService);
- } catch (Throwable e) {
- reportWtf("starting Audio Service", e);
- }
+ try {
+ Slog.i(TAG, "Audio Service");
+ audioService = new AudioService(context);
+ ServiceManager.addService(Context.AUDIO_SERVICE, audioService);
+ } catch (Throwable e) {
+ reportWtf("starting Audio Service", e);
}
if (!disableNonCoreServices) {
mSystemServiceManager.startService(DockObserver.class);
}
- if (!disableMedia) {
- try {
- Slog.i(TAG, "Wired Accessory Manager");
- // Listen for wired headset changes
- inputManager.setWiredAccessoryCallbacks(
- new WiredAccessoryManager(context, inputManager));
- } catch (Throwable e) {
- reportWtf("starting WiredAccessoryManager", e);
- }
+ try {
+ Slog.i(TAG, "Wired Accessory Manager");
+ // Listen for wired headset changes
+ inputManager.setWiredAccessoryCallbacks(
+ new WiredAccessoryManager(context, inputManager));
+ } catch (Throwable e) {
+ reportWtf("starting WiredAccessoryManager", e);
}
if (!disableNonCoreServices) {
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+ // Start MIDI Manager service
+ mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
+ }
+
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
|| mPackageManager.hasSystemFeature(
PackageManager.FEATURE_USB_ACCESSORY)) {
@@ -835,8 +841,6 @@ public final class SystemServer {
mSystemServiceManager.startService(TwilightService.class);
- mSystemServiceManager.startService(UiModeManagerService.class);
-
mSystemServiceManager.startService(JobSchedulerService.class);
if (!disableNonCoreServices) {
@@ -881,14 +885,12 @@ public final class SystemServer {
}
}
- if (!disableMedia) {
- try {
- Slog.i(TAG, "CommonTimeManagementService");
- commonTimeMgmtService = new CommonTimeManagementService(context);
- ServiceManager.addService("commontime_management", commonTimeMgmtService);
- } catch (Throwable e) {
- reportWtf("starting CommonTimeManagementService service", e);
- }
+ try {
+ Slog.i(TAG, "CommonTimeManagementService");
+ commonTimeMgmtService = new CommonTimeManagementService(context);
+ ServiceManager.addService("commontime_management", commonTimeMgmtService);
+ } catch (Throwable e) {
+ reportWtf("starting CommonTimeManagementService service", e);
}
if (!disableNetwork) {
@@ -915,6 +917,11 @@ public final class SystemServer {
}
}
+ if (!disableNonCoreServices) {
+ ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
+ new GraphicsStatsService(context));
+ }
+
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
}
@@ -958,6 +965,7 @@ public final class SystemServer {
if (!disableNonCoreServices) {
mSystemServiceManager.startService(MediaProjectionManagerService.class);
+ mSystemServiceManager.startService(DeviceIdleController.class);
}
// Before things start rolling, be sure we have decided whether
@@ -1036,7 +1044,6 @@ public final class SystemServer {
}
// These are needed to propagate to the runnable below.
- final MountService mountServiceF = mountService;
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
@@ -1084,11 +1091,6 @@ public final class SystemServer {
reportWtf("starting System UI", e);
}
try {
- if (mountServiceF != null) mountServiceF.systemReady();
- } catch (Throwable e) {
- reportWtf("making Mount Service ready", e);
- }
- try {
if (networkScoreF != null) networkScoreF.systemReady();
} catch (Throwable e) {
reportWtf("making Network Score Service ready", e);
diff --git a/services/midi/Android.mk b/services/midi/Android.mk
new file mode 100644
index 0000000..faac01c
--- /dev/null
+++ b/services/midi/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.midi
+
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,java)
+
+LOCAL_JAVA_LIBRARIES := services.core
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
new file mode 100644
index 0000000..c1c5c56
--- /dev/null
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 2014 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.midi;
+
+import android.content.Context;
+import android.content.Intent;
+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.res.XmlResourceParser;
+import android.media.midi.IMidiDeviceListener;
+import android.media.midi.IMidiDeviceServer;
+import android.media.midi.IMidiManager;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceService;
+import android.media.midi.MidiDeviceStatus;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
+import com.android.server.SystemService;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+public class MidiService extends IMidiManager.Stub {
+
+ public static class Lifecycle extends SystemService {
+ private MidiService mMidiService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mMidiService = new MidiService(getContext());
+ publishBinderService(Context.MIDI_SERVICE, mMidiService);
+ }
+ }
+
+ private static final String TAG = "MidiService";
+
+ private final Context mContext;
+
+ // list of all our clients, keyed by Binder token
+ private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
+
+ // list of all devices, keyed by MidiDeviceInfo
+ private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo
+ = new HashMap<MidiDeviceInfo, Device>();
+
+ // list of all devices, keyed by IMidiDeviceServer
+ private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>();
+
+ // used for assigning IDs to MIDI devices
+ private int mNextDeviceId = 1;
+
+ private final PackageManager mPackageManager;
+
+ // PackageMonitor for listening to package changes
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ addPackageDeviceServers(packageName);
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ removePackageDeviceServers(packageName);
+ addPackageDeviceServers(packageName);
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ removePackageDeviceServers(packageName);
+ }
+ };
+
+ private final class Client implements IBinder.DeathRecipient {
+ // Binder token for this client
+ private final IBinder mToken;
+ // This client's UID
+ private final int mUid;
+ // This client's PID
+ private final int mPid;
+ // List of all receivers for this client
+ private final ArrayList<IMidiDeviceListener> mListeners
+ = new ArrayList<IMidiDeviceListener>();
+
+ public Client(IBinder token) {
+ mToken = token;
+ mUid = Binder.getCallingUid();
+ mPid = Binder.getCallingPid();
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public void addListener(IMidiDeviceListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void removeListener(IMidiDeviceListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ removeClient(mToken);
+ }
+ }
+
+ public void deviceAdded(Device device) {
+ // ignore private devices that our client cannot access
+ if (!device.isUidAllowed(mUid)) return;
+
+ MidiDeviceInfo deviceInfo = device.getDeviceInfo();
+ try {
+ for (IMidiDeviceListener listener : mListeners) {
+ listener.onDeviceAdded(deviceInfo);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+
+ public void deviceRemoved(Device device) {
+ // ignore private devices that our client cannot access
+ if (!device.isUidAllowed(mUid)) return;
+
+ MidiDeviceInfo deviceInfo = device.getDeviceInfo();
+ try {
+ for (IMidiDeviceListener listener : mListeners) {
+ listener.onDeviceRemoved(deviceInfo);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+
+ public void deviceStatusChanged(Device device, MidiDeviceStatus status) {
+ // ignore private devices that our client cannot access
+ if (!device.isUidAllowed(mUid)) return;
+
+ try {
+ for (IMidiDeviceListener listener : mListeners) {
+ listener.onDeviceStatusChanged(status);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+
+ public void binderDied() {
+ removeClient(mToken);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Client: UID: ");
+ sb.append(mUid);
+ sb.append(" PID: ");
+ sb.append(mPid);
+ sb.append(" listener count: ");
+ sb.append(mListeners.size());
+ return sb.toString();
+ }
+ }
+
+ private Client getClient(IBinder token) {
+ synchronized (mClients) {
+ Client client = mClients.get(token);
+ if (client == null) {
+ client = new Client(token);
+
+ try {
+ token.linkToDeath(client, 0);
+ } catch (RemoteException e) {
+ return null;
+ }
+ mClients.put(token, client);
+ }
+ return client;
+ }
+ }
+
+ private void removeClient(IBinder token) {
+ mClients.remove(token);
+ }
+
+ private final class Device implements IBinder.DeathRecipient {
+ private final IMidiDeviceServer mServer;
+ private final MidiDeviceInfo mDeviceInfo;
+ private MidiDeviceStatus mDeviceStatus;
+ private IBinder mDeviceStatusToken;
+ // ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only)
+ private final ServiceInfo mServiceInfo;
+ // UID of device implementation
+ private final int mUid;
+
+ public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
+ ServiceInfo serviceInfo, int uid) {
+ mServer = server;
+ mDeviceInfo = deviceInfo;
+ mServiceInfo = serviceInfo;
+ mUid = uid;
+ }
+
+ public MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
+
+ public MidiDeviceStatus getDeviceStatus() {
+ return mDeviceStatus;
+ }
+
+ public void setDeviceStatus(IBinder token, MidiDeviceStatus status) {
+ mDeviceStatus = status;
+
+ if (mDeviceStatusToken == null && token != null) {
+ // register a death recipient so we can clear the status when the device dies
+ try {
+ token.linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ // reset to default status and clear the token
+ mDeviceStatus = new MidiDeviceStatus(mDeviceInfo);
+ mDeviceStatusToken = null;
+ notifyDeviceStatusChanged(Device.this, mDeviceStatus);
+ }
+ }, 0);
+ mDeviceStatusToken = token;
+ } catch (RemoteException e) {
+ // reset to default status
+ mDeviceStatus = new MidiDeviceStatus(mDeviceInfo);
+ }
+ }
+ }
+
+ public IMidiDeviceServer getDeviceServer() {
+ return mServer;
+ }
+
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ public String getPackageName() {
+ return (mServiceInfo == null ? null : mServiceInfo.packageName);
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public boolean isUidAllowed(int uid) {
+ return (!mDeviceInfo.isPrivate() || mUid == uid);
+ }
+
+ public void binderDied() {
+ synchronized (mDevicesByInfo) {
+ if (mDevicesByInfo.remove(mDeviceInfo) != null) {
+ removeDeviceLocked(this);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Device: ");
+ sb.append(mDeviceInfo);
+ sb.append(" UID: ");
+ sb.append(mUid);
+ return sb.toString();
+ }
+ }
+
+ public MidiService(Context context) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mPackageMonitor.register(context, null, true);
+
+ Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
+ List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent,
+ PackageManager.GET_META_DATA);
+ if (resolveInfos != null) {
+ int count = resolveInfos.size();
+ for (int i = 0; i < count; i++) {
+ ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
+ if (serviceInfo != null) {
+ addPackageDeviceServer(serviceInfo);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void registerListener(IBinder token, IMidiDeviceListener listener) {
+ Client client = getClient(token);
+ if (client == null) return;
+ client.addListener(listener);
+ }
+
+ @Override
+ public void unregisterListener(IBinder token, IMidiDeviceListener listener) {
+ Client client = getClient(token);
+ if (client == null) return;
+ client.removeListener(listener);
+ }
+
+ private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0];
+
+ public MidiDeviceInfo[] getDeviceList() {
+ ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
+ int uid = Binder.getCallingUid();
+
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ if (device.isUidAllowed(uid)) {
+ deviceInfos.add(device.getDeviceInfo());
+ }
+ }
+ }
+
+ return deviceInfos.toArray(EMPTY_DEVICE_INFO_ARRAY);
+ }
+
+ @Override
+ public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
+ Device device = mDevicesByInfo.get(deviceInfo);
+ if (device == null) {
+ Log.e(TAG, "device not found in openDevice: " + deviceInfo);
+ return null;
+ }
+
+ if (!device.isUidAllowed(Binder.getCallingUid())) {
+ throw new SecurityException("Attempt to open private device with wrong UID");
+ }
+
+ return device.getDeviceServer();
+ }
+
+ @Override
+ public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
+ int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
+ Bundle properties, int type) {
+ int uid = Binder.getCallingUid();
+ if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
+ throw new SecurityException("only system can create USB devices");
+ }
+
+ synchronized (mDevicesByInfo) {
+ return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
+ outputPortNames, properties, server, null, false, uid);
+ }
+ }
+
+ @Override
+ public void unregisterDeviceServer(IMidiDeviceServer server) {
+ synchronized (mDevicesByInfo) {
+ Device device = mDevicesByServer.get(server.asBinder());
+ if (device != null) {
+ mDevicesByInfo.remove(device.getDeviceInfo());
+ removeDeviceLocked(device);
+ }
+ }
+ }
+
+ @Override
+ public MidiDeviceInfo getServiceDeviceInfo(String packageName, String className) {
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ ServiceInfo serviceInfo = device.getServiceInfo();
+ if (serviceInfo != null &&
+ packageName.equals(serviceInfo.packageName) &&
+ className.equals(serviceInfo.name)) {
+ return device.getDeviceInfo();
+ }
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public MidiDeviceStatus getDeviceStatus(MidiDeviceInfo deviceInfo) {
+ Device device = mDevicesByInfo.get(deviceInfo);
+ if (device == null) {
+ throw new IllegalArgumentException("no such device for " + deviceInfo);
+ }
+ return device.getDeviceStatus();
+ }
+
+ @Override
+ public void setDeviceStatus(IBinder token, MidiDeviceStatus status) {
+ MidiDeviceInfo deviceInfo = status.getDeviceInfo();
+ Device device = mDevicesByInfo.get(deviceInfo);
+ if (device == null) {
+ // Just return quietly here if device no longer exists
+ return;
+ }
+ if (Binder.getCallingUid() != device.getUid()) {
+ throw new SecurityException("setDeviceStatus() caller UID " + Binder.getCallingUid()
+ + " does not match device's UID " + device.getUid());
+ }
+ device.setDeviceStatus(token, status);
+ notifyDeviceStatusChanged(device, status);
+ }
+
+ private void notifyDeviceStatusChanged(Device device, MidiDeviceStatus status) {
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceStatusChanged(device, status);
+ }
+ }
+ }
+
+ // synchronize on mDevicesByInfo
+ private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
+ String[] inputPortNames, String[] outputPortNames, Bundle properties,
+ IMidiDeviceServer server, ServiceInfo serviceInfo,
+ boolean isPrivate, int uid) {
+
+ int id = mNextDeviceId++;
+ MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
+ inputPortNames, outputPortNames, properties, isPrivate);
+ Device device = new Device(server, deviceInfo, serviceInfo, uid);
+
+ if (server != null) {
+ IBinder binder = server.asBinder();
+ try {
+ binder.linkToDeath(device, 0);
+ } catch (RemoteException e) {
+ return null;
+ }
+ mDevicesByServer.put(binder, device);
+ }
+ mDevicesByInfo.put(deviceInfo, device);
+
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceAdded(device);
+ }
+ }
+
+ return deviceInfo;
+ }
+
+ // synchronize on mDevicesByInfo
+ private void removeDeviceLocked(Device device) {
+ IMidiDeviceServer server = device.getDeviceServer();
+ if (server != null) {
+ mDevicesByServer.remove(server);
+ }
+
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceRemoved(device);
+ }
+ }
+ }
+
+ private void addPackageDeviceServers(String packageName) {
+ PackageInfo info;
+
+ try {
+ info = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
+ return;
+ }
+
+ ServiceInfo[] services = info.services;
+ if (services == null) return;
+ for (int i = 0; i < services.length; i++) {
+ addPackageDeviceServer(services[i]);
+ }
+ }
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ private void addPackageDeviceServer(ServiceInfo serviceInfo) {
+ XmlResourceParser parser = null;
+
+ try {
+ parser = serviceInfo.loadXmlMetaData(mPackageManager,
+ MidiDeviceService.SERVICE_INTERFACE);
+ if (parser == null) return;
+
+ Bundle properties = null;
+ int numInputPorts = 0;
+ int numOutputPorts = 0;
+ boolean isPrivate = false;
+ ArrayList<String> inputPortNames = new ArrayList<String>();
+ ArrayList<String> outputPortNames = new ArrayList<String>();
+
+ while (true) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ break;
+ } else if (eventType == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ if ("device".equals(tagName)) {
+ if (properties != null) {
+ Log.w(TAG, "nested <device> elements in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ properties = new Bundle();
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_SERVICE_INFO, serviceInfo);
+ numInputPorts = 0;
+ numOutputPorts = 0;
+ isPrivate = false;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ if ("private".equals(name)) {
+ isPrivate = "true".equals(value);
+ } else {
+ properties.putString(name, value);
+ }
+ }
+ } else if ("input-port".equals(tagName)) {
+ if (properties == null) {
+ Log.w(TAG, "<input-port> outside of <device> in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ numInputPorts++;
+
+ String portName = null;
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ if ("name".equals(name)) {
+ portName = value;
+ break;
+ }
+ }
+ inputPortNames.add(portName);
+ } else if ("output-port".equals(tagName)) {
+ if (properties == null) {
+ Log.w(TAG, "<output-port> outside of <device> in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ numOutputPorts++;
+
+ String portName = null;
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ if ("name".equals(name)) {
+ portName = value;
+ break;
+ }
+ }
+ outputPortNames.add(portName);
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ String tagName = parser.getName();
+ if ("device".equals(tagName)) {
+ if (properties != null) {
+ if (numInputPorts == 0 && numOutputPorts == 0) {
+ Log.w(TAG, "<device> with no ports in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+
+ int uid;
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+ serviceInfo.packageName, 0);
+ uid = appInfo.uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "could not fetch ApplicationInfo for "
+ + serviceInfo.packageName);
+ continue;
+ }
+
+ synchronized (mDevicesByInfo) {
+ addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL,
+ numInputPorts, numOutputPorts,
+ inputPortNames.toArray(EMPTY_STRING_ARRAY),
+ outputPortNames.toArray(EMPTY_STRING_ARRAY),
+ properties, null, serviceInfo, isPrivate, uid);
+ }
+ // setting properties to null signals that we are no longer
+ // processing a <device>
+ properties = null;
+ inputPortNames.clear();
+ outputPortNames.clear();
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + serviceInfo.toString(), e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ private void removePackageDeviceServers(String packageName) {
+ synchronized (mDevicesByInfo) {
+ Iterator<Device> iterator = mDevicesByInfo.values().iterator();
+ while (iterator.hasNext()) {
+ Device device = iterator.next();
+ if (packageName.equals(device.getPackageName())) {
+ iterator.remove();
+ removeDeviceLocked(device);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+
+ pw.println("MIDI Manager State:");
+ pw.increaseIndent();
+
+ pw.println("Devices:");
+ pw.increaseIndent();
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ pw.println(device.toString());
+ }
+ }
+ pw.decreaseIndent();
+
+ pw.println("Clients:");
+ pw.increaseIndent();
+ synchronized (mClients) {
+ for (Client client : mClients.values()) {
+ pw.println(client.toString());
+ }
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index e9203a4..ab56493 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -92,6 +92,7 @@ public class DhcpClient extends BaseDhcpStateMachine {
private static final boolean DBG = true;
private static final boolean STATE_DBG = false;
private static final boolean MSG_DBG = false;
+ private static final boolean PACKET_DBG = true;
// Timers and timeouts.
private static final int SECONDS = 1000;
@@ -329,6 +330,9 @@ public class DhcpClient extends BaseDhcpStateMachine {
if (packet != null) {
maybeLog("Received packet: " + packet);
sendMessage(CMD_RECEIVED_PACKET, packet);
+ } else if (PACKET_DBG) {
+ Log.d(TAG,
+ "Can't parse packet" + HexDump.dumpHexString(mPacket, 0, length));
}
} catch (IOException|ErrnoException e) {
if (!stopped) {
diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/services/net/java/android/net/dhcp/DhcpDeclinePacket.java
index 9d985ac..4a22b65 100644
--- a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java
+++ b/services/net/java/android/net/dhcp/DhcpDeclinePacket.java
@@ -53,6 +53,9 @@ class DhcpDeclinePacket extends DhcpPacket {
* Adds optional parameters to the DECLINE packet.
*/
void finishPacket(ByteBuffer buffer) {
- // None needed
+ addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DECLINE);
+ addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
+ // RFC 2131 says we MUST NOT include our common client TLVs or the parameter request list.
+ addTlvEnd(buffer);
}
}
diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java
index a031080..ed0fdc6 100644
--- a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java
@@ -52,6 +52,7 @@ class DhcpDiscoverPacket extends DhcpPacket {
*/
void finishPacket(ByteBuffer buffer) {
addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DISCOVER);
+ addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
addCommonClientTlvs(buffer);
addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
addTlvEnd(buffer);
diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/services/net/java/android/net/dhcp/DhcpInformPacket.java
index 8bc7cdd..2434fc9 100644
--- a/services/net/java/android/net/dhcp/DhcpInformPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpInformPacket.java
@@ -53,12 +53,9 @@ class DhcpInformPacket extends DhcpPacket {
* Adds additional parameters to the INFORM packet.
*/
void finishPacket(ByteBuffer buffer) {
- byte[] clientId = new byte[7];
-
- clientId[0] = CLIENT_ID_ETHER;
- System.arraycopy(mClientMac, 0, clientId, 1, 6);
-
- addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST);
+ addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_INFORM);
+ addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
+ addCommonClientTlvs(buffer);
addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
addTlvEnd(buffer);
}
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index d41629d..a64ee6f 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -285,6 +285,16 @@ abstract class DhcpPacket {
}
/**
+ * Returns the client ID. This follows RFC 2132 and is based on the hardware address.
+ */
+ public byte[] getClientId() {
+ byte[] clientId = new byte[mClientMac.length + 1];
+ clientId[0] = CLIENT_ID_ETHER;
+ System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length);
+ return clientId;
+ }
+
+ /**
* Creates a new L3 packet (including IP header) containing the
* DHCP udp packet. This method relies upon the delegated method
* finishPacket() to insert the per-packet contents.
diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/services/net/java/android/net/dhcp/DhcpRequestPacket.java
index 42b7b0c..5d378b8 100644
--- a/services/net/java/android/net/dhcp/DhcpRequestPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpRequestPacket.java
@@ -56,20 +56,14 @@ class DhcpRequestPacket extends DhcpPacket {
* Adds the optional parameters to the client-generated REQUEST packet.
*/
void finishPacket(ByteBuffer buffer) {
- byte[] clientId = new byte[7];
-
- // assemble client identifier
- clientId[0] = CLIENT_ID_ETHER;
- System.arraycopy(mClientMac, 0, clientId, 1, 6);
-
addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST);
+ addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
if (!INADDR_ANY.equals(mRequestedIp)) {
addTlv(buffer, DHCP_REQUESTED_IP, mRequestedIp);
}
if (!INADDR_ANY.equals(mServerIdentifier)) {
addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
}
- addTlv(buffer, DHCP_CLIENT_IDENTIFIER, clientId);
addCommonClientTlvs(buffer);
addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
addTlvEnd(buffer);
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 6785cb8..34347cf 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -761,7 +761,7 @@ public final class PrintManagerService extends SystemService {
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setShowWhen(true)
- .setColor(mContext.getResources().getColor(
+ .setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
NotificationManager notificationManager = (NotificationManager) mContext
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 33edb11..ae19dac 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -204,8 +204,8 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
IntentSender intentSender = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(mUserId))
- .getIntentSender();
+ | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ null, new UserHandle(mUserId)) .getIntentSender();
Bundle result = new Bundle();
result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
index 218f899..946d28e 100644
--- a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
+++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
@@ -16,35 +16,20 @@
package com.android.server.restrictions;
-import android.Manifest;
-import android.accounts.IAccountAuthenticator;
import android.app.AppGlobals;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.app.admin.IDevicePolicyManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
-import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.IUserManager;
import android.os.PersistableBundle;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.os.UserManager;
import android.util.Log;
import com.android.internal.util.ArrayUtils;
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index c198900..48d8ffb 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -16,7 +16,7 @@
package com.android.server;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -157,7 +157,7 @@ public class ConnectivityServiceTest extends AndroidTestCase {
// mMobile.link.addRoute(MOBILE_ROUTE_V6);
// mMobile.doReturnDefaults();
//
-// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION);
// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
// nextConnBroadcast.get();
//
@@ -177,7 +177,7 @@ public class ConnectivityServiceTest extends AndroidTestCase {
// mMobile.link.addRoute(MOBILE_ROUTE_V6);
// mMobile.doReturnDefaults();
//
-// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION);
// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
// nextConnBroadcast.get();
//
@@ -193,7 +193,7 @@ public class ConnectivityServiceTest extends AndroidTestCase {
// // expect that mobile will be torn down
// doReturn(true).when(mMobile.tracker).teardown();
//
-// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION);
// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget();
// nextConnBroadcast.get();
//
@@ -212,7 +212,7 @@ public class ConnectivityServiceTest extends AndroidTestCase {
// mMobile.link.clear();
// mMobile.doReturnDefaults();
//
-// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION);
// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
// nextConnBroadcast.get();
//
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
index bf0e75d..dae8447 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
@@ -219,31 +219,31 @@ public class LockSettingsStorageTests extends AndroidTestCase {
public void testPassword_Write() {
mStorage.writePasswordHash("thepassword".getBytes(), 0);
- assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0));
+ assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0).hash);
mStorage.clearCache();
- assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0));
+ assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0).hash);
}
public void testPassword_WriteProfileWritesParent() {
mStorage.writePasswordHash("parentpasswordd".getBytes(), 1);
mStorage.writePasswordHash("profilepassword".getBytes(), 2);
- assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1));
- assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2));
+ assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1).hash);
+ assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2).hash);
mStorage.clearCache();
- assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1));
- assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2));
+ assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1).hash);
+ assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2).hash);
}
public void testPassword_WriteParentWritesProfile() {
mStorage.writePasswordHash("profilepassword".getBytes(), 2);
mStorage.writePasswordHash("parentpasswordd".getBytes(), 1);
- assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1));
- assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2));
+ assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1).hash);
+ assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2).hash);
mStorage.clearCache();
- assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1));
- assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2));
+ assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1).hash);
+ assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2).hash);
}
public void testPattern_Default() {
@@ -253,31 +253,31 @@ public class LockSettingsStorageTests extends AndroidTestCase {
public void testPattern_Write() {
mStorage.writePatternHash("thepattern".getBytes(), 0);
- assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0));
+ assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0).hash);
mStorage.clearCache();
- assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0));
+ assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0).hash);
}
public void testPattern_WriteProfileWritesParent() {
mStorage.writePatternHash("parentpatternn".getBytes(), 1);
mStorage.writePatternHash("profilepattern".getBytes(), 2);
- assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1));
- assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2));
+ assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1).hash);
+ assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2).hash);
mStorage.clearCache();
- assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1));
- assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2));
+ assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1).hash);
+ assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2).hash);
}
public void testPattern_WriteParentWritesProfile() {
mStorage.writePatternHash("profilepattern".getBytes(), 2);
mStorage.writePatternHash("parentpatternn".getBytes(), 1);
- assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1));
- assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2));
+ assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1).hash);
+ assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2).hash);
mStorage.clearCache();
- assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1));
- assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2));
+ assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1).hash);
+ assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2).hash);
}
public void testPrefetch() {
@@ -289,8 +289,8 @@ public class LockSettingsStorageTests extends AndroidTestCase {
mStorage.prefetchUser(0);
assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0));
- assertArrayEquals("pattern".getBytes(), mStorage.readPatternHash(0));
- assertArrayEquals("password".getBytes(), mStorage.readPasswordHash(0));
+ assertArrayEquals("pattern".getBytes(), mStorage.readPatternHash(0).hash);
+ assertArrayEquals("password".getBytes(), mStorage.readPasswordHash(0).hash);
}
public void testFileLocation_Owner() {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 0b4d42e..72a458b 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -18,7 +18,7 @@ package com.android.server;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -597,7 +597,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
future = expectMeteredIfacesChanged();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
future.get();
verifyAndReset();
@@ -708,7 +708,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
future = expectMeteredIfacesChanged(TEST_IFACE);
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
future.get();
verifyAndReset();
}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 7383478..90b4f43 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -18,7 +18,7 @@ package com.android.server;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIMAX;
@@ -39,6 +39,7 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
@@ -191,7 +192,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
// verify service has empty history for wifi
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -245,7 +246,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
// verify service has empty history for wifi
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -336,7 +337,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// modify some number on wifi, and trigger poll event
@@ -388,7 +389,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic on first network
@@ -430,7 +431,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
verifyAndReset();
@@ -476,7 +477,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic
@@ -545,7 +546,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic
@@ -579,7 +580,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
verifyAndReset();
@@ -616,7 +617,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic for two apps
@@ -682,7 +683,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some initial traffic
@@ -747,7 +748,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some tethering traffic
@@ -787,7 +788,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic, but only for DEV, and across 1.5 buckets
@@ -879,7 +880,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectLastCall().anyTimes();
mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
- isA(PendingIntent.class), isA(WorkSource.class),
+ anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
isA(AlarmManager.AlarmClockInfo.class));
expectLastCall().atLeastOnce();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java
index 8e8e4e6..ca270e7 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java
@@ -65,7 +65,7 @@ public class ApplicationRestrictionsTest extends AndroidTestCase {
sDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0);
- sDpm.setProfileOwner(context.getPackageName(), "Test", UserHandle.myUserId());
+ sDpm.setProfileOwner(sAdminReceiver, "Test", UserHandle.myUserId());
Settings.Secure.putInt(context.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 1);
// Remove the admin if already registered. It's async, so add it back
@@ -132,4 +132,4 @@ public class ApplicationRestrictionsTest extends AndroidTestCase {
Bundle returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP);
assertEquals(returned.getString("KEY_FANCY_TEXT"), fancyText);
}
-} \ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java
index f913b97..7c3014c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
+import android.content.ComponentName;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
@@ -32,7 +33,7 @@ import java.io.ByteArrayOutputStream;
public class DeviceOwnerTest extends AndroidTestCase {
private ByteArrayInputStream mInputStreamForTest;
- private ByteArrayOutputStream mOutputStreamForTest = new ByteArrayOutputStream();
+ private final ByteArrayOutputStream mOutputStreamForTest = new ByteArrayOutputStream();
@SmallTest
public void testDeviceOwnerOnly() throws Exception {
@@ -46,13 +47,15 @@ public class DeviceOwnerTest extends AndroidTestCase {
assertEquals("some.device.owner.package", in.getDeviceOwnerPackageName());
assertEquals("owner", in.getDeviceOwnerName());
- assertNull(in.getProfileOwnerPackageName(1));
+ assertNull(in.getProfileOwnerComponent(1));
}
@SmallTest
public void testProfileOwnerOnly() throws Exception {
DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest);
- out.setProfileOwner("some.profile.owner.package", "some-company", 1);
+ ComponentName admin = new ComponentName(
+ "some.profile.owner.package", "some.profile.owner.package.Class");
+ out.setProfileOwner(admin, "some-company", 1);
out.writeOwnerFile();
mInputStreamForTest = new ByteArrayInputStream(mOutputStreamForTest.toByteArray());
@@ -61,16 +64,24 @@ public class DeviceOwnerTest extends AndroidTestCase {
assertNull(in.getDeviceOwnerPackageName());
assertNull(in.getDeviceOwnerName());
- assertEquals("some.profile.owner.package", in.getProfileOwnerPackageName(1));
+ assertEquals(admin, in.getProfileOwnerComponent(1));
assertEquals("some-company", in.getProfileOwnerName(1));
}
@SmallTest
public void testDeviceAndProfileOwners() throws Exception {
DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest);
+ ComponentName profileAdmin = new ComponentName(
+ "some.profile.owner.package", "some.profile.owner.package.Class");
+ ComponentName otherProfileAdmin = new ComponentName(
+ "some.other.profile.owner", "some.other.profile.owner.OtherClass");
+ // Old code used package name rather than component name, so the class
+ // bit could be empty.
+ ComponentName legacyComponentName = new ComponentName("legacy.profile.owner.package", "");
out.setDeviceOwner("some.device.owner.package", "owner");
- out.setProfileOwner("some.profile.owner.package", "some-company", 1);
- out.setProfileOwner("some.other.profile.owner", "some-other-company", 2);
+ out.setProfileOwner(profileAdmin, "some-company", 1);
+ out.setProfileOwner(otherProfileAdmin, "some-other-company", 2);
+ out.setProfileOwner(legacyComponentName, "legacy-company", 3);
out.writeOwnerFile();
mInputStreamForTest = new ByteArrayInputStream(mOutputStreamForTest.toByteArray());
@@ -80,9 +91,10 @@ public class DeviceOwnerTest extends AndroidTestCase {
assertEquals("some.device.owner.package", in.getDeviceOwnerPackageName());
assertEquals("owner", in.getDeviceOwnerName());
- assertEquals("some.profile.owner.package", in.getProfileOwnerPackageName(1));
+ assertEquals(profileAdmin, in.getProfileOwnerComponent(1));
assertEquals("some-company", in.getProfileOwnerName(1));
- assertEquals("some.other.profile.owner", in.getProfileOwnerPackageName(2));
+ assertEquals(otherProfileAdmin, in.getProfileOwnerComponent(2));
assertEquals("some-other-company", in.getProfileOwnerName(2));
+ assertEquals(legacyComponentName, in.getProfileOwnerComponent(3));
}
-} \ No newline at end of file
+}
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 b631331..a3f3a5d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -32,7 +32,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
public class PackageManagerSettingsTests extends AndroidTestCase {
-
private static final String PACKAGE_NAME_2 = "com.google.app2";
private static final String PACKAGE_NAME_3 = "com.android.app3";
private static final String PACKAGE_NAME_1 = "com.google.app1";
@@ -56,7 +55,7 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
writeFile(new File(getContext().getFilesDir(), "system/packages.xml"),
("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ "<packages>"
- + "<last-platform-version internal=\"15\" external=\"0\" />"
+ + "<last-platform-version internal=\"15\" external=\"0\" fingerprint=\"foo\" />"
+ "<permission-trees>"
+ "<item name=\"com.google.android.permtree\" package=\"com.google.android.permpackage\" />"
+ "</permission-trees>"
@@ -110,28 +109,32 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
.getBytes());
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ private void deleteSystemFolder() {
+ File systemFolder = new File(getContext().getFilesDir(), "system");
+ deleteFolder(systemFolder);
+ }
+
+ private static void deleteFolder(File folder) {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ deleteFolder(file);
+ }
+ }
+ folder.delete();
}
private void writeOldFiles() {
+ deleteSystemFolder();
writePackagesXml();
writeStoppedPackagesXml();
writePackagesList();
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
public void testSettingsReadOld() {
- // Debug.waitForDebugger();
-
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- Settings settings = new Settings(getContext(), getContext().getFilesDir());
+ Settings settings = new Settings(getContext().getFilesDir(), new Object());
assertEquals(true, settings.readLPw(null, null, 0, false));
assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_3));
assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_1));
@@ -149,11 +152,12 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
public void testNewPackageRestrictionsFile() {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- Settings settings = new Settings(getContext(), getContext().getFilesDir());
+ Settings settings = new Settings(getContext().getFilesDir(), new Object());
assertEquals(true, settings.readLPw(null, null, 0, false));
+ settings.writeLPr();
// Create Settings again to make it read from the new files
- settings = new Settings(getContext(), getContext().getFilesDir());
+ settings = new Settings(getContext().getFilesDir(), new Object());
assertEquals(true, settings.readLPw(null, null, 0, false));
PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_2);
@@ -164,7 +168,7 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
public void testEnableDisable() {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- Settings settings = new Settings(getContext(), getContext().getFilesDir());
+ Settings settings = new Settings(getContext().getFilesDir(), new Object());
assertEquals(true, settings.readLPw(null, null, 0, false));
// Enable/Disable a package
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
new file mode 100644
index 0000000..eb7eb15
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm;
+
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.Parcelable;
+import android.test.AndroidTestCase;
+import android.util.AtomicFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+
+public class UserManagerServiceTest extends AndroidTestCase {
+ private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["};
+ private File restrictionsFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml");
+ restrictionsFile.delete();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ restrictionsFile.delete();
+ super.tearDown();
+ }
+
+ public void testWriteReadApplicationRestrictions() throws IOException {
+ AtomicFile atomicFile = new AtomicFile(restrictionsFile);
+ Bundle bundle = createBundle();
+ UserManagerService.writeApplicationRestrictionsLocked(bundle, atomicFile);
+ assertTrue(atomicFile.getBaseFile().exists());
+ String s = FileUtils.readTextFile(restrictionsFile, 10000, "");
+ System.out.println("restrictionsFile: " + s);
+ bundle = UserManagerService.readApplicationRestrictionsLocked(atomicFile);
+ System.out.println("readApplicationRestrictionsLocked bundle: " + bundle);
+ assertBundle(bundle);
+ }
+
+ private Bundle createBundle() {
+ Bundle result = new Bundle();
+ // Tests for 6 allowed types: Integer, Boolean, String, String[], Bundle and Parcelable[]
+ result.putBoolean("boolean_0", false);
+ result.putBoolean("boolean_1", true);
+ result.putInt("integer", 100);
+ result.putString("empty", "");
+ result.putString("string", "text");
+ result.putStringArray("string[]", STRING_ARRAY);
+
+ Bundle bundle = new Bundle();
+ bundle.putString("bundle_string", "bundle_string");
+ bundle.putInt("bundle_int", 1);
+ result.putBundle("bundle", bundle);
+
+ Bundle[] bundleArray = new Bundle[2];
+ bundleArray[0] = new Bundle();
+ bundleArray[0].putString("bundle_array_string", "bundle_array_string");
+ bundleArray[0].putBundle("bundle_array_bundle", bundle);
+ bundleArray[1] = new Bundle();
+ bundleArray[1].putString("bundle_array_string2", "bundle_array_string2");
+ result.putParcelableArray("bundle_array", bundleArray);
+ return result;
+ }
+
+ private void assertBundle(Bundle bundle) {
+ assertFalse(bundle.getBoolean("boolean_0"));
+ assertTrue(bundle.getBoolean("boolean_1"));
+ assertEquals(100, bundle.getInt("integer"));
+ assertEquals("", bundle.getString("empty"));
+ assertEquals("text", bundle.getString("string"));
+ assertEquals(Arrays.asList(STRING_ARRAY), Arrays.asList(bundle.getStringArray("string[]")));
+ Parcelable[] bundle_array = bundle.getParcelableArray("bundle_array");
+ assertEquals(2, bundle_array.length);
+ Bundle bundle1 = (Bundle) bundle_array[0];
+ assertEquals("bundle_array_string", bundle1.getString("bundle_array_string"));
+ assertNotNull(bundle1.getBundle("bundle_array_bundle"));
+ Bundle bundle2 = (Bundle) bundle_array[1];
+ assertEquals("bundle_array_string2", bundle2.getString("bundle_array_string2"));
+ Bundle childBundle = bundle.getBundle("bundle");
+ assertEquals("bundle_string", childBundle.getString("bundle_string"));
+ assertEquals(1, childBundle.getInt("bundle_int"));
+ }
+
+}
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 a0b8c94..2d47c24 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -32,8 +32,9 @@ import java.util.List;
/** Test {@link UserManager} functionality. */
public class UserManagerTest extends AndroidTestCase {
- UserManager mUserManager = null;
- Object mUserLock = new Object();
+ private UserManager mUserManager = null;
+ private final Object mUserLock = new Object();
+ private List<Integer> usersToRemove;
@Override
public void setUp() throws Exception {
@@ -49,11 +50,19 @@ public class UserManagerTest extends AndroidTestCase {
}, filter);
removeExistingUsers();
+ usersToRemove = new ArrayList<>();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ for (Integer userId : usersToRemove) {
+ removeUser(userId);
+ }
+ super.tearDown();
}
private void removeExistingUsers() {
List<UserInfo> list = mUserManager.getUsers();
- boolean found = false;
for (UserInfo user : list) {
if (user.id != UserHandle.USER_OWNER) {
removeUser(user.id);
@@ -66,7 +75,7 @@ public class UserManagerTest extends AndroidTestCase {
}
public void testAddUser() throws Exception {
- UserInfo userInfo = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST);
assertTrue(userInfo != null);
List<UserInfo> list = mUserManager.getUsers();
@@ -83,12 +92,11 @@ public class UserManagerTest extends AndroidTestCase {
}
}
assertTrue(found);
- removeUser(userInfo.id);
}
public void testAdd2Users() throws Exception {
- UserInfo user1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
- UserInfo user2 = mUserManager.createUser("User 2", UserInfo.FLAG_ADMIN);
+ UserInfo user1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo user2 = createUser("User 2", UserInfo.FLAG_ADMIN);
assertTrue(user1 != null);
assertTrue(user2 != null);
@@ -96,41 +104,67 @@ public class UserManagerTest extends AndroidTestCase {
assertTrue(findUser(0));
assertTrue(findUser(user1.id));
assertTrue(findUser(user2.id));
- removeUser(user1.id);
- removeUser(user2.id);
}
public void testRemoveUser() throws Exception {
- UserInfo userInfo = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST);
removeUser(userInfo.id);
assertFalse(findUser(userInfo.id));
}
public void testAddGuest() throws Exception {
- UserInfo userInfo1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
- UserInfo userInfo2 = mUserManager.createUser("Guest 2", UserInfo.FLAG_GUEST);
+ UserInfo userInfo1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo userInfo2 = createUser("Guest 2", UserInfo.FLAG_GUEST);
assertNotNull(userInfo1);
assertNull(userInfo2);
-
- // Cleanup
- removeUser(userInfo1.id);
}
// Make sure only one managed profile can be created
public void testAddManagedProfile() throws Exception {
- UserInfo userInfo1 = mUserManager.createProfileForUser("Managed 1",
+ UserInfo userInfo1 = createProfileForUser("Managed 1",
UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_OWNER);
- UserInfo userInfo2 = mUserManager.createProfileForUser("Managed 2",
+ UserInfo userInfo2 = createProfileForUser("Managed 2",
UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_OWNER);
assertNotNull(userInfo1);
assertNull(userInfo2);
// Verify that current user is not a managed profile
assertFalse(mUserManager.isManagedProfile());
- // Cleanup
- removeUser(userInfo1.id);
}
+ public void testGetUserCreationTime() throws Exception {
+ UserInfo profile = createProfileForUser("Managed 1",
+ UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_OWNER);
+ assertNotNull(profile);
+ assertTrue("creationTime must be set when the profile is created",
+ profile.creationTime > 0);
+ assertEquals(profile.creationTime, mUserManager.getUserCreationTime(
+ new UserHandle(profile.id)));
+
+ long ownerCreationTime = mUserManager.getUserInfo(UserHandle.USER_OWNER).creationTime;
+ assertEquals(ownerCreationTime, mUserManager.getUserCreationTime(
+ new UserHandle(UserHandle.USER_OWNER)));
+
+ try {
+ int noSuchUserId = 100500;
+ mUserManager.getUserCreationTime(new UserHandle(noSuchUserId));
+ fail("SecurityException should be thrown for nonexistent user");
+ } catch (Exception e) {
+ assertTrue("SecurityException should be thrown for nonexistent user, but was: " + e,
+ e instanceof SecurityException);
+ }
+
+ UserInfo user = createUser("User 1", 0);
+ try {
+ mUserManager.getUserCreationTime(new UserHandle(user.id));
+ fail("SecurityException should be thrown for other user");
+ } catch (Exception e) {
+ assertTrue("SecurityException should be thrown for other user, but was: " + e,
+ e instanceof SecurityException);
+ }
+ }
+
+
private boolean findUser(int id) {
List<UserInfo> list = mUserManager.getUsers();
@@ -143,40 +177,29 @@ public class UserManagerTest extends AndroidTestCase {
}
public void testSerialNumber() {
- UserInfo user1 = mUserManager.createUser("User 1", UserInfo.FLAG_RESTRICTED);
+ UserInfo user1 = createUser("User 1", UserInfo.FLAG_RESTRICTED);
int serialNumber1 = user1.serialNumber;
assertEquals(serialNumber1, mUserManager.getUserSerialNumber(user1.id));
assertEquals(user1.id, mUserManager.getUserHandle(serialNumber1));
- removeUser(user1.id);
- UserInfo user2 = mUserManager.createUser("User 2", UserInfo.FLAG_RESTRICTED);
+ UserInfo user2 = createUser("User 2", UserInfo.FLAG_RESTRICTED);
int serialNumber2 = user2.serialNumber;
assertFalse(serialNumber1 == serialNumber2);
assertEquals(serialNumber2, mUserManager.getUserSerialNumber(user2.id));
assertEquals(user2.id, mUserManager.getUserHandle(serialNumber2));
- removeUser(user2.id);
}
public void testMaxUsers() {
int N = UserManager.getMaxSupportedUsers();
int count = mUserManager.getUsers().size();
- List<UserInfo> created = new ArrayList<UserInfo>();
// Create as many users as permitted and make sure creation passes
while (count < N) {
- UserInfo ui = mUserManager.createUser("User " + count, 0);
+ UserInfo ui = createUser("User " + count, 0);
assertNotNull(ui);
- created.add(ui);
count++;
}
// Try to create one more user and make sure it fails
- UserInfo extra = null;
- assertNull(extra = mUserManager.createUser("One more", 0));
- if (extra != null) {
- removeUser(extra.id);
- }
- while (!created.isEmpty()) {
- UserInfo user = created.remove(0);
- removeUser(user.id);
- }
+ UserInfo extra = createUser("One more", 0);
+ assertNull(extra);
}
public void testRestrictions() {
@@ -198,11 +221,27 @@ public class UserManagerTest extends AndroidTestCase {
mUserManager.removeUser(userId);
while (mUserManager.getUserInfo(userId) != null) {
try {
- mUserLock.wait(1000);
+ mUserLock.wait(500);
} catch (InterruptedException ie) {
}
}
}
}
+ private UserInfo createUser(String name, int flags) {
+ UserInfo user = mUserManager.createUser(name, flags);
+ if (user != null) {
+ usersToRemove.add(user.id);
+ }
+ return user;
+ }
+
+ private UserInfo createProfileForUser(String name, int flags, int userHandle) {
+ UserInfo profile = mUserManager.createProfileForUser(name, flags, userHandle);
+ if (profile != null) {
+ usersToRemove.add(profile.id);
+ }
+ return profile;
+ }
+
}
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index 5f639ab..869d6e1 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -81,6 +81,17 @@ class IntervalStats {
return event;
}
+ private boolean isStatefulEvent(int eventType) {
+ switch (eventType) {
+ case UsageEvents.Event.MOVE_TO_FOREGROUND:
+ case UsageEvents.Event.MOVE_TO_BACKGROUND:
+ case UsageEvents.Event.END_OF_DAY:
+ case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
+ return true;
+ }
+ return false;
+ }
+
void update(String packageName, long timeStamp, int eventType) {
UsageStats usageStats = getOrCreateUsageStats(packageName);
@@ -93,7 +104,11 @@ class IntervalStats {
usageStats.mTotalTimeInForeground += timeStamp - usageStats.mLastTimeUsed;
}
}
- usageStats.mLastEvent = eventType;
+
+ if (isStatefulEvent(eventType)) {
+ usageStats.mLastEvent = eventType;
+ }
+
usageStats.mLastTimeUsed = timeStamp;
usageStats.mEndTimeStamp = timeStamp;
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 26ced03..4498b84 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -18,6 +18,7 @@ package com.android.server.usage;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageStatsManager;
+import android.os.Build;
import android.util.AtomicFile;
import android.util.Slog;
@@ -35,7 +36,7 @@ import java.util.List;
* Provides an interface to query for UsageStat data from an XML database.
*/
class UsageStatsDatabase {
- private static final int CURRENT_VERSION = 2;
+ private static final int CURRENT_VERSION = 3;
private static final String TAG = "UsageStatsDatabase";
private static final boolean DEBUG = UsageStatsService.DEBUG;
@@ -47,6 +48,8 @@ class UsageStatsDatabase {
private final TimeSparseArray<AtomicFile>[] mSortedStatFiles;
private final UnixCalendar mCal;
private final File mVersionFile;
+ private boolean mFirstUpdate;
+ private boolean mNewUpdate;
public UsageStatsDatabase(File dir) {
mIntervalDirs = new File[] {
@@ -73,7 +76,7 @@ class UsageStatsDatabase {
}
}
- checkVersionLocked();
+ checkVersionAndBuildLocked();
indexFilesLocked();
// Delete files that are in the future.
@@ -194,10 +197,35 @@ class UsageStatsDatabase {
}
}
- private void checkVersionLocked() {
+ /**
+ * Is this the first update to the system from L to M?
+ */
+ boolean isFirstUpdate() {
+ return mFirstUpdate;
+ }
+
+ /**
+ * Is this a system update since we started tracking build fingerprint in the version file?
+ */
+ boolean isNewUpdate() {
+ return mNewUpdate;
+ }
+
+ private void checkVersionAndBuildLocked() {
int version;
+ String buildFingerprint;
+ String currentFingerprint = getBuildFingerprint();
+ mFirstUpdate = true;
+ mNewUpdate = true;
try (BufferedReader reader = new BufferedReader(new FileReader(mVersionFile))) {
version = Integer.parseInt(reader.readLine());
+ buildFingerprint = reader.readLine();
+ if (buildFingerprint != null) {
+ mFirstUpdate = false;
+ }
+ if (currentFingerprint.equals(buildFingerprint)) {
+ mNewUpdate = false;
+ }
} catch (NumberFormatException | IOException e) {
version = 0;
}
@@ -205,9 +233,15 @@ class UsageStatsDatabase {
if (version != CURRENT_VERSION) {
Slog.i(TAG, "Upgrading from version " + version + " to " + CURRENT_VERSION);
doUpgradeLocked(version);
+ }
+ if (version != CURRENT_VERSION || mNewUpdate) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(mVersionFile))) {
writer.write(Integer.toString(CURRENT_VERSION));
+ writer.write("\n");
+ writer.write(currentFingerprint);
+ writer.write("\n");
+ writer.flush();
} catch (IOException e) {
Slog.e(TAG, "Failed to write new version");
throw new RuntimeException(e);
@@ -215,6 +249,12 @@ class UsageStatsDatabase {
}
}
+ private String getBuildFingerprint() {
+ return Build.VERSION.RELEASE + ";"
+ + Build.VERSION.CODENAME + ";"
+ + Build.VERSION.INCREMENTAL;
+ }
+
private void doUpgradeLocked(int thisVersion) {
if (thisVersion < 2) {
// Delete all files if we are version 0. This is a pre-release version,
@@ -378,26 +418,27 @@ class UsageStatsDatabase {
}
}
- try {
- IntervalStats stats = new IntervalStats();
- ArrayList<T> results = new ArrayList<>();
- for (int i = startIndex; i <= endIndex; i++) {
- final AtomicFile f = intervalStats.valueAt(i);
+ final IntervalStats stats = new IntervalStats();
+ final ArrayList<T> results = new ArrayList<>();
+ for (int i = startIndex; i <= endIndex; i++) {
+ final AtomicFile f = intervalStats.valueAt(i);
- if (DEBUG) {
- Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath());
- }
+ if (DEBUG) {
+ Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath());
+ }
+ try {
UsageStatsXml.read(f, stats);
if (beginTime < stats.endTime) {
combiner.combine(stats, false, results);
}
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read usage stats file", e);
+ // We continue so that we return results that are not
+ // corrupt.
}
- return results;
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read usage stats file", e);
- return null;
}
+ return results;
}
}
@@ -450,6 +491,10 @@ class UsageStatsDatabase {
mCal.addDays(-7);
pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY],
mCal.getTimeInMillis());
+
+ // We must re-index our file list or we will be trying to read
+ // deleted files.
+ indexFilesLocked();
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 485b2a2..3d54dfb 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -21,8 +21,10 @@ import android.app.AppOpsManager;
import android.app.usage.ConfigurationStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -32,6 +34,8 @@ import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
@@ -42,17 +46,20 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.SystemConfig;
import com.android.server.SystemService;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -74,6 +81,7 @@ public class UsageStatsService extends SystemService implements
static final int MSG_REPORT_EVENT = 0;
static final int MSG_FLUSH_TO_DISK = 1;
static final int MSG_REMOVE_USER = 2;
+ static final int MSG_INFORM_LISTENERS = 3;
private final Object mLock = new Object();
Handler mHandler;
@@ -85,6 +93,12 @@ public class UsageStatsService extends SystemService implements
long mRealTimeSnapshot;
long mSystemTimeSnapshot;
+ private static final long DEFAULT_APP_IDLE_THRESHOLD_MILLIS = 1L * 24 * 60 * 60 * 1000; // 1 day
+ private long mAppIdleDurationMillis;
+
+ private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
+ mPackageAccessListeners = new ArrayList<>();
+
public UsageStatsService(Context context) {
super(context);
}
@@ -112,11 +126,24 @@ public class UsageStatsService extends SystemService implements
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
+ // Look at primary user's secure setting for this. TODO: Maybe apply different
+ // thresholds for different users.
+ mAppIdleDurationMillis = Settings.Secure.getLongForUser(getContext().getContentResolver(),
+ Settings.Secure.APP_IDLE_DURATION, DEFAULT_APP_IDLE_THRESHOLD_MILLIS,
+ UserHandle.USER_OWNER);
publishLocalService(UsageStatsManagerInternal.class, new LocalService());
publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
}
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ // Observe changes to the threshold
+ new SettingsObserver(mHandler).registerObserver();
+ }
+ }
+
private class UserRemovedReceiver extends BroadcastReceiver {
@Override
@@ -235,7 +262,19 @@ public class UsageStatsService extends SystemService implements
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+ final long lastUsed = service.getLastPackageAccessTime(event.mPackage);
+ final boolean previouslyIdle = hasPassedIdleDuration(lastUsed);
service.reportEvent(event);
+ // Inform listeners if necessary
+ if ((event.mEventType == Event.MOVE_TO_FOREGROUND
+ || event.mEventType == Event.MOVE_TO_BACKGROUND
+ || event.mEventType == Event.INTERACTION)) {
+ if (previouslyIdle) {
+ // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
+ /* idle = */ 0, event.mPackage));
+ }
+ }
}
}
@@ -308,6 +347,56 @@ public class UsageStatsService extends SystemService implements
}
}
+ /**
+ * Called by LocalService stub.
+ */
+ long getLastPackageAccessTime(String packageName, int userId) {
+ synchronized (mLock) {
+ final long timeNow = checkAndGetTimeLocked();
+ // android package is always considered non-idle.
+ // TODO: Add a generic whitelisting mechanism
+ if (packageName.equals("android")) {
+ return timeNow;
+ }
+ final UserUsageStatsService service =
+ getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+ return service.getLastPackageAccessTime(packageName);
+ }
+ }
+
+ void addListener(AppIdleStateChangeListener listener) {
+ synchronized (mLock) {
+ if (!mPackageAccessListeners.contains(listener)) {
+ mPackageAccessListeners.add(listener);
+ }
+ }
+ }
+
+ void removeListener(AppIdleStateChangeListener listener) {
+ synchronized (mLock) {
+ mPackageAccessListeners.remove(listener);
+ }
+ }
+
+ private boolean hasPassedIdleDuration(long lastUsed) {
+ final long now = System.currentTimeMillis();
+ return lastUsed < now - mAppIdleDurationMillis;
+ }
+
+ boolean isAppIdle(String packageName, int userId) {
+ if (SystemConfig.getInstance().getAllowInPowerSave().contains(packageName)) {
+ return false;
+ }
+ final long lastUsed = getLastPackageAccessTime(packageName, userId);
+ return hasPassedIdleDuration(lastUsed);
+ }
+
+ void informListeners(String packageName, int userId, boolean isIdle) {
+ for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
+ listener.onAppIdleStateChanged(packageName, userId, isIdle);
+ }
+ }
+
private static boolean validRange(long currentTime, long beginTime, long endTime) {
return beginTime <= currentTime && beginTime < endTime;
}
@@ -366,6 +455,10 @@ public class UsageStatsService extends SystemService implements
removeUser(msg.arg1);
break;
+ case MSG_INFORM_LISTENERS:
+ informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
+ break;
+
default:
super.handleMessage(msg);
break;
@@ -373,6 +466,29 @@ public class UsageStatsService extends SystemService implements
}
}
+ /**
+ * Observe settings changes for Settings.Secure.APP_IDLE_DURATION.
+ */
+ private class SettingsObserver extends ContentObserver {
+
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void registerObserver() {
+ getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.APP_IDLE_DURATION), false, this, UserHandle.USER_OWNER);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ mAppIdleDurationMillis = Settings.Secure.getLongForUser(getContext().getContentResolver(),
+ Settings.Secure.APP_IDLE_DURATION, DEFAULT_APP_IDLE_THRESHOLD_MILLIS,
+ UserHandle.USER_OWNER);
+ // TODO: Check if we need to update idle states of all the apps
+ }
+ }
+
private class BinderService extends IUsageStatsManager.Stub {
private boolean hasPermission(String callingPackage) {
@@ -488,6 +604,23 @@ public class UsageStatsService extends SystemService implements
}
@Override
+ public void reportEvent(String packageName, int userId, int eventType) {
+ if (packageName == null) {
+ Slog.w(TAG, "Event reported without a package name");
+ return;
+ }
+
+ UsageEvents.Event event = new UsageEvents.Event();
+ event.mPackage = packageName;
+
+ // This will later be converted to system time.
+ event.mTimeStamp = SystemClock.elapsedRealtime();
+
+ event.mEventType = eventType;
+ mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+ }
+
+ @Override
public void reportConfigurationChange(Configuration config, int userId) {
if (config == null) {
Slog.w(TAG, "Configuration event reported with a null config");
@@ -506,11 +639,32 @@ public class UsageStatsService extends SystemService implements
}
@Override
+ public boolean isAppIdle(String packageName, int userId) {
+ return UsageStatsService.this.isAppIdle(packageName, userId);
+ }
+
+ @Override
+ public long getLastPackageAccessTime(String packageName, int userId) {
+ return UsageStatsService.this.getLastPackageAccessTime(packageName, userId);
+ }
+
+ @Override
public void prepareShutdown() {
// This method *WILL* do IO work, but we must block until it is finished or else
// we might not shutdown cleanly. This is ok to do with the 'am' lock held, because
// we are shutting down.
shutdown();
}
+
+ @Override
+ public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
+ UsageStatsService.this.addListener(listener);
+ }
+
+ @Override
+ public void removeAppIdleStateChangeListener(
+ AppIdleStateChangeListener listener) {
+ UsageStatsService.this.removeListener(listener);
+ }
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index ef95a7b..bfb71c5 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -191,7 +191,7 @@ final class UsageStatsXmlV1 {
statsOut.events.clear();
}
- statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR);
+ statsOut.endTime = statsOut.beginTime + XmlUtils.readLongAttribute(parser, END_TIME_ATTR);
int eventCode;
int outerDepth = parser.getDepth();
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 6596781..0a9481a 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -19,8 +19,11 @@ package com.android.server.usage;
import android.app.usage.ConfigurationStats;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.SystemClock;
import android.content.Context;
@@ -60,18 +63,21 @@ class UserUsageStatsService {
private final UnixCalendar mDailyExpiryDate;
private final StatsUpdatedListener mListener;
private final String mLogPrefix;
+ private final int mUserId;
interface StatsUpdatedListener {
void onStatsUpdated();
}
- UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener) {
+ UserUsageStatsService(Context context, int userId, File usageStatsDir,
+ StatsUpdatedListener listener) {
mContext = context;
mDailyExpiryDate = new UnixCalendar(0);
mDatabase = new UsageStatsDatabase(usageStatsDir);
mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
mListener = listener;
mLogPrefix = "User[" + Integer.toString(userId) + "] ";
+ mUserId = userId;
}
void init(final long currentTimeMillis) {
@@ -127,6 +133,35 @@ class UserUsageStatsService {
stat.updateConfigurationStats(null, stat.lastTimeSaved);
}
+
+ if (mDatabase.isNewUpdate()) {
+ initializeDefaultsForApps(currentTimeMillis, mDatabase.isFirstUpdate());
+ }
+ }
+
+ /**
+ * If any of the apps don't have a last-used entry, add one now.
+ * @param currentTimeMillis the current time
+ * @param firstUpdate if it is the first update, touch all installed apps, otherwise only
+ * touch the system apps
+ */
+ private void initializeDefaultsForApps(long currentTimeMillis, boolean firstUpdate) {
+ PackageManager pm = mContext.getPackageManager();
+ List<PackageInfo> packages = pm.getInstalledPackages(0, mUserId);
+ final int packageCount = packages.size();
+ for (int i = 0; i < packageCount; i++) {
+ final PackageInfo pi = packages.get(i);
+ String packageName = pi.packageName;
+ if (pi.applicationInfo != null && (firstUpdate || pi.applicationInfo.isSystemApp())
+ && getLastPackageAccessTime(packageName) == -1) {
+ for (IntervalStats stats : mCurrentStats) {
+ stats.update(packageName, currentTimeMillis, Event.INTERACTION);
+ mStatsChanged = true;
+ }
+ }
+ }
+ // Persist the new OTA-related access stats.
+ persistActiveStats();
}
void onTimeChanged(long oldTime, long newTime) {
@@ -161,7 +196,9 @@ class UserUsageStatsService {
if (currentDailyStats.events == null) {
currentDailyStats.events = new TimeSparseArray<>();
}
- currentDailyStats.events.put(event.mTimeStamp, event);
+ if (event.mEventType != UsageEvents.Event.INTERACTION) {
+ currentDailyStats.events.put(event.mTimeStamp, event);
+ }
for (IntervalStats stats : mCurrentStats) {
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
@@ -328,6 +365,16 @@ class UserUsageStatsService {
return new UsageEvents(results, table);
}
+ long getLastPackageAccessTime(String packageName) {
+ final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
+ UsageStats packageUsage;
+ if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
+ return -1;
+ } else {
+ return packageUsage.getLastTimeUsed();
+ }
+ }
+
void persistActiveStats() {
if (mStatsChanged) {
Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
@@ -570,6 +617,8 @@ class UserUsageStatsService {
return "CONTINUE_PREVIOUS_DAY";
case UsageEvents.Event.CONFIGURATION_CHANGE:
return "CONFIGURATION_CHANGE";
+ case UsageEvents.Event.INTERACTION:
+ return "INTERACTION";
default:
return "UNKNOWN";
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
new file mode 100644
index 0000000..8f0c6c8
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2014 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.media.AudioSystem;
+import android.media.IAudioService;
+import android.media.midi.MidiDeviceInfo;
+import android.os.FileObserver;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.alsa.AlsaCardsParser;
+import com.android.internal.alsa.AlsaDevicesParser;
+import com.android.server.audio.AudioService;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+/**
+ * UsbAlsaManager manages USB audio and MIDI devices.
+ */
+public final class UsbAlsaManager {
+ private static final String TAG = UsbAlsaManager.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ private static final String ALSA_DIRECTORY = "/dev/snd/";
+
+ private final Context mContext;
+ private IAudioService mAudioService;
+ private final boolean mHasMidiFeature;
+
+ private final AlsaCardsParser mCardsParser = new AlsaCardsParser();
+ private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser();
+
+ // this is needed to map USB devices to ALSA Audio Devices, especially to remove an
+ // ALSA device when we are notified that its associated USB device has been removed.
+
+ private final HashMap<UsbDevice,UsbAudioDevice>
+ mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>();
+
+ private final HashMap<UsbDevice,UsbMidiDevice>
+ mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>();
+
+ private final HashMap<String,AlsaDevice>
+ mAlsaDevices = new HashMap<String,AlsaDevice>();
+
+ private UsbAudioDevice mAccessoryAudioDevice = null;
+
+ // UsbMidiDevice for USB peripheral mode (gadget) device
+ private UsbMidiDevice mPeripheralMidiDevice = null;
+
+ private final class AlsaDevice {
+ public static final int TYPE_UNKNOWN = 0;
+ public static final int TYPE_PLAYBACK = 1;
+ public static final int TYPE_CAPTURE = 2;
+ public static final int TYPE_MIDI = 3;
+
+ public int mCard;
+ public int mDevice;
+ public int mType;
+
+ public AlsaDevice(int type, int card, int device) {
+ mType = type;
+ mCard = card;
+ mDevice = device;
+ }
+
+ public boolean equals(Object obj) {
+ if (! (obj instanceof AlsaDevice)) {
+ return false;
+ }
+ AlsaDevice other = (AlsaDevice)obj;
+ return (mType == other.mType && mCard == other.mCard && mDevice == other.mDevice);
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("AlsaDevice: [card: " + mCard);
+ sb.append(", device: " + mDevice);
+ sb.append(", type: " + mType);
+ sb.append("]");
+ return sb.toString();
+ }
+ }
+
+ private final FileObserver mAlsaObserver = new FileObserver(ALSA_DIRECTORY,
+ FileObserver.CREATE | FileObserver.DELETE) {
+ public void onEvent(int event, String path) {
+ switch (event) {
+ case FileObserver.CREATE:
+ alsaFileAdded(path);
+ break;
+ case FileObserver.DELETE:
+ alsaFileRemoved(path);
+ break;
+ }
+ }
+ };
+
+ /* package */ UsbAlsaManager(Context context) {
+ mContext = context;
+ mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
+
+ // initial scan
+ mCardsParser.scan();
+ }
+
+ public void systemReady() {
+ mAudioService = IAudioService.Stub.asInterface(
+ ServiceManager.getService(Context.AUDIO_SERVICE));
+
+ mAlsaObserver.startWatching();
+
+ // add existing alsa devices
+ File[] files = new File(ALSA_DIRECTORY).listFiles();
+ for (int i = 0; i < files.length; i++) {
+ alsaFileAdded(files[i].getName());
+ }
+ }
+
+ // Notifies AudioService when a device is added or removed
+ // audioDevice - the AudioDevice that was added or removed
+ // enabled - if true, we're connecting a device (it's arrived), else disconnecting
+ private void notifyDeviceState(UsbAudioDevice audioDevice, boolean enabled) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyDeviceState " + enabled + " " + audioDevice);
+ }
+
+ if (mAudioService == null) {
+ Slog.e(TAG, "no AudioService");
+ return;
+ }
+
+ // FIXME Does not yet handle the case where the setting is changed
+ // after device connection. Ideally we should handle the settings change
+ // in SettingsObserver. Here we should log that a USB device is connected
+ // and disconnected with its address (card , device) and force the
+ // connection or disconnection when the setting changes.
+ int isDisabled = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0);
+ if (isDisabled != 0) {
+ return;
+ }
+
+ int state = (enabled ? 1 : 0);
+ int alsaCard = audioDevice.mCard;
+ int alsaDevice = audioDevice.mDevice;
+ if (alsaCard < 0 || alsaDevice < 0) {
+ Slog.e(TAG, "Invalid alsa card or device alsaCard: " + alsaCard +
+ " alsaDevice: " + alsaDevice);
+ return;
+ }
+
+ String address = AudioService.makeAlsaAddressString(alsaCard, alsaDevice);
+ try {
+ // Playback Device
+ if (audioDevice.mHasPlayback) {
+ int device = (audioDevice == mAccessoryAudioDevice ?
+ AudioSystem.DEVICE_OUT_USB_ACCESSORY :
+ AudioSystem.DEVICE_OUT_USB_DEVICE);
+ if (DEBUG) {
+ Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) +
+ " addr:" + address + " name:" + audioDevice.mDeviceName);
+ }
+ mAudioService.setWiredDeviceConnectionState(
+ device, state, address, audioDevice.mDeviceName, TAG);
+ }
+
+ // Capture Device
+ if (audioDevice.mHasCapture) {
+ int device = (audioDevice == mAccessoryAudioDevice ?
+ AudioSystem.DEVICE_IN_USB_ACCESSORY :
+ AudioSystem.DEVICE_IN_USB_DEVICE);
+ mAudioService.setWiredDeviceConnectionState(
+ device, state, address, audioDevice.mDeviceName, TAG);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
+ }
+ }
+
+ private AlsaDevice waitForAlsaDevice(int card, int device, int type) {
+ AlsaDevice testDevice = new AlsaDevice(type, card, device);
+
+ // This value was empirically determined.
+ final int kWaitTime = 2500; // ms
+
+ synchronized(mAlsaDevices) {
+ long timeout = SystemClock.elapsedRealtime() + kWaitTime;
+ do {
+ if (mAlsaDevices.values().contains(testDevice)) {
+ return testDevice;
+ }
+ long waitTime = timeout - SystemClock.elapsedRealtime();
+ if (waitTime > 0) {
+ try {
+ mAlsaDevices.wait(waitTime);
+ } catch (InterruptedException e) {
+ Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
+ }
+ }
+ } while (timeout > SystemClock.elapsedRealtime());
+ }
+
+ Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice);
+ return null;
+ }
+
+ private void alsaFileAdded(String name) {
+ int type = AlsaDevice.TYPE_UNKNOWN;
+ int card = -1, device = -1;
+
+ if (name.startsWith("pcmC")) {
+ if (name.endsWith("p")) {
+ type = AlsaDevice.TYPE_PLAYBACK;
+ } else if (name.endsWith("c")) {
+ type = AlsaDevice.TYPE_CAPTURE;
+ }
+ } else if (name.startsWith("midiC")) {
+ type = AlsaDevice.TYPE_MIDI;
+ }
+
+ if (type != AlsaDevice.TYPE_UNKNOWN) {
+ try {
+ int c_index = name.indexOf('C');
+ int d_index = name.indexOf('D');
+ int end = name.length();
+ if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) {
+ // skip trailing 'p' or 'c'
+ end--;
+ }
+ card = Integer.parseInt(name.substring(c_index + 1, d_index));
+ device = Integer.parseInt(name.substring(d_index + 1, end));
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not parse ALSA file name " + name, e);
+ return;
+ }
+ synchronized(mAlsaDevices) {
+ if (mAlsaDevices.get(name) == null) {
+ AlsaDevice alsaDevice = new AlsaDevice(type, card, device);
+ Slog.d(TAG, "Adding ALSA device " + alsaDevice);
+ mAlsaDevices.put(name, alsaDevice);
+ mAlsaDevices.notifyAll();
+ }
+ }
+ }
+ }
+
+ private void alsaFileRemoved(String path) {
+ synchronized(mAlsaDevices) {
+ AlsaDevice device = mAlsaDevices.remove(path);
+ if (device != null) {
+ Slog.d(TAG, "ALSA device removed: " + device);
+ }
+ }
+ }
+
+ /*
+ * Select the default device of the specified card.
+ */
+ /* package */ UsbAudioDevice selectAudioCard(int card) {
+ if (DEBUG) {
+ Slog.d(TAG, "selectAudioCard() card:" + card);
+ }
+ if (!mCardsParser.isCardUsb(card)) {
+ // Don't. AudioPolicyManager has logic for falling back to internal devices.
+ return null;
+ }
+
+ mDevicesParser.scan();
+ int device = mDevicesParser.getDefaultDeviceNum(card);
+
+ boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
+ boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
+ int deviceClass =
+ (mCardsParser.isCardUsb(card)
+ ? UsbAudioDevice.kAudioDeviceClass_External
+ : UsbAudioDevice.kAudioDeviceClass_Internal) |
+ UsbAudioDevice.kAudioDeviceMeta_Alsa;
+
+ // Playback device file needed/present?
+ if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) {
+ return null;
+ }
+
+ // Capture device file needed/present?
+ if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) {
+ return null;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
+ }
+
+ UsbAudioDevice audioDevice =
+ new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
+ AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card);
+ audioDevice.mDeviceName = cardRecord.mCardName;
+ audioDevice.mDeviceDescription = cardRecord.mCardDescription;
+
+ notifyDeviceState(audioDevice, true);
+
+ return audioDevice;
+ }
+
+ /* package */ UsbAudioDevice selectDefaultDevice() {
+ if (DEBUG) {
+ Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
+ }
+ mCardsParser.scan();
+ return selectAudioCard(mCardsParser.getDefaultCard());
+ }
+
+ /* package */ void usbDeviceAdded(UsbDevice usbDevice) {
+ if (DEBUG) {
+ Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() +
+ "nm:" + usbDevice.getProductName());
+ }
+
+ // Is there an audio interface in there?
+ boolean isAudioDevice = false;
+
+ // FIXME - handle multiple configurations?
+ int interfaceCount = usbDevice.getInterfaceCount();
+ for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount;
+ ntrfaceIndex++) {
+ UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex);
+ if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) {
+ isAudioDevice = true;
+ }
+ }
+ if (!isAudioDevice) {
+ return;
+ }
+
+ ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords();
+ mCardsParser.scan();
+
+ int addedCard = -1;
+ ArrayList<AlsaCardsParser.AlsaCardRecord>
+ newScanRecs = mCardsParser.getNewCardRecords(prevScanRecs);
+ if (newScanRecs.size() > 0) {
+ // This is where we select the just connected device
+ // NOTE - to switch to prefering the first-connected device, just always
+ // take the else clause below.
+ addedCard = newScanRecs.get(0).mCardNum;
+ } else {
+ addedCard = mCardsParser.getDefaultUsbCard();
+ }
+
+ // If the default isn't a USB device, let the existing "select internal mechanism"
+ // handle the selection.
+ if (mCardsParser.isCardUsb(addedCard)) {
+ UsbAudioDevice audioDevice = selectAudioCard(addedCard);
+ if (audioDevice != null) {
+ mAudioDevices.put(usbDevice, audioDevice);
+ }
+
+ // look for MIDI devices
+
+ // Don't need to call mDevicesParser.scan() because selectAudioCard() does this above.
+ // Uncomment this next line if that behavior changes in the fugure.
+ // mDevicesParser.scan()
+
+ boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard);
+ if (hasMidi && mHasMidiFeature) {
+ int device = mDevicesParser.getDefaultDeviceNum(addedCard);
+ AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI);
+ if (alsaDevice != null) {
+ Bundle properties = new Bundle();
+ String manufacturer = usbDevice.getManufacturerName();
+ String product = usbDevice.getProductName();
+ String name;
+ if (manufacturer == null || manufacturer.isEmpty()) {
+ name = product;
+ } else if (product == null || product.isEmpty()) {
+ name = manufacturer;
+ } else {
+ name = manufacturer + " " + product;
+ }
+ properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
+ properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
+ properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
+ properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
+ usbDevice.getSerialNumber());
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, alsaDevice.mCard);
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, alsaDevice.mDevice);
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
+
+ UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
+ alsaDevice.mCard, alsaDevice.mDevice);
+ if (usbMidiDevice != null) {
+ mMidiDevices.put(usbDevice, usbMidiDevice);
+ }
+ }
+ }
+ }
+ }
+
+ /* package */ void usbDeviceRemoved(UsbDevice usbDevice) {
+ if (DEBUG) {
+ Slog.d(TAG, "deviceRemoved(): " + usbDevice.getManufacturerName() +
+ " " + usbDevice.getProductName());
+ }
+
+ UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
+ if (audioDevice != null) {
+ if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
+ notifyDeviceState(audioDevice, false);
+
+ // if there any external devices left, select one of them
+ selectDefaultDevice();
+ }
+ }
+ UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice);
+ if (usbMidiDevice != null) {
+ IoUtils.closeQuietly(usbMidiDevice);
+ }
+ }
+
+ /* package */ void setAccessoryAudioState(boolean enabled, int card, int device) {
+ if (DEBUG) {
+ Slog.d(TAG, "setAccessoryAudioState " + enabled + " " + card + " " + device);
+ }
+ if (enabled) {
+ mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false,
+ UsbAudioDevice.kAudioDeviceClass_External);
+ notifyDeviceState(mAccessoryAudioDevice, true);
+ } else if (mAccessoryAudioDevice != null) {
+ notifyDeviceState(mAccessoryAudioDevice, false);
+ mAccessoryAudioDevice = null;
+ }
+ }
+
+ /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
+ if (!mHasMidiFeature) {
+ return;
+ }
+
+ if (enabled && mPeripheralMidiDevice == null) {
+ Bundle properties = new Bundle();
+ Resources r = mContext.getResources();
+ properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
+ com.android.internal.R.string.usb_midi_peripheral_name));
+ properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
+ com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
+ properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
+ com.android.internal.R.string.usb_midi_peripheral_product_name));
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
+ mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
+ } else if (!enabled && mPeripheralMidiDevice != null) {
+ IoUtils.closeQuietly(mPeripheralMidiDevice);
+ mPeripheralMidiDevice = null;
+ }
+ }
+
+ //
+ // Devices List
+ //
+ public ArrayList<UsbAudioDevice> getConnectedDevices() {
+ ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size());
+ for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
+ devices.add(entry.getValue());
+ }
+ return devices;
+ }
+
+ //
+ // Logging
+ //
+ public void dump(FileDescriptor fd, PrintWriter pw) {
+ pw.println(" USB Audio Devices:");
+ for (UsbDevice device : mAudioDevices.keySet()) {
+ pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device));
+ }
+ pw.println(" USB MIDI Devices:");
+ for (UsbDevice device : mMidiDevices.keySet()) {
+ pw.println(" " + device.getDeviceName() + ": " + mMidiDevices.get(device));
+ }
+ }
+
+ public void logDevicesList(String title) {
+ if (DEBUG) {
+ for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
+ Slog.i(TAG, "UsbDevice-------------------");
+ Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]"));
+ Slog.i(TAG, "UsbAudioDevice--------------");
+ Slog.i(TAG, "" + entry.getValue());
+ }
+ }
+ }
+
+ // This logs a more terse (and more readable) version of the devices list
+ public void logDevices(String title) {
+ if (DEBUG) {
+ Slog.i(TAG, title);
+ for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) {
+ Slog.i(TAG, entry.getValue().toShortString());
+ }
+ }
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbAudioDevice.java b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
new file mode 100644
index 0000000..bdd28e4
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+public final class UsbAudioDevice {
+ private static final String TAG = "UsbAudioDevice";
+ protected static final boolean DEBUG = false;
+
+ public int mCard;
+ public int mDevice;
+ public boolean mHasPlayback;
+ public boolean mHasCapture;
+
+ // Device "class" flags
+ public static final int kAudioDeviceClassMask = 0x00FFFFFF;
+ public static final int kAudioDeviceClass_Undefined = 0x00000000;
+ public static final int kAudioDeviceClass_Internal = 0x00000001;
+ public static final int kAudioDeviceClass_External = 0x00000002;
+ // Device meta-data flags
+ public static final int kAudioDeviceMetaMask = 0xFF000000;
+ public static final int kAudioDeviceMeta_Alsa = 0x80000000;
+ // This member is a combination of the above bit-flags
+ public int mDeviceClass;
+
+ public String mDeviceName = "";
+ public String mDeviceDescription = "";
+
+ public UsbAudioDevice(int card, int device,
+ boolean hasPlayback, boolean hasCapture, int deviceClass) {
+ mCard = card;
+ mDevice = device;
+ mHasPlayback = hasPlayback;
+ mHasCapture = hasCapture;
+ mDeviceClass = deviceClass;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("UsbAudioDevice: [card: " + mCard);
+ sb.append(", device: " + mDevice);
+ sb.append(", name: " + mDeviceName);
+ sb.append(", hasPlayback: " + mHasPlayback);
+ sb.append(", hasCapture: " + mHasCapture);
+ sb.append(", class: 0x" + Integer.toHexString(mDeviceClass) + "]");
+ return sb.toString();
+ }
+
+ public String toShortString() {
+ return "[card:" + mCard + " device:" + mDevice + " " + mDeviceName + "]";
+ }
+}
+
diff --git a/services/usb/java/com/android/server/usb/UsbAudioManager.java b/services/usb/java/com/android/server/usb/UsbAudioManager.java
deleted file mode 100644
index bb45ee8..0000000
--- a/services/usb/java/com/android/server/usb/UsbAudioManager.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2014 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 an
- * limitations under the License.
- */
-
-package com.android.server.usb;
-
-import android.alsa.AlsaCardsParser;
-import android.alsa.AlsaDevicesParser;
-import android.content.Context;
-import android.content.Intent;
-import android.hardware.usb.UsbConstants;
-import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbInterface;
-import android.media.AudioManager;
-import android.os.UserHandle;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.HashMap;
-
-/**
- * UsbAudioManager manages USB audio devices.
- */
-public class UsbAudioManager {
- private static final String TAG = UsbAudioManager.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private final Context mContext;
-
- private final class AudioDevice {
- public int mCard;
- public int mDevice;
- public boolean mHasPlayback;
- public boolean mHasCapture;
- public boolean mHasMIDI;
-
- public AudioDevice(int card, int device,
- boolean hasPlayback, boolean hasCapture, boolean hasMidi) {
- mCard = card;
- mDevice = device;
- mHasPlayback = hasPlayback;
- mHasCapture = hasCapture;
- mHasMIDI = hasMidi;
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("AudioDevice: [card: " + mCard);
- sb.append(", device: " + mDevice);
- sb.append(", hasPlayback: " + mHasPlayback);
- sb.append(", hasCapture: " + mHasCapture);
- sb.append(", hasMidi: " + mHasMIDI);
- sb.append("]");
- return sb.toString();
- }
- }
-
- private final HashMap<UsbDevice,AudioDevice> mAudioDevices
- = new HashMap<UsbDevice,AudioDevice>();
-
- /* package */ UsbAudioManager(Context context) {
- mContext = context;
- }
-
- // Broadcasts the arrival/departure of a USB audio interface
- // audioDevice - the AudioDevice that was added or removed
- // enabled - if true, we're connecting a device (it's arrived), else disconnecting
- private void sendDeviceNotification(AudioDevice audioDevice, boolean enabled) {
- // send a sticky broadcast containing current USB state
- Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_DEVICE_PLUG);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- intent.putExtra("state", enabled ? 1 : 0);
- intent.putExtra("card", audioDevice.mCard);
- intent.putExtra("device", audioDevice.mDevice);
- intent.putExtra("hasPlayback", audioDevice.mHasPlayback);
- intent.putExtra("hasCapture", audioDevice.mHasCapture);
- intent.putExtra("hasMIDI", audioDevice.mHasMIDI);
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
- }
-
- private boolean waitForAlsaFile(int card, int device, boolean capture) {
- // These values were empirically determined.
- final int kNumRetries = 5;
- final int kSleepTime = 500; // ms
- String alsaDevPath = "/dev/snd/pcmC" + card + "D" + device + (capture ? "c" : "p");
- File alsaDevFile = new File(alsaDevPath);
- boolean exists = false;
- for (int retry = 0; !exists && retry < kNumRetries; retry++) {
- exists = alsaDevFile.exists();
- if (!exists) {
- try {
- Thread.sleep(kSleepTime);
- } catch (IllegalThreadStateException ex) {
- Slog.d(TAG, "usb: IllegalThreadStateException while waiting for ALSA file.");
- } catch (java.lang.InterruptedException ex) {
- Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file.");
- }
- }
- }
-
- return exists;
- }
-
- /* package */ void deviceAdded(UsbDevice usbDevice) {
- // Is there an audio interface in there?
- boolean isAudioDevice = false;
-
- // FIXME - handle multiple configurations?
- int interfaceCount = usbDevice.getInterfaceCount();
- for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount;
- ntrfaceIndex++) {
- UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex);
- if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) {
- isAudioDevice = true;
- }
- }
- if (!isAudioDevice) {
- return;
- }
-
- //TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is
- // present, unlike the waitForAlsaFile() which waits on a file in /dev/snd. It is not
- // clear why this works, or that it can be relied on going forward. Needs further
- // research.
- AlsaCardsParser cardsParser = new AlsaCardsParser();
- cardsParser.scan();
- // cardsParser.Log();
-
- // But we need to parse the device to determine its capabilities.
- AlsaDevicesParser devicesParser = new AlsaDevicesParser();
- devicesParser.scan();
- // devicesParser.Log();
-
- // The protocol for now will be to select the last-connected (highest-numbered)
- // Alsa Card.
- int card = cardsParser.getNumCardRecords() - 1;
- int device = 0;
-
- boolean hasPlayback = devicesParser.hasPlaybackDevices(card);
- boolean hasCapture = devicesParser.hasCaptureDevices(card);
- boolean hasMidi = devicesParser.hasMIDIDevices(card);
-
- // Playback device file needed/present?
- if (hasPlayback &&
- !waitForAlsaFile(card, device, false)) {
- return;
- }
-
- // Capture device file needed/present?
- if (hasCapture &&
- !waitForAlsaFile(card, device, true)) {
- return;
- }
-
- if (DEBUG) {
- Slog.d(TAG,
- "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
- }
-
- AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi);
- mAudioDevices.put(usbDevice, audioDevice);
- sendDeviceNotification(audioDevice, true);
- }
-
- /* package */ void deviceRemoved(UsbDevice device) {
- if (DEBUG) {
- Slog.d(TAG, "deviceRemoved(): " + device);
- }
-
- AudioDevice audioDevice = mAudioDevices.remove(device);
- if (audioDevice != null) {
- sendDeviceNotification(audioDevice, false);
- }
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw) {
- pw.println(" USB AudioDevices:");
- for (UsbDevice device : mAudioDevices.keySet()) {
- pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device));
- }
- }
-}
diff --git a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
index 1cf00d2..8849acd 100644
--- a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
@@ -21,7 +21,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
@@ -31,8 +30,11 @@ import android.os.FileUtils;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
import android.util.Slog;
import android.util.Base64;
+
import com.android.server.FgThread;
import java.lang.Thread;
@@ -46,7 +48,7 @@ import java.io.PrintWriter;
import java.security.MessageDigest;
import java.util.Arrays;
-public class UsbDebuggingManager implements Runnable {
+public class UsbDebuggingManager {
private static final String TAG = "UsbDebuggingManager";
private static final boolean DEBUG = false;
@@ -57,86 +59,135 @@ public class UsbDebuggingManager implements Runnable {
private final Context mContext;
private final Handler mHandler;
- private Thread mThread;
+ private UsbDebuggingThread mThread;
private boolean mAdbEnabled = false;
private String mFingerprints;
- private LocalSocket mSocket = null;
- private OutputStream mOutputStream = null;
public UsbDebuggingManager(Context context) {
mHandler = new UsbDebuggingHandler(FgThread.get().getLooper());
mContext = context;
}
- private void listenToSocket() throws IOException {
- try {
- byte[] buffer = new byte[BUFFER_SIZE];
- LocalSocketAddress address = new LocalSocketAddress(ADBD_SOCKET,
- LocalSocketAddress.Namespace.RESERVED);
- InputStream inputStream = null;
-
- mSocket = new LocalSocket();
- mSocket.connect(address);
+ class UsbDebuggingThread extends Thread {
+ private boolean mStopped;
+ private LocalSocket mSocket;
+ private OutputStream mOutputStream;
+ private InputStream mInputStream;
- mOutputStream = mSocket.getOutputStream();
- inputStream = mSocket.getInputStream();
+ UsbDebuggingThread() {
+ super(TAG);
+ }
+ @Override
+ public void run() {
+ if (DEBUG) Slog.d(TAG, "Entering thread");
while (true) {
- int count = inputStream.read(buffer);
- if (count < 0) {
- break;
- }
-
- if (buffer[0] == 'P' && buffer[1] == 'K') {
- String key = new String(Arrays.copyOfRange(buffer, 2, count));
- Slog.d(TAG, "Received public key: " + key);
- Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_CONFIRM);
- msg.obj = key;
- mHandler.sendMessage(msg);
+ synchronized (this) {
+ if (mStopped) {
+ if (DEBUG) Slog.d(TAG, "Exiting thread");
+ return;
+ }
+ try {
+ openSocketLocked();
+ } catch (Exception e) {
+ /* Don't loop too fast if adbd dies, before init restarts it */
+ SystemClock.sleep(1000);
+ }
}
- else {
- Slog.e(TAG, "Wrong message: " + (new String(Arrays.copyOfRange(buffer, 0, 2))));
- break;
+ try {
+ listenToSocket();
+ } catch (Exception e) {
+ /* Don't loop too fast if adbd dies, before init restarts it */
+ SystemClock.sleep(1000);
}
}
- } finally {
- closeSocket();
}
- }
- @Override
- public void run() {
- while (mAdbEnabled) {
+ private void openSocketLocked() throws IOException {
try {
- listenToSocket();
- } catch (Exception e) {
- /* Don't loop too fast if adbd dies, before init restarts it */
- SystemClock.sleep(1000);
+ LocalSocketAddress address = new LocalSocketAddress(ADBD_SOCKET,
+ LocalSocketAddress.Namespace.RESERVED);
+ mInputStream = null;
+
+ if (DEBUG) Slog.d(TAG, "Creating socket");
+ mSocket = new LocalSocket();
+ mSocket.connect(address);
+
+ mOutputStream = mSocket.getOutputStream();
+ mInputStream = mSocket.getInputStream();
+ } catch (IOException ioe) {
+ closeSocketLocked();
+ throw ioe;
}
}
- }
- private void closeSocket() {
- try {
- mOutputStream.close();
- } catch (IOException e) {
- Slog.e(TAG, "Failed closing output stream: " + e);
- }
+ private void listenToSocket() throws IOException {
+ try {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ while (true) {
+ int count = mInputStream.read(buffer);
+ if (count < 0) {
+ break;
+ }
- try {
- mSocket.close();
- } catch (IOException ex) {
- Slog.e(TAG, "Failed closing socket: " + ex);
+ if (buffer[0] == 'P' && buffer[1] == 'K') {
+ String key = new String(Arrays.copyOfRange(buffer, 2, count));
+ Slog.d(TAG, "Received public key: " + key);
+ Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_CONFIRM);
+ msg.obj = key;
+ mHandler.sendMessage(msg);
+ } else {
+ Slog.e(TAG, "Wrong message: "
+ + (new String(Arrays.copyOfRange(buffer, 0, 2))));
+ break;
+ }
+ }
+ } finally {
+ synchronized (this) {
+ closeSocketLocked();
+ }
+ }
}
- }
- private void sendResponse(String msg) {
- if (mOutputStream != null) {
+ private void closeSocketLocked() {
+ if (DEBUG) Slog.d(TAG, "Closing socket");
+ try {
+ if (mOutputStream != null) {
+ mOutputStream.close();
+ mOutputStream = null;
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed closing output stream: " + e);
+ }
+
try {
- mOutputStream.write(msg.getBytes());
+ if (mSocket != null) {
+ mSocket.close();
+ mSocket = null;
+ }
+ } catch (IOException ex) {
+ Slog.e(TAG, "Failed closing socket: " + ex);
+ }
+ }
+
+ /** Call to stop listening on the socket and exit the thread. */
+ void stopListening() {
+ synchronized (this) {
+ mStopped = true;
+ closeSocketLocked();
}
- catch (IOException ex) {
- Slog.e(TAG, "Failed to write response:", ex);
+ }
+
+ void sendResponse(String msg) {
+ synchronized (this) {
+ if (!mStopped && mOutputStream != null) {
+ try {
+ mOutputStream.write(msg.getBytes());
+ }
+ catch (IOException ex) {
+ Slog.e(TAG, "Failed to write response:", ex);
+ }
+ }
}
}
}
@@ -161,7 +212,7 @@ public class UsbDebuggingManager implements Runnable {
mAdbEnabled = true;
- mThread = new Thread(UsbDebuggingManager.this, TAG);
+ mThread = new UsbDebuggingThread();
mThread.start();
break;
@@ -171,16 +222,12 @@ public class UsbDebuggingManager implements Runnable {
break;
mAdbEnabled = false;
- closeSocket();
- try {
- mThread.join();
- } catch (Exception ex) {
+ if (mThread != null) {
+ mThread.stopListening();
+ mThread = null;
}
- mThread = null;
- mOutputStream = null;
- mSocket = null;
break;
case MESSAGE_ADB_ALLOW: {
@@ -197,19 +244,33 @@ public class UsbDebuggingManager implements Runnable {
writeKey(key);
}
- sendResponse("OK");
+ if (mThread != null) {
+ mThread.sendResponse("OK");
+ }
break;
}
case MESSAGE_ADB_DENY:
- sendResponse("NO");
+ if (mThread != null) {
+ mThread.sendResponse("NO");
+ }
break;
case MESSAGE_ADB_CONFIRM: {
+ if ("trigger_restart_min_framework".equals(
+ SystemProperties.get("vold.decrypt"))) {
+ Slog.d(TAG, "Deferring adb confirmation until after vold decrypt");
+ if (mThread != null) {
+ mThread.sendResponse("NO");
+ }
+ break;
+ }
String key = (String)msg.obj;
String fingerprints = getFingerprints(key);
if ("".equals(fingerprints)) {
- sendResponse("NO");
+ if (mThread != null) {
+ mThread.sendResponse("NO");
+ }
break;
}
mFingerprints = fingerprints;
@@ -279,7 +340,7 @@ public class UsbDebuggingManager implements Runnable {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
try {
- mContext.startActivity(intent);
+ mContext.startActivityAsUser(intent, UserHandle.OWNER);
return true;
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "unable to start adb whitelist activity: " + componentName, e);
@@ -379,7 +440,7 @@ public class UsbDebuggingManager implements Runnable {
public void dump(FileDescriptor fd, PrintWriter pw) {
pw.println(" USB Debugging State:");
- pw.println(" Connected to adbd: " + (mOutputStream != null));
+ pw.println(" Connected to adbd: " + (mThread != null));
pw.println(" Last key received: " + mFingerprints);
pw.println(" User keys:");
try {
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index f3fa747..6adb8be 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -30,7 +30,6 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
-import android.media.AudioManager;
import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
@@ -85,6 +84,8 @@ public class UsbDeviceManager {
"/sys/class/android_usb/android0/f_rndis/ethaddr";
private static final String AUDIO_SOURCE_PCM_PATH =
"/sys/class/android_usb/android0/f_audio_source/pcm";
+ private static final String MIDI_ALSA_PATH =
+ "/sys/class/android_usb/android0/f_midi/alsa";
private static final int MSG_UPDATE_STATE = 0;
private static final int MSG_ENABLE_ADB = 1;
@@ -124,9 +125,13 @@ public class UsbDeviceManager {
private boolean mUseUsbNotification;
private boolean mAdbEnabled;
private boolean mAudioSourceEnabled;
+ private boolean mMidiEnabled;
+ private int mMidiCard;
+ private int mMidiDevice;
private Map<String, List<Pair<String, String>>> mOemModeMap;
private String[] mAccessoryStrings;
private UsbDebuggingManager mDebuggingManager;
+ private final UsbAlsaManager mUsbAlsaManager;
private class AdbSettingsObserver extends ContentObserver {
public AdbSettingsObserver() {
@@ -159,8 +164,9 @@ public class UsbDeviceManager {
}
};
- public UsbDeviceManager(Context context) {
+ public UsbDeviceManager(Context context, UsbAlsaManager alsaManager) {
mContext = context;
+ mUsbAlsaManager = alsaManager;
mContentResolver = context.getContentResolver();
PackageManager pm = mContext.getPackageManager();
mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
@@ -359,18 +365,6 @@ public class UsbDeviceManager {
updateState(state);
mAdbEnabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB);
- // Upgrade step for previous versions that used persist.service.adb.enable
- String value = SystemProperties.get("persist.service.adb.enable", "");
- if (value.length() > 0) {
- char enable = value.charAt(0);
- if (enable == '1') {
- setAdbEnabled(true);
- } else if (enable == '0') {
- setAdbEnabled(false);
- }
- SystemProperties.set("persist.service.adb.enable", "");
- }
-
// register observer to listen for settings changes
mContentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
@@ -594,19 +588,15 @@ public class UsbDeviceManager {
boolean enabled = containsFunction(mCurrentFunctions,
UsbManager.USB_FUNCTION_AUDIO_SOURCE);
if (enabled != mAudioSourceEnabled) {
- // send a sticky broadcast containing current USB state
- Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_ACCESSORY_PLUG);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- intent.putExtra("state", (enabled ? 1 : 0));
+ int card = -1;
+ int device = -1;
+
if (enabled) {
Scanner scanner = null;
try {
scanner = new Scanner(new File(AUDIO_SOURCE_PCM_PATH));
- int card = scanner.nextInt();
- int device = scanner.nextInt();
- intent.putExtra("card", card);
- intent.putExtra("device", device);
+ card = scanner.nextInt();
+ device = scanner.nextInt();
} catch (FileNotFoundException e) {
Slog.e(TAG, "could not open audio source PCM file", e);
} finally {
@@ -615,11 +605,34 @@ public class UsbDeviceManager {
}
}
}
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mUsbAlsaManager.setAccessoryAudioState(enabled, card, device);
mAudioSourceEnabled = enabled;
}
}
+ private void updateMidiFunction() {
+ boolean enabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MIDI);
+ if (enabled != mMidiEnabled) {
+ if (enabled) {
+ Scanner scanner = null;
+ try {
+ scanner = new Scanner(new File(MIDI_ALSA_PATH));
+ mMidiCard = scanner.nextInt();
+ mMidiDevice = scanner.nextInt();
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "could not open MIDI PCM file", e);
+ enabled = false;
+ } finally {
+ if (scanner != null) {
+ scanner.close();
+ }
+ }
+ }
+ mMidiEnabled = enabled;
+ }
+ mUsbAlsaManager.setPeripheralMidiState(mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -638,6 +651,7 @@ public class UsbDeviceManager {
if (mBootCompleted) {
updateUsbState();
updateAudioSourceFunction();
+ updateMidiFunction();
}
break;
case MSG_ENABLE_ADB:
@@ -653,6 +667,7 @@ public class UsbDeviceManager {
updateAdbNotification();
updateUsbState();
updateAudioSourceFunction();
+ updateMidiFunction();
break;
case MSG_BOOT_COMPLETED:
mBootCompleted = true;
@@ -703,6 +718,8 @@ public class UsbDeviceManager {
id = com.android.internal.R.string.usb_mtp_notification_title;
} else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP)) {
id = com.android.internal.R.string.usb_ptp_notification_title;
+ } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MIDI)) {
+ id = com.android.internal.R.string.usb_midi_notification_title;
} else if (containsFunction(mCurrentFunctions,
UsbManager.USB_FUNCTION_MASS_STORAGE)) {
id = com.android.internal.R.string.usb_cd_installer_notification_title;
@@ -742,7 +759,7 @@ public class UsbDeviceManager {
"com.android.settings.UsbSettings"));
PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
intent, 0, null, UserHandle.CURRENT);
- notification.color = mContext.getResources().getColor(
+ notification.color = mContext.getColor(
com.android.internal.R.color.system_notification_accent_color);
notification.setLatestEventInfo(mContext, title, message, pi);
notification.visibility = Notification.VISIBILITY_PUBLIC;
@@ -780,7 +797,7 @@ public class UsbDeviceManager {
"com.android.settings.DevelopmentSettings"));
PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
intent, 0, null, UserHandle.CURRENT);
- notification.color = mContext.getResources().getColor(
+ notification.color = mContext.getColor(
com.android.internal.R.color.system_notification_accent_color);
notification.setLatestEventInfo(mContext, title, message, pi);
notification.visibility = Notification.VISIBILITY_PUBLIC;
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index e769bda..5b58051 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -17,7 +17,6 @@
package com.android.server.usb;
import android.content.Context;
-import android.content.Intent;
import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
@@ -25,13 +24,11 @@ import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
@@ -60,16 +57,16 @@ public class UsbHostManager {
private ArrayList<UsbInterface> mNewInterfaces;
private ArrayList<UsbEndpoint> mNewEndpoints;
- private UsbAudioManager mUsbAudioManager;
+ private final UsbAlsaManager mUsbAlsaManager;
@GuardedBy("mLock")
private UsbSettingsManager mCurrentSettings;
- public UsbHostManager(Context context) {
+ public UsbHostManager(Context context, UsbAlsaManager alsaManager) {
mContext = context;
mHostBlacklist = context.getResources().getStringArray(
com.android.internal.R.array.config_usbHostBlacklist);
- mUsbAudioManager = new UsbAudioManager(context);
+ mUsbAlsaManager = alsaManager;
}
public void setCurrentSettings(UsbSettingsManager settings) {
@@ -222,7 +219,7 @@ public class UsbHostManager {
mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
Slog.d(TAG, "Added device " + mNewDevice);
getCurrentSettings().deviceAttached(mNewDevice);
- mUsbAudioManager.deviceAdded(mNewDevice);
+ mUsbAlsaManager.usbDeviceAdded(mNewDevice);
} else {
Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
}
@@ -238,7 +235,7 @@ public class UsbHostManager {
synchronized (mLock) {
UsbDevice device = mDevices.remove(deviceName);
if (device != null) {
- mUsbAudioManager.deviceRemoved(device);
+ mUsbAlsaManager.usbDeviceRemoved(device);
getCurrentSettings().deviceDetached(device);
}
}
@@ -290,7 +287,6 @@ public class UsbHostManager {
pw.println(" " + name + ": " + mDevices.get(name));
}
}
- mUsbAudioManager.dump(fd, pw);
}
private native void monitorUsbHostBus();
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
new file mode 100644
index 0000000..671cf01
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2014 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.content.Context;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceServer;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiSender;
+import android.os.Bundle;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructPollfd;
+import android.util.Log;
+
+import com.android.internal.midi.MidiEventScheduler;
+import com.android.internal.midi.MidiEventScheduler.MidiEvent;
+
+import libcore.io.IoUtils;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public final class UsbMidiDevice implements Closeable {
+ private static final String TAG = "UsbMidiDevice";
+
+ private MidiDeviceServer mServer;
+
+ // event schedulers for each output port
+ private final MidiEventScheduler[] mEventSchedulers;
+
+ private static final int BUFFER_SIZE = 512;
+
+ private final FileDescriptor[] mFileDescriptors;
+
+ // for polling multiple FileDescriptors for MIDI events
+ private final StructPollfd[] mPollFDs;
+ // streams for reading from ALSA driver
+ private final FileInputStream[] mInputStreams;
+ // streams for writing to ALSA driver
+ private final FileOutputStream[] mOutputStreams;
+
+ public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
+ // FIXME - support devices with different number of input and output ports
+ int subDevices = nativeGetSubdeviceCount(card, device);
+ if (subDevices <= 0) {
+ Log.e(TAG, "nativeGetSubdeviceCount failed");
+ return null;
+ }
+
+ // FIXME - support devices with different number of input and output ports
+ FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices);
+ if (fileDescriptors == null) {
+ Log.e(TAG, "nativeOpen failed");
+ return null;
+ }
+
+ UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors);
+ if (!midiDevice.register(context, properties)) {
+ IoUtils.closeQuietly(midiDevice);
+ Log.e(TAG, "createDeviceServer failed");
+ return null;
+ }
+ return midiDevice;
+ }
+
+ private UsbMidiDevice(FileDescriptor[] fileDescriptors) {
+ mFileDescriptors = fileDescriptors;
+ int inputCount = fileDescriptors.length;
+ int outputCount = fileDescriptors.length;
+
+ mPollFDs = new StructPollfd[inputCount];
+ mInputStreams = new FileInputStream[inputCount];
+ for (int i = 0; i < inputCount; i++) {
+ FileDescriptor fd = fileDescriptors[i];
+ StructPollfd pollfd = new StructPollfd();
+ pollfd.fd = fd;
+ pollfd.events = (short)OsConstants.POLLIN;
+ mPollFDs[i] = pollfd;
+ mInputStreams[i] = new FileInputStream(fd);
+ }
+
+ mOutputStreams = new FileOutputStream[outputCount];
+ mEventSchedulers = new MidiEventScheduler[outputCount];
+ for (int i = 0; i < outputCount; i++) {
+ mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
+ mEventSchedulers[i] = new MidiEventScheduler();
+ }
+ }
+
+ private boolean register(Context context, Bundle properties) {
+ MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+ if (midiManager == null) {
+ Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
+ return false;
+ }
+
+ int inputCount = mInputStreams.length;
+ int outputCount = mOutputStreams.length;
+ MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
+ for (int port = 0; port < inputCount; port++) {
+ inputPortReceivers[port] = mEventSchedulers[port].getReceiver();
+ }
+
+ mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
+ null, null, properties, MidiDeviceInfo.TYPE_USB, null);
+ if (mServer == null) {
+ return false;
+ }
+ final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
+
+ // Create input thread which will read from all input ports
+ new Thread("UsbMidiDevice input thread") {
+ @Override
+ public void run() {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ try {
+ boolean done = false;
+ while (!done) {
+ // look for a readable FileDescriptor
+ for (int index = 0; index < mPollFDs.length; index++) {
+ StructPollfd pfd = mPollFDs[index];
+ if ((pfd.revents & OsConstants.POLLIN) != 0) {
+ // clear readable flag
+ pfd.revents = 0;
+
+ int count = mInputStreams[index].read(buffer);
+ outputReceivers[index].send(buffer, 0, count);
+ } else if ((pfd.revents & (OsConstants.POLLERR
+ | OsConstants.POLLHUP)) != 0) {
+ done = true;
+ }
+ }
+
+ // wait until we have a readable port
+ Os.poll(mPollFDs, -1 /* infinite timeout */);
+ }
+ } catch (IOException e) {
+ Log.d(TAG, "reader thread exiting");
+ } catch (ErrnoException e) {
+ Log.d(TAG, "reader thread exiting");
+ }
+ Log.d(TAG, "input thread exit");
+ }
+ }.start();
+
+ // Create output thread for each output port
+ for (int port = 0; port < outputCount; port++) {
+ final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
+ final FileOutputStream outputStreamF = mOutputStreams[port];
+ final int portF = port;
+
+ new Thread("UsbMidiDevice output thread " + port) {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent)eventSchedulerF.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+ try {
+ outputStreamF.write(event.data, 0, event.count);
+ } catch (IOException e) {
+ Log.e(TAG, "write failed for port " + portF);
+ }
+ eventSchedulerF.addEventToPool(event);
+ }
+ Log.d(TAG, "output thread exit");
+ }
+ }.start();
+ }
+
+ return true;
+ }
+
+ @Override
+ public void close() throws IOException {
+ for (int i = 0; i < mEventSchedulers.length; i++) {
+ mEventSchedulers[i].close();
+ }
+
+ if (mServer != null) {
+ mServer.close();
+ }
+
+ for (int i = 0; i < mInputStreams.length; i++) {
+ mInputStreams[i].close();
+ }
+ for (int i = 0; i < mOutputStreams.length; i++) {
+ mOutputStreams[i].close();
+ }
+ nativeClose(mFileDescriptors);
+ }
+
+ private static native int nativeGetSubdeviceCount(int card, int device);
+ private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
+ private static native void nativeClose(FileDescriptor[] fileDescriptors);
+}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index fd83f92..fda8076 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -73,6 +73,7 @@ public class UsbService extends IUsbManager.Stub {
private UsbDeviceManager mDeviceManager;
private UsbHostManager mHostManager;
+ private final UsbAlsaManager mAlsaManager;
private final Object mLock = new Object();
@@ -95,12 +96,14 @@ public class UsbService extends IUsbManager.Stub {
public UsbService(Context context) {
mContext = context;
+ mAlsaManager = new UsbAlsaManager(context);
+
final PackageManager pm = mContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
- mHostManager = new UsbHostManager(context);
+ mHostManager = new UsbHostManager(context, mAlsaManager);
}
if (new File("/sys/class/android_usb").exists()) {
- mDeviceManager = new UsbDeviceManager(context);
+ mDeviceManager = new UsbDeviceManager(context, mAlsaManager);
}
setCurrentUser(UserHandle.USER_OWNER);
@@ -137,6 +140,8 @@ public class UsbService extends IUsbManager.Stub {
}
public void systemReady() {
+ mAlsaManager.systemReady();
+
if (mDeviceManager != null) {
mDeviceManager.systemReady();
}
@@ -305,6 +310,7 @@ public class UsbService extends IUsbManager.Stub {
if (mHostManager != null) {
mHostManager.dump(fd, pw);
}
+ mAlsaManager.dump(fd, pw);
synchronized (mLock) {
for (int i = 0; i < mSettingsByUser.size(); i++) {
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index 37b5c51..bfd1f13 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -27,7 +27,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index f5d4867..7dce83e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -39,7 +39,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionService;
@@ -51,6 +50,7 @@ import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
@@ -130,12 +130,14 @@ public class VoiceInteractionManagerService extends SystemService {
}
public void initForUser(int userHandle) {
- if (DEBUG) Slog.i(TAG, "initForUser user=" + userHandle);
+ if (DEBUG) Slog.d(TAG, "**************** initForUser user=" + userHandle);
String curInteractorStr = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.VOICE_INTERACTION_SERVICE, userHandle);
ComponentName curRecognizer = getCurRecognizer(userHandle);
VoiceInteractionServiceInfo curInteractorInfo = null;
+ if (DEBUG) Slog.d(TAG, "curInteractorStr=" + curInteractorStr
+ + " curRecognizer=" + curRecognizer);
if (curInteractorStr == null && curRecognizer != null
&& !ActivityManager.isLowRamDeviceStatic()) {
// If there is no interactor setting, that means we are upgrading
@@ -149,6 +151,8 @@ public class VoiceInteractionManagerService extends SystemService {
// Looks good! We'll apply this one. To make it happen, we clear the
// recognizer so that we don't think we have anything set and will
// re-apply the settings.
+ if (DEBUG) Slog.d(TAG, "No set interactor, found avail: "
+ + curInteractorInfo.getServiceInfo().name);
curRecognizer = null;
}
}
@@ -157,6 +161,7 @@ public class VoiceInteractionManagerService extends SystemService {
// enabled; if it is, turn it off.
if (ActivityManager.isLowRamDeviceStatic() && curInteractorStr != null) {
if (!TextUtils.isEmpty(curInteractorStr)) {
+ if (DEBUG) Slog.d(TAG, "Svelte device; disabling interactor");
setCurInteractor(null, userHandle);
curInteractorStr = "";
}
@@ -179,8 +184,11 @@ public class VoiceInteractionManagerService extends SystemService {
}
// If the apps for the currently set components still exist, then all is okay.
if (recognizerInfo != null && (curInteractor == null || interactorInfo != null)) {
+ if (DEBUG) Slog.d(TAG, "Current interactor/recognizer okay, done!");
return;
}
+ if (DEBUG) Slog.d(TAG, "Bad recognizer (" + recognizerInfo + ") or interactor ("
+ + interactorInfo + ")");
}
// Initializing settings, look for an interactor first (but only on non-svelte).
@@ -317,7 +325,7 @@ public class VoiceInteractionManagerService extends SystemService {
if (TextUtils.isEmpty(curInteractor)) {
return null;
}
- if (DEBUG) Slog.i(TAG, "getCurInteractor curInteractor=" + curInteractor
+ if (DEBUG) Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
+ " user=" + userHandle);
return ComponentName.unflattenFromString(curInteractor);
}
@@ -326,7 +334,7 @@ public class VoiceInteractionManagerService extends SystemService {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.VOICE_INTERACTION_SERVICE,
comp != null ? comp.flattenToShortString() : "", userHandle);
- if (DEBUG) Slog.i(TAG, "setCurInteractor comp=" + comp
+ if (DEBUG) Slog.d(TAG, "setCurInteractor comp=" + comp
+ " user=" + userHandle);
}
@@ -364,7 +372,7 @@ public class VoiceInteractionManagerService extends SystemService {
if (TextUtils.isEmpty(curRecognizer)) {
return null;
}
- if (DEBUG) Slog.i(TAG, "getCurRecognizer curRecognizer=" + curRecognizer
+ if (DEBUG) Slog.d(TAG, "getCurRecognizer curRecognizer=" + curRecognizer
+ " user=" + userHandle);
return ComponentName.unflattenFromString(curRecognizer);
}
@@ -373,23 +381,21 @@ public class VoiceInteractionManagerService extends SystemService {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.VOICE_RECOGNITION_SERVICE,
comp != null ? comp.flattenToShortString() : "", userHandle);
- if (DEBUG) Slog.i(TAG, "setCurRecognizer comp=" + comp
+ if (DEBUG) Slog.d(TAG, "setCurRecognizer comp=" + comp
+ " user=" + userHandle);
}
@Override
- public void startSession(IVoiceInteractionService service, Bundle args) {
+ public void showSession(IVoiceInteractionService service, Bundle args, int flags) {
synchronized (this) {
if (mImpl == null || mImpl.mService == null
|| service.asBinder() != mImpl.mService.asBinder()) {
throw new SecurityException(
"Caller is not the current voice interaction service");
}
- final int callingPid = Binder.getCallingPid();
- final int callingUid = Binder.getCallingUid();
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.startSessionLocked(callingPid, callingUid, args);
+ mImpl.showSessionLocked(args, flags, null /* showCallback */);
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -417,6 +423,40 @@ public class VoiceInteractionManagerService extends SystemService {
}
@Override
+ public boolean showSessionFromSession(IBinder token, Bundle sessionArgs, int flags) {
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "showSessionFromSession without running voice interaction service");
+ return false;
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mImpl.showSessionLocked(sessionArgs, flags, null /* showCallback */);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public boolean hideSessionFromSession(IBinder token) {
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "hideSessionFromSession without running voice interaction service");
+ return false;
+ }
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mImpl.hideSessionLocked(callingPid, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
public int startVoiceActivity(IBinder token, Intent intent, String resolvedType) {
synchronized (this) {
if (mImpl == null) {
@@ -426,9 +466,6 @@ public class VoiceInteractionManagerService extends SystemService {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long caller = Binder.clearCallingIdentity();
- if (!SystemProperties.getBoolean("persist.test.voice_interaction", false)) {
- throw new SecurityException("Voice interaction not supported");
- }
try {
return mImpl.startVoiceActivityLocked(callingPid, callingUid, token,
intent, resolvedType);
@@ -439,34 +476,44 @@ public class VoiceInteractionManagerService extends SystemService {
}
@Override
- public void finish(IBinder token) {
+ public void setKeepAwake(IBinder token, boolean keepAwake) {
synchronized (this) {
if (mImpl == null) {
- Slog.w(TAG, "finish without running voice interaction service");
+ Slog.w(TAG, "setKeepAwake without running voice interaction service");
return;
}
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.finishLocked(callingPid, callingUid, token);
+ mImpl.setKeepAwakeLocked(callingPid, callingUid, token, keepAwake);
} finally {
Binder.restoreCallingIdentity(caller);
}
}
}
- //----------------- Model management APIs --------------------------------//
-
@Override
- public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
+ public void finish(IBinder token) {
synchronized (this) {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller does not hold the permission "
- + Manifest.permission.MANAGE_VOICE_KEYPHRASES);
+ if (mImpl == null) {
+ Slog.w(TAG, "finish without running voice interaction service");
+ return;
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.finishLocked(token);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
}
}
+ }
+
+ //----------------- Model management APIs --------------------------------//
+
+ @Override
+ public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
+ enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES);
if (bcp47Locale == null) {
throw new IllegalArgumentException("Illegal argument(s) in getKeyphraseSoundModel");
@@ -483,15 +530,9 @@ public class VoiceInteractionManagerService extends SystemService {
@Override
public int updateKeyphraseSoundModel(KeyphraseSoundModel model) {
- synchronized (this) {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller does not hold the permission "
- + Manifest.permission.MANAGE_VOICE_KEYPHRASES);
- }
- if (model == null) {
- throw new IllegalArgumentException("Model must not be null");
- }
+ enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES);
+ if (model == null) {
+ throw new IllegalArgumentException("Model must not be null");
}
final long caller = Binder.clearCallingIdentity();
@@ -514,13 +555,7 @@ public class VoiceInteractionManagerService extends SystemService {
@Override
public int deleteKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
- synchronized (this) {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller does not hold the permission "
- + Manifest.permission.MANAGE_VOICE_KEYPHRASES);
- }
- }
+ enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES);
if (bcp47Locale == null) {
throw new IllegalArgumentException(
@@ -649,6 +684,52 @@ public class VoiceInteractionManagerService extends SystemService {
}
@Override
+ public ComponentName getActiveServiceComponentName() {
+ enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+ synchronized (this) {
+ return mImpl != null ? mImpl.mComponent : null;
+ }
+ }
+
+ @Override
+ public void showSessionForActiveService(IVoiceInteractionSessionShowCallback showCallback) {
+ enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "showSessionForActiveService without running voice interaction"
+ + "service");
+ return;
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.showSessionLocked(new Bundle() /* sessionArgs */,
+ VoiceInteractionService.START_SOURCE_ASSIST_GESTURE
+ | VoiceInteractionService.START_WITH_ASSIST
+ | VoiceInteractionService.START_WITH_SCREENSHOT,
+ showCallback);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public boolean isSessionRunning() {
+ enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+ synchronized (this) {
+ return mImpl != null && mImpl.mActiveSession != null;
+ }
+ }
+
+ @Override
+ public boolean activeServiceSupportsAssistGesture() {
+ enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+ synchronized (this) {
+ return mImpl != null && mImpl.mInfo.getSupportsAssistGesture();
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -668,6 +749,12 @@ public class VoiceInteractionManagerService extends SystemService {
mSoundTriggerHelper.dump(fd, pw, args);
}
+ private void enforceCallingPermission(String permission) {
+ if (mContext.checkCallingPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller does not hold the permission " + permission);
+ }
+ }
+
class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
super(handler);
@@ -696,7 +783,7 @@ public class VoiceInteractionManagerService extends SystemService {
@Override
public void onSomePackagesChanged() {
int userHandle = getChangingUserId();
- if (DEBUG) Slog.i(TAG, "onSomePackagesChanged user=" + userHandle);
+ if (DEBUG) Slog.d(TAG, "onSomePackagesChanged user=" + userHandle);
ComponentName curInteractor = getCurInteractor(userHandle);
ComponentName curRecognizer = getCurRecognizer(userHandle);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b36b611..61ec162 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -26,7 +26,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -35,19 +34,18 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
-import android.service.voice.IVoiceInteractionSessionService;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
import android.util.Slog;
import android.view.IWindowManager;
-import android.view.WindowManager;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-class VoiceInteractionManagerServiceImpl {
+class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
final static String TAG = "VoiceInteractionServiceManager";
final boolean mValid;
@@ -64,7 +62,7 @@ class VoiceInteractionManagerServiceImpl {
boolean mBound = false;
IVoiceInteractionService mService;
- SessionConnection mActiveSession;
+ VoiceInteractionSessionConnection mActiveSession;
final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -100,91 +98,6 @@ class VoiceInteractionManagerServiceImpl {
}
};
- final class SessionConnection implements ServiceConnection {
- final IBinder mToken = new Binder();
- final Bundle mArgs;
- boolean mBound;
- IVoiceInteractionSessionService mService;
- IVoiceInteractionSession mSession;
- IVoiceInteractor mInteractor;
-
- SessionConnection(Bundle args) {
- mArgs = args;
- Intent serviceIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
- serviceIntent.setComponent(mSessionComponentName);
- mBound = mContext.bindServiceAsUser(serviceIntent, this,
- Context.BIND_AUTO_CREATE, new UserHandle(mUser));
- if (mBound) {
- try {
- mIWindowManager.addWindowToken(mToken,
- WindowManager.LayoutParams.TYPE_VOICE_INTERACTION);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed adding window token", e);
- }
- } else {
- Slog.w(TAG, "Failed binding to voice interaction session service " + mComponent);
- }
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (mLock) {
- mService = IVoiceInteractionSessionService.Stub.asInterface(service);
- if (mActiveSession == this) {
- try {
- mService.newSession(mToken, mArgs);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed adding window token", e);
- }
- }
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mService = null;
- }
-
- public void cancel() {
- if (mBound) {
- if (mSession != null) {
- try {
- mSession.destroy();
- } catch (RemoteException e) {
- Slog.w(TAG, "Voice interation session already dead");
- }
- }
- if (mSession != null) {
- try {
- mAm.finishVoiceTask(mSession);
- } catch (RemoteException e) {
- }
- }
- mContext.unbindService(this);
- try {
- mIWindowManager.removeWindowToken(mToken);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed removing window token", e);
- }
- mBound = false;
- mService = null;
- mSession = null;
- mInteractor = null;
- }
- }
-
- public void dump(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mToken="); pw.println(mToken);
- pw.print(prefix); pw.print("mArgs="); pw.println(mArgs);
- pw.print(prefix); pw.print("mBound="); pw.println(mBound);
- if (mBound) {
- pw.print(prefix); pw.print("mService="); pw.println(mService);
- pw.print(prefix); pw.print("mSession="); pw.println(mSession);
- pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
- }
- }
- };
-
VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock,
int userHandle, ComponentName service) {
mContext = context;
@@ -222,12 +135,20 @@ class VoiceInteractionManagerServiceImpl {
mContext.registerReceiver(mBroadcastReceiver, filter, null, handler);
}
- public void startSessionLocked(int callingPid, int callingUid, Bundle args) {
+ public boolean showSessionLocked(Bundle args, int flags,
+ IVoiceInteractionSessionShowCallback showCallback) {
+ if (mActiveSession == null) {
+ mActiveSession = new VoiceInteractionSessionConnection(mLock, mSessionComponentName,
+ mUser, mContext, this, mInfo.getServiceInfo().applicationInfo.uid);
+ }
+ return mActiveSession.showLocked(args, flags, showCallback);
+ }
+
+ public boolean hideSessionLocked(int callingPid, int callingUid) {
if (mActiveSession != null) {
- mActiveSession.cancel();
- mActiveSession = null;
+ return mActiveSession.hideLocked();
}
- mActiveSession = new SessionConnection(args);
+ return false;
}
public boolean deliverNewSessionLocked(int callingPid, int callingUid, IBinder token,
@@ -236,8 +157,7 @@ class VoiceInteractionManagerServiceImpl {
Slog.w(TAG, "deliverNewSession does not match active session");
return false;
}
- mActiveSession.mSession = session;
- mActiveSession.mInteractor = interactor;
+ mActiveSession.deliverNewSessionLocked(session, interactor);
return true;
}
@@ -248,6 +168,10 @@ class VoiceInteractionManagerServiceImpl {
Slog.w(TAG, "startVoiceActivity does not match active session");
return ActivityManager.START_CANCELED;
}
+ if (!mActiveSession.mShown) {
+ Slog.w(TAG, "startVoiceActivity not allowed on hidden session");
+ return ActivityManager.START_CANCELED;
+ }
intent = new Intent(intent);
intent.addCategory(Intent.CATEGORY_VOICE);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -259,8 +183,20 @@ class VoiceInteractionManagerServiceImpl {
}
}
+ public void setKeepAwakeLocked(int callingPid, int callingUid, IBinder token,
+ boolean keepAwake) {
+ try {
+ if (mActiveSession == null || token != mActiveSession.mToken) {
+ Slog.w(TAG, "setKeepAwake does not match active session");
+ return;
+ }
+ mAm.setVoiceKeepAwake(mActiveSession.mSession, keepAwake);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Unexpected remote error", e);
+ }
+ }
- public void finishLocked(int callingPid, int callingUid, IBinder token) {
+ public void finishLocked(IBinder token) {
if (mActiveSession == null || token != mActiveSession.mToken) {
Slog.w(TAG, "finish does not match active session");
return;
@@ -327,4 +263,11 @@ class VoiceInteractionManagerServiceImpl {
Slog.w(TAG, "RemoteException while calling soundModelsChanged", e);
}
}
+
+ @Override
+ public void sessionConnectionGone(VoiceInteractionSessionConnection connection) {
+ synchronized (mLock) {
+ finishLocked(connection.mToken);
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
new file mode 100644
index 0000000..9634ab8
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2015 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.voiceinteraction;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
+import android.app.AssistContent;
+import android.app.IActivityManager;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.IVoiceInteractionSessionService;
+import android.service.voice.VoiceInteractionService;
+import android.util.Slog;
+import android.view.IWindowManager;
+import android.view.WindowManager;
+import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.os.IResultReceiver;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+final class VoiceInteractionSessionConnection implements ServiceConnection {
+ final static String TAG = "VoiceInteractionServiceManager";
+
+ final IBinder mToken = new Binder();
+ final Object mLock;
+ final ComponentName mSessionComponentName;
+ final Intent mBindIntent;
+ final int mUser;
+ final Context mContext;
+ final Callback mCallback;
+ final int mCallingUid;
+ final IActivityManager mAm;
+ final IWindowManager mIWindowManager;
+ final AppOpsManager mAppOps;
+ final IBinder mPermissionOwner;
+ boolean mShown;
+ Bundle mShowArgs;
+ int mShowFlags;
+ boolean mBound;
+ boolean mFullyBound;
+ boolean mCanceled;
+ IVoiceInteractionSessionService mService;
+ IVoiceInteractionSession mSession;
+ IVoiceInteractor mInteractor;
+ boolean mHaveAssistData;
+ Bundle mAssistData;
+ boolean mHaveScreenshot;
+ Bitmap mScreenshot;
+ ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
+
+ IVoiceInteractionSessionShowCallback mShowCallback =
+ new IVoiceInteractionSessionShowCallback.Stub() {
+ @Override
+ public void onFailed() throws RemoteException {
+ synchronized (mLock) {
+ notifyPendingShowCallbacksFailedLocked();
+ }
+ }
+
+ @Override
+ public void onShown() throws RemoteException {
+ synchronized (mLock) {
+ // TODO: Figure out whether this is good enough or whether we need to hook into
+ // Window manager to actually wait for the window to be drawn.
+ notifyPendingShowCallbacksShownLocked();
+ }
+ }
+ };
+
+ public interface Callback {
+ public void sessionConnectionGone(VoiceInteractionSessionConnection connection);
+ }
+
+ final ServiceConnection mFullConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+
+ final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) throws RemoteException {
+ synchronized (mLock) {
+ if (mShown) {
+ mHaveAssistData = true;
+ mAssistData = resultData;
+ deliverSessionDataLocked();
+ }
+ }
+ }
+ };
+
+ final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
+ @Override
+ public void send(Bitmap screenshot) throws RemoteException {
+ synchronized (mLock) {
+ if (mShown) {
+ mHaveScreenshot = true;
+ mScreenshot = screenshot;
+ deliverSessionDataLocked();
+ }
+ }
+ }
+ };
+
+ public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
+ Context context, Callback callback, int callingUid) {
+ mLock = lock;
+ mSessionComponentName = component;
+ mUser = user;
+ mContext = context;
+ mCallback = callback;
+ mCallingUid = callingUid;
+ mAm = ActivityManagerNative.getDefault();
+ mIWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService(Context.WINDOW_SERVICE));
+ mAppOps = context.getSystemService(AppOpsManager.class);
+ IBinder permOwner = null;
+ try {
+ permOwner = mAm.newUriPermissionOwner("voicesession:"
+ + component.flattenToShortString());
+ } catch (RemoteException e) {
+ Slog.w("voicesession", "AM dead", e);
+ }
+ mPermissionOwner = permOwner;
+ mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
+ mBindIntent.setComponent(mSessionComponentName);
+ mBound = mContext.bindServiceAsUser(mBindIntent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
+ if (mBound) {
+ try {
+ mIWindowManager.addWindowToken(mToken,
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed adding window token", e);
+ }
+ } else {
+ Slog.w(TAG, "Failed binding to voice interaction session service "
+ + mSessionComponentName);
+ }
+ }
+
+ public boolean showLocked(Bundle args, int flags,
+ IVoiceInteractionSessionShowCallback showCallback) {
+ if (mBound) {
+ if (!mFullyBound) {
+ mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
+ Context.BIND_AUTO_CREATE|Context.BIND_TREAT_LIKE_ACTIVITY,
+ new UserHandle(mUser));
+ }
+ mShown = true;
+ mShowArgs = args;
+ mShowFlags = flags;
+ mHaveAssistData = false;
+ if ((flags&VoiceInteractionService.START_WITH_ASSIST) != 0) {
+ if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
+ mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED) {
+ try {
+ mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
+ mAssistReceiver);
+ } catch (RemoteException e) {
+ }
+ } else {
+ mHaveAssistData = true;
+ mAssistData = null;
+ }
+ } else {
+ mAssistData = null;
+ }
+ mHaveScreenshot = false;
+ if ((flags&VoiceInteractionService.START_WITH_SCREENSHOT) != 0) {
+ if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
+ mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED) {
+ try {
+ mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
+ } catch (RemoteException e) {
+ }
+ } else {
+ mHaveScreenshot = true;
+ mScreenshot = null;
+ }
+ } else {
+ mScreenshot = null;
+ }
+ if (mSession != null) {
+ try {
+ mSession.show(mShowArgs, mShowFlags, showCallback);
+ mShowArgs = null;
+ mShowFlags = 0;
+ } catch (RemoteException e) {
+ }
+ deliverSessionDataLocked();
+ } else if (showCallback != null) {
+ mPendingShowCallbacks.add(showCallback);
+ }
+ return true;
+ }
+ if (showCallback != null) {
+ try {
+ showCallback.onFailed();
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
+ }
+
+ void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
+ if (!"content".equals(uri.getScheme())) {
+ return;
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ // This will throw SecurityException for us.
+ mAm.checkGrantUriPermission(srcUid, null, ContentProvider.getUriWithoutUserId(uri),
+ mode, ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(srcUid)));
+ // No security exception, do the grant.
+ int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
+ uri = ContentProvider.getUriWithoutUserId(uri);
+ mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
+ uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
+ } catch (RemoteException e) {
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't propagate permission", e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ }
+
+ void grantClipDataItemPermission(ClipData.Item item, int mode, int srcUid, int destUid,
+ String destPkg) {
+ if (item.getUri() != null) {
+ grantUriPermission(item.getUri(), mode, srcUid, destUid, destPkg);
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ grantUriPermission(intent.getData(), mode, srcUid, destUid, destPkg);
+ }
+ }
+
+ void grantClipDataPermissions(ClipData data, int mode, int srcUid, int destUid,
+ String destPkg) {
+ final int N = data.getItemCount();
+ for (int i=0; i<N; i++) {
+ grantClipDataItemPermission(data.getItemAt(i), mode, srcUid, destUid, destPkg);
+ }
+ }
+
+ void deliverSessionDataLocked() {
+ if (mSession == null) {
+ return;
+ }
+ if (mHaveAssistData) {
+ if (mAssistData != null) {
+ int uid = mAssistData.getInt(Intent.EXTRA_ASSIST_UID, -1);
+ if (uid >= 0) {
+ Bundle assistContext = mAssistData.getBundle(Intent.EXTRA_ASSIST_CONTEXT);
+ if (assistContext != null) {
+ AssistContent content = AssistContent.getAssistContent(assistContext);
+ if (content != null) {
+ Intent intent = content.getIntent();
+ if (intent != null) {
+ ClipData data = intent.getClipData();
+ if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
+ grantClipDataPermissions(data, intent.getFlags(), uid,
+ mCallingUid, mSessionComponentName.getPackageName());
+ }
+ }
+ ClipData data = content.getClipData();
+ if (data != null) {
+ grantClipDataPermissions(data,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ uid, mCallingUid, mSessionComponentName.getPackageName());
+ }
+ }
+ }
+ }
+ }
+ try {
+ mSession.handleAssist(mAssistData);
+ } catch (RemoteException e) {
+ }
+ mAssistData = null;
+ mHaveAssistData = false;
+ }
+ if (mHaveScreenshot) {
+ try {
+ mSession.handleScreenshot(mScreenshot);
+ } catch (RemoteException e) {
+ }
+ mScreenshot = null;
+ mHaveScreenshot = false;
+ }
+ }
+
+ public boolean hideLocked() {
+ if (mBound) {
+ if (mShown) {
+ mShown = false;
+ mShowArgs = null;
+ mShowFlags = 0;
+ mHaveAssistData = false;
+ mAssistData = null;
+ if (mSession != null) {
+ try {
+ mSession.hide();
+ } catch (RemoteException e) {
+ }
+ }
+ try {
+ mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ mUser);
+ } catch (RemoteException e) {
+ }
+ if (mSession != null) {
+ try {
+ mAm.finishVoiceTask(mSession);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ if (mFullyBound) {
+ mContext.unbindService(mFullConnection);
+ mFullyBound = false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean deliverNewSessionLocked(IVoiceInteractionSession session,
+ IVoiceInteractor interactor) {
+ mSession = session;
+ mInteractor = interactor;
+ if (mShown) {
+ try {
+ session.show(mShowArgs, mShowFlags, mShowCallback);
+ mShowArgs = null;
+ mShowFlags = 0;
+ } catch (RemoteException e) {
+ }
+ deliverSessionDataLocked();
+ }
+ return true;
+ }
+
+ private void notifyPendingShowCallbacksShownLocked() {
+ for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
+ try {
+ mPendingShowCallbacks.get(i).onShown();
+ } catch (RemoteException e) {
+ }
+ }
+ mPendingShowCallbacks.clear();
+ }
+
+ private void notifyPendingShowCallbacksFailedLocked() {
+ for (int i = 0; i < mPendingShowCallbacks.size(); i++) {
+ try {
+ mPendingShowCallbacks.get(i).onFailed();
+ } catch (RemoteException e) {
+ }
+ }
+ mPendingShowCallbacks.clear();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mService = IVoiceInteractionSessionService.Stub.asInterface(service);
+ if (!mCanceled) {
+ try {
+ mService.newSession(mToken, mShowArgs, mShowFlags);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed adding window token", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mCallback.sessionConnectionGone(this);
+ mService = null;
+ }
+
+ public void cancel() {
+ mCanceled = true;
+ if (mBound) {
+ if (mSession != null) {
+ try {
+ mSession.destroy();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Voice interation session already dead");
+ }
+ }
+ if (mSession != null) {
+ try {
+ mAm.finishVoiceTask(mSession);
+ } catch (RemoteException e) {
+ }
+ }
+ mContext.unbindService(this);
+ try {
+ mIWindowManager.removeWindowToken(mToken);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed removing window token", e);
+ }
+ mBound = false;
+ mService = null;
+ mSession = null;
+ mInteractor = null;
+ }
+ if (mFullyBound) {
+ mContext.unbindService(mFullConnection);
+ mFullyBound = false;
+ }
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mToken="); pw.println(mToken);
+ pw.print(prefix); pw.print("mShown="); pw.println(mShown);
+ pw.print(prefix); pw.print("mShowArgs="); pw.println(mShowArgs);
+ pw.print(prefix); pw.print("mShowFlags=0x"); pw.println(Integer.toHexString(mShowFlags));
+ pw.print(prefix); pw.print("mBound="); pw.println(mBound);
+ if (mBound) {
+ pw.print(prefix); pw.print("mService="); pw.println(mService);
+ pw.print(prefix); pw.print("mSession="); pw.println(mSession);
+ pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
+ }
+ pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
+ if (mHaveAssistData) {
+ pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
+ }
+ }
+};