summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--api/current.txt23
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java39
-rw-r--r--cmds/bootanimation/BootAnimation.cpp9
-rw-r--r--cmds/installd/commands.c8
-rw-r--r--cmds/installd/installd.c78
-rw-r--r--cmds/installd/installd.h1
-rw-r--r--cmds/installd/utils.c39
-rw-r--r--cmds/pm/src/com/android/commands/pm/Pm.java61
-rw-r--r--cmds/screencap/screencap.cpp17
-rw-r--r--core/java/android/app/ActivityManager.java13
-rw-r--r--core/java/android/app/ActivityManagerNative.java27
-rw-r--r--core/java/android/app/ActivityThread.java3
-rw-r--r--core/java/android/app/ContextImpl.java9
-rw-r--r--core/java/android/app/IActivityManager.java2
-rw-r--r--core/java/android/app/IStopUserCallback.aidl27
-rwxr-xr-xcore/java/android/bluetooth/BluetoothAdapter.java3
-rw-r--r--core/java/android/bluetooth/BluetoothTetheringDataTracker.java5
-rw-r--r--core/java/android/content/Intent.java10
-rw-r--r--core/java/android/content/pm/PackageManager.java7
-rw-r--r--core/java/android/hardware/Camera.java51
-rw-r--r--core/java/android/hardware/display/DisplayManager.java206
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java273
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl6
-rw-r--r--core/java/android/hardware/display/IDisplayManagerCallback.aidl22
-rw-r--r--core/java/android/net/BaseNetworkStateTracker.java5
-rw-r--r--core/java/android/net/CaptivePortalTracker.java282
-rw-r--r--core/java/android/net/ConnectivityManager.java11
-rw-r--r--core/java/android/net/DhcpStateMachine.java8
-rw-r--r--core/java/android/net/DummyDataStateTracker.java4
-rw-r--r--core/java/android/net/EthernetDataTracker.java5
-rw-r--r--core/java/android/net/IConnectivityManager.aidl2
-rw-r--r--core/java/android/net/MobileDataStateTracker.java5
-rw-r--r--core/java/android/net/NetworkInfo.java5
-rw-r--r--core/java/android/net/NetworkStateTracker.java5
-rw-r--r--core/java/android/net/VpnService.java2
-rw-r--r--core/java/android/os/Handler.java81
-rw-r--r--core/java/android/os/Process.java2
-rw-r--r--core/java/android/provider/Settings.java58
-rw-r--r--core/java/android/speech/tts/BlockingAudioTrack.java35
-rw-r--r--core/java/android/view/Choreographer.java10
-rw-r--r--core/java/android/view/Display.java63
-rw-r--r--core/java/android/view/DisplayEventReceiver.java10
-rw-r--r--core/java/android/view/DisplayInfo.java4
-rw-r--r--core/java/android/view/GestureDetector.java118
-rw-r--r--core/java/android/view/InputEventReceiver.java10
-rw-r--r--core/java/android/view/ScaleGestureDetector.java446
-rw-r--r--core/java/android/view/Surface.java936
-rw-r--r--core/java/android/view/SurfaceSession.java42
-rw-r--r--core/java/android/view/View.java39
-rw-r--r--core/java/android/view/ViewRootImpl.java10
-rw-r--r--core/java/android/view/WindowManager.java9
-rw-r--r--core/java/android/view/WindowManagerImpl.java3
-rw-r--r--core/java/android/webkit/AccessibilityInjector.java18
-rw-r--r--core/java/android/webkit/BrowserDownloadListener.java57
-rw-r--r--core/java/android/webkit/BrowserFrame.java4
-rw-r--r--core/java/android/webkit/CallbackProxy.java14
-rw-r--r--core/java/android/webkit/WebSettingsClassic.java1
-rw-r--r--core/java/android/webkit/WebViewClassic.java24
-rw-r--r--core/java/android/widget/AbsListView.java4
-rw-r--r--core/java/android/widget/MediaController.java3
-rw-r--r--core/java/android/widget/Toast.java9
-rw-r--r--core/java/android/widget/ViewAnimator.java15
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java8
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java2
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android/graphics/Bitmap.cpp14
-rw-r--r--core/jni/android_app_NativeActivity.cpp2
-rw-r--r--core/jni/android_opengl_EGL14.cpp2
-rw-r--r--core/jni/android_view_Surface.cpp1015
-rw-r--r--core/jni/android_view_SurfaceSession.cpp79
-rw-r--r--core/jni/com_google_android_gles_jni_EGLImpl.cpp2
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/res/res/anim/keyguard_security_animate_in.xml33
-rw-r--r--core/res/res/anim/keyguard_security_animate_out.xml32
-rw-r--r--core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.pngbin0 -> 2212 bytes
-rw-r--r--core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.pngbin0 -> 1451 bytes
-rw-r--r--core/res/res/layout-land/keyguard_host_view.xml58
-rw-r--r--core/res/res/layout-port/keyguard_host_view.xml47
-rw-r--r--core/res/res/layout-sw600dp-land/keyguard_host_view.xml67
-rw-r--r--core/res/res/layout-sw600dp-port/keyguard_host_view.xml61
-rw-r--r--core/res/res/layout/keyguard_account_view.xml72
-rw-r--r--core/res/res/layout/keyguard_face_unlock_view.xml31
-rw-r--r--core/res/res/layout/keyguard_navigation.xml61
-rw-r--r--core/res/res/layout/keyguard_password_view.xml100
-rw-r--r--core/res/res/layout/keyguard_pattern_view.xml55
-rw-r--r--core/res/res/layout/keyguard_selector_view.xml82
-rw-r--r--core/res/res/layout/keyguard_sim_pin_view.xml82
-rw-r--r--core/res/res/layout/keyguard_sim_puk_view.xml133
-rw-r--r--core/res/res/layout/keyguard_status_view.xml119
-rw-r--r--core/res/res/layout/overlay_display_window.xml27
-rw-r--r--core/res/res/values-af/strings.xml8
-rw-r--r--core/res/res/values-am/strings.xml10
-rw-r--r--core/res/res/values-ar/strings.xml8
-rw-r--r--core/res/res/values-be/strings.xml8
-rw-r--r--core/res/res/values-bg/strings.xml8
-rw-r--r--core/res/res/values-ca/strings.xml8
-rw-r--r--core/res/res/values-cs/strings.xml8
-rw-r--r--core/res/res/values-da/strings.xml8
-rw-r--r--core/res/res/values-de/strings.xml8
-rw-r--r--core/res/res/values-el/strings.xml8
-rw-r--r--core/res/res/values-en-rGB/strings.xml8
-rw-r--r--core/res/res/values-es-rUS/strings.xml12
-rw-r--r--core/res/res/values-es/strings.xml8
-rw-r--r--core/res/res/values-et/strings.xml8
-rw-r--r--core/res/res/values-fa/strings.xml8
-rw-r--r--core/res/res/values-fi/strings.xml8
-rw-r--r--core/res/res/values-fr/strings.xml8
-rw-r--r--core/res/res/values-hi/strings.xml10
-rw-r--r--core/res/res/values-hr/strings.xml8
-rw-r--r--core/res/res/values-hu/strings.xml8
-rw-r--r--core/res/res/values-in/strings.xml8
-rw-r--r--core/res/res/values-it/strings.xml8
-rw-r--r--core/res/res/values-iw/strings.xml8
-rw-r--r--core/res/res/values-ja/strings.xml8
-rw-r--r--core/res/res/values-ko/strings.xml10
-rw-r--r--core/res/res/values-lt/strings.xml8
-rw-r--r--core/res/res/values-lv/strings.xml10
-rw-r--r--core/res/res/values-ms/strings.xml8
-rw-r--r--core/res/res/values-nb/strings.xml8
-rw-r--r--core/res/res/values-nl/strings.xml8
-rw-r--r--core/res/res/values-pl/strings.xml8
-rw-r--r--core/res/res/values-pt-rPT/strings.xml8
-rw-r--r--core/res/res/values-pt/strings.xml8
-rw-r--r--core/res/res/values-rm/strings.xml8
-rw-r--r--core/res/res/values-ro/strings.xml8
-rw-r--r--core/res/res/values-ru/strings.xml8
-rw-r--r--core/res/res/values-sk/strings.xml8
-rw-r--r--core/res/res/values-sl/strings.xml8
-rw-r--r--core/res/res/values-sr/strings.xml8
-rw-r--r--core/res/res/values-sv/strings.xml8
-rw-r--r--core/res/res/values-sw/strings.xml8
-rw-r--r--core/res/res/values-th/strings.xml8
-rw-r--r--core/res/res/values-tl/strings.xml8
-rw-r--r--core/res/res/values-tr/strings.xml8
-rw-r--r--core/res/res/values-uk/strings.xml8
-rw-r--r--core/res/res/values-vi/strings.xml8
-rw-r--r--core/res/res/values-zh-rCN/strings.xml8
-rw-r--r--core/res/res/values-zh-rTW/strings.xml8
-rw-r--r--core/res/res/values-zu/strings.xml8
-rwxr-xr-xcore/res/res/values/attrs.xml18
-rw-r--r--core/res/res/values/dimens.xml16
-rw-r--r--core/res/res/values/integers.xml21
-rw-r--r--core/res/res/values/public.xml64
-rwxr-xr-xcore/res/res/values/strings.xml43
-rw-r--r--data/etc/platform.xml5
-rw-r--r--graphics/jni/android_renderscript_RenderScript.cpp4
-rw-r--r--include/android_runtime/android_view_Surface.h11
-rw-r--r--include/android_runtime/android_view_SurfaceSession.h32
-rw-r--r--libs/hwui/FontRenderer.cpp32
-rw-r--r--libs/hwui/FontRenderer.h9
-rw-r--r--libs/hwui/GradientCache.cpp10
-rw-r--r--libs/hwui/OpenGLRenderer.cpp123
-rw-r--r--libs/hwui/OpenGLRenderer.h5
-rw-r--r--libs/hwui/ProgramCache.cpp17
-rw-r--r--libs/hwui/SkiaShader.cpp35
-rw-r--r--libs/hwui/SkiaShader.h7
-rw-r--r--media/java/android/media/AudioService.java4
-rw-r--r--media/jni/android_media_MediaCodec.cpp2
-rw-r--r--media/jni/android_media_MediaPlayer.cpp2
-rw-r--r--native/android/native_window.cpp2
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java2
-rw-r--r--packages/SystemUI/res/layout-sw600dp/navigation_bar.xml14
-rw-r--r--packages/SystemUI/res/layout/navigation_bar.xml16
-rw-r--r--packages/SystemUI/res/values-am/strings.xml2
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml2
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml4
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml4
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml4
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml2
-rw-r--r--packages/SystemUI/res/values-th/strings.xml2
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml2
-rw-r--r--packages/SystemUI/res/values/attrs.xml12
-rw-r--r--packages/SystemUI/res/values/config.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java2
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java22
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java (renamed from policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java542
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java317
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java194
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java488
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java59
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java340
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java367
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java71
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java85
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java64
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java288
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java236
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java301
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java44
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java570
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java710
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java)5
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java254
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java318
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java1342
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java96
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java167
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/PagedView.java1704
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java80
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java (renamed from policy/src/com/android/internal/policy/impl/FaceUnlock.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardScreen.java)4
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java)6
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java96
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewBase.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java)4
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewManager.java)4
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java)3
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardWindowController.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java (renamed from policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java)5
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java (renamed from policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java (renamed from policy/src/com/android/internal/policy/impl/LockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/SimUnlockScreen.java)2
-rw-r--r--policy/tests/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewTest.java (renamed from policy/tests/src/com/android/internal/policy/impl/LockPatternKeyguardViewTest.java)9
-rw-r--r--services/input/SpriteController.cpp5
-rw-r--r--services/java/com/android/server/AlarmManagerService.java32
-rw-r--r--services/java/com/android/server/ConnectivityService.java84
-rwxr-xr-xservices/java/com/android/server/NotificationManagerService.java29
-rw-r--r--services/java/com/android/server/SystemServer.java62
-rw-r--r--services/java/com/android/server/WallpaperManagerService.java20
-rw-r--r--services/java/com/android/server/Watchdog.java60
-rw-r--r--services/java/com/android/server/WifiService.java25
-rw-r--r--services/java/com/android/server/am/ActiveServices.java13
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java422
-rwxr-xr-xservices/java/com/android/server/am/ActivityStack.java33
-rw-r--r--services/java/com/android/server/am/BroadcastQueue.java24
-rw-r--r--services/java/com/android/server/am/BroadcastRecord.java24
-rw-r--r--services/java/com/android/server/am/PendingIntentRecord.java12
-rw-r--r--services/java/com/android/server/am/UserStartedState.java43
-rw-r--r--services/java/com/android/server/display/DisplayAdapter.java79
-rw-r--r--services/java/com/android/server/display/DisplayDevice.java38
-rw-r--r--services/java/com/android/server/display/DisplayDeviceInfo.java20
-rw-r--r--services/java/com/android/server/display/DisplayManagerService.java536
-rw-r--r--services/java/com/android/server/display/HeadlessDisplayAdapter.java30
-rw-r--r--services/java/com/android/server/display/LocalDisplayAdapter.java74
-rw-r--r--services/java/com/android/server/display/OverlayDisplayAdapter.java530
-rw-r--r--services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java58
-rw-r--r--services/java/com/android/server/net/LockdownVpnTracker.java37
-rw-r--r--services/java/com/android/server/pm/PackageManagerService.java93
-rw-r--r--services/java/com/android/server/pm/PackageSettingBase.java6
-rw-r--r--services/java/com/android/server/pm/UserManagerService.java37
-rw-r--r--services/java/com/android/server/power/DisplayPowerController.java40
-rw-r--r--services/java/com/android/server/power/ElectronBeam.java5
-rw-r--r--services/java/com/android/server/power/PowerManagerService.java3
-rw-r--r--services/java/com/android/server/usb/UsbDebuggingManager.java6
-rw-r--r--services/java/com/android/server/wm/BlackFrame.java11
-rw-r--r--services/java/com/android/server/wm/DimAnimator.java17
-rw-r--r--services/java/com/android/server/wm/DimSurface.java15
-rw-r--r--services/java/com/android/server/wm/ScreenRotationAnimation.java9
-rw-r--r--services/java/com/android/server/wm/StrictModeFlash.java6
-rw-r--r--services/java/com/android/server/wm/Watermark.java8
-rwxr-xr-xservices/java/com/android/server/wm/WindowManagerService.java162
-rw-r--r--services/java/com/android/server/wm/WindowStateAnimator.java16
-rw-r--r--services/jni/Android.mk1
-rw-r--r--services/jni/com_android_server_display_SurfaceFlingerDisplayAdapter.cpp91
-rw-r--r--services/jni/onload.cpp2
-rw-r--r--telephony/java/android/telephony/CellSignalStrength.java6
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthCdma.java14
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthGsm.java10
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthLte.java10
-rw-r--r--tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java34
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl2
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java7
-rw-r--r--wifi/java/android/net/wifi/WifiMonitor.java12
-rw-r--r--wifi/java/android/net/wifi/WifiNative.java37
-rw-r--r--wifi/java/android/net/wifi/WifiStateMachine.java48
-rw-r--r--wifi/java/android/net/wifi/WifiStateTracker.java14
-rw-r--r--wifi/java/android/net/wifi/WifiWatchdogStateMachine.java182
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pConfig.java20
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pDevice.java15
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pGroup.java58
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl19
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java229
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pManager.java76
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pService.java569
290 files changed, 16736 insertions, 2863 deletions
diff --git a/Android.mk b/Android.mk
index 0665e60..07500b1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -74,6 +74,7 @@ LOCAL_SRC_FILES += \
core/java/android/app/ISearchManager.aidl \
core/java/android/app/ISearchManagerCallback.aidl \
core/java/android/app/IServiceConnection.aidl \
+ core/java/android/app/IStopUserCallback.aidl \
core/java/android/app/IThumbnailReceiver.aidl \
core/java/android/app/IThumbnailRetriever.aidl \
core/java/android/app/ITransientNotification.aidl \
@@ -115,6 +116,7 @@ LOCAL_SRC_FILES += \
core/java/android/database/IContentObserver.aidl \
core/java/android/hardware/ISerialManager.aidl \
core/java/android/hardware/display/IDisplayManager.aidl \
+ core/java/android/hardware/display/IDisplayManagerCallback.aidl \
core/java/android/hardware/input/IInputManager.aidl \
core/java/android/hardware/input/IInputDevicesChangedListener.aidl \
core/java/android/hardware/usb/IUsbManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index 720681a..7d1c203 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -76,6 +76,7 @@ package android {
field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
field public static final java.lang.String NET_TUNNELING = "android.permission.NET_TUNNELING";
field public static final java.lang.String NFC = "android.permission.NFC";
+ field public static final java.lang.String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT";
field public static final deprecated java.lang.String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
field public static final java.lang.String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
field public static final java.lang.String READ_CALENDAR = "android.permission.READ_CALENDAR";
@@ -9996,7 +9997,8 @@ package android.hardware {
package android.hardware.display {
public final class DisplayManager {
- method public android.view.Display getDisplay(int, android.content.Context);
+ method public android.view.Display getDisplay(int);
+ method public android.view.Display[] getDisplays();
method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
}
@@ -12606,6 +12608,7 @@ package android.net {
method public static final android.net.NetworkInfo.DetailedState[] values();
enum_constant public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
enum_constant public static final android.net.NetworkInfo.DetailedState BLOCKED;
+ enum_constant public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK;
enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTED;
enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTING;
enum_constant public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
@@ -20438,6 +20441,9 @@ package android.telephony {
public abstract class CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
method public abstract boolean equals(java.lang.Object);
+ method public abstract int getAsuLevel();
+ method public abstract int getDbm();
+ method public abstract int getLevel();
method public abstract int hashCode();
method public abstract void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
@@ -20445,11 +20451,16 @@ package android.telephony {
public class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public boolean equals(java.lang.Object);
+ method public int getAsuLevel();
method public int getCdmaDbm();
method public int getCdmaEcio();
+ method public int getCdmaLevel();
+ method public int getDbm();
method public int getEvdoDbm();
method public int getEvdoEcio();
+ method public int getEvdoLevel();
method public int getEvdoSnr();
+ method public int getLevel();
method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
@@ -20457,6 +20468,9 @@ package android.telephony {
public class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public boolean equals(java.lang.Object);
+ method public int getAsuLevel();
+ method public int getDbm();
+ method public int getLevel();
method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
@@ -20464,6 +20478,9 @@ package android.telephony {
public class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public boolean equals(java.lang.Object);
+ method public int getAsuLevel();
+ method public int getDbm();
+ method public int getLevel();
method public int getTimingAdvance();
method public int hashCode();
method public void writeToParcel(android.os.Parcel, int);
@@ -23437,6 +23454,7 @@ package android.view {
method public int getRotation();
method public void getSize(android.graphics.Point);
method public deprecated int getWidth();
+ method public boolean isValid();
field public static final int DEFAULT_DISPLAY = 0; // 0x0
}
@@ -24427,7 +24445,7 @@ package android.view {
method public android.graphics.Canvas lockCanvas(android.graphics.Rect) throws java.lang.IllegalArgumentException, android.view.Surface.OutOfResourcesException;
method public void readFromParcel(android.os.Parcel);
method public void release();
- method public void unlockCanvas(android.graphics.Canvas);
+ method public deprecated void unlockCanvas(android.graphics.Canvas);
method public void unlockCanvasAndPost(android.graphics.Canvas);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
@@ -29565,6 +29583,7 @@ package android.widget {
public class ViewAnimator extends android.widget.FrameLayout {
ctor public ViewAnimator(android.content.Context);
ctor public ViewAnimator(android.content.Context, android.util.AttributeSet);
+ method public boolean getAnimateFirstView();
method public android.view.View getCurrentView();
method public int getDisplayedChild();
method public android.view.animation.Animation getInAnimation();
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 47d6a02..7f3dbe5 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -31,7 +31,6 @@ import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
-import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -141,6 +140,8 @@ public class Am {
runToUri(true);
} else if (op.equals("switch-user")) {
runSwitchUser();
+ } else if (op.equals("stop-user")) {
+ runStopUser();
} else {
throw new IllegalArgumentException("Unknown command: " + op);
}
@@ -323,7 +324,6 @@ public class Am {
mUserId = Integer.parseInt(nextArgRequired());
} else {
System.err.println("Error: Unknown option: " + opt);
- showUsage();
return null;
}
}
@@ -594,7 +594,6 @@ public class Am {
no_window_animation = true;
} else {
System.err.println("Error: Unknown option: " + opt);
- showUsage();
return;
}
}
@@ -738,7 +737,6 @@ public class Am {
persistent = true;
} else {
System.err.println("Error: Unknown option: " + opt);
- showUsage();
return;
}
}
@@ -752,13 +750,27 @@ public class Am {
}
private void runSwitchUser() throws Exception {
- if (android.os.Process.myUid() != 0) {
- throw new RuntimeException("switchuser can only be run as root");
- }
String user = nextArgRequired();
mAm.switchUser(Integer.parseInt(user));
}
+ private void runStopUser() throws Exception {
+ String user = nextArgRequired();
+ int res = mAm.stopUser(Integer.parseInt(user), null);
+ if (res != ActivityManager.USER_OP_SUCCESS) {
+ String txt = "";
+ switch (res) {
+ case ActivityManager.USER_OP_IS_CURRENT:
+ txt = " (Can't stop current user)";
+ break;
+ case ActivityManager.USER_OP_UNKNOWN_USER:
+ txt = " (Unknown user " + user + ")";
+ break;
+ }
+ System.err.println("Switch failed: " + res + txt);
+ }
+ }
+
class MyActivityController extends IActivityController.Stub {
final String mGdbPort;
@@ -1047,7 +1059,6 @@ public class Am {
gdbPort = nextArgRequired();
} else {
System.err.println("Error: Unknown option: " + opt);
- showUsage();
return;
}
}
@@ -1065,7 +1076,6 @@ public class Am {
enabled = false;
} else {
System.err.println("Error: enabled mode must be 'on' or 'off' at " + mode);
- showUsage();
return;
}
@@ -1090,7 +1100,6 @@ public class Am {
int div = size.indexOf('x');
if (div <= 0 || div >= (size.length()-1)) {
System.err.println("Error: bad size " + size);
- showUsage();
return;
}
String mstr = size.substring(0, div);
@@ -1100,7 +1109,6 @@ public class Am {
n = Integer.parseInt(nstr);
} catch (NumberFormatException e) {
System.err.println("Error: bad number " + e);
- showUsage();
return;
}
}
@@ -1139,12 +1147,10 @@ public class Am {
density = Integer.parseInt(densityStr);
} catch (NumberFormatException e) {
System.err.println("Error: bad number " + e);
- showUsage();
return;
}
if (density < 72) {
System.err.println("Error: density must be >= 72");
- showUsage();
return;
}
}
@@ -1345,6 +1351,7 @@ public class Am {
" am to-uri [INTENT]\n" +
" am to-intent-uri [INTENT]\n" +
" am switch-user <USER_ID>\n" +
+ " am stop-user <USER_ID>\n" +
"\n" +
"am start: start an Activity. Options are:\n" +
" -D: enable debugging\n" +
@@ -1403,6 +1410,12 @@ public class Am {
"\n" +
"am to-intent-uri: print the given Intent specification as an intent: URI.\n" +
"\n" +
+ "am switch-user: switch to put USER_ID in the foreground, starting" +
+ " execution of that user if it is currently stopped.\n" +
+ "\n" +
+ "am stop-user: stop execution of USER_ID, not allowing it to run any" +
+ " code until a later explicit switch to it.\n" +
+ "\n" +
"<INTENT> specifications include these flags and arguments:\n" +
" [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
" [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 2471a2e..8511735 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -38,6 +38,7 @@
#include <ui/DisplayInfo.h>
#include <ui/FramebufferNativeWindow.h>
+#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
@@ -216,14 +217,16 @@ status_t BootAnimation::initTexture(void* buffer, size_t len)
status_t BootAnimation::readyToRun() {
mAssets.addDefaultAssets();
+ sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
+ ISurfaceComposer::eDisplayIdMain));
DisplayInfo dinfo;
- status_t status = SurfaceComposerClient::getDisplayInfo(0, &dinfo);
+ status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
if (status)
return -1;
// create the native surface
- sp<SurfaceControl> control = session()->createSurface(
- 0, dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
+ sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
+ dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
SurfaceComposerClient::openGlobalTransaction();
control->setLayer(0x40000000);
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index a52f74a..ab64747 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -241,7 +241,6 @@ int clone_persona_data(uid_t src_persona, uid_t target_persona, int copy)
{
char src_data_dir[PKG_PATH_MAX];
char pkg_path[PKG_PATH_MAX];
- char media_path[PATH_MAX];
DIR *d;
struct dirent *de;
struct stat s;
@@ -250,9 +249,6 @@ int clone_persona_data(uid_t src_persona, uid_t target_persona, int copy)
if (create_persona_path(src_data_dir, src_persona)) {
return -1;
}
- if (create_persona_media_path(media_path, (userid_t) target_persona) == -1) {
- return -1;
- }
d = opendir(src_data_dir);
if (d != NULL) {
@@ -281,10 +277,10 @@ int clone_persona_data(uid_t src_persona, uid_t target_persona, int copy)
closedir(d);
}
- // ensure /data/media/<user_id> exists
- if (ensure_dir(media_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
+ if (ensure_media_user_dirs((userid_t) target_persona) == -1) {
return -1;
}
+
return 0;
}
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index d51004a..d559639 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -333,19 +333,16 @@ int initialize_globals() {
int initialize_directories() {
int res = -1;
- int version = 0;
- FILE* file;
// Read current filesystem layout version to handle upgrade paths
char version_path[PATH_MAX];
- if (snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path) > PATH_MAX) {
- return -1;
- }
- file = fopen(version_path, "r");
- if (file != NULL) {
- fscanf(file, "%d", &version);
- fclose(file);
+ snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path);
+
+ int oldVersion;
+ if (fs_read_atomic_int(version_path, &oldVersion) == -1) {
+ oldVersion = 0;
}
+ int version = oldVersion;
// /data/user
char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX);
@@ -376,16 +373,12 @@ int initialize_directories() {
}
}
- // /data/media/0
- char owner_media_dir[PATH_MAX];
- create_persona_media_path(owner_media_dir, 0);
-
if (version == 0) {
// Introducing multi-user, so migrate /data/media contents into /data/media/0
- ALOGD("Migrating /data/media for multi-user");
+ ALOGD("Upgrading /data/media for multi-user");
// Ensure /data/media
- if (ensure_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
+ if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
goto fail;
}
@@ -402,10 +395,14 @@ int initialize_directories() {
}
// Create /data/media again
- if (ensure_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
+ if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
goto fail;
}
+ // /data/media/0
+ char owner_media_dir[PATH_MAX];
+ snprintf(owner_media_dir, PATH_MAX, "%s0", android_media_dir.path);
+
// Move any owner data into place
if (access(media_tmp_dir, F_OK) == 0) {
if (rename(media_tmp_dir, owner_media_dir) == -1) {
@@ -433,8 +430,7 @@ int initialize_directories() {
// /data/media/<user_id>
snprintf(user_media_dir, PATH_MAX, "%s%s", android_media_dir.path, name);
- if (ensure_dir(user_media_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
- ALOGE("Failed to ensure %s: %s", user_media_dir, strerror(errno));
+ if (fs_prepare_dir(user_media_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
goto fail;
}
}
@@ -445,22 +441,46 @@ int initialize_directories() {
version = 1;
}
- // Ensure /data/media/0 is always ready
- if (ensure_dir(owner_media_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
- goto fail;
+ // /data/media/obb
+ char media_obb_dir[PATH_MAX];
+ snprintf(media_obb_dir, PATH_MAX, "%sobb", android_media_dir.path);
+
+ if (version == 1) {
+ // Introducing /data/media/obb for sharing OBB across users; migrate
+ // any existing OBB files from owner.
+ ALOGD("Upgrading to shared /data/media/obb");
+
+ // /data/media/0/Android/obb
+ char owner_obb_path[PATH_MAX];
+ snprintf(owner_obb_path, PATH_MAX, "%s0/Android/obb", android_media_dir.path);
+
+ // Only move if target doesn't already exist
+ if (access(media_obb_dir, F_OK) != 0 && access(owner_obb_path, F_OK) == 0) {
+ if (rename(owner_obb_path, media_obb_dir) == -1) {
+ ALOGE("Failed to move OBB from owner: %s", strerror(errno));
+ goto fail;
+ }
+ }
+
+ version = 2;
}
- // Persist our current version
- file = fopen(version_path, "w");
- if (file != NULL) {
- fprintf(file, "%d", version);
- fsync(fileno(file));
- fclose(file);
- } else {
- ALOGE("Failed to save version to %s: %s", version_path, strerror(errno));
+ if (ensure_media_user_dirs(0) == -1) {
+ ALOGE("Failed to setup media for user 0");
+ goto fail;
+ }
+ if (fs_prepare_dir(media_obb_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
goto fail;
}
+ // Persist layout version if changed
+ if (version != oldVersion) {
+ if (fs_write_atomic_int(version_path, version) == -1) {
+ ALOGE("Failed to save version to %s: %s", version_path, strerror(errno));
+ goto fail;
+ }
+ }
+
// Success!
res = 0;
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index 3201427..5b81d2c 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -32,6 +32,7 @@
#include <sys/types.h>
#include <sys/wait.h>
+#include <cutils/fs.h>
#include <cutils/sockets.h>
#include <cutils/log.h>
#include <cutils/properties.h>
diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c
index 80247f1..625a35e 100644
--- a/cmds/installd/utils.c
+++ b/cmds/installd/utils.c
@@ -991,39 +991,14 @@ char *build_string3(char *s1, char *s2, char *s3) {
return result;
}
-/* Ensure that directory exists with given mode and owners. */
-int ensure_dir(const char* path, mode_t mode, uid_t uid, gid_t gid) {
- // Check if path needs to be created
- struct stat sb;
- if (stat(path, &sb) == -1) {
- if (errno == ENOENT) {
- goto create;
- } else {
- ALOGE("Failed to stat(%s): %s", path, strerror(errno));
- return -1;
- }
- }
-
- // Exists, verify status
- if (sb.st_mode == mode || sb.st_uid == uid || sb.st_gid == gid) {
- return 0;
- } else {
- goto fixup;
- }
-
-create:
- if (mkdir(path, mode) == -1) {
- ALOGE("Failed to mkdir(%s): %s", path, strerror(errno));
- return -1;
- }
+/* Ensure that /data/media directories are prepared for given user. */
+int ensure_media_user_dirs(userid_t userid) {
+ char media_user_path[PATH_MAX];
+ char path[PATH_MAX];
-fixup:
- if (chown(path, uid, gid) == -1) {
- ALOGE("Failed to chown(%s, %d, %d): %s", path, uid, gid, strerror(errno));
- return -1;
- }
- if (chmod(path, mode) == -1) {
- ALOGE("Failed to chown(%s, %d): %s", path, mode, strerror(errno));
+ // Ensure /data/media/<userid> exists
+ create_persona_media_path(media_user_path, userid);
+ if (fs_prepare_dir(media_user_path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
return -1;
}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 8cc4e69..e621ceb 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -178,11 +178,6 @@ public final class Pm {
return;
}
- if ("list-users".equals(op)) {
- runListUsers();
- return;
- }
-
try {
if (args.length == 1) {
if (args[0].equalsIgnoreCase("-l")) {
@@ -222,7 +217,6 @@ public final class Pm {
String type = nextArg();
if (type == null) {
System.err.println("Error: didn't specify type of data to list");
- showUsage();
return;
}
if ("package".equals(type) || "packages".equals(type)) {
@@ -241,7 +235,6 @@ public final class Pm {
runListUsers();
} else {
System.err.println("Error: unknown list type '" + type + "'");
- showUsage();
}
}
@@ -276,13 +269,11 @@ public final class Pm {
getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES;
} else {
System.err.println("Error: Unknown option: " + opt);
- showUsage();
return;
}
}
} catch (RuntimeException ex) {
System.err.println("Error: " + ex.toString());
- showUsage();
return;
}
@@ -431,13 +422,11 @@ public final class Pm {
targetPackage = opt;
} else {
System.err.println("Error: Unknown option: " + opt);
- showUsage();
return;
}
}
} catch (RuntimeException ex) {
System.err.println("Error: " + ex.toString());
- showUsage();
return;
}
@@ -529,7 +518,6 @@ public final class Pm {
dangerousOnly = true;
} else {
System.err.println("Error: Unknown option: " + opt);
- showUsage();
return;
}
}
@@ -678,7 +666,6 @@ public final class Pm {
String pkg = nextArg();
if (pkg == null) {
System.err.println("Error: no package specified");
- showUsage();
return;
}
displayPackageFilePath(pkg);
@@ -736,20 +723,17 @@ public final class Pm {
String arg = nextArg();
if (arg == null) {
System.err.println("Error: no install location specified.");
- showUsage();
return;
}
try {
loc = Integer.parseInt(arg);
} catch (NumberFormatException e) {
System.err.println("Error: install location has to be a number.");
- showUsage();
return;
}
try {
if (!mPm.setInstallLocation(loc)) {
System.err.println("Error: install location has to be a number.");
- showUsage();
}
} catch (RemoteException e) {
System.err.println(e.toString());
@@ -800,7 +784,6 @@ public final class Pm {
installerPackageName = nextOptionData();
if (installerPackageName == null) {
System.err.println("Error: no value specified for -i");
- showUsage();
return;
}
} else if (opt.equals("-t")) {
@@ -817,61 +800,52 @@ public final class Pm {
algo = nextOptionData();
if (algo == null) {
System.err.println("Error: must supply argument for --algo");
- showUsage();
return;
}
} else if (opt.equals("--iv")) {
iv = hexToBytes(nextOptionData());
if (iv == null) {
System.err.println("Error: must supply argument for --iv");
- showUsage();
return;
}
} else if (opt.equals("--key")) {
key = hexToBytes(nextOptionData());
if (key == null) {
System.err.println("Error: must supply argument for --key");
- showUsage();
return;
}
} else if (opt.equals("--macalgo")) {
macAlgo = nextOptionData();
if (macAlgo == null) {
System.err.println("Error: must supply argument for --macalgo");
- showUsage();
return;
}
} else if (opt.equals("--mackey")) {
macKey = hexToBytes(nextOptionData());
if (macKey == null) {
System.err.println("Error: must supply argument for --mackey");
- showUsage();
return;
}
} else if (opt.equals("--tag")) {
tag = hexToBytes(nextOptionData());
if (tag == null) {
System.err.println("Error: must supply argument for --tag");
- showUsage();
return;
}
} else if (opt.equals("--originating-uri")) {
originatingUriString = nextOptionData();
if (originatingUriString == null) {
System.err.println("Error: must supply argument for --originating-uri");
- showUsage();
return;
}
} else if (opt.equals("--referrer")) {
referrer = nextOptionData();
if (referrer == null) {
System.err.println("Error: must supply argument for --referrer");
- showUsage();
return;
}
} else {
System.err.println("Error: Unknown option: " + opt);
- showUsage();
return;
}
}
@@ -881,7 +855,6 @@ public final class Pm {
|| tag != null) {
if (algo == null || iv == null || key == null) {
System.err.println("Error: all of --algo, --iv, and --key must be specified");
- showUsage();
return;
}
@@ -889,7 +862,6 @@ public final class Pm {
if (macAlgo == null || macKey == null || tag == null) {
System.err.println("Error: all of --macalgo, --mackey, and --tag must "
+ "be specified");
- showUsage();
return;
}
}
@@ -938,7 +910,6 @@ public final class Pm {
apkURI = Uri.fromFile(new File(apkFilePath));
} else {
System.err.println("Error: no package specified");
- showUsage();
return;
}
@@ -1012,23 +983,16 @@ public final class Pm {
}
public void runCreateUser() {
- // Need to be run as root
- if (Process.myUid() != ROOT_UID) {
- System.err.println("Error: create-user must be run as root");
- return;
- }
String name;
String arg = nextArg();
if (arg == null) {
System.err.println("Error: no user name specified.");
- showUsage();
return;
}
name = arg;
try {
if (mUm.createUser(name, 0) == null) {
System.err.println("Error: couldn't create User.");
- showUsage();
}
} catch (RemoteException e) {
System.err.println(e.toString());
@@ -1038,29 +1002,21 @@ public final class Pm {
}
public void runRemoveUser() {
- // Need to be run as root
- if (Process.myUid() != ROOT_UID) {
- System.err.println("Error: remove-user must be run as root");
- return;
- }
int userId;
String arg = nextArg();
if (arg == null) {
System.err.println("Error: no user id specified.");
- showUsage();
return;
}
try {
userId = Integer.parseInt(arg);
} catch (NumberFormatException e) {
- System.err.println("Error: user id has to be a number.");
- showUsage();
+ System.err.println("Error: user id '" + arg + "' is not a number.");
return;
}
try {
if (!mUm.removeUser(userId)) {
- System.err.println("Error: couldn't remove user.");
- showUsage();
+ System.err.println("Error: couldn't remove user #" + userId + ".");
}
} catch (RemoteException e) {
System.err.println(e.toString());
@@ -1069,11 +1025,6 @@ public final class Pm {
}
public void runListUsers() {
- // Need to be run as root
- if (Process.myUid() != ROOT_UID) {
- System.err.println("Error: list-users must be run as root");
- return;
- }
try {
List<UserInfo> users = mUm.getUsers();
if (users == null) {
@@ -1521,6 +1472,8 @@ public final class Pm {
System.err.println("");
System.err.println("pm list features: prints all features of the system.");
System.err.println("");
+ System.err.println("pm list users: prints all users on the system.");
+ System.err.println("");
System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
System.err.println("");
System.err.println("pm install: installs a package to the system. Options:");
@@ -1557,5 +1510,11 @@ public final class Pm {
System.err.println(" 2 [external]: Install on external media");
System.err.println("");
System.err.println("pm trim-caches: trim cache files to reach the given free space.");
+ System.err.println("");
+ System.err.println("pm create-user: create a new user with the given USER_NAME,");
+ System.err.println(" printing the new user identifier of the user.");
+ System.err.println("");
+ System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,");
+ System.err.println(" deleting all data associated with that user");
}
}
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 46e41e3..a1ea81a 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -25,6 +25,7 @@
#include <binder/IMemory.h>
#include <gui/SurfaceComposerClient.h>
+#include <gui/ISurfaceComposer.h>
#include <SkImageEncoder.h>
#include <SkBitmap.h>
@@ -33,15 +34,18 @@
using namespace android;
+static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain;
+
static void usage(const char* pname)
{
fprintf(stderr,
- "usage: %s [-hp] [FILENAME]\n"
+ "usage: %s [-hp] [-d display-id] [FILENAME]\n"
" -h: this message\n"
" -p: save the file as a png.\n"
+ " -d: specify the display id to capture, default %d.\n"
"If FILENAME ends with .png it will be saved as a png.\n"
"If FILENAME is not given, the results will be printed to stdout.\n",
- pname
+ pname, DEFAULT_DISPLAY_ID
);
}
@@ -87,12 +91,16 @@ int main(int argc, char** argv)
{
const char* pname = argv[0];
bool png = false;
+ int32_t displayId = DEFAULT_DISPLAY_ID;
int c;
- while ((c = getopt(argc, argv, "ph")) != -1) {
+ while ((c = getopt(argc, argv, "phd:")) != -1) {
switch (c) {
case 'p':
png = true;
break;
+ case 'd':
+ displayId = atoi(optarg);
+ break;
case '?':
case 'h':
usage(pname);
@@ -131,7 +139,8 @@ int main(int argc, char** argv)
size_t size = 0;
ScreenshotClient screenshot;
- if (screenshot.update() == NO_ERROR) {
+ sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId);
+ if (display != NULL && screenshot.update(display) == NO_ERROR) {
base = screenshot.getPixels();
w = screenshot.getWidth();
h = screenshot.getHeight();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e644db4..26d8c17 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -31,6 +31,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -210,6 +211,15 @@ public class ActivityManager {
*/
public static final int INTENT_SENDER_SERVICE = 4;
+ /** @hide User operation call: success! */
+ public static final int USER_OP_SUCCESS = 0;
+
+ /** @hide User operation call: given user id is not known. */
+ public static final int USER_OP_UNKNOWN_USER = -1;
+
+ /** @hide User operation call: given user id is the current user, can't be stopped. */
+ public static final int USER_OP_IS_CURRENT = -2;
+
/*package*/ ActivityManager(Context context, Handler handler) {
mContext = context;
mHandler = handler;
@@ -376,7 +386,8 @@ public class ActivityManager {
return true;
}
- Display display = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+ Display display = DisplayManagerGlobal.getInstance().getRealDisplay(
+ Display.DEFAULT_DISPLAY);
Point p = new Point();
display.getRealSize(p);
int pixels = p.x * p.y;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index adc9434..05c009f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1570,6 +1570,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case STOP_USER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int userid = data.readInt();
+ IStopUserCallback callback = IStopUserCallback.Stub.asInterface(
+ data.readStrongBinder());
+ int result = stopUser(userid, callback);
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
+
case GET_CURRENT_USER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
UserInfo userInfo = getCurrentUser();
@@ -3756,11 +3767,25 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
+ public int stopUser(int userid, IStopUserCallback callback) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(userid);
+ data.writeStrongInterface(callback);
+ mRemote.transact(STOP_USER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int result = reply.readInt();
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
public UserInfo getCurrentUser() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
- mRemote.transact(SWITCH_USER_TRANSACTION, data, reply, 0);
+ mRemote.transact(GET_CURRENT_USER_TRANSACTION, data, reply, 0);
reply.readException();
UserInfo userInfo = UserInfo.CREATOR.createFromParcel(reply);
reply.recycle();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b8e16c5..4a1bf75 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,6 +43,7 @@ import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.net.IConnectivityManager;
import android.net.Proxy;
import android.net.ProxyProperties;
@@ -1557,7 +1558,7 @@ public final class ActivityThread {
return dm;
}
- DisplayManager displayManager = DisplayManager.getInstance();
+ DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
if (displayManager == null) {
// may be null early in system startup
dm = new DisplayMetrics();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 32086d7..efe4b7b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -349,10 +349,11 @@ class ContextImpl extends Context {
return InputManager.getInstance();
}});
- registerService(DISPLAY_SERVICE, new StaticServiceFetcher() {
- public Object createStaticService() {
- return DisplayManager.getInstance();
- }});
+ registerService(DISPLAY_SERVICE, new ServiceFetcher() {
+ @Override
+ public Object createService(ContextImpl ctx) {
+ return new DisplayManager(ctx.getOuterContext());
+ }});
registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index c3e911e..70d8445 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -331,6 +331,7 @@ public interface IActivityManager extends IInterface {
// Multi-user APIs
public boolean switchUser(int userid) throws RemoteException;
+ public int stopUser(int userid, IStopUserCallback callback) throws RemoteException;
public UserInfo getCurrentUser() throws RemoteException;
public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException;
@@ -611,4 +612,5 @@ public interface IActivityManager extends IInterface {
int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+150;
int IS_INTENT_SENDER_AN_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+151;
int START_ACTIVITY_AS_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+152;
+ int STOP_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+153;
}
diff --git a/core/java/android/app/IStopUserCallback.aidl b/core/java/android/app/IStopUserCallback.aidl
new file mode 100644
index 0000000..19ac1d5
--- /dev/null
+++ b/core/java/android/app/IStopUserCallback.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.app;
+
+/**
+ * Callback to find out when we have finished stopping a user.
+ * {@hide}
+ */
+interface IStopUserCallback
+{
+ void userStopped(int userId);
+ void userStopAborted(int userId);
+}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 17d404d..f817fb4 100755
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -75,6 +75,7 @@ import java.util.UUID;
public final class BluetoothAdapter {
private static final String TAG = "BluetoothAdapter";
private static final boolean DBG = true;
+ private static final boolean VDBG = false;
/**
* Sentinel error value for this class. Guaranteed to not equal any other
@@ -465,7 +466,7 @@ public final class BluetoothAdapter {
if (mService != null)
{
int state= mService.getState();
- if (DBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state);
+ if (VDBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state);
return state;
}
// TODO(BT) there might be a small gap during STATE_TURNING_ON that
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index b2b5d81..30406e9 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -133,6 +133,11 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
return true;
}
+ @Override
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
/**
* Re-enable connectivity to a network after a {@link #teardown()}.
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 06edf32..53e0a75 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -27,7 +27,6 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
-import android.media.RemoteControlClient;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -2287,6 +2286,15 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.USER_ADDED";
/**
+ * Broadcast sent to the system when a user is stopped. Carries an extra EXTRA_USER_HANDLE that has
+ * the userHandle of the user. This is similar to {@link #ACTION_PACKAGE_RESTARTED},
+ * but for an entire user instead of a specific package.
+ * @hide
+ */
+ public static final String ACTION_USER_STOPPED =
+ "android.intent.action.USER_STOPPED";
+
+ /**
* Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has
* the userHandle of the user.
* @hide
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0d99d3f..0aa094f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2319,6 +2319,9 @@ public abstract class PackageManager {
* {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra
* @param verificationCode either {@link PackageManager#VERIFICATION_ALLOW}
* or {@link PackageManager#VERIFICATION_REJECT}.
+ * @throws SecurityException if the caller does not have the
+ * {@link android.Manifest.permission#PACKAGE_VERIFICATION_AGENT}
+ * permission.
*/
public abstract void verifyPendingInstall(int id, int verificationCode);
@@ -2342,9 +2345,11 @@ public abstract class PackageManager {
* @param millisecondsToDelay the amount of time requested for the timeout.
* Must be positive and less than
* {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}.
- *
* @throws IllegalArgumentException if {@code millisecondsToDelay} is out
* of bounds or {@code verificationCodeAtTimeout} is unknown.
+ * @throws SecurityException if the caller does not have the
+ * {@link android.Manifest.permission#PACKAGE_VERIFICATION_AGENT}
+ * permission.
*/
public abstract void extendVerificationTimeout(int id,
int verificationCodeAtTimeout, long millisecondsToDelay);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 4d9077f..829620b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -26,6 +26,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
+import android.text.TextUtils;
import android.view.Surface;
import android.view.SurfaceHolder;
@@ -34,7 +35,6 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.StringTokenizer;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -1905,7 +1905,7 @@ public class Camera {
private HashMap<String, String> mMap;
private Parameters() {
- mMap = new HashMap<String, String>();
+ mMap = new HashMap<String, String>(64);
}
/**
@@ -1929,7 +1929,7 @@ public class Camera {
* semi-colon delimited key-value pairs
*/
public String flatten() {
- StringBuilder flattened = new StringBuilder();
+ StringBuilder flattened = new StringBuilder(128);
for (String k : mMap.keySet()) {
flattened.append(k);
flattened.append("=");
@@ -1952,9 +1952,9 @@ public class Camera {
public void unflatten(String flattened) {
mMap.clear();
- StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
- while (tokenizer.hasMoreElements()) {
- String kv = tokenizer.nextToken();
+ TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(';');
+ splitter.setString(flattened);
+ for (String kv : splitter) {
int pos = kv.indexOf('=');
if (pos == -1) {
continue;
@@ -3488,11 +3488,11 @@ public class Camera {
private ArrayList<String> split(String str) {
if (str == null) return null;
- // Use StringTokenizer because it is faster than split.
- StringTokenizer tokenizer = new StringTokenizer(str, ",");
+ TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
+ splitter.setString(str);
ArrayList<String> substrings = new ArrayList<String>();
- while (tokenizer.hasMoreElements()) {
- substrings.add(tokenizer.nextToken());
+ for (String s : splitter) {
+ substrings.add(s);
}
return substrings;
}
@@ -3502,11 +3502,11 @@ public class Camera {
private ArrayList<Integer> splitInt(String str) {
if (str == null) return null;
- StringTokenizer tokenizer = new StringTokenizer(str, ",");
+ TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
+ splitter.setString(str);
ArrayList<Integer> substrings = new ArrayList<Integer>();
- while (tokenizer.hasMoreElements()) {
- String token = tokenizer.nextToken();
- substrings.add(Integer.parseInt(token));
+ for (String s : splitter) {
+ substrings.add(Integer.parseInt(s));
}
if (substrings.size() == 0) return null;
return substrings;
@@ -3515,11 +3515,11 @@ public class Camera {
private void splitInt(String str, int[] output) {
if (str == null) return;
- StringTokenizer tokenizer = new StringTokenizer(str, ",");
+ TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
+ splitter.setString(str);
int index = 0;
- while (tokenizer.hasMoreElements()) {
- String token = tokenizer.nextToken();
- output[index++] = Integer.parseInt(token);
+ for (String s : splitter) {
+ output[index++] = Integer.parseInt(s);
}
}
@@ -3527,11 +3527,11 @@ public class Camera {
private void splitFloat(String str, float[] output) {
if (str == null) return;
- StringTokenizer tokenizer = new StringTokenizer(str, ",");
+ TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
+ splitter.setString(str);
int index = 0;
- while (tokenizer.hasMoreElements()) {
- String token = tokenizer.nextToken();
- output[index++] = Float.parseFloat(token);
+ for (String s : splitter) {
+ output[index++] = Float.parseFloat(s);
}
}
@@ -3558,10 +3558,11 @@ public class Camera {
private ArrayList<Size> splitSize(String str) {
if (str == null) return null;
- StringTokenizer tokenizer = new StringTokenizer(str, ",");
+ TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
+ splitter.setString(str);
ArrayList<Size> sizeList = new ArrayList<Size>();
- while (tokenizer.hasMoreElements()) {
- Size size = strToSize(tokenizer.nextToken());
+ for (String s : splitter) {
+ Size size = strToSize(s);
if (size != null) sizeList.add(size);
}
if (sizeList.size() == 0) return null;
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 98d2f69..74996da 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -18,20 +18,12 @@ package android.hardware.display;
import android.content.Context;
import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
+import android.util.SparseArray;
import android.view.CompatibilityInfoHolder;
import android.view.Display;
-import android.view.DisplayInfo;
-
-import java.util.ArrayList;
/**
- * Manages the properties, media routing and power state of attached displays.
+ * Manages the properties of attached displays.
* <p>
* Get an instance of this class by calling
* {@link android.content.Context#getSystemService(java.lang.String)
@@ -43,110 +35,79 @@ public final class DisplayManager {
private static final String TAG = "DisplayManager";
private static final boolean DEBUG = false;
- private static final int MSG_DISPLAY_ADDED = 1;
- private static final int MSG_DISPLAY_REMOVED = 2;
- private static final int MSG_DISPLAY_CHANGED = 3;
-
- private static DisplayManager sInstance;
-
- private final IDisplayManager mDm;
-
- // Guarded by mDisplayLock
- private final Object mDisplayLock = new Object();
- private final ArrayList<DisplayListenerDelegate> mDisplayListeners =
- new ArrayList<DisplayListenerDelegate>();
+ private final Context mContext;
+ private final DisplayManagerGlobal mGlobal;
+ private final Object mLock = new Object();
+ private final SparseArray<Display> mDisplays = new SparseArray<Display>();
- private DisplayManager(IDisplayManager dm) {
- mDm = dm;
+ /** @hide */
+ public DisplayManager(Context context) {
+ mContext = context;
+ mGlobal = DisplayManagerGlobal.getInstance();
}
/**
- * Gets an instance of the display manager.
+ * Gets information about a logical display.
*
- * @return The display manager instance, may be null early in system startup
- * before the display manager has been fully initialized.
+ * The display metrics may be adjusted to provide compatibility
+ * for legacy applications.
*
- * @hide
+ * @param displayId The logical display id.
+ * @return The display object, or null if there is no valid display with the given id.
*/
- public static DisplayManager getInstance() {
- synchronized (DisplayManager.class) {
- if (sInstance == null) {
- IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
- if (b != null) {
- sInstance = new DisplayManager(IDisplayManager.Stub.asInterface(b));
- }
- }
- return sInstance;
+ public Display getDisplay(int displayId) {
+ synchronized (mLock) {
+ return getOrCreateDisplayLocked(displayId, false /*assumeValid*/);
}
}
/**
- * Get information about a particular logical display.
+ * Gets all currently valid logical displays.
*
- * @param displayId The logical display id.
- * @param outInfo A structure to populate with the display info.
- * @return True if the logical display exists, false otherwise.
- * @hide
+ * @return An array containing all displays.
*/
- public boolean getDisplayInfo(int displayId, DisplayInfo outInfo) {
- try {
- return mDm.getDisplayInfo(displayId, outInfo);
- } catch (RemoteException ex) {
- Log.e(TAG, "Could not get display information from display manager.", ex);
- return false;
+ public Display[] getDisplays() {
+ int[] displayIds = mGlobal.getDisplayIds();
+ int expectedCount = displayIds.length;
+ Display[] displays = new Display[expectedCount];
+ synchronized (mLock) {
+ int actualCount = 0;
+ for (int i = 0; i < expectedCount; i++) {
+ Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
+ if (display != null) {
+ displays[actualCount++] = display;
+ }
+ }
+ if (actualCount != expectedCount) {
+ Display[] oldDisplays = displays;
+ displays = new Display[actualCount];
+ System.arraycopy(oldDisplays, 0, displays, 0, actualCount);
+ }
}
+ return displays;
}
- /**
- * Gets information about a logical display.
- *
- * The display metrics may be adjusted to provide compatibility
- * for legacy applications.
- *
- * @param displayId The logical display id.
- * @param applicationContext The application context from which to obtain
- * compatible metrics.
- * @return The display object.
- */
- public Display getDisplay(int displayId, Context applicationContext) {
- if (applicationContext == null) {
- throw new IllegalArgumentException("applicationContext must not be null");
+ private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) {
+ Display display = mDisplays.get(displayId);
+ if (display == null) {
+ display = mGlobal.getCompatibleDisplay(displayId,
+ getCompatibilityInfoForDisplayLocked(displayId));
+ if (display != null) {
+ mDisplays.put(displayId, display);
+ }
+ } else if (!assumeValid && !display.isValid()) {
+ display = null;
}
+ return display;
+ }
+ private CompatibilityInfoHolder getCompatibilityInfoForDisplayLocked(int displayId) {
CompatibilityInfoHolder cih = null;
if (displayId == Display.DEFAULT_DISPLAY) {
- cih = applicationContext.getCompatibilityInfo();
+ cih = mContext.getCompatibilityInfo();
}
- return getCompatibleDisplay(displayId, cih);
- }
-
- /**
- * Gets information about a logical display.
- *
- * The display metrics may be adjusted to provide compatibility
- * for legacy applications.
- *
- * @param displayId The logical display id.
- * @param cih The compatibility info, or null if none is required.
- * @return The display object.
- *
- * @hide
- */
- public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) {
- return new Display(displayId, cih);
- }
-
- /**
- * Gets information about a logical display without applying any compatibility metrics.
- *
- * @param displayId The logical display id.
- * @return The display object.
- *
- * @hide
- */
- public Display getRealDisplay(int displayId) {
- return getCompatibleDisplay(displayId, null);
+ return cih;
}
/**
@@ -160,16 +121,7 @@ public final class DisplayManager {
* @see #unregisterDisplayListener
*/
public void registerDisplayListener(DisplayListener listener, Handler handler) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
-
- synchronized (mDisplayLock) {
- int index = findDisplayListenerLocked(listener);
- if (index < 0) {
- mDisplayListeners.add(new DisplayListenerDelegate(listener, handler));
- }
- }
+ mGlobal.registerDisplayListener(listener, handler);
}
/**
@@ -180,28 +132,7 @@ public final class DisplayManager {
* @see #registerDisplayListener
*/
public void unregisterDisplayListener(DisplayListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener must not be null");
- }
-
- synchronized (mDisplayLock) {
- int index = findDisplayListenerLocked(listener);
- if (index >= 0) {
- DisplayListenerDelegate d = mDisplayListeners.get(index);
- d.removeCallbacksAndMessages(null);
- mDisplayListeners.remove(index);
- }
- }
- }
-
- private int findDisplayListenerLocked(DisplayListener listener) {
- final int numListeners = mDisplayListeners.size();
- for (int i = 0; i < numListeners; i++) {
- if (mDisplayListeners.get(i).mListener == listener) {
- return i;
- }
- }
- return -1;
+ mGlobal.unregisterDisplayListener(listener);
}
/**
@@ -210,7 +141,8 @@ public final class DisplayManager {
public interface DisplayListener {
/**
* Called whenever a logical display has been added to the system.
- * Use {@link DisplayManager#getDisplay} to get more information about the display.
+ * Use {@link DisplayManager#getDisplay} to get more information about
+ * the display.
*
* @param displayId The id of the logical display that was added.
*/
@@ -230,28 +162,4 @@ public final class DisplayManager {
*/
void onDisplayChanged(int displayId);
}
-
- private static final class DisplayListenerDelegate extends Handler {
- public final DisplayListener mListener;
-
- public DisplayListenerDelegate(DisplayListener listener, Handler handler) {
- super(handler != null ? handler.getLooper() : Looper.myLooper());
- mListener = listener;
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DISPLAY_ADDED:
- mListener.onDisplayAdded(msg.arg1);
- break;
- case MSG_DISPLAY_REMOVED:
- mListener.onDisplayRemoved(msg.arg1);
- break;
- case MSG_DISPLAY_CHANGED:
- mListener.onDisplayChanged(msg.arg1);
- break;
- }
- }
- }
}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
new file mode 100644
index 0000000..69c0319
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.CompatibilityInfoHolder;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Manager communication with the display manager service on behalf of
+ * an application process. You're probably looking for {@link DisplayManager}.
+ *
+ * @hide
+ */
+public final class DisplayManagerGlobal {
+ private static final String TAG = "DisplayManager";
+ private static final boolean DEBUG = false;
+
+ public static final int EVENT_DISPLAY_ADDED = 1;
+ public static final int EVENT_DISPLAY_CHANGED = 2;
+ public static final int EVENT_DISPLAY_REMOVED = 3;
+
+ private static DisplayManagerGlobal sInstance;
+
+ private final Object mLock = new Object();
+
+ private final IDisplayManager mDm;
+
+ private DisplayManagerCallback mCallback;
+ private final ArrayList<DisplayListenerDelegate> mDisplayListeners =
+ new ArrayList<DisplayListenerDelegate>();
+
+ private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
+ private int[] mDisplayIdCache;
+
+ private DisplayManagerGlobal(IDisplayManager dm) {
+ mDm = dm;
+ }
+
+ /**
+ * Gets an instance of the display manager global singleton.
+ *
+ * @return The display manager instance, may be null early in system startup
+ * before the display manager has been fully initialized.
+ */
+ public static DisplayManagerGlobal getInstance() {
+ synchronized (DisplayManagerGlobal.class) {
+ if (sInstance == null) {
+ IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
+ if (b != null) {
+ sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b));
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Get information about a particular logical display.
+ *
+ * @param displayId The logical display id.
+ * @return Information about the specified display, or null if it does not exist.
+ * This object belongs to an internal cache and should be treated as if it were immutable.
+ */
+ public DisplayInfo getDisplayInfo(int displayId) {
+ try {
+ synchronized (mLock) {
+ DisplayInfo info = mDisplayInfoCache.get(displayId);
+ if (info != null) {
+ return info;
+ }
+
+ info = mDm.getDisplayInfo(displayId);
+ if (info == null) {
+ return null;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
+ }
+
+ mDisplayInfoCache.put(displayId, info);
+ registerCallbackIfNeededLocked();
+ return info;
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Could not get display information from display manager.", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Gets all currently valid logical display ids.
+ *
+ * @return An array containing all display ids.
+ */
+ public int[] getDisplayIds() {
+ try {
+ synchronized (mLock) {
+ if (mDisplayIdCache == null) {
+ mDisplayIdCache = mDm.getDisplayIds();
+ registerCallbackIfNeededLocked();
+ }
+ return mDisplayIdCache;
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Could not get display ids from display manager.", ex);
+ return new int[] { Display.DEFAULT_DISPLAY };
+ }
+ }
+
+ /**
+ * Gets information about a logical display.
+ *
+ * The display metrics may be adjusted to provide compatibility
+ * for legacy applications.
+ *
+ * @param displayId The logical display id.
+ * @param cih The compatibility info, or null if none is required.
+ * @return The display object, or null if there is no display with the given id.
+ */
+ public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) {
+ DisplayInfo displayInfo = getDisplayInfo(displayId);
+ if (displayInfo == null) {
+ return null;
+ }
+ return new Display(this, displayId, displayInfo, cih);
+ }
+
+ /**
+ * Gets information about a logical display without applying any compatibility metrics.
+ *
+ * @param displayId The logical display id.
+ * @return The display object, or null if there is no display with the given id.
+ */
+ public Display getRealDisplay(int displayId) {
+ return getCompatibleDisplay(displayId, null);
+ }
+
+ public void registerDisplayListener(DisplayListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mLock) {
+ int index = findDisplayListenerLocked(listener);
+ if (index < 0) {
+ mDisplayListeners.add(new DisplayListenerDelegate(listener, handler));
+ registerCallbackIfNeededLocked();
+ }
+ }
+ }
+
+ public void unregisterDisplayListener(DisplayListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mLock) {
+ int index = findDisplayListenerLocked(listener);
+ if (index >= 0) {
+ DisplayListenerDelegate d = mDisplayListeners.get(index);
+ d.clearEvents();
+ mDisplayListeners.remove(index);
+ }
+ }
+ }
+
+ private int findDisplayListenerLocked(DisplayListener listener) {
+ final int numListeners = mDisplayListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (mDisplayListeners.get(i).mListener == listener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void registerCallbackIfNeededLocked() {
+ if (mCallback == null) {
+ mCallback = new DisplayManagerCallback();
+ try {
+ mDm.registerCallback(mCallback);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to register callback with display manager service.", ex);
+ mCallback = null;
+ }
+ }
+ }
+
+ private void handleDisplayEvent(int displayId, int event) {
+ synchronized (mLock) {
+ mDisplayInfoCache.remove(displayId);
+
+ if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) {
+ mDisplayIdCache = null;
+ }
+
+ final int numListeners = mDisplayListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ mDisplayListeners.get(i).sendDisplayEvent(displayId, event);
+ }
+ }
+ }
+
+ private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
+ @Override
+ public void onDisplayEvent(int displayId, int event) {
+ if (DEBUG) {
+ Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);
+ }
+ handleDisplayEvent(displayId, event);
+ }
+ }
+
+ private static final class DisplayListenerDelegate extends Handler {
+ public final DisplayListener mListener;
+
+ public DisplayListenerDelegate(DisplayListener listener, Handler handler) {
+ super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/);
+ mListener = listener;
+ }
+
+ public void sendDisplayEvent(int displayId, int event) {
+ Message msg = obtainMessage(event, displayId, 0);
+ sendMessage(msg);
+ }
+
+ public void clearEvents() {
+ removeCallbacksAndMessages(null);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_DISPLAY_ADDED:
+ mListener.onDisplayAdded(msg.arg1);
+ break;
+ case EVENT_DISPLAY_CHANGED:
+ mListener.onDisplayChanged(msg.arg1);
+ break;
+ case EVENT_DISPLAY_REMOVED:
+ mListener.onDisplayRemoved(msg.arg1);
+ break;
+ }
+ }
+ }
+}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index fd8c35f..d802aa1 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -16,9 +16,13 @@
package android.hardware.display;
+import android.hardware.display.IDisplayManagerCallback;
import android.view.DisplayInfo;
/** @hide */
interface IDisplayManager {
- boolean getDisplayInfo(int displayId, out DisplayInfo outInfo);
+ DisplayInfo getDisplayInfo(int displayId);
+ int[] getDisplayIds();
+
+ void registerCallback(in IDisplayManagerCallback callback);
}
diff --git a/core/java/android/hardware/display/IDisplayManagerCallback.aidl b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
new file mode 100644
index 0000000..c50e3fb
--- /dev/null
+++ b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+/** @hide */
+interface IDisplayManagerCallback {
+ oneway void onDisplayEvent(int displayId, int event);
+}
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 99bd647..4b60f07 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -96,6 +96,11 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
}
@Override
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
+ @Override
public boolean setRadio(boolean turnOn) {
// Base tracker doesn't handle radios
return true;
diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java
new file mode 100644
index 0000000..aa392d0
--- /dev/null
+++ b/core/java/android/net/CaptivePortalTracker.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.android.internal.R;
+
+/**
+ * This class allows captive portal detection
+ * @hide
+ */
+public class CaptivePortalTracker {
+ private static final boolean DBG = true;
+ private static final String TAG = "CaptivePortalTracker";
+
+ private static final String DEFAULT_SERVER = "clients3.google.com";
+ private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
+
+ private static final int SOCKET_TIMEOUT_MS = 10000;
+
+ private String mServer;
+ private String mUrl;
+ private boolean mNotificationShown = false;
+ private boolean mIsCaptivePortalCheckEnabled = false;
+ private InternalHandler mHandler;
+ private IConnectivityManager mConnService;
+ private Context mContext;
+ private NetworkInfo mNetworkInfo;
+ private boolean mIsCaptivePortal = false;
+
+ private static final int DETECT_PORTAL = 0;
+ private static final int HANDLE_CONNECT = 1;
+
+ /**
+ * Activity Action: Switch to the captive portal network
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ */
+ public static final String ACTION_SWITCH_TO_CAPTIVE_PORTAL
+ = "android.net.SWITCH_TO_CAPTIVE_PORTAL";
+
+ private CaptivePortalTracker(Context context, NetworkInfo info, IConnectivityManager cs) {
+ mContext = context;
+ mNetworkInfo = info;
+ mConnService = cs;
+
+ HandlerThread handlerThread = new HandlerThread("CaptivePortalThread");
+ handlerThread.start();
+ mHandler = new InternalHandler(handlerThread.getLooper());
+ mHandler.obtainMessage(DETECT_PORTAL).sendToTarget();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_SWITCH_TO_CAPTIVE_PORTAL);
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+
+ mContext.registerReceiver(mReceiver, filter);
+
+ mServer = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.CAPTIVE_PORTAL_SERVER);
+ if (mServer == null) mServer = DEFAULT_SERVER;
+
+ mIsCaptivePortalCheckEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ACTION_SWITCH_TO_CAPTIVE_PORTAL)) {
+ notifyPortalCheckComplete();
+ } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ NetworkInfo info = intent.getParcelableExtra(
+ ConnectivityManager.EXTRA_NETWORK_INFO);
+ mHandler.obtainMessage(HANDLE_CONNECT, info).sendToTarget();
+ }
+ }
+ };
+
+ public static CaptivePortalTracker detect(Context context, NetworkInfo info,
+ IConnectivityManager cs) {
+ CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, info, cs);
+ return captivePortal;
+ }
+
+ private class InternalHandler extends Handler {
+ public InternalHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case DETECT_PORTAL:
+ InetAddress server = lookupHost(mServer);
+ if (server != null) {
+ requestRouteToHost(server);
+ if (isCaptivePortal(server)) {
+ if (DBG) log("Captive portal " + mNetworkInfo);
+ setNotificationVisible(true);
+ mIsCaptivePortal = true;
+ break;
+ }
+ }
+ notifyPortalCheckComplete();
+ quit();
+ break;
+ case HANDLE_CONNECT:
+ NetworkInfo info = (NetworkInfo) msg.obj;
+ if (info.getType() != mNetworkInfo.getType()) break;
+
+ if (info.getState() == NetworkInfo.State.CONNECTED ||
+ info.getState() == NetworkInfo.State.DISCONNECTED) {
+ setNotificationVisible(false);
+ }
+
+ /* Connected to a captive portal */
+ if (info.getState() == NetworkInfo.State.CONNECTED &&
+ mIsCaptivePortal) {
+ launchBrowser();
+ quit();
+ }
+ break;
+ default:
+ loge("Unhandled message " + msg);
+ break;
+ }
+ }
+
+ private void quit() {
+ mIsCaptivePortal = false;
+ getLooper().quit();
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+
+ private void launchBrowser() {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl));
+ intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ private void notifyPortalCheckComplete() {
+ try {
+ mConnService.captivePortalCheckComplete(mNetworkInfo);
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void requestRouteToHost(InetAddress server) {
+ try {
+ mConnService.requestRouteToHostAddress(mNetworkInfo.getType(),
+ server.getAddress());
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Do a URL fetch on a known server to see if we get the data we expect
+ */
+ private boolean isCaptivePortal(InetAddress server) {
+ HttpURLConnection urlConnection = null;
+ if (!mIsCaptivePortalCheckEnabled) return false;
+
+ mUrl = "http://" + server.getHostAddress() + "/generate_204";
+ try {
+ URL url = new URL(mUrl);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setInstanceFollowRedirects(false);
+ urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
+ urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
+ urlConnection.setUseCaches(false);
+ urlConnection.getInputStream();
+ // we got a valid response, but not from the real google
+ return urlConnection.getResponseCode() != 204;
+ } catch (IOException e) {
+ if (DBG) log("Probably not a portal: exception " + e);
+ return false;
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
+ }
+
+ private InetAddress lookupHost(String hostname) {
+ InetAddress inetAddress[];
+ try {
+ inetAddress = InetAddress.getAllByName(hostname);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+
+ for (InetAddress a : inetAddress) {
+ if (a instanceof Inet4Address) return a;
+ }
+ return null;
+ }
+
+ private void setNotificationVisible(boolean visible) {
+ // if it should be hidden and it is already hidden, then noop
+ if (!visible && !mNotificationShown) {
+ return;
+ }
+
+ Resources r = Resources.getSystem();
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (visible) {
+ CharSequence title = r.getString(R.string.wifi_available_sign_in, 0);
+ CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed,
+ mNetworkInfo.getExtraInfo());
+
+ Notification notification = new Notification();
+ notification.when = 0;
+ notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range;
+ notification.flags = Notification.FLAG_AUTO_CANCEL;
+ notification.contentIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(CaptivePortalTracker.ACTION_SWITCH_TO_CAPTIVE_PORTAL), 0);
+
+ notification.tickerText = title;
+ notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
+
+ notificationManager.notify(NOTIFICATION_ID, 1, notification);
+ } else {
+ notificationManager.cancel(NOTIFICATION_ID, 1);
+ }
+ mNotificationShown = visible;
+ }
+
+ private static void log(String s) {
+ Log.d(TAG, s);
+ }
+
+ private static void loge(String s) {
+ Log.e(TAG, s);
+ }
+
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 60bf4d6..a570473 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -921,4 +921,15 @@ public class ConnectivityManager {
return false;
}
}
+
+ /**
+ * {@hide}
+ */
+ public void captivePortalCheckComplete(NetworkInfo info) {
+ try {
+ mService.captivePortalCheckComplete(info);
+ } catch (RemoteException e) {
+ }
+ }
+
}
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
index cc3e34f..874e80a 100644
--- a/core/java/android/net/DhcpStateMachine.java
+++ b/core/java/android/net/DhcpStateMachine.java
@@ -92,10 +92,12 @@ public class DhcpStateMachine extends StateMachine {
/* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
* success/failure */
public static final int CMD_POST_DHCP_ACTION = BASE + 5;
+ /* Notification from DHCP state machine before quitting */
+ public static final int CMD_ON_QUIT = BASE + 6;
/* Command from controller to indicate DHCP discovery/renewal can continue
* after pre DHCP action is complete */
- public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 6;
+ public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 7;
/* Message.arg1 arguments to CMD_POST_DHCP notification */
public static final int DHCP_SUCCESS = 1;
@@ -172,6 +174,10 @@ public class DhcpStateMachine extends StateMachine {
quit();
}
+ protected void onQuitting() {
+ mController.sendMessage(CMD_ON_QUIT);
+ }
+
class DefaultState extends State {
@Override
public void exit() {
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index ccd96ff..39440c2 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -119,6 +119,10 @@ public class DummyDataStateTracker implements NetworkStateTracker {
return true;
}
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
/**
* Record the detailed state of a network, and if it is a
* change from the previous state, send a notification to
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index c690430..c52aa9e 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -274,6 +274,11 @@ public class EthernetDataTracker implements NetworkStateTracker {
return mLinkUp;
}
+ @Override
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
/**
* Turn the wireless radio off for a network.
* @param turnOn {@code true} to turn the radio on, {@code false}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 3614045..056fa03 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -124,4 +124,6 @@ interface IConnectivityManager
LegacyVpnInfo getLegacyVpnInfo();
boolean updateLockdownVpn();
+
+ void captivePortalCheckComplete(in NetworkInfo info);
}
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index d59fa6a..b35d61c 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -381,6 +381,11 @@ public class MobileDataStateTracker implements NetworkStateTracker {
return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
}
+ @Override
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
/**
* Record the detailed state of a network, and if it is a
* change from the previous state, send a notification to
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 0bc6b58..0b23cb7 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -79,7 +79,9 @@ public class NetworkInfo implements Parcelable {
/** Access to this network is blocked. */
BLOCKED,
/** Link has poor connectivity. */
- VERIFYING_POOR_LINK
+ VERIFYING_POOR_LINK,
+ /** Checking if network is a captive portal */
+ CAPTIVE_PORTAL_CHECK,
}
/**
@@ -97,6 +99,7 @@ public class NetworkInfo implements Parcelable {
stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING);
+ stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING);
stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index eae89f1..0a0c1e0 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -123,6 +123,11 @@ public interface NetworkStateTracker {
public boolean reconnect();
/**
+ * Ready to switch on to the network after captive portal check
+ */
+ public void captivePortalCheckComplete();
+
+ /**
* Turn the wireless radio off for a network.
* @param turnOn {@code true} to turn the radio on, {@code false}
*/
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index fb5263d..65d3f2b 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -329,7 +329,7 @@ public class VpnService extends Service {
throw new IllegalArgumentException("Bad address");
}
- mAddresses.append(String.format(" %s/%d", address.getHostAddress(), prefixLength));
+ mAddresses.append(' ' + address.getHostAddress() + '/' + prefixLength);
return this;
}
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 4e2b5c0..0f9be9c 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -412,6 +412,50 @@ public class Handler {
}
/**
+ * Runs the specified task synchronously.
+ *
+ * If the current thread is the same as the handler thread, then the runnable
+ * runs immediately without being enqueued. Otherwise, posts the runnable
+ * to the handler and waits for it to complete before returning.
+ *
+ * This method is dangerous! Improper use can result in deadlocks.
+ * Never call this method while any locks are held or use it in a
+ * possibly re-entrant manner.
+ *
+ * This method is occasionally useful in situations where a background thread
+ * must synchronously await completion of a task that must run on the
+ * handler's thread. However, this problem is often a symptom of bad design.
+ * Consider improving the design (if possible) before resorting to this method.
+ *
+ * One example of where you might want to use this method is when you just
+ * set up a Handler thread and need to perform some initialization steps on
+ * it before continuing execution.
+ *
+ * @param r The Runnable that will be executed synchronously.
+ *
+ * @return Returns true if the Runnable was successfully executed.
+ * Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ *
+ * @hide This method is prone to abuse and should probably not be in the API.
+ * If we ever do make it part of the API, we might want to rename it to something
+ * less funny like runUnsafe().
+ */
+ public final boolean runWithScissors(final Runnable r) {
+ if (r == null) {
+ throw new IllegalArgumentException("runnable must not be null");
+ }
+
+ if (Looper.myLooper() == mLooper) {
+ r.run();
+ return true;
+ }
+
+ BlockingRunnable br = new BlockingRunnable(r);
+ return br.postAndWait(this);
+ }
+
+ /**
* Remove any pending posts of Runnable r that are in the message queue.
*/
public final void removeCallbacks(Runnable r)
@@ -678,4 +722,41 @@ public class Handler {
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;
+
+ private static final class BlockingRunnable implements Runnable {
+ private final Runnable mTask;
+ private boolean mDone;
+
+ public BlockingRunnable(Runnable task) {
+ mTask = task;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mTask.run();
+ } finally {
+ synchronized (this) {
+ mDone = true;
+ notifyAll();
+ }
+ }
+ }
+
+ public boolean postAndWait(Handler handler) {
+ if (!handler.post(this)) {
+ return false;
+ }
+
+ synchronized (this) {
+ while (!mDone) {
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ return true;
+ }
+ }
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 851b8df..d5fca4d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -584,6 +584,8 @@ public class Process {
}
if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) {
argsForZygote.add("--mount-external-multiuser");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) {
+ argsForZygote.add("--mount-external-multiuser-all");
}
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1f6f0dd..b4841b1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3279,13 +3279,6 @@ public final class Settings {
/**
- * ms delay before rechecking a connect SSID for walled garden with a http download.
- * @hide
- */
- public static final String WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS =
- "wifi_watchdog_walled_garden_interval_ms";
-
- /**
* Number of ARP pings per check.
* @hide
*/
@@ -3322,23 +3315,6 @@ public final class Settings {
"wifi_suspend_optimizations_enabled";
/**
- * Setting to turn off walled garden test on Wi-Fi. Feature is enabled by default and
- * the setting needs to be set to 0 to disable it.
- * @hide
- */
- public static final String WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED =
- "wifi_watchdog_walled_garden_test_enabled";
-
- /**
- * The URL used for walled garden check upon a new conection. WifiWatchdogService
- * fetches the URL and checks to see if {@link #WIFI_WATCHDOG_WALLED_GARDEN_PATTERN}
- * is not part of the title string to notify the user on the presence of a walled garden.
- * @hide
- */
- public static final String WIFI_WATCHDOG_WALLED_GARDEN_URL =
- "wifi_watchdog_walled_garden_url";
-
- /**
* The maximum number of times we will retry a connection to an access
* point for which we have failed in acquiring an IP address from DHCP.
* A value of N means that we will make N+1 connection attempts in all.
@@ -3362,6 +3338,21 @@ public final class Settings {
public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
/**
+ * Setting to turn off captive portal detection. Feature is enabled by default and
+ * the setting needs to be set to 0 to disable it.
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_DETECTION_ENABLED =
+ "captive_portal_detection_enabled";
+
+ /**
+ * The server used for captive portal detection upon a new conection. A 204 response
+ * code from the server is used for validation.
+ * @hide
+ */
+ public static final String CAPTIVE_PORTAL_SERVER = "captive_portal_server";
+
+ /**
* Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile
* data connectivity to be established after a disconnect from Wi-Fi.
*/
@@ -4360,6 +4351,25 @@ public final class Settings {
public static final String SMS_SHORT_CODES_PREFIX = "sms_short_codes_";
/**
+ * Overlay display devices setting.
+ * The associated value is a specially formatted string that describes the
+ * size and density of simulated secondary display devices.
+ * <p>
+ * Format: {width}x{height}/{dpi};...
+ * </p><p>
+ * Example:
+ * <ul>
+ * <li><code>1280x720/213</code>: make one overlay that is 1280x720 at 213dpi.</li>
+ * <li><code>1920x1080/320;1280x720/213</code>: make two overlays, the first
+ * at 1080p and the second at 720p.</li>
+ * <li>If the value is empty, then no overlay display devices are created.</li>
+ * </ul></p>
+ *
+ * @hide
+ */
+ public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java
index 47e2129..186cb49 100644
--- a/core/java/android/speech/tts/BlockingAudioTrack.java
+++ b/core/java/android/speech/tts/BlockingAudioTrack.java
@@ -69,7 +69,8 @@ class BlockingAudioTrack {
// Need to be seen by stop() which can be called from another thread. mAudioTrack will be
// set to null only after waitAndRelease().
- private volatile AudioTrack mAudioTrack;
+ private Object mAudioTrackLock = new Object();
+ private AudioTrack mAudioTrack;
private volatile boolean mStopped;
BlockingAudioTrack(int streamType, int sampleRate,
@@ -93,7 +94,9 @@ class BlockingAudioTrack {
public boolean init() {
AudioTrack track = createStreamingAudioTrack();
- mAudioTrack = track;
+ synchronized (mAudioTrackLock) {
+ mAudioTrack = track;
+ }
if (track == null) {
return false;
@@ -103,24 +106,34 @@ class BlockingAudioTrack {
}
public void stop() {
- AudioTrack track = mAudioTrack;
- if (track != null) {
- track.stop();
+ synchronized (mAudioTrackLock) {
+ if (mAudioTrack != null) {
+ mAudioTrack.stop();
+ }
+ mStopped = true;
}
- mStopped = true;
}
public int write(byte[] data) {
- if (mAudioTrack == null || mStopped) {
+ AudioTrack track = null;
+ synchronized (mAudioTrackLock) {
+ track = mAudioTrack;
+ }
+
+ if (track == null || mStopped) {
return -1;
}
- final int bytesWritten = writeToAudioTrack(mAudioTrack, data);
+ final int bytesWritten = writeToAudioTrack(track, data);
+
mBytesWritten += bytesWritten;
return bytesWritten;
}
public void waitAndRelease() {
- AudioTrack track = mAudioTrack;
+ AudioTrack track = null;
+ synchronized (mAudioTrackLock) {
+ track = mAudioTrack;
+ }
if (track == null) {
if (DBG) Log.d(TAG, "Audio track null [duplicate call to waitAndRelease ?]");
return;
@@ -152,8 +165,10 @@ class BlockingAudioTrack {
// all data from the audioTrack has been sent to the mixer, so
// it's safe to release at this point.
if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]");
+ synchronized(mAudioTrackLock) {
+ mAudioTrack = null;
+ }
track.release();
- mAudioTrack = null;
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 392d1f2..6848606 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -17,6 +17,7 @@
package android.view;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -166,8 +167,7 @@ public final class Choreographer {
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
mLastFrameTimeNanos = Long.MIN_VALUE;
- Display d = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
- mFrameIntervalNanos = (long)(1000000000 / d.getRefreshRate());
+ mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
@@ -175,6 +175,12 @@ public final class Choreographer {
}
}
+ private static float getRefreshRate() {
+ DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
+ Display.DEFAULT_DISPLAY);
+ return di.refreshRate;
+ }
+
/**
* Gets the choreographer for the calling thread. Must be called from
* a thread that already has a {@link android.os.Looper} associated with it.
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 6f8ca13..ec635a2 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -19,7 +19,7 @@ package android.view;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -49,10 +49,14 @@ import android.util.Log;
*/
public final class Display {
private static final String TAG = "Display";
+ private static final boolean DEBUG = false;
+ private final DisplayManagerGlobal mGlobal;
private final int mDisplayId;
private final CompatibilityInfoHolder mCompatibilityInfo;
- private final DisplayInfo mDisplayInfo = new DisplayInfo();
+
+ private DisplayInfo mDisplayInfo; // never null
+ private boolean mIsValid;
// Temporary display metrics structure used for compatibility mode.
private final DisplayMetrics mTempMetrics = new DisplayMetrics();
@@ -80,9 +84,14 @@ public final class Display {
*
* @hide
*/
- public Display(int displayId, CompatibilityInfoHolder compatibilityInfo) {
+ public Display(DisplayManagerGlobal global,
+ int displayId, DisplayInfo displayInfo /*not null*/,
+ CompatibilityInfoHolder compatibilityInfo) {
+ mGlobal = global;
mDisplayId = displayId;
+ mDisplayInfo = displayInfo;
mCompatibilityInfo = compatibilityInfo;
+ mIsValid = true;
}
/**
@@ -97,15 +106,37 @@ public final class Display {
}
/**
+ * Returns true if this display is still valid, false if the display has been removed.
+ *
+ * If the display is invalid, then the methods of this class will
+ * continue to report the most recently observed display information.
+ * However, it is unwise (and rather fruitless) to continue using a
+ * {@link Display} object after the display's demise.
+ *
+ * It's possible for a display that was previously invalid to become
+ * valid again if a display with the same id is reconnected.
+ *
+ * @return True if the display is still valid.
+ */
+ public boolean isValid() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mIsValid;
+ }
+ }
+
+ /**
* Gets a full copy of the display information.
*
* @param outDisplayInfo The object to receive the copy of the display information.
+ * @return True if the display is still valid.
* @hide
*/
- public void getDisplayInfo(DisplayInfo outDisplayInfo) {
+ public boolean getDisplayInfo(DisplayInfo outDisplayInfo) {
synchronized (this) {
updateDisplayInfoLocked();
outDisplayInfo.copyFrom(mDisplayInfo);
+ return mIsValid;
}
}
@@ -366,9 +397,25 @@ public final class Display {
}
private void updateDisplayInfoLocked() {
- // TODO: only refresh the display information when needed
- if (!DisplayManager.getInstance().getDisplayInfo(mDisplayId, mDisplayInfo)) {
- Log.e(TAG, "Could not get information about logical display " + mDisplayId);
+ // Note: The display manager caches display info objects on our behalf.
+ DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
+ if (newInfo == null) {
+ // Preserve the old mDisplayInfo after the display is removed.
+ if (mIsValid) {
+ mIsValid = false;
+ if (DEBUG) {
+ Log.d(TAG, "Logical display " + mDisplayId + " was removed.");
+ }
+ }
+ } else {
+ // Use the new display info. (It might be the same object if nothing changed.)
+ mDisplayInfo = newInfo;
+ if (!mIsValid) {
+ mIsValid = true;
+ if (DEBUG) {
+ Log.d(TAG, "Logical display " + mDisplayId + " was recreated.");
+ }
+ }
}
}
@@ -390,7 +437,7 @@ public final class Display {
updateDisplayInfoLocked();
mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
return "Display id " + mDisplayId + ": " + mDisplayInfo
- + ", " + mTempMetrics;
+ + ", " + mTempMetrics + ", isValid=" + mIsValid;
}
}
}
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 6c2e540..0b138c2 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -66,7 +66,7 @@ public abstract class DisplayEventReceiver {
@Override
protected void finalize() throws Throwable {
try {
- dispose();
+ dispose(true);
} finally {
super.finalize();
}
@@ -76,9 +76,17 @@ public abstract class DisplayEventReceiver {
* Disposes the receiver.
*/
public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
mCloseGuard.close();
}
+
if (mReceiverPtr != 0) {
nativeDispose(mReceiverPtr);
mReceiverPtr = 0;
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index e38f245..593e8c4 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -138,6 +138,10 @@ public final class DisplayInfo implements Parcelable {
public DisplayInfo() {
}
+ public DisplayInfo(DisplayInfo other) {
+ copyFrom(other);
+ }
+
private DisplayInfo(Parcel source) {
readFromParcel(source);
}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 0114a41..23337f0 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -226,17 +226,12 @@ public class GestureDetector {
*/
private boolean mIsDoubleTapping;
- private float mLastMotionY;
- private float mLastMotionX;
+ private float mLastFocusX;
+ private float mLastFocusY;
+ private float mDownFocusX;
+ private float mDownFocusY;
private boolean mIsLongpressEnabled;
-
- /**
- * True if we are at a target API level of >= Froyo or the developer can
- * explicitly set it. If true, input events with > 1 pointer will be ignored
- * so we can work side by side with multitouch gesture detectors.
- */
- private boolean mIgnoreMultitouch;
/**
* Determines speed during touch scrolling
@@ -349,8 +344,16 @@ public class GestureDetector {
* @throws NullPointerException if {@code listener} is null.
*/
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
- this(context, listener, handler, context != null &&
- context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO);
+ if (handler != null) {
+ mHandler = new GestureHandler(handler);
+ } else {
+ mHandler = new GestureHandler();
+ }
+ mListener = listener;
+ if (listener instanceof OnDoubleTapListener) {
+ setOnDoubleTapListener((OnDoubleTapListener) listener);
+ }
+ init(context);
}
/**
@@ -362,31 +365,19 @@ public class GestureDetector {
* @param listener the listener invoked for all the callbacks, this must
* not be null.
* @param handler the handler to use
- * @param ignoreMultitouch whether events involving more than one pointer should
- * be ignored.
*
* @throws NullPointerException if {@code listener} is null.
*/
public GestureDetector(Context context, OnGestureListener listener, Handler handler,
- boolean ignoreMultitouch) {
- if (handler != null) {
- mHandler = new GestureHandler(handler);
- } else {
- mHandler = new GestureHandler();
- }
- mListener = listener;
- if (listener instanceof OnDoubleTapListener) {
- setOnDoubleTapListener((OnDoubleTapListener) listener);
- }
- init(context, ignoreMultitouch);
+ boolean unused) {
+ this(context, listener, handler);
}
- private void init(Context context, boolean ignoreMultitouch) {
+ private void init(Context context) {
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
- mIgnoreMultitouch = ignoreMultitouch;
// Fallback to support pre-donuts releases
int touchSlop, doubleTapSlop, doubleTapTouchSlop;
@@ -456,34 +447,40 @@ public class GestureDetector {
}
final int action = ev.getAction();
- final float y = ev.getY();
- final float x = ev.getX();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
+ final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
+
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int count = ev.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ sumX += ev.getX(i);
+ sumY += ev.getY(i);
+ }
+ final int div = pointerUp ? count - 1 : count;
+ final float focusX = sumX / div;
+ final float focusY = sumY / div;
+
boolean handled = false;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
- if (mIgnoreMultitouch) {
- // Multitouch event - abort.
- cancel();
- }
+ mDownFocusX = mLastFocusX = focusX;
+ mDownFocusY = mLastFocusY = focusY;
+ // Cancel long press and taps
+ cancelTaps();
break;
case MotionEvent.ACTION_POINTER_UP:
- // Ending a multitouch gesture and going back to 1 finger
- if (mIgnoreMultitouch && ev.getPointerCount() == 2) {
- int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
- mLastMotionX = ev.getX(index);
- mLastMotionY = ev.getY(index);
- mVelocityTracker.recycle();
- mVelocityTracker = VelocityTracker.obtain();
- }
+ mDownFocusX = mLastFocusX = focusX;
+ mDownFocusY = mLastFocusY = focusY;
break;
case MotionEvent.ACTION_DOWN:
@@ -504,8 +501,8 @@ public class GestureDetector {
}
}
- mLastMotionX = x;
- mLastMotionY = y;
+ mDownFocusX = mLastFocusX = focusX;
+ mDownFocusY = mLastFocusY = focusY;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
@@ -525,22 +522,22 @@ public class GestureDetector {
break;
case MotionEvent.ACTION_MOVE:
- if (mInLongPress || (mIgnoreMultitouch && ev.getPointerCount() > 1)) {
+ if (mInLongPress) {
break;
}
- final float scrollX = mLastMotionX - x;
- final float scrollY = mLastMotionY - y;
+ final float scrollX = mLastFocusX - focusX;
+ final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) {
// Give the move events of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
- final int deltaX = (int) (x - mCurrentDownEvent.getX());
- final int deltaY = (int) (y - mCurrentDownEvent.getY());
+ final int deltaX = (int) (focusX - mDownFocusX);
+ final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
if (distance > mTouchSlopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
- mLastMotionX = x;
- mLastMotionY = y;
+ mLastFocusX = focusX;
+ mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
@@ -551,8 +548,8 @@ public class GestureDetector {
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
- mLastMotionX = x;
- mLastMotionY = y;
+ mLastFocusX = focusX;
+ mLastFocusY = focusY;
}
break;
@@ -571,9 +568,10 @@ public class GestureDetector {
// A fling must travel the minimum tap distance
final VelocityTracker velocityTracker = mVelocityTracker;
+ final int pointerId = ev.getPointerId(0);
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
- final float velocityY = velocityTracker.getYVelocity();
- final float velocityX = velocityTracker.getXVelocity();
+ final float velocityY = velocityTracker.getYVelocity(pointerId);
+ final float velocityX = velocityTracker.getXVelocity(pointerId);
if ((Math.abs(velocityY) > mMinimumFlingVelocity)
|| (Math.abs(velocityX) > mMinimumFlingVelocity)){
@@ -622,6 +620,18 @@ public class GestureDetector {
}
}
+ private void cancelTaps() {
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
+ mIsDoubleTapping = false;
+ mAlwaysInTapRegion = false;
+ mAlwaysInBiggerTapRegion = false;
+ if (mInLongPress) {
+ mInLongPress = false;
+ }
+ }
+
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 9c56782..117c101 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -73,7 +73,7 @@ public abstract class InputEventReceiver {
@Override
protected void finalize() throws Throwable {
try {
- dispose();
+ dispose(true);
} finally {
super.finalize();
}
@@ -83,9 +83,17 @@ public abstract class InputEventReceiver {
* Disposes the receiver.
*/
public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
mCloseGuard.close();
}
+
if (mReceiverPtr != 0) {
nativeDispose(mReceiverPtr);
mReceiverPtr = 0;
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 73f94bc..dc36088 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -17,14 +17,13 @@
package android.view;
import android.content.Context;
-import android.util.DisplayMetrics;
import android.util.FloatMath;
-import android.util.Log;
/**
- * Detects transformation gestures involving more than one pointer ("multitouch")
- * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
- * callback will notify users when a particular gesture event has occurred.
+ * Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
+ * The {@link OnScaleGestureListener} callback will notify users when a particular
+ * gesture event has occurred.
+ *
* This class should only be used with {@link MotionEvent}s reported via touch.
*
* To use this class:
@@ -121,43 +120,21 @@ public class ScaleGestureDetector {
}
}
- /**
- * This value is the threshold ratio between our previous combined pressure
- * and the current combined pressure. We will only fire an onScale event if
- * the computed ratio between the current and previous event pressures is
- * greater than this value. When pressure decreases rapidly between events
- * the position values can often be imprecise, as it usually indicates
- * that the user is in the process of lifting a pointer off of the device.
- * Its value was tuned experimentally.
- */
- private static final float PRESSURE_THRESHOLD = 0.67f;
-
private final Context mContext;
private final OnScaleGestureListener mListener;
- private boolean mGestureInProgress;
-
- private MotionEvent mPrevEvent;
- private MotionEvent mCurrEvent;
private float mFocusX;
private float mFocusY;
- private float mPrevFingerDiffX;
- private float mPrevFingerDiffY;
- private float mCurrFingerDiffX;
- private float mCurrFingerDiffY;
- private float mCurrLen;
- private float mPrevLen;
- private float mScaleFactor;
- private float mCurrPressure;
- private float mPrevPressure;
- private long mTimeDelta;
-
- private boolean mInvalidGesture;
-
- // Pointer IDs currently responsible for the two fingers controlling the gesture
- private int mActiveId0;
- private int mActiveId1;
- private boolean mActive0MostRecent;
+
+ private float mCurrSpan;
+ private float mPrevSpan;
+ private float mCurrSpanX;
+ private float mCurrSpanY;
+ private float mPrevSpanX;
+ private float mPrevSpanY;
+ private long mCurrTime;
+ private long mPrevTime;
+ private boolean mInProgress;
/**
* Consistency verifier for debugging purposes.
@@ -171,6 +148,18 @@ public class ScaleGestureDetector {
mListener = listener;
}
+ /**
+ * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
+ * when appropriate.
+ *
+ * <p>Applications should pass a complete and consistent event stream to this method.
+ * A complete and consistent event stream involves all MotionEvents from the initial
+ * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
+ *
+ * @param event The event to process
+ * @return true if the event was processed and the detector wants to receive the
+ * rest of the MotionEvents in this event stream.
+ */
public boolean onTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
@@ -178,265 +167,110 @@ public class ScaleGestureDetector {
final int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_DOWN) {
- reset(); // Start fresh
- }
-
- boolean handled = true;
- if (mInvalidGesture) {
- handled = false;
- } else if (!mGestureInProgress) {
- switch (action) {
- case MotionEvent.ACTION_DOWN: {
- mActiveId0 = event.getPointerId(0);
- mActive0MostRecent = true;
- }
- break;
-
- case MotionEvent.ACTION_UP:
- reset();
- break;
-
- case MotionEvent.ACTION_POINTER_DOWN: {
- // We have a new multi-finger gesture
- if (mPrevEvent != null) mPrevEvent.recycle();
- mPrevEvent = MotionEvent.obtain(event);
- mTimeDelta = 0;
-
- int index1 = event.getActionIndex();
- int index0 = event.findPointerIndex(mActiveId0);
- mActiveId1 = event.getPointerId(index1);
- if (index0 < 0 || index0 == index1) {
- // Probably someone sending us a broken event stream.
- index0 = findNewActiveIndex(event, mActiveId1, -1);
- mActiveId0 = event.getPointerId(index0);
- }
- mActive0MostRecent = false;
-
- setContext(event);
-
- mGestureInProgress = mListener.onScaleBegin(this);
- break;
- }
+ final boolean streamComplete = action == MotionEvent.ACTION_UP ||
+ action == MotionEvent.ACTION_CANCEL;
+ if (action == MotionEvent.ACTION_DOWN || streamComplete) {
+ // Reset any scale in progress with the listener.
+ // If it's an ACTION_DOWN we're beginning a new event stream.
+ // This means the app probably didn't give us all the events. Shame on it.
+ if (mInProgress) {
+ mListener.onScaleEnd(this);
+ mInProgress = false;
}
- } else {
- // Transform gesture in progress - attempt to handle it
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN: {
- // End the old gesture and begin a new one with the most recent two fingers.
- mListener.onScaleEnd(this);
- final int oldActive0 = mActiveId0;
- final int oldActive1 = mActiveId1;
- reset();
-
- mPrevEvent = MotionEvent.obtain(event);
- mActiveId0 = mActive0MostRecent ? oldActive0 : oldActive1;
- mActiveId1 = event.getPointerId(event.getActionIndex());
- mActive0MostRecent = false;
-
- int index0 = event.findPointerIndex(mActiveId0);
- if (index0 < 0 || mActiveId0 == mActiveId1) {
- // Probably someone sending us a broken event stream.
- Log.e(TAG, "Got " + MotionEvent.actionToString(action) +
- " with bad state while a gesture was in progress. " +
- "Did you forget to pass an event to " +
- "ScaleGestureDetector#onTouchEvent?");
- index0 = findNewActiveIndex(event, mActiveId1, -1);
- mActiveId0 = event.getPointerId(index0);
- }
-
- setContext(event);
-
- mGestureInProgress = mListener.onScaleBegin(this);
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP: {
- final int pointerCount = event.getPointerCount();
- final int actionIndex = event.getActionIndex();
- final int actionId = event.getPointerId(actionIndex);
-
- boolean gestureEnded = false;
- if (pointerCount > 2) {
- if (actionId == mActiveId0) {
- final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex);
- if (newIndex >= 0) {
- mListener.onScaleEnd(this);
- mActiveId0 = event.getPointerId(newIndex);
- mActive0MostRecent = true;
- mPrevEvent = MotionEvent.obtain(event);
- setContext(event);
- mGestureInProgress = mListener.onScaleBegin(this);
- } else {
- gestureEnded = true;
- }
- } else if (actionId == mActiveId1) {
- final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex);
- if (newIndex >= 0) {
- mListener.onScaleEnd(this);
- mActiveId1 = event.getPointerId(newIndex);
- mActive0MostRecent = false;
- mPrevEvent = MotionEvent.obtain(event);
- setContext(event);
- mGestureInProgress = mListener.onScaleBegin(this);
- } else {
- gestureEnded = true;
- }
- }
- mPrevEvent.recycle();
- mPrevEvent = MotionEvent.obtain(event);
- setContext(event);
- } else {
- gestureEnded = true;
- }
-
- if (gestureEnded) {
- // Gesture ended
- setContext(event);
-
- // Set focus point to the remaining finger
- final int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0;
- final int index = event.findPointerIndex(activeId);
- mFocusX = event.getX(index);
- mFocusY = event.getY(index);
-
- mListener.onScaleEnd(this);
- reset();
- mActiveId0 = activeId;
- mActive0MostRecent = true;
- }
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- mListener.onScaleEnd(this);
- reset();
- break;
-
- case MotionEvent.ACTION_UP:
- reset();
- break;
-
- case MotionEvent.ACTION_MOVE: {
- setContext(event);
-
- // Only accept the event if our relative pressure is within
- // a certain limit - this can help filter shaky data as a
- // finger is lifted.
- if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
- final boolean updatePrevious = mListener.onScale(this);
-
- if (updatePrevious) {
- mPrevEvent.recycle();
- mPrevEvent = MotionEvent.obtain(event);
- }
- }
- }
- break;
+
+ if (streamComplete) {
+ return true;
}
}
- if (!handled && mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ final boolean configChanged =
+ action == MotionEvent.ACTION_POINTER_UP ||
+ action == MotionEvent.ACTION_POINTER_DOWN;
+ final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int count = event.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ sumX += event.getX(i);
+ sumY += event.getY(i);
}
- return handled;
- }
-
- private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int removedPointerIndex) {
- final int pointerCount = ev.getPointerCount();
-
- // It's ok if this isn't found and returns -1, it simply won't match.
- final int otherActiveIndex = ev.findPointerIndex(otherActiveId);
-
- // Pick a new id and update tracking state.
- for (int i = 0; i < pointerCount; i++) {
- if (i != removedPointerIndex && i != otherActiveIndex) {
- return i;
- }
+ final int div = pointerUp ? count - 1 : count;
+ final float focusX = sumX / div;
+ final float focusY = sumY / div;
+
+ // Determine average deviation from focal point
+ float devSumX = 0, devSumY = 0;
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ devSumX += Math.abs(event.getX(i) - focusX);
+ devSumY += Math.abs(event.getY(i) - focusY);
}
- return -1;
- }
-
- private void setContext(MotionEvent curr) {
- if (mCurrEvent != null) {
- mCurrEvent.recycle();
+ final float devX = devSumX / div;
+ final float devY = devSumY / div;
+
+ // Span is the average distance between touch points through the focal point;
+ // i.e. the diameter of the circle with a radius of the average deviation from
+ // the focal point.
+ final float spanX = devX * 2;
+ final float spanY = devY * 2;
+ final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY);
+
+ // Dispatch begin/end events as needed.
+ // If the configuration changes, notify the app to reset its current state by beginning
+ // a fresh scale event stream.
+ if (mInProgress && (span == 0 || configChanged)) {
+ mListener.onScaleEnd(this);
+ mInProgress = false;
+ }
+ if (configChanged) {
+ mPrevSpanX = mCurrSpanX = spanX;
+ mPrevSpanY = mCurrSpanY = spanY;
+ mPrevSpan = mCurrSpan = span;
+ }
+ if (!mInProgress && span != 0) {
+ mFocusX = focusX;
+ mFocusY = focusY;
+ mInProgress = mListener.onScaleBegin(this);
}
- mCurrEvent = MotionEvent.obtain(curr);
-
- mCurrLen = -1;
- mPrevLen = -1;
- mScaleFactor = -1;
-
- final MotionEvent prev = mPrevEvent;
- final int prevIndex0 = prev.findPointerIndex(mActiveId0);
- final int prevIndex1 = prev.findPointerIndex(mActiveId1);
- final int currIndex0 = curr.findPointerIndex(mActiveId0);
- final int currIndex1 = curr.findPointerIndex(mActiveId1);
+ // Handle motion; focal point and span/scale factor are changing.
+ if (action == MotionEvent.ACTION_MOVE) {
+ mCurrSpanX = spanX;
+ mCurrSpanY = spanY;
+ mCurrSpan = span;
+ mFocusX = focusX;
+ mFocusY = focusY;
+
+ boolean updatePrev = true;
+ if (mInProgress) {
+ updatePrev = mListener.onScale(this);
+ }
- if (prevIndex0 < 0 || prevIndex1 < 0 || currIndex0 < 0 || currIndex1 < 0) {
- mInvalidGesture = true;
- Log.e(TAG, "Invalid MotionEvent stream detected.", new Throwable());
- if (mGestureInProgress) {
- mListener.onScaleEnd(this);
+ if (updatePrev) {
+ mPrevSpanX = mCurrSpanX;
+ mPrevSpanY = mCurrSpanY;
+ mPrevSpan = mCurrSpan;
}
- return;
}
- final float px0 = prev.getX(prevIndex0);
- final float py0 = prev.getY(prevIndex0);
- final float px1 = prev.getX(prevIndex1);
- final float py1 = prev.getY(prevIndex1);
- final float cx0 = curr.getX(currIndex0);
- final float cy0 = curr.getY(currIndex0);
- final float cx1 = curr.getX(currIndex1);
- final float cy1 = curr.getY(currIndex1);
-
- final float pvx = px1 - px0;
- final float pvy = py1 - py0;
- final float cvx = cx1 - cx0;
- final float cvy = cy1 - cy0;
- mPrevFingerDiffX = pvx;
- mPrevFingerDiffY = pvy;
- mCurrFingerDiffX = cvx;
- mCurrFingerDiffY = cvy;
-
- mFocusX = cx0 + cvx * 0.5f;
- mFocusY = cy0 + cvy * 0.5f;
- mTimeDelta = curr.getEventTime() - prev.getEventTime();
- mCurrPressure = curr.getPressure(currIndex0) + curr.getPressure(currIndex1);
- mPrevPressure = prev.getPressure(prevIndex0) + prev.getPressure(prevIndex1);
- }
-
- private void reset() {
- if (mPrevEvent != null) {
- mPrevEvent.recycle();
- mPrevEvent = null;
- }
- if (mCurrEvent != null) {
- mCurrEvent.recycle();
- mCurrEvent = null;
- }
- mGestureInProgress = false;
- mActiveId0 = -1;
- mActiveId1 = -1;
- mInvalidGesture = false;
+ return true;
}
/**
- * Returns {@code true} if a two-finger scale gesture is in progress.
- * @return {@code true} if a scale gesture is in progress, {@code false} otherwise.
+ * Returns {@code true} if a scale gesture is in progress.
*/
public boolean isInProgress() {
- return mGestureInProgress;
+ return mInProgress;
}
/**
* Get the X coordinate of the current gesture's focal point.
- * If a gesture is in progress, the focal point is directly between
- * the two pointers forming the gesture.
- * If a gesture is ending, the focal point is the location of the
- * remaining pointer on the screen.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
* If {@link #isInProgress()} would return false, the result of this
* function is undefined.
*
@@ -448,10 +282,9 @@ public class ScaleGestureDetector {
/**
* Get the Y coordinate of the current gesture's focal point.
- * If a gesture is in progress, the focal point is directly between
- * the two pointers forming the gesture.
- * If a gesture is ending, the focal point is the location of the
- * remaining pointer on the screen.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
* If {@link #isInProgress()} would return false, the result of this
* function is undefined.
*
@@ -462,73 +295,63 @@ public class ScaleGestureDetector {
}
/**
- * Return the current distance between the two pointers forming the
- * gesture in progress.
+ * Return the average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Distance between pointers in pixels.
*/
public float getCurrentSpan() {
- if (mCurrLen == -1) {
- final float cvx = mCurrFingerDiffX;
- final float cvy = mCurrFingerDiffY;
- mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy);
- }
- return mCurrLen;
+ return mCurrSpan;
}
/**
- * Return the current x distance between the two pointers forming the
- * gesture in progress.
+ * Return the average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Distance between pointers in pixels.
*/
public float getCurrentSpanX() {
- return mCurrFingerDiffX;
+ return mCurrSpanX;
}
/**
- * Return the current y distance between the two pointers forming the
- * gesture in progress.
+ * Return the average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Distance between pointers in pixels.
*/
public float getCurrentSpanY() {
- return mCurrFingerDiffY;
+ return mCurrSpanY;
}
/**
- * Return the previous distance between the two pointers forming the
- * gesture in progress.
+ * Return the previous average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Previous distance between pointers in pixels.
*/
public float getPreviousSpan() {
- if (mPrevLen == -1) {
- final float pvx = mPrevFingerDiffX;
- final float pvy = mPrevFingerDiffY;
- mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy);
- }
- return mPrevLen;
+ return mPrevSpan;
}
/**
- * Return the previous x distance between the two pointers forming the
- * gesture in progress.
+ * Return the previous average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Previous distance between pointers in pixels.
*/
public float getPreviousSpanX() {
- return mPrevFingerDiffX;
+ return mPrevSpanX;
}
/**
- * Return the previous y distance between the two pointers forming the
- * gesture in progress.
+ * Return the previous average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
*
* @return Previous distance between pointers in pixels.
*/
public float getPreviousSpanY() {
- return mPrevFingerDiffY;
+ return mPrevSpanY;
}
/**
@@ -539,10 +362,7 @@ public class ScaleGestureDetector {
* @return The current scaling factor.
*/
public float getScaleFactor() {
- if (mScaleFactor == -1) {
- mScaleFactor = getCurrentSpan() / getPreviousSpan();
- }
- return mScaleFactor;
+ return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
}
/**
@@ -552,7 +372,7 @@ public class ScaleGestureDetector {
* @return Time difference since the last scaling event in milliseconds.
*/
public long getTimeDelta() {
- return mTimeDelta;
+ return mCurrTime - mPrevTime;
}
/**
@@ -561,6 +381,6 @@ public class ScaleGestureDetector {
* @return Current event time in milliseconds.
*/
public long getEventTime() {
- return mCurrEvent.getEventTime();
+ return mCurrTime;
}
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index a6d1a3f..cf1767d 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -16,8 +16,16 @@
package android.view;
+import dalvik.system.CloseGuard;
+
import android.content.res.CompatibilityInfo.Translator;
-import android.graphics.*;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.SurfaceTexture;
+import android.os.IBinder;
import android.os.Parcelable;
import android.os.Parcel;
import android.os.SystemProperties;
@@ -27,206 +35,187 @@ import android.util.Log;
* Handle onto a raw buffer that is being managed by the screen compositor.
*/
public class Surface implements Parcelable {
- private static final String LOG_TAG = "Surface";
- private static final boolean DEBUG_RELEASE = false;
-
- /* orientations for setOrientation() */
- public static final int ROTATION_0 = 0;
- public static final int ROTATION_90 = 1;
- public static final int ROTATION_180 = 2;
- public static final int ROTATION_270 = 3;
+ private static final String TAG = "Surface";
- private static final boolean headless = "1".equals(
+ private static final boolean HEADLESS = "1".equals(
SystemProperties.get("ro.config.headless", "0"));
- private static void checkHeadless() {
- if(headless) {
- throw new UnsupportedOperationException("Device is headless");
+ public static final Parcelable.Creator<Surface> CREATOR =
+ new Parcelable.Creator<Surface>() {
+ public Surface createFromParcel(Parcel source) {
+ try {
+ Surface s = new Surface();
+ s.readFromParcel(source);
+ return s;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating surface from parcel", e);
+ return null;
+ }
}
- }
- /**
- * Create Surface from a {@link SurfaceTexture}.
- *
- * Images drawn to the Surface will be made available to the {@link
- * SurfaceTexture}, which can attach them an OpenGL ES texture via {@link
- * SurfaceTexture#updateTexImage}.
- *
- * @param surfaceTexture The {@link SurfaceTexture} that is updated by this
- * Surface.
- */
- public Surface(SurfaceTexture surfaceTexture) {
- checkHeadless();
-
- if (DEBUG_RELEASE) {
- mCreationStack = new Exception();
+ public Surface[] newArray(int size) {
+ return new Surface[size];
}
- mCanvas = new CompatibleCanvas();
- initFromSurfaceTexture(surfaceTexture);
- }
+ };
/**
- * Does this object hold a valid surface? Returns true if it holds
- * a physical surface, so lockCanvas() will succeed. Otherwise
- * returns false.
+ * Rotation constant: 0 degree rotation (natural orientation)
*/
- public native boolean isValid();
+ public static final int ROTATION_0 = 0;
- /** Release the local reference to the server-side surface.
- * Always call release() when you're done with a Surface. This will
- * make the surface invalid.
+ /**
+ * Rotation constant: 90 degree rotation.
*/
- public native void release();
+ public static final int ROTATION_90 = 1;
- /** draw into a surface */
- public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException, IllegalArgumentException {
- /*
- * the dirty rectangle may be expanded to the surface's size, if for
- * instance it has been resized or if the bits were lost, since the last
- * call.
- */
- return lockCanvasNative(dirty);
- }
-
- /** unlock the surface and asks a page flip */
- public native void unlockCanvasAndPost(Canvas canvas);
-
- /**
- * unlock the surface. the screen won't be updated until
- * post() or postAll() is called
+ /**
+ * Rotation constant: 180 degree rotation.
*/
- public native void unlockCanvas(Canvas canvas);
+ public static final int ROTATION_180 = 2;
- @Override
- public String toString() {
- return "Surface(name=" + mName + ", identity=" + getIdentity() + ")";
- }
-
- public int describeContents() {
- return 0;
- }
+ /**
+ * Rotation constant: 270 degree rotation.
+ */
+ public static final int ROTATION_270 = 3;
- public native void readFromParcel(Parcel source);
- public native void writeToParcel(Parcel dest, int flags);
+ /* built-in physical display ids (keep in sync with ISurfaceComposer.h)
+ * these are different from the logical display ids used elsewhere in the framework */
/**
- * Exception thrown when a surface couldn't be created or resized
+ * Built-in physical display id: Main display.
+ * Use only with {@link #getBuiltInDisplay()}.
+ * @hide
*/
- public static class OutOfResourcesException extends Exception {
- public OutOfResourcesException() {
- }
- public OutOfResourcesException(String name) {
- super(name);
- }
- }
-
- /*
- * -----------------------------------------------------------------------
- * No user serviceable parts beyond this point
- * -----------------------------------------------------------------------
+ public static final int BUILT_IN_DISPLAY_ID_MAIN = 0;
+
+ /**
+ * Built-in physical display id: Attached HDMI display.
+ * Use only with {@link #getBuiltInDisplay()}.
+ * @hide
*/
+ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
- /* flags used in constructor (keep in sync with ISurfaceComposer.h) */
+ /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
- /** Surface is created hidden @hide */
- public static final int HIDDEN = 0x00000004;
+ /**
+ * Surface creation flag: Surface is created hidden
+ * @hide */
+ public static final int HIDDEN = 0x00000004;
- /** The surface contains secure content, special measures will
- * be taken to disallow the surface's content to be copied from
- * another process. In particular, screenshots and VNC servers will
+ /**
+ * Surface creation flag: The surface contains secure content, special
+ * measures will be taken to disallow the surface's content to be copied
+ * from another process. In particular, screenshots and VNC servers will
* be disabled, but other measures can take place, for instance the
* surface might not be hardware accelerated.
- * @hide*/
- public static final int SECURE = 0x00000080;
-
- /** Creates a surface where color components are interpreted as
- * "non pre-multiplied" by their alpha channel. Of course this flag is
- * meaningless for surfaces without an alpha channel. By default
- * surfaces are pre-multiplied, which means that each color component is
- * already multiplied by its alpha value. In this case the blending
- * equation used is:
- *
+ * @hide
+ */
+ public static final int SECURE = 0x00000080;
+
+ /**
+ * Surface creation flag: Creates a surface where color components are interpreted
+ * as "non pre-multiplied" by their alpha channel. Of course this flag is
+ * meaningless for surfaces without an alpha channel. By default
+ * surfaces are pre-multiplied, which means that each color component is
+ * already multiplied by its alpha value. In this case the blending
+ * equation used is:
+ *
* DEST = SRC + DEST * (1-SRC_ALPHA)
- *
- * By contrast, non pre-multiplied surfaces use the following equation:
- *
+ *
+ * By contrast, non pre-multiplied surfaces use the following equation:
+ *
* DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA)
- *
- * pre-multiplied surfaces must always be used if transparent pixels are
- * composited on top of each-other into the surface. A pre-multiplied
- * surface can never lower the value of the alpha component of a given
- * pixel.
- *
- * In some rare situations, a non pre-multiplied surface is preferable.
- *
- * @hide
- */
- public static final int NON_PREMULTIPLIED = 0x00000100;
-
+ *
+ * pre-multiplied surfaces must always be used if transparent pixels are
+ * composited on top of each-other into the surface. A pre-multiplied
+ * surface can never lower the value of the alpha component of a given
+ * pixel.
+ *
+ * In some rare situations, a non pre-multiplied surface is preferable.
+ * @hide
+ */
+ public static final int NON_PREMULTIPLIED = 0x00000100;
+
/**
- * Indicates that the surface must be considered opaque, even if its
- * pixel format is set to translucent. This can be useful if an
+ * Surface creation flag: Indicates that the surface must be considered opaque,
+ * even if its pixel format is set to translucent. This can be useful if an
* application needs full RGBA 8888 support for instance but will
* still draw every pixel opaque.
- *
* @hide
*/
- public static final int OPAQUE = 0x00000400;
-
+ public static final int OPAQUE = 0x00000400;
+
/**
- * Application requires a hardware-protected path to an
+ * Surface creation flag: Application requires a hardware-protected path to an
* external display sink. If a hardware-protected path is not available,
* then this surface will not be displayed on the external sink.
- *
* @hide
*/
- public static final int PROTECTED_APP = 0x00000800;
+ public static final int PROTECTED_APP = 0x00000800;
// 0x1000 is reserved for an independent DRM protected flag in framework
- /** Creates a normal surface. This is the default. @hide */
+ /**
+ * Surface creation flag: Creates a normal surface.
+ * This is the default.
+ * @hide
+ */
public static final int FX_SURFACE_NORMAL = 0x00000000;
-
- /** Creates a Blur surface. Everything behind this surface is blurred
- * by some amount. The quality and refresh speed of the blur effect
- * is not settable or guaranteed.
- * It is an error to lock a Blur surface, since it doesn't have
- * a backing store.
+
+ /**
+ * Surface creation flag: Creates a Blur surface.
+ * Everything behind this surface is blurred by some amount.
+ * The quality and refresh speed of the blur effect is not settable or guaranteed.
+ * It is an error to lock a Blur surface, since it doesn't have a backing store.
* @hide
* @deprecated
*/
@Deprecated
- public static final int FX_SURFACE_BLUR = 0x00010000;
-
- /** Creates a Dim surface. Everything behind this surface is dimmed
- * by the amount specified in {@link #setAlpha}.
- * It is an error to lock a Dim surface, since it doesn't have
- * a backing store.
+ public static final int FX_SURFACE_BLUR = 0x00010000;
+
+ /**
+ * Surface creation flag: Creates a Dim surface.
+ * Everything behind this surface is dimmed by the amount specified
+ * in {@link #setAlpha}. It is an error to lock a Dim surface, since it
+ * doesn't have a backing store.
* @hide
*/
- public static final int FX_SURFACE_DIM = 0x00020000;
+ public static final int FX_SURFACE_DIM = 0x00020000;
- /** @hide */
- public static final int FX_SURFACE_SCREENSHOT = 0x00030000;
+ /**
+ * @hide
+ */
+ public static final int FX_SURFACE_SCREENSHOT = 0x00030000;
- /** Mask used for FX values above @hide */
- public static final int FX_SURFACE_MASK = 0x000F0000;
+ /**
+ * Mask used for FX values above.
+ * @hide
+ */
+ public static final int FX_SURFACE_MASK = 0x000F0000;
/* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */
- /** Hide the surface. Equivalent to calling hide(). @hide */
- public static final int SURFACE_HIDDEN = 0x01;
+ /**
+ * Surface flag: Hide the surface.
+ * Equivalent to calling hide().
+ * @hide
+ */
+ public static final int SURFACE_HIDDEN = 0x01;
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private String mName;
+
+ // Note: These fields are accessed by native code.
// The mSurfaceControl will only be present for Surfaces used by the window
// server or system processes. When this class is parceled we defer to the
// mSurfaceControl to do the parceling. Otherwise we parcel the
// mNativeSurface.
- private int mSurfaceControl;
- private int mSaveCount;
- private Canvas mCanvas;
- private int mNativeSurface;
- private int mSurfaceGenerationId;
- private String mName;
+ private int mNativeSurface; // Surface*
+ private int mNativeSurfaceControl; // SurfaceControl*
+ private int mGenerationId; // incremented each time mNativeSurface changes
+ private final Canvas mCanvas = new CompatibleCanvas();
+ private int mCanvasSaveCount; // Canvas save count at time of lockCanvas()
// The Translator for density compatibility mode. This is used for scaling
// the canvas to perform the appropriate density transformation.
@@ -236,141 +225,245 @@ public class Surface implements Parcelable {
// non compatibility mode.
private Matrix mCompatibleMatrix;
- private Exception mCreationStack;
+ private native void nativeCreate(SurfaceSession session, String name,
+ int w, int h, int format, int flags)
+ throws OutOfResourcesException;
+ private native void nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
+ throws OutOfResourcesException;
+ private native void nativeRelease();
+ private native void nativeDestroy();
+
+ private native boolean nativeIsValid();
+ private native int nativeGetIdentity();
+ private native boolean nativeIsConsumerRunningBehind();
+
+ private native Canvas nativeLockCanvas(Rect dirty);
+ private native void nativeUnlockCanvasAndPost(Canvas canvas);
+
+ private static native Bitmap nativeScreenshot(IBinder displayToken,
+ int width, int height, int minLayer, int maxLayer, boolean allLayers);
+
+ private static native void nativeOpenTransaction();
+ private static native void nativeCloseTransaction();
+
+ private native void nativeSetLayer(int zorder);
+ private native void nativeSetPosition(float x, float y);
+ private native void nativeSetSize(int w, int h);
+ private native void nativeSetTransparentRegionHint(Region region);
+ private native void nativeSetAlpha(float alpha);
+ private native void nativeSetMatrix(float dsdx, float dtdx, float dsdy, float dtdy);
+ private native void nativeSetFlags(int flags, int mask);
+ private native void nativeSetWindowCrop(Rect crop);
+ private native void nativeSetLayerStack(int layerStack);
+
+ private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
+ private static native IBinder nativeCreateDisplay(String name);
+ private static native void nativeSetDisplaySurface(
+ IBinder displayToken, SurfaceTexture surfaceTexture);
+ private static native void nativeSetDisplayLayerStack(
+ IBinder displayToken, int layerStack);
+ private static native void nativeSetDisplayOrientation(
+ IBinder displayToken, int orientation);
+ private static native void nativeSetDisplayViewport(
+ IBinder displayToken, Rect viewport);
+ private static native void nativeSetDisplayFrame(
+ IBinder displayToken, Rect frame);
+ private static native boolean nativeGetDisplayInfo(
+ IBinder displayToken, PhysicalDisplayInfo outInfo);
+
+ private native void nativeCopyFrom(Surface other);
+ private native void nativeTransferFrom(Surface other);
+ private native void nativeReadFromParcel(Parcel source);
+ private native void nativeWriteToParcel(Parcel dest);
- /*
- * We use a class initializer to allow the native code to cache some
- * field offsets.
+ /**
+ * Create an empty surface, which will later be filled in by readFromParcel().
+ * @hide
*/
- native private static void nativeClassInit();
- static { nativeClassInit(); }
-
- /** create a surface with a name @hide */
- public Surface(SurfaceSession s,
- int pid, String name, int layerStack, int w, int h, int format, int flags)
- throws OutOfResourcesException {
+ public Surface() {
checkHeadless();
- if (DEBUG_RELEASE) {
- mCreationStack = new Exception();
+ mCloseGuard.open("release");
+ }
+
+ /**
+ * Create a surface with a name.
+ *
+ * The surface creation flags specify what kind of surface to create and
+ * certain options such as whether the surface can be assumed to be opaque
+ * and whether it should be initially hidden. Surfaces should always be
+ * created with the {@link #HIDDEN} flag set to ensure that they are not
+ * made visible prematurely before all of the surface's properties have been
+ * configured.
+ *
+ * Good practice is to first create the surface with the {@link #HIDDEN} flag
+ * specified, open a transaction, set the surface layer, layer stack, alpha,
+ * and position, call {@link #show} if appropriate, and close the transaction.
+ *
+ * @param session The surface session, must not be null.
+ * @param name The surface name, must not be null.
+ * @param w The surface initial width.
+ * @param h The surface initial height.
+ * @param flags The surface creation flags. Should always include {@link #HIDDEN}
+ * in the creation flags.
+ * @hide
+ */
+ public Surface(SurfaceSession session,
+ String name, int w, int h, int format, int flags)
+ throws OutOfResourcesException {
+ if (session == null) {
+ throw new IllegalArgumentException("session must not be null");
+ }
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+
+ if ((flags & HIDDEN) == 0) {
+ Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set "
+ + "to ensure that they are not made visible prematurely before "
+ + "all of the surface's properties have been configured. "
+ + "Set the other properties and make the surface visible within "
+ + "a transaction. New surface name: " + name,
+ new Throwable());
}
- mCanvas = new CompatibleCanvas();
- init(s, pid, name, layerStack, w, h, format, flags);
+
+ checkHeadless();
+
mName = name;
+ nativeCreate(session, name, w, h, format, flags);
+
+ mCloseGuard.open("release");
}
/**
- * Create an empty surface, which will later be filled in by
- * readFromParcel().
- * @hide
+ * Create Surface from a {@link SurfaceTexture}.
+ *
+ * Images drawn to the Surface will be made available to the {@link
+ * SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link
+ * SurfaceTexture#updateTexImage}.
+ *
+ * @param surfaceTexture The {@link SurfaceTexture} that is updated by this
+ * Surface.
*/
- public Surface() {
+ public Surface(SurfaceTexture surfaceTexture) {
+ if (surfaceTexture == null) {
+ throw new IllegalArgumentException("surfaceTexture must not be null");
+ }
+
checkHeadless();
- if (DEBUG_RELEASE) {
- mCreationStack = new Exception();
+ mName = surfaceTexture.toString();
+ try {
+ nativeCreateFromSurfaceTexture(surfaceTexture);
+ } catch (OutOfResourcesException ex) {
+ // We can't throw OutOfResourcesException because it would be an API change.
+ throw new RuntimeException(ex);
}
- mCanvas = new CompatibleCanvas();
+
+ mCloseGuard.open("release");
}
- private Surface(Parcel source) throws OutOfResourcesException {
- init(source);
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ nativeRelease();
+ } finally {
+ super.finalize();
+ }
}
/**
- * Copy another surface to this one. This surface now holds a reference
- * to the same data as the original surface, and is -not- the owner.
- * This is for use by the window manager when returning a window surface
- * back from a client, converting it from the representation being managed
- * by the window manager to the representation the client uses to draw
- * in to it.
+ * Release the local reference to the server-side surface.
+ * Always call release() when you're done with a Surface.
+ * This will make the surface invalid.
+ */
+ public void release() {
+ nativeRelease();
+ mCloseGuard.close();
+ }
+
+ /**
+ * Free all server-side state associated with this surface and
+ * release this object's reference. This method can only be
+ * called from the process that created the service.
* @hide
*/
- public native void copyFrom(Surface o);
+ public void destroy() {
+ nativeDestroy();
+ mCloseGuard.close();
+ }
/**
- * Transfer the native state from 'o' to this surface, releasing it
- * from 'o'. This is for use in the client side for drawing into a
- * surface; not guaranteed to work on the window manager side.
- * This is for use by the client to move the underlying surface from
- * one Surface object to another, in particular in SurfaceFlinger.
- * @hide.
+ * Returns true if this object holds a valid surface.
+ *
+ * @return True if it holds a physical surface, so lockCanvas() will succeed.
+ * Otherwise returns false.
*/
- public native void transferFrom(Surface o);
+ public boolean isValid() {
+ return nativeIsValid();
+ }
- /** @hide */
+ /**
+ * Gets the generation number of this surface, incremented each time
+ * the native surface contained within this object changes.
+ *
+ * @return The current generation number.
+ * @hide
+ */
public int getGenerationId() {
- return mSurfaceGenerationId;
+ return mGenerationId;
}
-
/**
- * Whether the consumer of this Surface is running behind the producer;
- * that is, isConsumerRunningBehind() returns true if the consumer is more
- * than one buffer ahead of the producer.
+ * Returns true if the consumer of this Surface is running behind the producer.
+ *
+ * @return True if the consumer is more than one buffer ahead of the producer.
* @hide
*/
- public native boolean isConsumerRunningBehind();
+ public boolean isConsumerRunningBehind() {
+ return nativeIsConsumerRunningBehind();
+ }
/**
- * A Canvas class that can handle the compatibility mode. This does two
- * things differently.
- * <ul>
- * <li>Returns the width and height of the target metrics, rather than
- * native. For example, the canvas returns 320x480 even if an app is running
- * in WVGA high density.
- * <li>Scales the matrix in setMatrix by the application scale, except if
- * the matrix looks like obtained from getMatrix. This is a hack to handle
- * the case that an application uses getMatrix to keep the original matrix,
- * set matrix of its own, then set the original matrix back. There is no
- * perfect solution that works for all cases, and there are a lot of cases
- * that this model does not work, but we hope this works for many apps.
- * </ul>
+ * Gets a {@link Canvas} for drawing into this surface.
+ *
+ * After drawing into the provided {@link Canvas}, the caller should
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * @param dirty A rectangle that represents the dirty region that the caller wants
+ * to redraw. This function may choose to expand the dirty rectangle if for example
+ * the surface has been resized or if the previous contents of the surface were
+ * not available. The caller should redraw the entire dirty region as represented
+ * by the contents of the dirty rect upon return from this function.
+ * The caller may also pass <code>null</code> instead, in the case where the
+ * entire surface should be redrawn.
+ * @return A canvas for drawing into the surface.
*/
- private class CompatibleCanvas extends Canvas {
- // A temp matrix to remember what an application obtained via {@link getMatrix}
- private Matrix mOrigMatrix = null;
-
- @Override
- public int getWidth() {
- int w = super.getWidth();
- if (mCompatibilityTranslator != null) {
- w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f);
- }
- return w;
- }
-
- @Override
- public int getHeight() {
- int h = super.getHeight();
- if (mCompatibilityTranslator != null) {
- h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f);
- }
- return h;
- }
+ public Canvas lockCanvas(Rect dirty)
+ throws OutOfResourcesException, IllegalArgumentException {
+ return nativeLockCanvas(dirty);
+ }
- @Override
- public void setMatrix(Matrix matrix) {
- if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
- // don't scale the matrix if it's not compatibility mode, or
- // the matrix was obtained from getMatrix.
- super.setMatrix(matrix);
- } else {
- Matrix m = new Matrix(mCompatibleMatrix);
- m.preConcat(matrix);
- super.setMatrix(m);
- }
- }
+ /**
+ * Posts the new contents of the {@link Canvas} to the surface and
+ * releases the {@link Canvas}.
+ *
+ * @param canvas The canvas previously obtained from {@link #lockCanvas}.
+ */
+ public void unlockCanvasAndPost(Canvas canvas) {
+ nativeUnlockCanvasAndPost(canvas);
+ }
- @Override
- public void getMatrix(Matrix m) {
- super.getMatrix(m);
- if (mOrigMatrix == null) {
- mOrigMatrix = new Matrix();
- }
- mOrigMatrix.set(m);
- }
+ /**
+ * @deprecated This API has been removed and is not supported. Do not use.
+ */
+ @Deprecated
+ public void unlockCanvas(Canvas canvas) {
+ throw new UnsupportedOperationException();
}
/**
@@ -384,20 +477,6 @@ public class Surface implements Parcelable {
mCompatibleMatrix.setScale(appScale, appScale);
}
}
-
- /** Free all server-side state associated with this surface and
- * release this object's reference. @hide */
- public native void destroy();
-
- private native Canvas lockCanvasNative(Rect dirty) throws OutOfResourcesException;
-
- /**
- * set the orientation of the given display.
- * @param display
- * @param orientation
- * @hide
- */
- public static native void setOrientation(int display, int orientation);
/**
* Like {@link #screenshot(int, int, int, int)} but includes all
@@ -405,8 +484,12 @@ public class Surface implements Parcelable {
*
* @hide
*/
- public static native Bitmap screenshot(int width, int height);
-
+ public static Bitmap screenshot(int width, int height) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, width, height, 0, 0, true);
+ }
+
/**
* Copy the current screen contents into a bitmap and return it.
*
@@ -418,91 +501,318 @@ public class Surface implements Parcelable {
* include in the screenshot.
* @param maxLayer The highest (top-most Z order) surface layer to
* include in the screenshot.
- * @return Returns a Bitmap containing the screen contents.
+ * @return Returns a Bitmap containing the screen contents, or null
+ * if an error occurs.
*
* @hide
*/
- public static native Bitmap screenshot(int width, int height, int minLayer, int maxLayer);
+ public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false);
+ }
-
/*
* set surface parameters.
* needs to be inside open/closeTransaction block
*/
-
+
/** start a transaction @hide */
- public static native void openTransaction();
+ public static void openTransaction() {
+ nativeOpenTransaction();
+ }
+
/** end a transaction @hide */
- public static native void closeTransaction();
+ public static void closeTransaction() {
+ nativeCloseTransaction();
+ }
+
/** @hide */
- public native void setLayer(int zorder);
+ public void setLayer(int zorder) {
+ nativeSetLayer(zorder);
+ }
+
/** @hide */
- public void setPosition(int x, int y) { setPosition((float)x, (float)y); }
+ public void setPosition(int x, int y) {
+ nativeSetPosition((float)x, (float)y);
+ }
+
/** @hide */
- public native void setPosition(float x, float y);
+ public void setPosition(float x, float y) {
+ nativeSetPosition(x, y);
+ }
+
/** @hide */
- public native void setSize(int w, int h);
+ public void setSize(int w, int h) {
+ nativeSetSize(w, h);
+ }
+
/** @hide */
- public native void hide();
+ public void hide() {
+ nativeSetFlags(SURFACE_HIDDEN, SURFACE_HIDDEN);
+ }
+
/** @hide */
- public native void show();
+ public void show() {
+ nativeSetFlags(0, SURFACE_HIDDEN);
+ }
+
/** @hide */
- public native void setTransparentRegionHint(Region region);
+ public void setTransparentRegionHint(Region region) {
+ nativeSetTransparentRegionHint(region);
+ }
+
/** @hide */
- public native void setAlpha(float alpha);
+ public void setAlpha(float alpha) {
+ nativeSetAlpha(alpha);
+ }
+
/** @hide */
- public native void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy);
+ public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+ nativeSetMatrix(dsdx, dtdx, dsdy, dtdy);
+ }
+
/** @hide */
- public native void setFlags(int flags, int mask);
+ public void setFlags(int flags, int mask) {
+ nativeSetFlags(flags, mask);
+ }
+
/** @hide */
- public native void setWindowCrop(Rect crop);
+ public void setWindowCrop(Rect crop) {
+ nativeSetWindowCrop(crop);
+ }
+
/** @hide */
- public native void setLayerStack(int layerStack);
+ public void setLayerStack(int layerStack) {
+ nativeSetLayerStack(layerStack);
+ }
+ /** @hide */
+ public static IBinder getBuiltInDisplay(int builtInDisplayId) {
+ return nativeGetBuiltInDisplay(builtInDisplayId);
+ }
-
- public static final Parcelable.Creator<Surface> CREATOR
- = new Parcelable.Creator<Surface>()
- {
- public Surface createFromParcel(Parcel source) {
- try {
- return new Surface(source);
- } catch (Exception e) {
- Log.e(LOG_TAG, "Exception creating surface from parcel", e);
- }
- return null;
+ /** @hide */
+ public static IBinder createDisplay(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
}
+ return nativeCreateDisplay(name);
+ }
- public Surface[] newArray(int size) {
- return new Surface[size];
+ /** @hide */
+ public static void setDisplaySurface(IBinder displayToken, SurfaceTexture surfaceTexture) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
}
- };
+ nativeSetDisplaySurface(displayToken, surfaceTexture);
+ }
+
+ /** @hide */
+ public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayLayerStack(displayToken, layerStack);
+ }
+
+ /** @hide */
+ public static void setDisplayOrientation(IBinder displayToken, int orientation) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayOrientation(displayToken, orientation);
+ }
+
+ /** @hide */
+ public static void setDisplayViewport(IBinder displayToken, Rect viewport) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (viewport == null) {
+ throw new IllegalArgumentException("viewport must not be null");
+ }
+ nativeSetDisplayViewport(displayToken, viewport);
+ }
+
+ /** @hide */
+ public static void setDisplayFrame(IBinder displayToken, Rect frame) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (frame == null) {
+ throw new IllegalArgumentException("frame must not be null");
+ }
+ nativeSetDisplayFrame(displayToken, frame);
+ }
+
+ /** @hide */
+ public static boolean getDisplayInfo(IBinder displayToken, PhysicalDisplayInfo outInfo) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (outInfo == null) {
+ throw new IllegalArgumentException("outInfo must not be null");
+ }
+ return nativeGetDisplayInfo(displayToken, outInfo);
+ }
+
+ /**
+ * Copy another surface to this one. This surface now holds a reference
+ * to the same data as the original surface, and is -not- the owner.
+ * This is for use by the window manager when returning a window surface
+ * back from a client, converting it from the representation being managed
+ * by the window manager to the representation the client uses to draw
+ * in to it.
+ * @hide
+ */
+ public void copyFrom(Surface other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null");
+ }
+ if (other != this) {
+ nativeCopyFrom(other);
+ }
+ }
+
+ /**
+ * Transfer the native state from 'other' to this surface, releasing it
+ * from 'other'. This is for use in the client side for drawing into a
+ * surface; not guaranteed to work on the window manager side.
+ * This is for use by the client to move the underlying surface from
+ * one Surface object to another, in particular in SurfaceFlinger.
+ * @hide.
+ */
+ public void transferFrom(Surface other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null");
+ }
+ if (other != this) {
+ nativeTransferFrom(other);
+ }
+ }
@Override
- protected void finalize() throws Throwable {
- try {
- super.finalize();
- } finally {
- if (mNativeSurface != 0 || mSurfaceControl != 0) {
- if (DEBUG_RELEASE) {
- Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() ("
- + mNativeSurface + ", " + mSurfaceControl + ")", mCreationStack);
- } else {
- Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() ("
- + mNativeSurface + ", " + mSurfaceControl + ")");
- }
- }
- release();
+ public int describeContents() {
+ return 0;
+ }
+
+ public void readFromParcel(Parcel source) {
+ if (source == null) {
+ throw new IllegalArgumentException("source must not be null");
}
+
+ mName = source.readString();
+ nativeReadFromParcel(source);
}
-
- private native void init(SurfaceSession s,
- int pid, String name, int layerStack, int w, int h, int format, int flags)
- throws OutOfResourcesException;
- private native void init(Parcel source) throws OutOfResourcesException;
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (dest == null) {
+ throw new IllegalArgumentException("dest must not be null");
+ }
- private native void initFromSurfaceTexture(SurfaceTexture surfaceTexture);
+ dest.writeString(mName);
+ nativeWriteToParcel(dest);
+ if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+ release();
+ }
+ }
- private native int getIdentity();
+ @Override
+ public String toString() {
+ return "Surface(name=" + mName + ", identity=" + nativeGetIdentity() + ")";
+ }
+
+ private static void checkHeadless() {
+ if (HEADLESS) {
+ throw new UnsupportedOperationException("Device is headless");
+ }
+ }
+
+ /**
+ * Exception thrown when a surface couldn't be created or resized.
+ */
+ public static class OutOfResourcesException extends Exception {
+ public OutOfResourcesException() {
+ }
+
+ public OutOfResourcesException(String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Describes the properties of a physical display.
+ * @hide
+ */
+ public static final class PhysicalDisplayInfo {
+ // TODO: redesign this
+ public int width;
+ public int height;
+ public float refreshRate;
+ public float density;
+ public float xDpi;
+ public float yDpi;
+ }
+
+ /**
+ * A Canvas class that can handle the compatibility mode.
+ * This does two things differently.
+ * <ul>
+ * <li>Returns the width and height of the target metrics, rather than
+ * native. For example, the canvas returns 320x480 even if an app is running
+ * in WVGA high density.
+ * <li>Scales the matrix in setMatrix by the application scale, except if
+ * the matrix looks like obtained from getMatrix. This is a hack to handle
+ * the case that an application uses getMatrix to keep the original matrix,
+ * set matrix of its own, then set the original matrix back. There is no
+ * perfect solution that works for all cases, and there are a lot of cases
+ * that this model does not work, but we hope this works for many apps.
+ * </ul>
+ */
+ private final class CompatibleCanvas extends Canvas {
+ // A temp matrix to remember what an application obtained via {@link getMatrix}
+ private Matrix mOrigMatrix = null;
+
+ @Override
+ public int getWidth() {
+ int w = super.getWidth();
+ if (mCompatibilityTranslator != null) {
+ w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f);
+ }
+ return w;
+ }
+
+ @Override
+ public int getHeight() {
+ int h = super.getHeight();
+ if (mCompatibilityTranslator != null) {
+ h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f);
+ }
+ return h;
+ }
+
+ @Override
+ public void setMatrix(Matrix matrix) {
+ if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
+ // don't scale the matrix if it's not compatibility mode, or
+ // the matrix was obtained from getMatrix.
+ super.setMatrix(matrix);
+ } else {
+ Matrix m = new Matrix(mCompatibleMatrix);
+ m.preConcat(matrix);
+ super.setMatrix(m);
+ }
+ }
+
+ @Override
+ public void getMatrix(Matrix m) {
+ super.getMatrix(m);
+ if (mOrigMatrix == null) {
+ mOrigMatrix = new Matrix();
+ }
+ mOrigMatrix.set(m);
+ }
+ }
}
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index 2a04675..0dfd94a 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -16,34 +16,44 @@
package android.view;
-
/**
* An instance of this class represents a connection to the surface
- * flinger, in which you can create one or more Surface instances that will
+ * flinger, from which you can create one or more Surface instances that will
* be composited to the screen.
* {@hide}
*/
-public class SurfaceSession {
+public final class SurfaceSession {
+ // Note: This field is accessed by native code.
+ private int mNativeClient; // SurfaceComposerClient*
+
+ private static native int nativeCreate();
+ private static native void nativeDestroy(int ptr);
+ private static native void nativeKill(int ptr);
+
/** Create a new connection with the surface flinger. */
public SurfaceSession() {
- init();
+ mNativeClient = nativeCreate();
}
- /** Forcibly detach native resources associated with this object.
- * Unlike destroy(), after this call any surfaces that were created
- * from the session will no longer work. The session itself is destroyed.
- */
- public native void kill();
-
/* no user serviceable parts here ... */
@Override
protected void finalize() throws Throwable {
- destroy();
+ try {
+ if (mNativeClient != 0) {
+ nativeDestroy(mNativeClient);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Forcibly detach native resources associated with this object.
+ * Unlike destroy(), after this call any surfaces that were created
+ * from the session will no longer work.
+ */
+ public void kill() {
+ nativeKill(mNativeClient);
}
-
- private native void init();
- private native void destroy();
-
- private int mClient;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b1f5e9e..745e1b8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,6 +40,7 @@ import android.graphics.Shader;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -4925,6 +4926,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Returns the delegate for implementing accessibility support via
+ * composition. For more details see {@link AccessibilityDelegate}.
+ *
+ * @return The delegate, or null if none set.
+ *
+ * @hide
+ */
+ public AccessibilityDelegate getAccessibilityDelegate() {
+ return mAccessibilityDelegate;
+ }
+
+ /**
* Sets a delegate for implementing accessibility support via compositon as
* opposed to inheritance. The delegate's primary use is for implementing
* backwards compatible widgets. For more details see {@link AccessibilityDelegate}.
@@ -7347,7 +7360,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
outRect.bottom -= insets.bottom;
return;
}
- Display d = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+ // The view is not attached to a display so we don't have a context.
+ // Make a best guess about the display size.
+ Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
d.getRectSize(outRect);
}
@@ -17610,23 +17625,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// use use a height of 1, and then wack the matrix each time we
// actually use it.
shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
-
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+
this.host = host;
}
public void setFadeColor(int color) {
- if (color != 0 && color != mLastColor) {
+ if (color != mLastColor) {
mLastColor = color;
- color |= 0xFF000000;
- shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000,
- color & 0x00FFFFFF, Shader.TileMode.CLAMP);
-
- paint.setShader(shader);
- // Restore the default transfer mode (src_over)
- paint.setXfermode(null);
+ if (color != 0) {
+ shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000,
+ color & 0x00FFFFFF, Shader.TileMode.CLAMP);
+ paint.setShader(shader);
+ // Restore the default transfer mode (src_over)
+ paint.setXfermode(null);
+ } else {
+ shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+ paint.setShader(shader);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ }
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 725d9b5..ffd495e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -214,6 +214,9 @@ public final class ViewRootImpl implements ViewParent,
boolean mTraversalScheduled;
int mTraversalBarrier;
boolean mWillDrawSoon;
+ /** Set to true while in performTraversals for detecting when die(true) is called from internal
+ * callbacks such as onMeasure, onPreDraw, onDraw and deferring doDie() until later. */
+ boolean mIsInTraversal;
boolean mFitSystemWindowsRequested;
boolean mLayoutRequested;
boolean mFirst;
@@ -1104,6 +1107,7 @@ public final class ViewRootImpl implements ViewParent,
if (host == null || !mAdded)
return;
+ mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
boolean newSurface = false;
@@ -1842,6 +1846,8 @@ public final class ViewRootImpl implements ViewParent,
mPendingTransitions.clear();
}
}
+
+ mIsInTraversal = false;
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
@@ -3956,7 +3962,9 @@ public final class ViewRootImpl implements ViewParent,
}
public void die(boolean immediate) {
- if (immediate) {
+ // Make sure we do execute immediately if we are in the middle of a traversal or the damage
+ // done by dispatchDetachedFromWindow will cause havoc on return.
+ if (immediate && !mIsInTraversal) {
doDie();
} else {
if (!mIsDrawing) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 123d9e7..f30952c 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -179,7 +179,8 @@ public interface WindowManager extends ViewManager {
@ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS, to = "TYPE_BOOT_PROGRESS"),
@ViewDebug.IntToString(from = TYPE_HIDDEN_NAV_CONSUMER, to = "TYPE_HIDDEN_NAV_CONSUMER"),
@ViewDebug.IntToString(from = TYPE_DREAM, to = "TYPE_DREAM"),
- @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL")
+ @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY")
})
public int type;
@@ -435,6 +436,12 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;
/**
+ * Window type: Display overlay window. Used to simulate secondary display devices.
+ * @hide
+ */
+ public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index bf061df..aa9179f 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -52,8 +52,9 @@ public final class WindowManagerImpl implements WindowManager {
private final Window mParentWindow;
public WindowManagerImpl(Context context, int displayId) {
+ DisplayManager dm = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
mContext = context;
- mDisplay = DisplayManager.getInstance().getDisplay(displayId, mContext);
+ mDisplay = dm.getDisplay(displayId);
mParentWindow = null;
}
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
index 7dfb5bb..fd73fda 100644
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -279,6 +279,7 @@ class AccessibilityInjector {
}
if (!shouldInjectJavaScript(url)) {
+ mAccessibilityScriptInjected = false;
toggleFallbackAccessibilityInjector(true);
return;
}
@@ -292,6 +293,23 @@ class AccessibilityInjector {
}
/**
+ * Adjusts the accessibility injection state to reflect changes in the
+ * JavaScript enabled state.
+ *
+ * @param enabled Whether JavaScript is enabled.
+ */
+ public void updateJavaScriptEnabled(boolean enabled) {
+ if (enabled) {
+ addAccessibilityApisIfNecessary();
+ } else {
+ removeAccessibilityApisIfNecessary();
+ }
+
+ // We have to reload the page after adding or removing APIs.
+ mWebView.reload();
+ }
+
+ /**
* Toggles the non-JavaScript method for handling accessibility.
*
* @param enabled {@code true} to enable the non-JavaScript method, or
diff --git a/core/java/android/webkit/BrowserDownloadListener.java b/core/java/android/webkit/BrowserDownloadListener.java
new file mode 100644
index 0000000..724cc62
--- /dev/null
+++ b/core/java/android/webkit/BrowserDownloadListener.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+/**
+ * An abstract download listener that allows passing extra information as
+ * part of onDownloadStart callback.
+ * @hide
+ */
+public abstract class BrowserDownloadListener implements DownloadListener {
+
+ /**
+ * Notify the host application that a file should be downloaded
+ * @param url The full url to the content that should be downloaded
+ * @param userAgent the user agent to be used for the download.
+ * @param contentDisposition Content-disposition http header, if
+ * present.
+ * @param mimetype The mimetype of the content reported by the server
+ * @param referer The referer associated with this url
+ * @param contentLength The file size reported by the server
+ */
+ public abstract void onDownloadStart(String url, String userAgent,
+ String contentDisposition, String mimetype, String referer,
+ long contentLength);
+
+
+ /**
+ * Notify the host application that a file should be downloaded
+ * @param url The full url to the content that should be downloaded
+ * @param userAgent the user agent to be used for the download.
+ * @param contentDisposition Content-disposition http header, if
+ * present.
+ * @param mimetype The mimetype of the content reported by the server
+ * @param contentLength The file size reported by the server
+ */
+ @Override
+ public void onDownloadStart(String url, String userAgent,
+ String contentDisposition, String mimetype, long contentLength) {
+
+ onDownloadStart(url, userAgent, contentDisposition, mimetype, null,
+ contentLength);
+ }
+}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index fe812af..1b23b18 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -1137,7 +1137,7 @@ class BrowserFrame extends Handler {
* DownloadListener.
*/
private void downloadStart(String url, String userAgent,
- String contentDisposition, String mimeType, long contentLength) {
+ String contentDisposition, String mimeType, String referer, long contentLength) {
// This will only work if the url ends with the filename
if (mimeType.isEmpty()) {
try {
@@ -1157,7 +1157,7 @@ class BrowserFrame extends Handler {
mKeyStoreHandler = new KeyStoreHandler(mimeType);
} else {
mCallbackProxy.onDownloadStart(url, userAgent,
- contentDisposition, mimeType, contentLength);
+ contentDisposition, mimeType, referer, contentLength);
}
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 6b87ded..b47cba8 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -452,10 +452,16 @@ class CallbackProxy extends Handler {
String contentDisposition =
msg.getData().getString("contentDisposition");
String mimetype = msg.getData().getString("mimetype");
+ String referer = msg.getData().getString("referer");
Long contentLength = msg.getData().getLong("contentLength");
- mDownloadListener.onDownloadStart(url, userAgent,
- contentDisposition, mimetype, contentLength);
+ if (mDownloadListener instanceof BrowserDownloadListener) {
+ ((BrowserDownloadListener) mDownloadListener).onDownloadStart(url,
+ userAgent, contentDisposition, mimetype, referer, contentLength);
+ } else {
+ mDownloadListener.onDownloadStart(url, userAgent,
+ contentDisposition, mimetype, contentLength);
+ }
}
break;
@@ -1179,7 +1185,8 @@ class CallbackProxy extends Handler {
* return false.
*/
public boolean onDownloadStart(String url, String userAgent,
- String contentDisposition, String mimetype, long contentLength) {
+ String contentDisposition, String mimetype, String referer,
+ long contentLength) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mDownloadListener == null) {
@@ -1192,6 +1199,7 @@ class CallbackProxy extends Handler {
bundle.putString("url", url);
bundle.putString("userAgent", userAgent);
bundle.putString("mimetype", mimetype);
+ bundle.putString("referer", referer);
bundle.putLong("contentLength", contentLength);
bundle.putString("contentDisposition", contentDisposition);
sendMessage(msg);
diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java
index d1f8b4b..1bbe7bb 100644
--- a/core/java/android/webkit/WebSettingsClassic.java
+++ b/core/java/android/webkit/WebSettingsClassic.java
@@ -1122,6 +1122,7 @@ public class WebSettingsClassic extends WebSettings {
if (mJavaScriptEnabled != flag) {
mJavaScriptEnabled = flag;
postSync();
+ mWebView.updateJavaScriptEnabled(flag);
}
}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 9df4852..591b87f 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -687,6 +687,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// It's used to dismiss the dialog in destroy if not done before.
private AlertDialog mListBoxDialog = null;
+ // Reference to the save password dialog so it can be dimissed in
+ // destroy if not done before.
+ private AlertDialog mSavePasswordDialog = null;
+
static final String LOGTAG = "webview";
private ZoomManager mZoomManager;
@@ -1370,7 +1374,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (action == MotionEvent.ACTION_POINTER_DOWN) {
cancelTouch();
action = MotionEvent.ACTION_DOWN;
- } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) {
+ } else if (action == MotionEvent.ACTION_POINTER_UP) {
// set mLastTouchX/Y to the remaining points for multi-touch.
mLastTouchX = Math.round(x);
mLastTouchY = Math.round(y);
@@ -1633,6 +1637,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mZoomManager.updateMultiTouchSupport(context);
}
+ void updateJavaScriptEnabled(boolean enabled) {
+ if (isAccessibilityInjectionEnabled()) {
+ getAccessibilityInjector().updateJavaScriptEnabled(enabled);
+ }
+ }
+
private void init() {
OnTrimMemoryListener.init(mContext);
mWebView.setWillNotDraw(false);
@@ -1830,7 +1840,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
neverRemember.getData().putString("password", password);
neverRemember.obj = resumeMsg;
- new AlertDialog.Builder(mContext)
+ mSavePasswordDialog = new AlertDialog.Builder(mContext)
.setTitle(com.android.internal.R.string.save_password_label)
.setMessage(com.android.internal.R.string.save_password_message)
.setPositiveButton(com.android.internal.R.string.save_password_notnow,
@@ -1841,6 +1851,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
resumeMsg.sendToTarget();
mResumeMsg = null;
}
+ mSavePasswordDialog = null;
}
})
.setNeutralButton(com.android.internal.R.string.save_password_remember,
@@ -1851,6 +1862,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
remember.sendToTarget();
mResumeMsg = null;
}
+ mSavePasswordDialog = null;
}
})
.setNegativeButton(com.android.internal.R.string.save_password_never,
@@ -1861,6 +1873,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
neverRemember.sendToTarget();
mResumeMsg = null;
}
+ mSavePasswordDialog = null;
}
})
.setOnCancelListener(new OnCancelListener() {
@@ -1870,6 +1883,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
resumeMsg.sendToTarget();
mResumeMsg = null;
}
+ mSavePasswordDialog = null;
}
}).show();
// Return true so that WebViewCore will pause while the dialog is
@@ -2109,6 +2123,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mListBoxDialog.dismiss();
mListBoxDialog = null;
}
+ if (mSavePasswordDialog != null) {
+ mSavePasswordDialog.dismiss();
+ mSavePasswordDialog = null;
+ }
if (mWebViewCore != null) {
// Tell WebViewCore to destroy itself
synchronized (this) {
@@ -6981,6 +6999,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ // Check if we are destroyed
+ if (mWebViewCore == null) return false;
// FIXME: If a subwindow is showing find, and the user touches the
// background window, it can steal focus.
if (mFindIsUp) return false;
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 423135f..920d44f 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2158,7 +2158,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mAccessibilityDelegate == null) {
mAccessibilityDelegate = new ListItemAccessibilityDelegate();
}
- child.setAccessibilityDelegate(mAccessibilityDelegate);
+ if (child.getAccessibilityDelegate() == null) {
+ child.setAccessibilityDelegate(mAccessibilityDelegate);
+ }
}
return child;
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index fc35f05..f76ab2b 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -477,7 +477,8 @@ public class MediaController extends FrameLayout {
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_VOLUME_UP
- || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
+ || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE
+ || keyCode == KeyEvent.KEYCODE_CAMERA) {
// don't show the controls for volume adjustment
return super.dispatchKeyEvent(event);
} else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 053ade7..e8bf9d9 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -305,12 +305,14 @@ public class Toast {
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
+ @Override
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
+ @Override
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
@@ -329,7 +331,7 @@ public class Toast {
View mView;
View mNextView;
-
+
WindowManager mWM;
TN() {
@@ -350,6 +352,7 @@ public class Toast {
/**
* schedule handleShow into the right thread
*/
+ @Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
@@ -358,6 +361,7 @@ public class Toast {
/**
* schedule handleHide into the right thread
*/
+ @Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
@@ -370,7 +374,8 @@ public class Toast {
// remove the old view if necessary
handleHide();
mView = mNextView;
- mWM = (WindowManager)mView.getContext().getSystemService(Context.WINDOW_SERVICE);
+ mWM = (WindowManager)mView.getContext().getApplicationContext()
+ .getSystemService(Context.WINDOW_SERVICE);
// We can resolve the Gravity here by using the Locale for getting
// the layout direction
final Configuration config = mView.getContext().getResources().getConfiguration();
diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java
index 6a68240..eee914e 100644
--- a/core/java/android/widget/ViewAnimator.java
+++ b/core/java/android/widget/ViewAnimator.java
@@ -329,8 +329,21 @@ public class ViewAnimator extends FrameLayout {
}
/**
+ * Returns whether the current View should be animated the first time the ViewAnimator
+ * is displayed.
+ *
+ * @return true if the current View will be animated the first time it is displayed,
+ * false otherwise.
+ *
+ * @see #setAnimateFirstView(boolean)
+ */
+ public boolean getAnimateFirstView() {
+ return mAnimateFirstTime;
+ }
+
+ /**
* Indicates whether the current View should be animated the first time
- * the ViewAnimation is displayed.
+ * the ViewAnimator is displayed.
*
* @param animate True to animate the current View the first time it is displayed,
* false otherwise.
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 650681a..3477a90 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -49,6 +49,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
sPackageFilt.addAction(Intent.ACTION_UID_REMOVED);
sPackageFilt.addDataScheme("package");
sNonDataFilt.addAction(Intent.ACTION_UID_REMOVED);
+ sNonDataFilt.addAction(Intent.ACTION_USER_STOPPED);
sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
}
@@ -136,6 +137,9 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
return false;
}
+
+ public void onHandleUserStop(Intent intent, int userHandle) {
+ }
public void onUidRemoved(int uid) {
}
@@ -307,6 +311,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
intent.getIntExtra(Intent.EXTRA_UID, 0), true);
} else if (Intent.ACTION_UID_REMOVED.equals(action)) {
onUidRemoved(intent.getIntExtra(Intent.EXTRA_UID, 0));
+ } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+ if (intent.hasExtra(Intent.EXTRA_USER_HANDLE)) {
+ onHandleUserStop(intent, intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ }
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
mAppearingPackages = pkgList;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index d6f1807..d24513a 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -529,6 +529,8 @@ class ZygoteConnection {
niceName = arg.substring(arg.indexOf('=') + 1);
} else if (arg.equals("--mount-external-multiuser")) {
mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
+ } else if (arg.equals("--mount-external-multiuser-all")) {
+ mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
} else {
break;
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index f950d3d..9d45479 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -46,6 +46,7 @@ LOCAL_SRC_FILES:= \
android_emoji_EmojiFactory.cpp \
android_view_DisplayEventReceiver.cpp \
android_view_Surface.cpp \
+ android_view_SurfaceSession.cpp \
android_view_TextureView.cpp \
android_view_InputChannel.cpp \
android_view_InputDevice.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 0c88297..55563a8 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -121,6 +121,7 @@ extern int register_android_view_GLES20DisplayList(JNIEnv* env);
extern int register_android_view_GLES20Canvas(JNIEnv* env);
extern int register_android_view_HardwareRenderer(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
+extern int register_android_view_SurfaceSession(JNIEnv* env);
extern int register_android_view_TextureView(JNIEnv* env);
extern int register_android_database_CursorWindow(JNIEnv* env);
extern int register_android_database_SQLiteConnection(JNIEnv* env);
@@ -1094,6 +1095,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_view_GLES20Canvas),
REG_JNI(register_android_view_HardwareRenderer),
REG_JNI(register_android_view_Surface),
+ REG_JNI(register_android_view_SurfaceSession),
REG_JNI(register_android_view_TextureView),
REG_JNI(register_com_google_android_gles_jni_EGLImpl),
REG_JNI(register_com_google_android_gles_jni_GLImpl),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 3c27caf..1bba5b4 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -298,8 +298,18 @@ static bool Bitmap_compress(JNIEnv* env, jobject clazz, SkBitmap* bitmap,
}
bool success = false;
- SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
- if (NULL != strm) {
+ if (NULL != bitmap) {
+ SkAutoLockPixels alp(*bitmap);
+
+ if (NULL == bitmap->getPixels()) {
+ return false;
+ }
+
+ SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
+ if (NULL == strm) {
+ return false;
+ }
+
SkImageEncoder* encoder = SkImageEncoder::Create(fm);
if (NULL != encoder) {
success = encoder->encodeStream(strm, *bitmap, quality);
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 074afa3..21162f4 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -436,7 +436,7 @@ struct NativeCode : public ANativeActivity {
void setSurface(jobject _surface) {
if (_surface != NULL) {
- nativeWindow = android_Surface_getNativeWindow(env, _surface);
+ nativeWindow = android_view_Surface_getNativeWindow(env, _surface);
} else {
nativeWindow = NULL;
}
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index c271aeb..b1664c6 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -549,7 +549,7 @@ not_valid_surface:
goto exit;
}
- window = android::android_Surface_getNativeWindow(_env, win);
+ window = android::android_view_Surface_getNativeWindow(_env, win);
if (window == NULL)
goto not_valid_surface;
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index bada329..4d5e680 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -18,15 +18,19 @@
#include <stdio.h>
+#include "android_os_Parcel.h"
#include "android_util_Binder.h"
#include "android/graphics/GraphicsJNI.h"
+#include "android/graphics/Region.h"
#include <binder/IMemory.h>
+#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SurfaceTexture.h>
+#include <ui/DisplayInfo.h>
#include <ui/Rect.h>
#include <ui/Region.h>
@@ -41,127 +45,135 @@
#include "JNIHelp.h"
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
+#include <android_runtime/android_view_SurfaceSession.h>
#include <android_runtime/android_graphics_SurfaceTexture.h>
#include <utils/misc.h>
+#include <ScopedUtfChars.h>
+
// ----------------------------------------------------------------------------
namespace android {
-enum {
- // should match Parcelable.java
- PARCELABLE_WRITE_RETURN_VALUE = 0x0001
-};
-
-// ----------------------------------------------------------------------------
-
static const char* const OutOfResourcesException =
"android/view/Surface$OutOfResourcesException";
-const char* const kSurfaceSessionClassPathName = "android/view/SurfaceSession";
-const char* const kSurfaceClassPathName = "android/view/Surface";
+static struct {
+ jclass clazz;
+ jfieldID mNativeSurface;
+ jfieldID mNativeSurfaceControl;
+ jfieldID mGenerationId;
+ jfieldID mCanvas;
+ jfieldID mCanvasSaveCount;
+} gSurfaceClassInfo;
+
+static struct {
+ jfieldID left;
+ jfieldID top;
+ jfieldID right;
+ jfieldID bottom;
+} gRectClassInfo;
+
+static struct {
+ jfieldID mNativeCanvas;
+ jfieldID mSurfaceFormat;
+} gCanvasClassInfo;
+
+static struct {
+ jfieldID width;
+ jfieldID height;
+ jfieldID refreshRate;
+ jfieldID density;
+ jfieldID xDpi;
+ jfieldID yDpi;
+} gPhysicalDisplayInfoClassInfo;
-struct sso_t {
- jfieldID client;
-};
-static sso_t sso;
-
-struct so_t {
- jfieldID surfaceControl;
- jfieldID surfaceGenerationId;
- jfieldID surface;
- jfieldID saveCount;
- jfieldID canvas;
-};
-static so_t so;
-struct ro_t {
- jfieldID l;
- jfieldID t;
- jfieldID r;
- jfieldID b;
-};
-static ro_t ro;
+class ScreenshotPixelRef : public SkPixelRef {
+public:
+ ScreenshotPixelRef(SkColorTable* ctable) {
+ fCTable = ctable;
+ SkSafeRef(ctable);
+ setImmutable();
+ }
-struct po_t {
- jfieldID x;
- jfieldID y;
-};
-static po_t po;
+ virtual ~ScreenshotPixelRef() {
+ SkSafeUnref(fCTable);
+ }
-struct co_t {
- jfieldID surfaceFormat;
-};
-static co_t co;
+ status_t update(const sp<IBinder>& display, int width, int height,
+ int minLayer, int maxLayer, bool allLayers) {
+ status_t res = (width > 0 && height > 0)
+ ? (allLayers
+ ? mScreenshot.update(display, width, height)
+ : mScreenshot.update(display, width, height, minLayer, maxLayer))
+ : mScreenshot.update(display);
+ if (res != NO_ERROR) {
+ return res;
+ }
-struct no_t {
- jfieldID native_canvas;
- jfieldID native_region;
- jfieldID native_parcel;
-};
-static no_t no;
+ return NO_ERROR;
+ }
+ uint32_t getWidth() const {
+ return mScreenshot.getWidth();
+ }
-// ----------------------------------------------------------------------------
-// ----------------------------------------------------------------------------
-// ----------------------------------------------------------------------------
+ uint32_t getHeight() const {
+ return mScreenshot.getHeight();
+ }
-static void SurfaceSession_init(JNIEnv* env, jobject clazz)
-{
- sp<SurfaceComposerClient> client = new SurfaceComposerClient;
- client->incStrong(clazz);
- env->SetIntField(clazz, sso.client, (int)client.get());
-}
+ uint32_t getStride() const {
+ return mScreenshot.getStride();
+ }
-static void SurfaceSession_destroy(JNIEnv* env, jobject clazz)
-{
- SurfaceComposerClient* client =
- (SurfaceComposerClient*)env->GetIntField(clazz, sso.client);
- if (client != 0) {
- client->decStrong(clazz);
- env->SetIntField(clazz, sso.client, 0);
+ uint32_t getFormat() const {
+ return mScreenshot.getFormat();
}
-}
-static void SurfaceSession_kill(JNIEnv* env, jobject clazz)
-{
- SurfaceComposerClient* client =
- (SurfaceComposerClient*)env->GetIntField(clazz, sso.client);
- if (client != 0) {
- client->dispose();
- client->decStrong(clazz);
- env->SetIntField(clazz, sso.client, 0);
+protected:
+ // overrides from SkPixelRef
+ virtual void* onLockPixels(SkColorTable** ct) {
+ *ct = fCTable;
+ return (void*)mScreenshot.getPixels();
}
-}
+
+ virtual void onUnlockPixels() {
+ }
+
+private:
+ ScreenshotClient mScreenshot;
+ SkColorTable* fCTable;
+
+ typedef SkPixelRef INHERITED;
+};
+
// ----------------------------------------------------------------------------
-static sp<SurfaceControl> getSurfaceControl(JNIEnv* env, jobject clazz)
-{
- SurfaceControl* const p =
- (SurfaceControl*)env->GetIntField(clazz, so.surfaceControl);
- return sp<SurfaceControl>(p);
+static sp<SurfaceControl> getSurfaceControl(JNIEnv* env, jobject surfaceObj) {
+ return reinterpret_cast<SurfaceControl*>(
+ env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurfaceControl));
}
-static void setSurfaceControl(JNIEnv* env, jobject clazz,
- const sp<SurfaceControl>& surface)
-{
- SurfaceControl* const p =
- (SurfaceControl*)env->GetIntField(clazz, so.surfaceControl);
+static void setSurfaceControl(JNIEnv* env, jobject surfaceObj,
+ const sp<SurfaceControl>& surface) {
+ SurfaceControl* const p = reinterpret_cast<SurfaceControl*>(
+ env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurfaceControl));
if (surface.get()) {
- surface->incStrong(clazz);
+ surface->incStrong(surfaceObj);
}
if (p) {
- p->decStrong(clazz);
+ p->decStrong(surfaceObj);
}
- env->SetIntField(clazz, so.surfaceControl, (int)surface.get());
+ env->SetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurfaceControl,
+ reinterpret_cast<jint>(surface.get()));
}
-static sp<Surface> getSurface(JNIEnv* env, jobject clazz)
-{
- sp<Surface> result(Surface_getSurface(env, clazz));
- if (result == 0) {
+static sp<Surface> getSurface(JNIEnv* env, jobject surfaceObj) {
+ sp<Surface> result(android_view_Surface_getSurface(env, surfaceObj));
+ if (result == NULL) {
/*
* if this method is called from the WindowManager's process, it means
* the client is is not remote, and therefore is allowed to have
@@ -170,94 +182,81 @@ static sp<Surface> getSurface(JNIEnv* env, jobject clazz)
* process.
*/
- SurfaceControl* const control =
- (SurfaceControl*)env->GetIntField(clazz, so.surfaceControl);
+ SurfaceControl* const control = reinterpret_cast<SurfaceControl*>(
+ env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurfaceControl));
if (control) {
result = control->getSurface();
- if (result != 0) {
- result->incStrong(clazz);
- env->SetIntField(clazz, so.surface, (int)result.get());
+ if (result != NULL) {
+ result->incStrong(surfaceObj);
+ env->SetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurface,
+ reinterpret_cast<jint>(result.get()));
}
}
}
return result;
}
-sp<ANativeWindow> android_Surface_getNativeWindow(
- JNIEnv* env, jobject clazz) {
- return getSurface(env, clazz);
+sp<ANativeWindow> android_view_Surface_getNativeWindow(JNIEnv* env, jobject surfaceObj) {
+ return getSurface(env, surfaceObj);
}
-bool android_Surface_isInstanceOf(JNIEnv* env, jobject obj) {
- jclass surfaceClass = env->FindClass(kSurfaceClassPathName);
- return env->IsInstanceOf(obj, surfaceClass);
+bool android_view_Surface_isInstanceOf(JNIEnv* env, jobject obj) {
+ return env->IsInstanceOf(obj, gSurfaceClassInfo.clazz);
}
-sp<Surface> Surface_getSurface(JNIEnv* env, jobject clazz) {
- sp<Surface> surface((Surface*)env->GetIntField(clazz, so.surface));
- return surface;
+sp<Surface> android_view_Surface_getSurface(JNIEnv* env, jobject surfaceObj) {
+ return reinterpret_cast<Surface*>(
+ env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurface));
}
-void setSurface(JNIEnv* env, jobject clazz, const sp<Surface>& surface)
-{
- Surface* const p = (Surface*)env->GetIntField(clazz, so.surface);
+static void setSurface(JNIEnv* env, jobject surfaceObj, const sp<Surface>& surface) {
+ Surface* const p = reinterpret_cast<Surface*>(
+ env->GetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurface));
if (surface.get()) {
- surface->incStrong(clazz);
+ surface->incStrong(surfaceObj);
}
if (p) {
- p->decStrong(clazz);
+ p->decStrong(surfaceObj);
}
- env->SetIntField(clazz, so.surface, (int)surface.get());
+ env->SetIntField(surfaceObj, gSurfaceClassInfo.mNativeSurface,
+ reinterpret_cast<jint>(surface.get()));
+
// This test is conservative and it would be better to compare the ISurfaces
if (p && p != surface.get()) {
- jint generationId = env->GetIntField(clazz, so.surfaceGenerationId);
+ jint generationId = env->GetIntField(surfaceObj,
+ gSurfaceClassInfo.mGenerationId);
generationId++;
- env->SetIntField(clazz, so.surfaceGenerationId, generationId);
+ env->SetIntField(surfaceObj,
+ gSurfaceClassInfo.mGenerationId, generationId);
}
}
// ----------------------------------------------------------------------------
-static void Surface_init(
- JNIEnv* env, jobject clazz,
- jobject session,
- jint, jstring jname, jint layerStack, jint w, jint h, jint format, jint flags)
-{
- if (session == NULL) {
- doThrowNPE(env);
- return;
- }
-
- SurfaceComposerClient* client =
- (SurfaceComposerClient*)env->GetIntField(session, sso.client);
+static void nativeCreate(JNIEnv* env, jobject surfaceObj, jobject sessionObj,
+ jstring nameStr, jint w, jint h, jint format, jint flags) {
+ ScopedUtfChars name(env, nameStr);
+ sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
- sp<SurfaceControl> surface;
- if (jname == NULL) {
- surface = client->createSurface(layerStack, w, h, format, flags);
- } else {
- const jchar* str = env->GetStringCritical(jname, 0);
- const String8 name(str, env->GetStringLength(jname));
- env->ReleaseStringCritical(jname, str);
- surface = client->createSurface(name, layerStack, w, h, format, flags);
- }
-
- if (surface == 0) {
+ sp<SurfaceControl> surface = client->createSurface(
+ String8(name.c_str()), w, h, format, flags);
+ if (surface == NULL) {
jniThrowException(env, OutOfResourcesException, NULL);
return;
}
- setSurfaceControl(env, clazz, surface);
-}
-static void Surface_initFromSurfaceTexture(
- JNIEnv* env, jobject clazz, jobject jst)
-{
- sp<SurfaceTexture> st(SurfaceTexture_getSurfaceTexture(env, jst));
+ setSurfaceControl(env, surfaceObj, surface);
+}
+static void nativeCreateFromSurfaceTexture(JNIEnv* env, jobject surfaceObj,
+ jobject surfaceTextureObj) {
+ sp<SurfaceTexture> st(SurfaceTexture_getSurfaceTexture(env, surfaceTextureObj));
if (st == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"SurfaceTexture has already been released");
return;
}
+
sp<ISurfaceTexture> bq = st->getBufferQueue();
sp<Surface> surface(new Surface(bq));
@@ -265,72 +264,62 @@ static void Surface_initFromSurfaceTexture(
jniThrowException(env, OutOfResourcesException, NULL);
return;
}
- setSurfaceControl(env, clazz, NULL);
- setSurface(env, clazz, surface);
-}
-static void Surface_initParcel(JNIEnv* env, jobject clazz, jobject argParcel)
-{
- Parcel* parcel = (Parcel*)env->GetIntField(argParcel, no.native_parcel);
- if (parcel == NULL) {
- doThrowNPE(env);
- return;
- }
-
- sp<Surface> sur(Surface::readFromParcel(*parcel));
- setSurface(env, clazz, sur);
+ setSurface(env, surfaceObj, surface);
}
-static jint Surface_getIdentity(JNIEnv* env, jobject clazz)
-{
- const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
- if (control != 0) return (jint) control->getIdentity();
- const sp<Surface>& surface(getSurface(env, clazz));
- if (surface != 0) return (jint) surface->getIdentity();
- return -1;
+static void nativeRelease(JNIEnv* env, jobject surfaceObj) {
+ setSurfaceControl(env, surfaceObj, NULL);
+ setSurface(env, surfaceObj, NULL);
}
-static void Surface_destroy(JNIEnv* env, jobject clazz, uintptr_t *ostack)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (SurfaceControl::isValid(surface)) {
- surface->clear();
+static void nativeDestroy(JNIEnv* env, jobject surfaceObj) {
+ sp<SurfaceControl> surfaceControl(getSurfaceControl(env, surfaceObj));
+ if (SurfaceControl::isValid(surfaceControl)) {
+ surfaceControl->clear();
}
- setSurfaceControl(env, clazz, 0);
- setSurface(env, clazz, 0);
+ setSurfaceControl(env, surfaceObj, NULL);
+ setSurface(env, surfaceObj, NULL);
}
-static void Surface_release(JNIEnv* env, jobject clazz, uintptr_t *ostack)
-{
- setSurfaceControl(env, clazz, 0);
- setSurface(env, clazz, 0);
-}
-
-static jboolean Surface_isValid(JNIEnv* env, jobject clazz)
-{
- const sp<SurfaceControl>& surfaceControl(getSurfaceControl(env, clazz));
- if (surfaceControl != 0) {
+static jboolean nativeIsValid(JNIEnv* env, jobject surfaceObj) {
+ sp<SurfaceControl> surfaceControl(getSurfaceControl(env, surfaceObj));
+ if (surfaceControl != NULL) {
return SurfaceControl::isValid(surfaceControl) ? JNI_TRUE : JNI_FALSE;
}
- const sp<Surface>& surface(getSurface(env, clazz));
+
+ sp<Surface> surface(getSurface(env, surfaceObj));
return Surface::isValid(surface) ? JNI_TRUE : JNI_FALSE;
}
-static jboolean Surface_isConsumerRunningBehind(JNIEnv* env, jobject clazz)
-{
- int value = 0;
- const sp<Surface>& surface(getSurface(env, clazz));
+static jint nativeGetIdentity(JNIEnv* env, jobject surfaceObj) {
+ sp<SurfaceControl> control(getSurfaceControl(env, surfaceObj));
+ if (control != NULL) {
+ return jint(control->getIdentity());
+ }
+
+ sp<Surface> surface(getSurface(env, surfaceObj));
+ if (surface != NULL) {
+ return jint(surface->getIdentity());
+ }
+
+ return -1;
+}
+
+static jboolean nativeIsConsumerRunningBehind(JNIEnv* env, jobject surfaceObj) {
+ sp<Surface> surface(getSurface(env, surfaceObj));
if (!Surface::isValid(surface)) {
doThrowIAE(env);
- return 0;
+ return JNI_FALSE;
}
- ANativeWindow* anw = static_cast<ANativeWindow *>(surface.get());
+
+ int value = 0;
+ ANativeWindow* anw = static_cast<ANativeWindow*>(surface.get());
anw->query(anw, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &value);
- return (jboolean)value;
+ return value;
}
-static inline SkBitmap::Config convertPixelFormat(PixelFormat format)
-{
+static inline SkBitmap::Config convertPixelFormat(PixelFormat format) {
/* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then
we can map to SkBitmap::kARGB_8888_Config, and optionally call
bitmap.setIsOpaque(true) on the resulting SkBitmap (as an accelerator)
@@ -345,44 +334,44 @@ static inline SkBitmap::Config convertPixelFormat(PixelFormat format)
}
}
-static jobject Surface_lockCanvas(JNIEnv* env, jobject clazz, jobject dirtyRect)
-{
- const sp<Surface>& surface(getSurface(env, clazz));
+static jobject nativeLockCanvas(JNIEnv* env, jobject surfaceObj, jobject dirtyRectObj) {
+ sp<Surface> surface(getSurface(env, surfaceObj));
if (!Surface::isValid(surface)) {
doThrowIAE(env);
- return 0;
+ return NULL;
}
// get dirty region
Region dirtyRegion;
- if (dirtyRect) {
+ if (dirtyRectObj) {
Rect dirty;
- dirty.left = env->GetIntField(dirtyRect, ro.l);
- dirty.top = env->GetIntField(dirtyRect, ro.t);
- dirty.right = env->GetIntField(dirtyRect, ro.r);
- dirty.bottom= env->GetIntField(dirtyRect, ro.b);
+ dirty.left = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
+ dirty.top = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
+ dirty.right = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
+ dirty.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
if (!dirty.isEmpty()) {
dirtyRegion.set(dirty);
}
} else {
- dirtyRegion.set(Rect(0x3FFF,0x3FFF));
+ dirtyRegion.set(Rect(0x3FFF, 0x3FFF));
}
Surface::SurfaceInfo info;
status_t err = surface->lock(&info, &dirtyRegion);
if (err < 0) {
const char* const exception = (err == NO_MEMORY) ?
- OutOfResourcesException :
- "java/lang/IllegalArgumentException";
+ OutOfResourcesException :
+ "java/lang/IllegalArgumentException";
jniThrowException(env, exception, NULL);
- return 0;
+ return NULL;
}
// Associate a SkCanvas object to this surface
- jobject canvas = env->GetObjectField(clazz, so.canvas);
- env->SetIntField(canvas, co.surfaceFormat, info.format);
+ jobject canvasObj = env->GetObjectField(surfaceObj, gSurfaceClassInfo.mCanvas);
+ env->SetIntField(canvasObj, gCanvasClassInfo.mSurfaceFormat, info.format);
- SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas, no.native_canvas);
+ SkCanvas* nativeCanvas = reinterpret_cast<SkCanvas*>(
+ env->GetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas));
SkBitmap bitmap;
ssize_t bpr = info.s * bytesPerPixel(info.format);
bitmap.setConfig(convertPixelFormat(info.format), info.w, info.h, bpr);
@@ -413,38 +402,38 @@ static jobject Surface_lockCanvas(JNIEnv* env, jobject clazz, jobject dirtyRect)
nativeCanvas->clipRegion(clipReg);
int saveCount = nativeCanvas->save();
- env->SetIntField(clazz, so.saveCount, saveCount);
+ env->SetIntField(surfaceObj, gSurfaceClassInfo.mCanvasSaveCount, saveCount);
- if (dirtyRect) {
+ if (dirtyRectObj) {
const Rect& bounds(dirtyRegion.getBounds());
- env->SetIntField(dirtyRect, ro.l, bounds.left);
- env->SetIntField(dirtyRect, ro.t, bounds.top);
- env->SetIntField(dirtyRect, ro.r, bounds.right);
- env->SetIntField(dirtyRect, ro.b, bounds.bottom);
+ env->SetIntField(dirtyRectObj, gRectClassInfo.left, bounds.left);
+ env->SetIntField(dirtyRectObj, gRectClassInfo.top, bounds.top);
+ env->SetIntField(dirtyRectObj, gRectClassInfo.right, bounds.right);
+ env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, bounds.bottom);
}
- return canvas;
+ return canvasObj;
}
-static void Surface_unlockCanvasAndPost(
- JNIEnv* env, jobject clazz, jobject argCanvas)
-{
- jobject canvas = env->GetObjectField(clazz, so.canvas);
- if (env->IsSameObject(canvas, argCanvas) == JNI_FALSE) {
+static void nativeUnlockCanvasAndPost(JNIEnv* env, jobject surfaceObj, jobject canvasObj) {
+ jobject ownCanvasObj = env->GetObjectField(surfaceObj, gSurfaceClassInfo.mCanvas);
+ if (!env->IsSameObject(ownCanvasObj, canvasObj)) {
doThrowIAE(env);
return;
}
- const sp<Surface>& surface(getSurface(env, clazz));
- if (!Surface::isValid(surface))
+ sp<Surface> surface(getSurface(env, surfaceObj));
+ if (!Surface::isValid(surface)) {
return;
+ }
// detach the canvas from the surface
- SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas, no.native_canvas);
- int saveCount = env->GetIntField(clazz, so.saveCount);
+ SkCanvas* nativeCanvas = reinterpret_cast<SkCanvas*>(
+ env->GetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas));
+ int saveCount = env->GetIntField(surfaceObj, gSurfaceClassInfo.mCanvasSaveCount);
nativeCanvas->restoreToCount(saveCount);
nativeCanvas->setBitmapDevice(SkBitmap());
- env->SetIntField(clazz, so.saveCount, 0);
+ env->SetIntField(surfaceObj, gSurfaceClassInfo.mCanvasSaveCount, 0);
// unlock surface
status_t err = surface->unlockAndPost();
@@ -453,98 +442,18 @@ static void Surface_unlockCanvasAndPost(
}
}
-static void Surface_unlockCanvas(
- JNIEnv* env, jobject clazz, jobject argCanvas)
-{
- // XXX: this API has been removed
- doThrowIAE(env);
-}
-
-static void Surface_openTransaction(
- JNIEnv* env, jobject clazz)
-{
- SurfaceComposerClient::openGlobalTransaction();
-}
-
-static void Surface_closeTransaction(
- JNIEnv* env, jobject clazz)
-{
- SurfaceComposerClient::closeGlobalTransaction();
-}
-
-static void Surface_setOrientation(
- JNIEnv* env, jobject clazz, jint display, jint orientation)
-{
- int err = SurfaceComposerClient::setOrientation(display, orientation, 0);
- if (err < 0) {
- doThrowIAE(env);
+static jobject nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj,
+ jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) {
+ sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
+ if (displayToken == NULL) {
+ return NULL;
}
-}
-class ScreenshotPixelRef : public SkPixelRef {
-public:
- ScreenshotPixelRef(SkColorTable* ctable) {
- fCTable = ctable;
- SkSafeRef(ctable);
- setImmutable();
- }
- virtual ~ScreenshotPixelRef() {
- SkSafeUnref(fCTable);
- }
-
- status_t update(int width, int height, int minLayer, int maxLayer, bool allLayers) {
- status_t res = (width > 0 && height > 0)
- ? (allLayers
- ? mScreenshot.update(width, height)
- : mScreenshot.update(width, height, minLayer, maxLayer))
- : mScreenshot.update();
- if (res != NO_ERROR) {
- return res;
- }
-
- return NO_ERROR;
- }
-
- uint32_t getWidth() const {
- return mScreenshot.getWidth();
- }
-
- uint32_t getHeight() const {
- return mScreenshot.getHeight();
- }
-
- uint32_t getStride() const {
- return mScreenshot.getStride();
- }
-
- uint32_t getFormat() const {
- return mScreenshot.getFormat();
- }
-
-protected:
- // overrides from SkPixelRef
- virtual void* onLockPixels(SkColorTable** ct) {
- *ct = fCTable;
- return (void*)mScreenshot.getPixels();
- }
-
- virtual void onUnlockPixels() {
- }
-
-private:
- ScreenshotClient mScreenshot;
- SkColorTable* fCTable;
-
- typedef SkPixelRef INHERITED;
-};
-
-static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height,
- jint minLayer, jint maxLayer, bool allLayers)
-{
ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
- if (pixels->update(width, height, minLayer, maxLayer, allLayers) != NO_ERROR) {
+ if (pixels->update(displayToken, width, height,
+ minLayer, maxLayer, allLayers) != NO_ERROR) {
delete pixels;
- return 0;
+ return NULL;
}
uint32_t w = pixels->getWidth();
@@ -571,94 +480,68 @@ static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height,
return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
}
-static jobject Surface_screenshotAll(JNIEnv* env, jobject clazz, jint width, jint height)
-{
- return doScreenshot(env, clazz, width, height, 0, 0, true);
+static void nativeOpenTransaction(JNIEnv* env, jclass clazz) {
+ SurfaceComposerClient::openGlobalTransaction();
}
-static jobject Surface_screenshot(JNIEnv* env, jobject clazz, jint width, jint height,
- jint minLayer, jint maxLayer)
-{
- return doScreenshot(env, clazz, width, height, minLayer, maxLayer, false);
+static void nativeCloseTransaction(JNIEnv* env, jclass clazz) {
+ SurfaceComposerClient::closeGlobalTransaction();
}
-static void Surface_setLayer(
- JNIEnv* env, jobject clazz, jint zorder)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (surface == 0) return;
+static void nativeSetLayer(JNIEnv* env, jobject surfaceObj, jint zorder) {
+ sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj));
+ if (surface == NULL) return;
+
status_t err = surface->setLayer(zorder);
- if (err<0 && err!=NO_INIT) {
+ if (err < 0 && err != NO_INIT) {
doThrowIAE(env);
}
}
-static void Surface_setPosition(
- JNIEnv* env, jobject clazz, jfloat x, jfloat y)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (surface == 0) return;
+static void nativeSetPosition(JNIEnv* env, jobject surfaceObj, jfloat x, jfloat y) {
+ sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj));
+ if (surface == NULL) return;
+
status_t err = surface->setPosition(x, y);
- if (err<0 && err!=NO_INIT) {
+ if (err < 0 && err != NO_INIT) {
doThrowIAE(env);
}
}
-static void Surface_setSize(
- JNIEnv* env, jobject clazz, jint w, jint h)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (surface == 0) return;
+static void nativeSetSize(JNIEnv* env, jobject surfaceObj, jint w, jint h) {
+ sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj));
+ if (surface == NULL) return;
+
status_t err = surface->setSize(w, h);
- if (err<0 && err!=NO_INIT) {
+ if (err < 0 && err != NO_INIT) {
doThrowIAE(env);
}
}
-static void Surface_hide(
- JNIEnv* env, jobject clazz)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (surface == 0) return;
- status_t err = surface->hide();
- if (err<0 && err!=NO_INIT) {
- doThrowIAE(env);
- }
-}
+static void nativeSetFlags(JNIEnv* env, jobject surfaceObj, jint flags, jint mask) {
+ sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj));
+ if (surface == NULL) return;
-static void Surface_show(
- JNIEnv* env, jobject clazz)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (surface == 0) return;
- status_t err = surface->show();
- if (err<0 && err!=NO_INIT) {
+ status_t err = surface->setFlags(flags, mask);
+ if (err < 0 && err != NO_INIT) {
doThrowIAE(env);
}
}
-static void Surface_setFlags(
- JNIEnv* env, jobject clazz, jint flags, jint mask)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (surface == 0) return;
- status_t err = surface->setFlags(flags, mask);
- if (err<0 && err!=NO_INIT) {
+static void nativeSetTransparentRegionHint(JNIEnv* env, jobject surfaceObj, jobject regionObj) {
+ sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj));
+ if (surface == NULL) return;
+
+ SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
+ if (!region) {
doThrowIAE(env);
+ return;
}
-}
-
-static void Surface_setTransparentRegion(
- JNIEnv* env, jobject clazz, jobject argRegion)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (surface == 0) return;
- SkRegion* nativeRegion = (SkRegion*)env->GetIntField(argRegion, no.native_region);
- const SkIRect& b(nativeRegion->getBounds());
+ const SkIRect& b(region->getBounds());
Region reg(Rect(b.fLeft, b.fTop, b.fRight, b.fBottom));
- if (nativeRegion->isComplex()) {
- SkRegion::Iterator it(*nativeRegion);
+ if (region->isComplex()) {
+ SkRegion::Iterator it(*region);
while (!it.done()) {
const SkIRect& r(it.rect());
reg.addRectUnchecked(r.fLeft, r.fTop, r.fRight, r.fBottom);
@@ -667,130 +550,197 @@ static void Surface_setTransparentRegion(
}
status_t err = surface->setTransparentRegionHint(reg);
- if (err<0 && err!=NO_INIT) {
+ if (err < 0 && err != NO_INIT) {
doThrowIAE(env);
}
}
-static void Surface_setAlpha(
- JNIEnv* env, jobject clazz, jfloat alpha)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (surface == 0) return;
+static void nativeSetAlpha(JNIEnv* env, jobject surfaceObj, jfloat alpha) {
+ sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj));
+ if (surface == NULL) return;
+
status_t err = surface->setAlpha(alpha);
- if (err<0 && err!=NO_INIT) {
+ if (err < 0 && err != NO_INIT) {
doThrowIAE(env);
}
}
-static void Surface_setMatrix(
- JNIEnv* env, jobject clazz,
- jfloat dsdx, jfloat dtdx, jfloat dsdy, jfloat dtdy)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
- if (surface == 0) return;
+static void nativeSetMatrix(JNIEnv* env, jobject surfaceObj,
+ jfloat dsdx, jfloat dtdx, jfloat dsdy, jfloat dtdy) {
+ sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj));
+ if (surface == NULL) return;
+
status_t err = surface->setMatrix(dsdx, dtdx, dsdy, dtdy);
- if (err<0 && err!=NO_INIT) {
+ if (err < 0 && err != NO_INIT) {
doThrowIAE(env);
}
}
-static void Surface_setWindowCrop(JNIEnv* env, jobject thiz, jobject crop)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, thiz));
- if (surface == 0) return;
-
- Rect nativeCrop;
- if (crop) {
- nativeCrop.left = env->GetIntField(crop, ro.l);
- nativeCrop.top = env->GetIntField(crop, ro.t);
- nativeCrop.right = env->GetIntField(crop, ro.r);
- nativeCrop.bottom= env->GetIntField(crop, ro.b);
+static void nativeSetWindowCrop(JNIEnv* env, jobject surfaceObj, jobject cropObj) {
+ const sp<SurfaceControl>& surface(getSurfaceControl(env, surfaceObj));
+ if (surface == NULL) return;
+
+ Rect crop;
+ if (cropObj) {
+ crop.left = env->GetIntField(cropObj, gRectClassInfo.left);
+ crop.top = env->GetIntField(cropObj, gRectClassInfo.top);
+ crop.right = env->GetIntField(cropObj, gRectClassInfo.right);
+ crop.bottom = env->GetIntField(cropObj, gRectClassInfo.bottom);
} else {
- nativeCrop.left = nativeCrop.top = nativeCrop.right =
- nativeCrop.bottom = 0;
+ crop.left = crop.top = crop.right = crop.bottom = 0;
}
- status_t err = surface->setCrop(nativeCrop);
- if (err<0 && err!=NO_INIT) {
+ status_t err = surface->setCrop(crop);
+ if (err < 0 && err != NO_INIT) {
doThrowIAE(env);
}
}
-static void Surface_setLayerStack(JNIEnv* env, jobject thiz, jint layerStack)
-{
- const sp<SurfaceControl>& surface(getSurfaceControl(env, thiz));
- if (surface == 0) return;
+static void nativeSetLayerStack(JNIEnv* env, jobject surfaceObj, jint layerStack) {
+ sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj));
+ if (surface == NULL) return;
- // TODO(mathias): Everything.
+ status_t err = surface->setLayerStack(layerStack);
+ if (err < 0 && err != NO_INIT) {
+ doThrowIAE(env);
+ }
}
-// ----------------------------------------------------------------------------
+static jobject nativeGetBuiltInDisplay(JNIEnv* env, jclass clazz, jint id) {
+ sp<IBinder> token(SurfaceComposerClient::getBuiltInDisplay(id));
+ return javaObjectForIBinder(env, token);
+}
-static void Surface_copyFrom(
- JNIEnv* env, jobject clazz, jobject other)
-{
- if (clazz == other)
+static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj) {
+ ScopedUtfChars name(env, nameObj);
+ // TODO: pass the name to SF.
+ sp<IBinder> token(SurfaceComposerClient::createDisplay());
+ return javaObjectForIBinder(env, token);
+}
+
+static void nativeSetDisplaySurface(JNIEnv* env, jclass clazz,
+ jobject tokenObj, jobject surfaceTextureObj) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == NULL) return;
+
+ if (!surfaceTextureObj) {
+ SurfaceComposerClient::setDisplaySurface(token, NULL);
return;
+ }
- if (other == NULL) {
- doThrowNPE(env);
+ sp<SurfaceTexture> st(SurfaceTexture_getSurfaceTexture(env, surfaceTextureObj));
+ if (st == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "SurfaceTexture has already been released");
return;
}
+ sp<ISurfaceTexture> bq = st->getBufferQueue();
+ SurfaceComposerClient::setDisplaySurface(token, bq);
+}
+
+static void nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz,
+ jobject tokenObj, jint layerStack) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == NULL) return;
+
+ SurfaceComposerClient::setDisplayLayerStack(token, layerStack);
+}
+
+static void nativeSetDisplayOrientation(JNIEnv* env, jclass clazz,
+ jobject tokenObj, jint orientation) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == NULL) return;
+
+ SurfaceComposerClient::setDisplayOrientation(token, orientation);
+}
+
+static void nativeSetDisplayViewport(JNIEnv* env, jclass clazz,
+ jobject tokenObj, jobject rectObj) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == NULL) return;
+
+ Rect rect;
+ rect.left = env->GetIntField(rectObj, gRectClassInfo.left);
+ rect.top = env->GetIntField(rectObj, gRectClassInfo.top);
+ rect.right = env->GetIntField(rectObj, gRectClassInfo.right);
+ rect.bottom = env->GetIntField(rectObj, gRectClassInfo.bottom);
+ SurfaceComposerClient::setDisplayViewport(token, rect);
+}
+
+static void nativeSetDisplayFrame(JNIEnv* env, jclass clazz,
+ jobject tokenObj, jobject rectObj) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == NULL) return;
+
+ Rect rect;
+ rect.left = env->GetIntField(rectObj, gRectClassInfo.left);
+ rect.top = env->GetIntField(rectObj, gRectClassInfo.top);
+ rect.right = env->GetIntField(rectObj, gRectClassInfo.right);
+ rect.bottom = env->GetIntField(rectObj, gRectClassInfo.bottom);
+ SurfaceComposerClient::setDisplayFrame(token, rect);
+}
+
+static jboolean nativeGetDisplayInfo(JNIEnv* env, jclass clazz,
+ jobject tokenObj, jobject infoObj) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == NULL) return JNI_FALSE;
+
+ DisplayInfo info;
+ if (SurfaceComposerClient::getDisplayInfo(token, &info)) {
+ return JNI_FALSE;
+ }
+
+ env->SetIntField(infoObj, gPhysicalDisplayInfoClassInfo.width, info.w);
+ env->SetIntField(infoObj, gPhysicalDisplayInfoClassInfo.height, info.h);
+ env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.refreshRate, info.fps);
+ env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.density, info.density);
+ env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.xDpi, info.xdpi);
+ env->SetFloatField(infoObj, gPhysicalDisplayInfoClassInfo.yDpi, info.ydpi);
+ return JNI_TRUE;
+}
+
+// ----------------------------------------------------------------------------
+
+static void nativeCopyFrom(JNIEnv* env, jobject surfaceObj, jobject otherObj) {
/*
* This is used by the WindowManagerService just after constructing
* a Surface and is necessary for returning the Surface reference to
* the caller. At this point, we should only have a SurfaceControl.
*/
- const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz);
- const sp<SurfaceControl>& rhs = getSurfaceControl(env, other);
- if (!SurfaceControl::isSameSurface(surface, rhs)) {
+ sp<SurfaceControl> surface(getSurfaceControl(env, surfaceObj));
+ sp<SurfaceControl> other(getSurfaceControl(env, otherObj));
+ if (!SurfaceControl::isSameSurface(surface, other)) {
// we reassign the surface only if it's a different one
// otherwise we would loose our client-side state.
- setSurfaceControl(env, clazz, rhs);
+ setSurfaceControl(env, surfaceObj, other);
}
}
-static void Surface_transferFrom(
- JNIEnv* env, jobject clazz, jobject other)
-{
- if (clazz == other)
- return;
-
- if (other == NULL) {
- doThrowNPE(env);
- return;
- }
-
- sp<SurfaceControl> control(getSurfaceControl(env, other));
- sp<Surface> surface(Surface_getSurface(env, other));
- setSurfaceControl(env, clazz, control);
- setSurface(env, clazz, surface);
- setSurfaceControl(env, other, 0);
- setSurface(env, other, 0);
+static void nativeTransferFrom(JNIEnv* env, jobject surfaceObj, jobject otherObj) {
+ sp<SurfaceControl> control(getSurfaceControl(env, otherObj));
+ sp<Surface> surface(android_view_Surface_getSurface(env, otherObj));
+ setSurfaceControl(env, surfaceObj, control);
+ setSurface(env, surfaceObj, surface);
+ setSurfaceControl(env, otherObj, NULL);
+ setSurface(env, otherObj, NULL);
}
-static void Surface_readFromParcel(
- JNIEnv* env, jobject clazz, jobject argParcel)
-{
- Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel);
+static void nativeReadFromParcel(JNIEnv* env, jobject surfaceObj, jobject parcelObj) {
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel == NULL) {
doThrowNPE(env);
return;
}
- sp<Surface> sur(Surface::readFromParcel(*parcel));
- setSurface(env, clazz, sur);
+ sp<Surface> surface(Surface::readFromParcel(*parcel));
+ setSurfaceControl(env, surfaceObj, NULL);
+ setSurface(env, surfaceObj, surface);
}
-static void Surface_writeToParcel(
- JNIEnv* env, jobject clazz, jobject argParcel, jint flags)
-{
- Parcel* parcel = (Parcel*)env->GetIntField(
- argParcel, no.native_parcel);
-
+static void nativeWriteToParcel(JNIEnv* env, jobject surfaceObj, jobject parcelObj) {
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel == NULL) {
doThrowNPE(env);
return;
@@ -802,110 +752,125 @@ static void Surface_writeToParcel(
// available we let it parcel itself. Finally, if the Surface is also
// NULL we fall back to using the SurfaceControl path which sends an
// empty surface; this matches legacy behavior.
- const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
+ sp<SurfaceControl> control(getSurfaceControl(env, surfaceObj));
if (control != NULL) {
SurfaceControl::writeSurfaceToParcel(control, parcel);
} else {
- sp<Surface> surface(Surface_getSurface(env, clazz));
+ sp<Surface> surface(android_view_Surface_getSurface(env, surfaceObj));
if (surface != NULL) {
Surface::writeToParcel(surface, parcel);
} else {
SurfaceControl::writeSurfaceToParcel(NULL, parcel);
}
}
- if (flags & PARCELABLE_WRITE_RETURN_VALUE) {
- setSurfaceControl(env, clazz, NULL);
- setSurface(env, clazz, NULL);
- }
}
// ----------------------------------------------------------------------------
-// ----------------------------------------------------------------------------
-// ----------------------------------------------------------------------------
-
-static void nativeClassInit(JNIEnv* env, jclass clazz);
-
-static JNINativeMethod gSurfaceSessionMethods[] = {
- {"init", "()V", (void*)SurfaceSession_init },
- {"destroy", "()V", (void*)SurfaceSession_destroy },
- {"kill", "()V", (void*)SurfaceSession_kill },
-};
static JNINativeMethod gSurfaceMethods[] = {
- {"nativeClassInit", "()V", (void*)nativeClassInit },
- {"init", "(Landroid/view/SurfaceSession;ILjava/lang/String;IIIII)V", (void*)Surface_init },
- {"init", "(Landroid/os/Parcel;)V", (void*)Surface_initParcel },
- {"initFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)V", (void*)Surface_initFromSurfaceTexture },
- {"getIdentity", "()I", (void*)Surface_getIdentity },
- {"destroy", "()V", (void*)Surface_destroy },
- {"release", "()V", (void*)Surface_release },
- {"copyFrom", "(Landroid/view/Surface;)V", (void*)Surface_copyFrom },
- {"transferFrom", "(Landroid/view/Surface;)V", (void*)Surface_transferFrom },
- {"isValid", "()Z", (void*)Surface_isValid },
- {"lockCanvasNative", "(Landroid/graphics/Rect;)Landroid/graphics/Canvas;", (void*)Surface_lockCanvas },
- {"unlockCanvasAndPost", "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvasAndPost },
- {"unlockCanvas", "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvas },
- {"openTransaction", "()V", (void*)Surface_openTransaction },
- {"closeTransaction", "()V", (void*)Surface_closeTransaction },
- {"setOrientation", "(II)V", (void*)Surface_setOrientation },
- {"screenshot", "(II)Landroid/graphics/Bitmap;", (void*)Surface_screenshotAll },
- {"screenshot", "(IIII)Landroid/graphics/Bitmap;", (void*)Surface_screenshot },
- {"setLayer", "(I)V", (void*)Surface_setLayer },
- {"setPosition", "(FF)V",(void*)Surface_setPosition },
- {"setSize", "(II)V",(void*)Surface_setSize },
- {"hide", "()V", (void*)Surface_hide },
- {"show", "()V", (void*)Surface_show },
- {"setFlags", "(II)V",(void*)Surface_setFlags },
- {"setTransparentRegionHint","(Landroid/graphics/Region;)V", (void*)Surface_setTransparentRegion },
- {"setAlpha", "(F)V", (void*)Surface_setAlpha },
- {"setMatrix", "(FFFF)V", (void*)Surface_setMatrix },
- {"readFromParcel", "(Landroid/os/Parcel;)V", (void*)Surface_readFromParcel },
- {"writeToParcel", "(Landroid/os/Parcel;I)V", (void*)Surface_writeToParcel },
- {"isConsumerRunningBehind", "()Z", (void*)Surface_isConsumerRunningBehind },
- {"setWindowCrop", "(Landroid/graphics/Rect;)V", (void*)Surface_setWindowCrop },
- {"setLayerStack", "(I)V", (void*)Surface_setLayerStack },
+ {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIII)V",
+ (void*)nativeCreate },
+ {"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)V",
+ (void*)nativeCreateFromSurfaceTexture },
+ {"nativeRelease", "()V",
+ (void*)nativeRelease },
+ {"nativeDestroy", "()V",
+ (void*)nativeDestroy },
+ {"nativeIsValid", "()Z",
+ (void*)nativeIsValid },
+ {"nativeGetIdentity", "()I",
+ (void*)nativeGetIdentity },
+ {"nativeIsConsumerRunningBehind", "()Z",
+ (void*)nativeIsConsumerRunningBehind },
+ {"nativeLockCanvas", "(Landroid/graphics/Rect;)Landroid/graphics/Canvas;",
+ (void*)nativeLockCanvas },
+ {"nativeUnlockCanvasAndPost", "(Landroid/graphics/Canvas;)V",
+ (void*)nativeUnlockCanvasAndPost },
+ {"nativeScreenshot", "(Landroid/os/IBinder;IIIIZ)Landroid/graphics/Bitmap;",
+ (void*)nativeScreenshot },
+ {"nativeOpenTransaction", "()V",
+ (void*)nativeOpenTransaction },
+ {"nativeCloseTransaction", "()V",
+ (void*)nativeCloseTransaction },
+ {"nativeSetLayer", "(I)V",
+ (void*)nativeSetLayer },
+ {"nativeSetPosition", "(FF)V",
+ (void*)nativeSetPosition },
+ {"nativeSetSize", "(II)V",
+ (void*)nativeSetSize },
+ {"nativeSetTransparentRegionHint", "(Landroid/graphics/Region;)V",
+ (void*)nativeSetTransparentRegionHint },
+ {"nativeSetAlpha", "(F)V",
+ (void*)nativeSetAlpha },
+ {"nativeSetMatrix", "(FFFF)V",
+ (void*)nativeSetMatrix },
+ {"nativeSetFlags", "(II)V",
+ (void*)nativeSetFlags },
+ {"nativeSetWindowCrop", "(Landroid/graphics/Rect;)V",
+ (void*)nativeSetWindowCrop },
+ {"nativeSetLayerStack", "(I)V",
+ (void*)nativeSetLayerStack },
+ {"nativeGetBuiltInDisplay", "(I)Landroid/os/IBinder;",
+ (void*)nativeGetBuiltInDisplay },
+ {"nativeCreateDisplay", "(Ljava/lang/String;)Landroid/os/IBinder;",
+ (void*)nativeCreateDisplay },
+ {"nativeSetDisplaySurface", "(Landroid/os/IBinder;Landroid/graphics/SurfaceTexture;)V",
+ (void*)nativeSetDisplaySurface },
+ {"nativeSetDisplayLayerStack", "(Landroid/os/IBinder;I)V",
+ (void*)nativeSetDisplayLayerStack },
+ {"nativeSetDisplayOrientation", "(Landroid/os/IBinder;I)V",
+ (void*)nativeSetDisplayOrientation },
+ {"nativeSetDisplayViewport", "(Landroid/os/IBinder;Landroid/graphics/Rect;)V",
+ (void*)nativeSetDisplayViewport },
+ {"nativeSetDisplayFrame", "(Landroid/os/IBinder;Landroid/graphics/Rect;)V",
+ (void*)nativeSetDisplayFrame },
+ {"nativeGetDisplayInfo", "(Landroid/os/IBinder;Landroid/view/Surface$PhysicalDisplayInfo;)Z",
+ (void*)nativeGetDisplayInfo },
+ {"nativeCopyFrom", "(Landroid/view/Surface;)V",
+ (void*)nativeCopyFrom },
+ {"nativeTransferFrom", "(Landroid/view/Surface;)V",
+ (void*)nativeTransferFrom },
+ {"nativeReadFromParcel", "(Landroid/os/Parcel;)V",
+ (void*)nativeReadFromParcel },
+ {"nativeWriteToParcel", "(Landroid/os/Parcel;)V",
+ (void*)nativeWriteToParcel },
};
-void nativeClassInit(JNIEnv* env, jclass clazz)
-{
- so.surface = env->GetFieldID(clazz, ANDROID_VIEW_SURFACE_JNI_ID, "I");
- so.surfaceGenerationId = env->GetFieldID(clazz, "mSurfaceGenerationId", "I");
- so.surfaceControl = env->GetFieldID(clazz, "mSurfaceControl", "I");
- so.saveCount = env->GetFieldID(clazz, "mSaveCount", "I");
- so.canvas = env->GetFieldID(clazz, "mCanvas", "Landroid/graphics/Canvas;");
-
- jclass surfaceSession = env->FindClass("android/view/SurfaceSession");
- sso.client = env->GetFieldID(surfaceSession, "mClient", "I");
-
- jclass canvas = env->FindClass("android/graphics/Canvas");
- no.native_canvas = env->GetFieldID(canvas, "mNativeCanvas", "I");
- co.surfaceFormat = env->GetFieldID(canvas, "mSurfaceFormat", "I");
-
- jclass region = env->FindClass("android/graphics/Region");
- no.native_region = env->GetFieldID(region, "mNativeRegion", "I");
-
- jclass parcel = env->FindClass("android/os/Parcel");
- no.native_parcel = env->GetFieldID(parcel, "mNativePtr", "I");
-
- jclass rect = env->FindClass("android/graphics/Rect");
- ro.l = env->GetFieldID(rect, "left", "I");
- ro.t = env->GetFieldID(rect, "top", "I");
- ro.r = env->GetFieldID(rect, "right", "I");
- ro.b = env->GetFieldID(rect, "bottom", "I");
-
- jclass point = env->FindClass("android/graphics/Point");
- po.x = env->GetFieldID(point, "x", "I");
- po.y = env->GetFieldID(point, "y", "I");
-}
-
int register_android_view_Surface(JNIEnv* env)
{
- int err;
- err = AndroidRuntime::registerNativeMethods(env, kSurfaceSessionClassPathName,
- gSurfaceSessionMethods, NELEM(gSurfaceSessionMethods));
-
- err |= AndroidRuntime::registerNativeMethods(env, kSurfaceClassPathName,
+ int err = AndroidRuntime::registerNativeMethods(env, "android/view/Surface",
gSurfaceMethods, NELEM(gSurfaceMethods));
+
+ jclass clazz = env->FindClass("android/view/Surface");
+ gSurfaceClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
+ gSurfaceClassInfo.mNativeSurface =
+ env->GetFieldID(gSurfaceClassInfo.clazz, ANDROID_VIEW_SURFACE_JNI_ID, "I");
+ gSurfaceClassInfo.mNativeSurfaceControl =
+ env->GetFieldID(gSurfaceClassInfo.clazz, "mNativeSurfaceControl", "I");
+ gSurfaceClassInfo.mGenerationId =
+ env->GetFieldID(gSurfaceClassInfo.clazz, "mGenerationId", "I");
+ gSurfaceClassInfo.mCanvas =
+ env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvas", "Landroid/graphics/Canvas;");
+ gSurfaceClassInfo.mCanvasSaveCount =
+ env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvasSaveCount", "I");
+
+ clazz = env->FindClass("android/graphics/Canvas");
+ gCanvasClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas", "I");
+ gCanvasClassInfo.mSurfaceFormat = env->GetFieldID(clazz, "mSurfaceFormat", "I");
+
+ clazz = env->FindClass("android/graphics/Rect");
+ gRectClassInfo.left = env->GetFieldID(clazz, "left", "I");
+ gRectClassInfo.top = env->GetFieldID(clazz, "top", "I");
+ gRectClassInfo.right = env->GetFieldID(clazz, "right", "I");
+ gRectClassInfo.bottom = env->GetFieldID(clazz, "bottom", "I");
+
+ clazz = env->FindClass("android/view/Surface$PhysicalDisplayInfo");
+ gPhysicalDisplayInfoClassInfo.width = env->GetFieldID(clazz, "width", "I");
+ gPhysicalDisplayInfoClassInfo.height = env->GetFieldID(clazz, "height", "I");
+ gPhysicalDisplayInfoClassInfo.refreshRate = env->GetFieldID(clazz, "refreshRate", "F");
+ gPhysicalDisplayInfoClassInfo.density = env->GetFieldID(clazz, "density", "F");
+ gPhysicalDisplayInfoClassInfo.xDpi = env->GetFieldID(clazz, "xDpi", "F");
+ gPhysicalDisplayInfoClassInfo.yDpi = env->GetFieldID(clazz, "yDpi", "F");
return err;
}
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
new file mode 100644
index 0000000..1494bc5
--- /dev/null
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SurfaceSession"
+
+#include "JNIHelp.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_view_SurfaceSession.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <gui/SurfaceComposerClient.h>
+
+namespace android {
+
+static struct {
+ jfieldID mNativeClient;
+} gSurfaceSessionClassInfo;
+
+
+sp<SurfaceComposerClient> android_view_SurfaceSession_getClient(
+ JNIEnv* env, jobject surfaceSessionObj) {
+ return reinterpret_cast<SurfaceComposerClient*>(
+ env->GetIntField(surfaceSessionObj, gSurfaceSessionClassInfo.mNativeClient));
+}
+
+
+static jint nativeCreate(JNIEnv* env, jclass clazz) {
+ SurfaceComposerClient* client = new SurfaceComposerClient();
+ client->incStrong(clazz);
+ return reinterpret_cast<jint>(client);
+}
+
+static void nativeDestroy(JNIEnv* env, jclass clazz, jint ptr) {
+ SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
+ client->decStrong(clazz);
+}
+
+static void nativeKill(JNIEnv* env, jclass clazz, jint ptr) {
+ SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
+ client->dispose();
+}
+
+
+static JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "nativeCreate", "()I",
+ (void*)nativeCreate },
+ { "nativeDestroy", "(I)V",
+ (void*)nativeDestroy },
+ { "nativeKill", "(I)V",
+ (void*)nativeKill }
+};
+
+int register_android_view_SurfaceSession(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/view/SurfaceSession",
+ gMethods, NELEM(gMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ jclass clazz = env->FindClass("android/view/SurfaceSession");
+ gSurfaceSessionClassInfo.mNativeClient = env->GetFieldID(clazz, "mNativeClient", "I");
+ return 0;
+}
+
+} // namespace android
diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
index 9c6c7de..f8904bd 100644
--- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
@@ -326,7 +326,7 @@ not_valid_surface:
return 0;
}
- window = android_Surface_getNativeWindow(_env, native_window);
+ window = android_view_Surface_getNativeWindow(_env, native_window);
if (window == NULL)
goto not_valid_surface;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1c9b440..abb9c0f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -62,6 +62,7 @@
<protected-broadcast android:name="android.intent.action.MASTER_CLEAR_NOTIFICATION" />
<protected-broadcast android:name="android.intent.action.USER_ADDED" />
<protected-broadcast android:name="android.intent.action.USER_REMOVED" />
+ <protected-broadcast android:name="android.intent.action.USER_STOPPED" />
<protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
<protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
@@ -724,6 +725,13 @@
android:description="@string/permdesc_mediaStorageWrite"
android:protectionLevel="signature|system" />
+ <!-- Allows an application to access all multi-user external storage @hide -->
+ <permission android:name="android.permission.ACCESS_ALL_EXTERNAL_STORAGE"
+ android:permissionGroup="android.permission-group.DEVELOPMENT_TOOLS"
+ android:label="@string/permlab_sdcardAccessAll"
+ android:description="@string/permdesc_sdcardAccessAll"
+ android:protectionLevel="signature" />
+
<!-- ============================================ -->
<!-- Permissions for low-level system interaction -->
<!-- ============================================ -->
@@ -1662,7 +1670,6 @@
<!-- Package verifier needs to have this permission before the PackageManager will
trust it to verify packages.
- @hide
-->
<permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT"
android:label="@string/permlab_packageVerificationAgent"
diff --git a/core/res/res/anim/keyguard_security_animate_in.xml b/core/res/res/anim/keyguard_security_animate_in.xml
new file mode 100644
index 0000000..6e1e17a
--- /dev/null
+++ b/core/res/res/anim/keyguard_security_animate_in.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
+
+ <scale
+ android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromXScale="0.0"
+ android:toXScale="1.0"
+ android:fromYScale="1.0"
+ android:toYScale="1.0"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillAfter="true"
+ android:duration="@integer/flip_duration"
+ android:startOffset="@integer/flip_duration" />
+
+</set>
+
diff --git a/core/res/res/anim/keyguard_security_animate_out.xml b/core/res/res/anim/keyguard_security_animate_out.xml
new file mode 100644
index 0000000..5d65cd0
--- /dev/null
+++ b/core/res/res/anim/keyguard_security_animate_out.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
+
+ <scale
+ android:interpolator="@android:anim/accelerate_interpolator"
+ android:fromXScale="1.0"
+ android:toXScale="0.0"
+ android:fromYScale="1.0"
+ android:toYScale="1.0"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillAfter="true"
+ android:duration="@integer/flip_duration" />
+
+</set>
+
diff --git a/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.png b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.png
new file mode 100644
index 0000000..c30eb1c
--- /dev/null
+++ b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.png
Binary files differ
diff --git a/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.png b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.png
new file mode 100644
index 0000000..e5d5771
--- /dev/null
+++ b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.png
Binary files differ
diff --git a/core/res/res/layout-land/keyguard_host_view.xml b/core/res/res/layout-land/keyguard_host_view.xml
new file mode 100644
index 0000000..b404155
--- /dev/null
+++ b/core/res/res/layout-land/keyguard_host_view.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the host view that generally contains two sub views: the widget view
+ and the security view. -->
+<com.android.internal.policy.impl.keyguard.KeyguardHostView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_host_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager
+ android:id="@+id/app_widget_container"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:visibility="gone">
+
+ <!-- TODO: Remove this once supported as a widget -->
+ <include layout="@layout/keyguard_status_view"/>
+ </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
+
+
+ <ViewFlipper
+ android:id="@+id/view_flipper"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center">
+
+ <include layout="@layout/keyguard_selector_view"/>
+ <include layout="@layout/keyguard_account_view"/>
+ <include layout="@layout/keyguard_pattern_view"/>
+ <include layout="@layout/keyguard_password_view"/>
+ <include layout="@layout/keyguard_sim_pin_view"/>
+ <include layout="@layout/keyguard_sim_puk_view"/>
+ <include layout="@layout/keyguard_face_unlock_view"/>
+
+ </ViewFlipper>
+
+</com.android.internal.policy.impl.keyguard.KeyguardHostView>
diff --git a/core/res/res/layout-port/keyguard_host_view.xml b/core/res/res/layout-port/keyguard_host_view.xml
new file mode 100644
index 0000000..5dc2225
--- /dev/null
+++ b/core/res/res/layout-port/keyguard_host_view.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the host view that generally contains two sub views: the widget view
+ and the security view. -->
+<com.android.internal.policy.impl.keyguard.KeyguardHostView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_host_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:clipChildren="false">
+
+ <ViewFlipper
+ android:id="@+id/view_flipper"
+ android:layout_height="match_parent"
+ android:gravity="center">
+
+ <include layout="@layout/keyguard_selector_view"/>
+ <include layout="@layout/keyguard_account_view"/>
+ <include layout="@layout/keyguard_pattern_view"/>
+ <include layout="@layout/keyguard_password_view"/>
+ <include layout="@layout/keyguard_sim_pin_view"/>
+ <include layout="@layout/keyguard_sim_puk_view"/>
+ <include layout="@layout/keyguard_face_unlock_view"/>
+
+ </ViewFlipper>
+
+</com.android.internal.policy.impl.keyguard.KeyguardHostView>
+
diff --git a/core/res/res/layout-sw600dp-land/keyguard_host_view.xml b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
new file mode 100644
index 0000000..e77f584
--- /dev/null
+++ b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the host view that generally contains two sub views: the widget view
+ and the security view. -->
+<com.android.internal.policy.impl.keyguard.KeyguardHostView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_host_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager
+ android:id="@+id/app_widget_container"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:visibility="gone">
+
+ <!-- TODO: Remove this once supported as a widget -->
+ <include layout="@layout/keyguard_status_view"/>
+
+ </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
+
+ <FrameLayout
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center">
+
+ <ViewFlipper
+ android:id="@+id/view_flipper"
+ android:layout_width="@dimen/kg_security_view_width"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:layout_weight="1"
+ android:gravity="center">
+
+ <include layout="@layout/keyguard_selector_view"/>
+ <include layout="@layout/keyguard_account_view"/>
+ <include layout="@layout/keyguard_pattern_view"/>
+ <include layout="@layout/keyguard_password_view"/>
+ <include layout="@layout/keyguard_sim_pin_view"/>
+ <include layout="@layout/keyguard_sim_puk_view"/>
+ <include layout="@layout/keyguard_face_unlock_view"/>
+
+ </ViewFlipper>
+
+ </FrameLayout>
+
+</com.android.internal.policy.impl.keyguard.KeyguardHostView>
diff --git a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
new file mode 100644
index 0000000..50636f1
--- /dev/null
+++ b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the host view that generally contains two sub views: the widget view
+ and the security view. -->
+<com.android.internal.policy.impl.keyguard.KeyguardHostView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_host_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:clipChildren="false">
+
+ <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager
+ android:id="@+id/app_widget_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:visibility="gone">
+
+ <!-- TODO: Remove this once supported as a widget -->
+ <include layout="@layout/keyguard_status_view"/>
+
+ </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
+
+ <ViewFlipper
+ android:id="@+id/view_flipper"
+ android:layout_width="@dimen/kg_security_view_width"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:layout_gravity="center">
+
+ <include layout="@layout/keyguard_selector_view"/>
+ <include layout="@layout/keyguard_account_view"/>
+ <include layout="@layout/keyguard_pattern_view"/>
+ <include layout="@layout/keyguard_password_view"/>
+ <include layout="@layout/keyguard_sim_pin_view"/>
+ <include layout="@layout/keyguard_sim_puk_view"/>
+ <include layout="@layout/keyguard_face_unlock_view"/>
+
+ </ViewFlipper>
+
+</com.android.internal.policy.impl.keyguard.KeyguardHostView>
+
diff --git a/core/res/res/layout/keyguard_account_view.xml b/core/res/res/layout/keyguard_account_view.xml
new file mode 100644
index 0000000..481f0c1
--- /dev/null
+++ b/core/res/res/layout/keyguard_account_view.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.internal.policy.impl.keyguard.KeyguardAccountView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_account_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+
+ <EditText
+ android:id="@+id/login"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="7dip"
+ android:layout_marginEnd="7dip"
+ android:layout_alignParentTop="true"
+ android:hint="@string/kg_login_username_hint"
+ android:inputType="textEmailAddress"
+ />
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/login"
+ android:layout_marginTop="15dip"
+ android:layout_marginStart="7dip"
+ android:layout_marginEnd="7dip"
+ android:inputType="textPassword"
+ android:hint="@string/kg_login_password_hint"
+ android:nextFocusRight="@+id/ok"
+ android:nextFocusDown="@+id/ok"
+ />
+
+ <!-- ok below password, aligned to right of screen -->
+ <Button
+ android:id="@+id/ok"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="7dip"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentBottom="true"
+ android:text="@string/kg_login_submit_button"
+ />
+
+ </RelativeLayout>
+
+</com.android.internal.policy.impl.keyguard.KeyguardAccountView>
diff --git a/core/res/res/layout/keyguard_face_unlock_view.xml b/core/res/res/layout/keyguard_face_unlock_view.xml
new file mode 100644
index 0000000..572c013
--- /dev/null
+++ b/core/res/res/layout/keyguard_face_unlock_view.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+ the user how to unlock their device, or make an emergency call. This
+ is the portrait layout. -->
+<com.android.internal.policy.impl.keyguard.KeyguardFaceUnlockView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_face_unlock_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- TODO -->
+
+</com.android.internal.policy.impl.keyguard.KeyguardFaceUnlockView>
diff --git a/core/res/res/layout/keyguard_navigation.xml b/core/res/res/layout/keyguard_navigation.xml
new file mode 100644
index 0000000..569f93d
--- /dev/null
+++ b/core/res/res/layout/keyguard_navigation.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="left">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/back"
+ android:layout_width="20dip"
+ android:layout_height="wrap_content"
+ android:textSize="28dp"
+ android:text="@string/kg_temp_back_string" />
+
+ <!-- message area for security screen -->
+ <TextView
+ android:id="@+id/message_area"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:layout_marginEnd="6dip"
+ android:layout_marginStart="6dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+ </LinearLayout>
+
+ <!-- This is currently only uses for pattern unlock -->
+ <Button android:id="@+id/forgot_password_button"
+ android:layout_gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button"
+ android:drawablePadding="0dip"
+ android:visibility="gone"/>
+
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_password_view.xml b/core/res/res/layout/keyguard_password_view.xml
new file mode 100644
index 0000000..b9a70e5
--- /dev/null
+++ b/core/res/res/layout/keyguard_password_view.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<com.android.internal.policy.impl.keyguard.KeyguardPasswordView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_password_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"/>
+
+ <!-- Password entry field -->
+ <!-- Note: the entire container is styled to look like the edit field,
+ since the backspace/IME switcher looks better inside -->
+ <LinearLayout
+ android:layout_gravity="center_vertical|fill_horizontal"
+ android:layout_width="match_parent"
+ android:orientation="horizontal"
+ android:background="@*android:drawable/lockscreen_password_field_dark"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip">
+
+ <EditText android:id="@+id/passwordEntry"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="center_horizontal"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="@*android:dimen/keyguard_lockscreen_pin_margin_left"
+ android:singleLine="true"
+ android:textStyle="normal"
+ android:inputType="textPassword"
+ android:textSize="36sp"
+ android:background="@null"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="#ffffffff"
+ android:imeOptions="flagForceAscii|actionDone"
+ />
+
+ <!-- This delete button is only visible for numeric PIN entry -->
+ <ImageButton android:id="@+id/delete_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@*android:drawable/ic_input_delete"
+ android:clickable="true"
+ android:padding="8dip"
+ android:layout_gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone"
+ />
+
+ <ImageView android:id="@+id/switch_ime_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@*android:drawable/ic_lockscreen_ime"
+ android:clickable="true"
+ android:padding="8dip"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone"
+ />
+
+ </LinearLayout>
+
+ <!-- Numeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_width="match_parent"
+ android:layout_marginStart="4dip"
+ android:layout_marginEnd="4dip"
+ android:paddingTop="4dip"
+ android:paddingBottom="4dip"
+ android:background="#40000000"
+ android:keyBackground="@*android:drawable/btn_keyboard_key_ics"
+ android:visibility="gone"
+ android:clickable="true"
+ />
+
+</com.android.internal.policy.impl.keyguard.KeyguardPasswordView>
diff --git a/core/res/res/layout/keyguard_pattern_view.xml b/core/res/res/layout/keyguard_pattern_view.xml
new file mode 100644
index 0000000..9cba609
--- /dev/null
+++ b/core/res/res/layout/keyguard_pattern_view.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+ the user how to unlock their device, or make an emergency call. This
+ is the portrait layout. -->
+<com.android.internal.policy.impl.keyguard.KeyguardPatternView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_pattern_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <GridLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <Space android:layout_gravity="fill" />
+
+ <!-- We need MATCH_PARENT here only to force the size of the parent to be passed to
+ the pattern view for it to compute its size. This is an unusual case, caused by
+ LockPatternView's requirement to maintain a square aspect ratio based on the width
+ of the screen. -->
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPatternView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="8dip"
+ android:layout_marginBottom="4dip"
+ android:layout_marginStart="8dip"
+ android:layout_gravity="center_horizontal"
+ />
+
+ </GridLayout>
+
+</com.android.internal.policy.impl.keyguard.KeyguardPatternView>
diff --git a/core/res/res/layout/keyguard_selector_view.xml b/core/res/res/layout/keyguard_selector_view.xml
new file mode 100644
index 0000000..d516369
--- /dev/null
+++ b/core/res/res/layout/keyguard_selector_view.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the selector widget that allows the user to select an action. -->
+<com.android.internal.policy.impl.keyguard.KeyguardSelectorView
+ xmlns:prvandroid="http://schemas.android.com/apk/prv/res/android"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_selector_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager
+ android:id="@+id/app_widget_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:visibility="gone">
+ <!-- TODO: Remove this once supported as a widget -->
+ <include layout="@layout/keyguard_status_view"/>
+ </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:layout_gravity="center"
+ android:gravity="center">
+
+ <com.android.internal.widget.multiwaveview.GlowPadView
+ android:id="@+id/glow_pad_view"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+
+ prvandroid:targetDrawables="@*android:array/lockscreen_targets_with_camera"
+ prvandroid:targetDescriptions="@*android:array/lockscreen_target_descriptions_with_camera"
+ prvandroid:directionDescriptions="@*android:array/lockscreen_direction_descriptions"
+ prvandroid:handleDrawable="@*android:drawable/ic_lockscreen_handle"
+ prvandroid:outerRingDrawable="@*android:drawable/ic_lockscreen_outerring"
+ prvandroid:outerRadius="@*android:dimen/glowpadview_target_placement_radius"
+ prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
+ prvandroid:snapMargin="@*android:dimen/glowpadview_snap_margin"
+ prvandroid:feedbackCount="1"
+ prvandroid:vibrationDuration="20"
+ prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius"
+ prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"/>
+
+ <Button
+ android:id="@+id/emergency_call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:drawableLeft="@*android:drawable/lockscreen_emergency_button"
+ android:text="@string/kg_emergency_call_label"
+ style="?android:attr/buttonBarButtonStyle"
+ android:drawablePadding="8dip"
+ android:layout_alignRight="@id/glow_pad_view"
+ android:layout_alignTop="@id/glow_pad_view"
+ />
+
+ </RelativeLayout>
+
+</com.android.internal.policy.impl.keyguard.KeyguardSelectorView>
+
diff --git a/core/res/res/layout/keyguard_sim_pin_view.xml b/core/res/res/layout/keyguard_sim_pin_view.xml
new file mode 100644
index 0000000..122484a
--- /dev/null
+++ b/core/res/res/layout/keyguard_sim_pin_view.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<!-- This is the SIM PIN view that allows the user to enter a SIM PIN to unlock the device. -->
+<com.android.internal.policy.impl.keyguard.KeyguardSimPinView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_sim_pin_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"/>
+
+ <!-- Password entry field -->
+ <!-- Note: the entire container is styled to look like the edit field,
+ since the backspace/IME switcher looks better inside -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="6dip"
+ android:layout_marginStart="6dip"
+ android:gravity="center_vertical"
+ android:background="@android:drawable/edit_text">
+
+ <!-- displays dots as user enters pin -->
+ <EditText android:id="@+id/sim_pin_entry"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLargeInverse"
+ android:textColor="@*android:color/primary_text_holo_light"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ android:imeOptions="flagForceAscii|actionDone"
+ />
+
+ <ImageButton android:id="@+id/delete_button"
+ android:src="@android:drawable/ic_input_delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="-3dip"
+ android:layout_marginBottom="-3dip"
+ />
+ </LinearLayout>
+
+ <!-- Numeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_width="match_parent"
+ android:layout_marginStart="4dip"
+ android:layout_marginEnd="4dip"
+ android:paddingTop="4dip"
+ android:paddingBottom="4dip"
+ android:background="#80ffffff"
+ android:keyBackground="@*android:drawable/btn_keyboard_key_ics"
+ android:clickable="true"
+ />
+
+</com.android.internal.policy.impl.keyguard.KeyguardSimPinView>
diff --git a/core/res/res/layout/keyguard_sim_puk_view.xml b/core/res/res/layout/keyguard_sim_puk_view.xml
new file mode 100644
index 0000000..8bb76c1
--- /dev/null
+++ b/core/res/res/layout/keyguard_sim_puk_view.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<com.android.internal.policy.impl.keyguard.KeyguardSimPukView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_sim_puk_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center_horizontal">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"/>
+
+ <LinearLayout android:id="@+id/topDisplayGroup"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:paddingEnd="0dip"
+ android:layout_marginEnd="10dip"
+ android:layout_marginStart="10dip">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="6dip"
+ android:layout_marginStart="6dip"
+ android:gravity="center_vertical"
+ android:background="@*android:drawable/edit_text">
+
+ <!-- displays dots as user enters puk -->
+ <EditText android:id="@+id/sim_puk_entry"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ android:textColor="#000"
+ android:hint="@string/kg_puk_enter_puk_hint"
+ />
+
+ <ImageButton android:id="@+id/puk_delete_button"
+ android:src="@*android:drawable/ic_input_delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="-3dip"
+ android:layout_marginBottom="-3dip"
+ />
+
+ </LinearLayout>
+
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="6dip"
+ android:layout_marginStart="6dip"
+ android:gravity="center_vertical"
+ android:background="@*android:drawable/edit_text">
+
+ <!-- displays dots as user enters new pin -->
+ <EditText android:id="@+id/sim_pin_entry"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ android:textColor="#000"
+ android:hint="@string/kg_puk_enter_pin_hint"
+ />
+
+ <ImageButton android:id="@+id/pin_delete_button"
+ android:src="@*android:drawable/ic_input_delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="-3dip"
+ android:layout_marginBottom="-3dip"
+ />
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Numeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_width="match_parent"
+ android:layout_marginStart="4dip"
+ android:layout_marginEnd="4dip"
+ android:paddingTop="4dip"
+ android:paddingBottom="4dip"
+ android:background="#80ffffff"
+ android:keyBackground="@*android:drawable/btn_keyboard_key_ics"
+ android:clickable="true"
+ />
+
+</com.android.internal.policy.impl.keyguard.KeyguardSimPukView>
diff --git a/core/res/res/layout/keyguard_status_view.xml b/core/res/res/layout/keyguard_status_view.xml
new file mode 100644
index 0000000..f8d05b7
--- /dev/null
+++ b/core/res/res/layout/keyguard_status_view.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is a view that shows general status information in Keyguard. -->
+<com.android.internal.policy.impl.keyguard.KeyguardWidgetFrame
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal">
+
+ <com.android.internal.policy.impl.keyguard.KeyguardStatusView
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal">
+
+ <com.android.internal.widget.DigitalClock android:id="@+id/time"
+ android:layout_marginTop="@*android:dimen/keyguard_lockscreen_status_line_clockfont_top_margin"
+ android:layout_marginBottom="12dip"
+ android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:layout_gravity="end">
+
+ <!-- Because we can't have multi-tone fonts, we render two TextViews, one on
+ top of the other. Hence the redundant layout... -->
+ <TextView android:id="@*android:id/timeDisplayBackground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="@*android:dimen/keyguard_lockscreen_clock_font_size"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_marginBottom="6dip"
+ android:textColor="@*android:color/lockscreen_clock_background"
+ />
+
+ <TextView android:id="@*android:id/timeDisplayForeground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="@*android:dimen/keyguard_lockscreen_clock_font_size"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_marginBottom="6dip"
+ android:textColor="@*android:color/lockscreen_clock_foreground"
+ android:layout_alignStart="@*android:id/timeDisplayBackground"
+ android:layout_alignTop="@*android:id/timeDisplayBackground"
+ />
+
+ </com.android.internal.widget.DigitalClock>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_gravity="end"
+ android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin">
+
+ <TextView
+ android:id="@*android:id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ />
+
+ <TextView
+ android:id="@*android:id/alarm_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@*android:id/status1"
+ android:layout_gravity="end"
+ android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ />
+
+ <TextView
+ android:id="@*android:id/carrier"
+ android:layout_gravity="end"
+ android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ />
+
+ </com.android.internal.policy.impl.keyguard.KeyguardStatusView>
+</com.android.internal.policy.impl.keyguard.KeyguardWidgetFrame> \ No newline at end of file
diff --git a/core/res/res/layout/overlay_display_window.xml b/core/res/res/layout/overlay_display_window.xml
new file mode 100644
index 0000000..25c792a
--- /dev/null
+++ b/core/res/res/layout/overlay_display_window.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextureView android:id="@+id/overlay_display_window_texture"
+ android:layout_width="0px"
+ android:layout_height="0px" />
+ <TextView android:id="@+id/overlay_display_window_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|center_horizontal" />
+</FrameLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 560a6a8..7aa8bf0 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN is geaktiveer deur <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Raak om die netwerk te bestuur."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Gekoppel aan <xliff:g id="SESSION">%s</xliff:g>. Raak om die netwerk te bestuur."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Kies lêer"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Geen lêer gekies nie"</string>
<string name="reset" msgid="2448168080964209908">"Stel terug"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index fc6d4b8..3068fb9 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -225,7 +225,7 @@
<string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"በተለያዩ ተጠቃሚዎች መካከል መስተጋብር ለመፍጠር ሙሉ ፍቃድ"</string>
<string name="permdesc_interactAcrossUsersFull" msgid="376841368395502366">"በተለያዩ ተጠቃሚዎች ላይ ሊኖሩ የሚችሉ መስተጋብሮችን ሁሉ ይፈቅዳል።"</string>
<string name="permlab_manageUsers" msgid="1676150911672282428">"ተጠቃሚዎችን ያስተዳድሩ"</string>
- <string name="permdesc_manageUsers" msgid="8409306667645355638">"መተግበሪያዎች በመሣሪያዎች ላይ ያሉ ተጠቃሚዎችን እንዲያቀናብር ያስችለዋል፣ መጠየቅን፣ መፍጠርንና መሰረዝን ጨምሮ።"</string>
+ <string name="permdesc_manageUsers" msgid="8409306667645355638">"መተግበሪያዎች በመሣሪያው ላይ ያሉ ተጠቃሚዎችን እንዲያቀናብር ያስችለዋል፣ መጠየቅን፣ መፍጠርንና መሰረዝን ጨምሮ።"</string>
<string name="permlab_getDetailedTasks" msgid="6229468674753529501">"እየሄዱ ስላሉ የመተግበሪያዎች ዝርዝሮች አምጣ"</string>
<string name="permdesc_getDetailedTasks" msgid="153824741440717599">"መተግበሪያው በአሁኑ ጊዜ እየተካሄዱ ስላሉና በቅርብ ጊዜ ስለተካሄዱ ተግባሮች መረጃ ዝርዝር እንዲያወጣ ይፈቅድለታል። ተንኮል-አዘል መተግበሪያዎች ስለ ሌሎች መተግበሪያዎች የግል መረጃ ሊያገኙ ይችላሉ።"</string>
<string name="permlab_reorderTasks" msgid="2018575526934422779">"አሂድ ትግበራዎችን ድጋሚ ደርድር"</string>
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN በ<xliff:g id="APP">%s</xliff:g>ገብሯል"</string>
<string name="vpn_text" msgid="3011306607126450322">"አውታረመረብ ለማደራጀት ንካ።"</string>
<string name="vpn_text_long" msgid="6407351006249174473">"ለ<xliff:g id="SESSION">%s</xliff:g>የተገናኘ። አውታረመረቡን ለማደራጀት ንካ።"</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"ፋይል ምረጥ"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ምንም ፋይል አልተመረጠም"</string>
<string name="reset" msgid="2448168080964209908">"ዳግም አስጀምር"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index d3b8f04..df3d114 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"تم تنشيط VPN بواسطة <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"المس لإدارة الشبكة."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"تم الاتصال بـ <xliff:g id="SESSION">%s</xliff:g>. المس لإدارة الشبكة."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"اختيار ملف"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"لم يتم اختيار أي ملف"</string>
<string name="reset" msgid="2448168080964209908">"إعادة تعيين"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index d801628..55621b3 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN актывуецца прыкладаннем <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Дакраніцеся, каб кіраваць сеткай."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Падлучаны да сеанса \"<xliff:g id="SESSION">%s</xliff:g>\". Дакраніцеся, каб кiраваць сеткай."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Выберыце файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Файл не выбраны"</string>
<string name="reset" msgid="2448168080964209908">"Скінуць"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 7e7c4e6..4aea25f 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN е активирана от <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Докоснете за управление на мрежата."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Свързана със: <xliff:g id="SESSION">%s</xliff:g>. Докоснете, за да управлявате мрежата."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Избор на файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Няма избран файл"</string>
<string name="reset" msgid="2448168080964209908">"Повторно задаване"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 0781217..097e86c 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"<xliff:g id="APP">%s</xliff:g> ha activat VPN"</string>
<string name="vpn_text" msgid="3011306607126450322">"Toca per gestionar la xarxa."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Connectat a <xliff:g id="SESSION">%s</xliff:g>. Toca per gestionar la xarxa."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Trieu un fitxer"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No s\'ha escollit cap fitxer"</string>
<string name="reset" msgid="2448168080964209908">"Reinicia"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 50cd013..c7f3f88 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Aplikace <xliff:g id="APP">%s</xliff:g> aktivovala síť VPN"</string>
<string name="vpn_text" msgid="3011306607126450322">"Dotykem zobrazíte správu sítě."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Připojeno k relaci <xliff:g id="SESSION">%s</xliff:g>. Dotykem můžete síť spravovat."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Zvolit soubor"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Není vybrán žádný soubor"</string>
<string name="reset" msgid="2448168080964209908">"Resetovat"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index ea40879..d390c3f 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN aktiveres af <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Tryk for at administrere netværket."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Forbundet til <xliff:g id="SESSION">%s</xliff:g>. Tryk for at administrere netværket."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
<string name="reset" msgid="2448168080964209908">"Nulstil"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index a0280c8..1bd213e 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN wurde von <xliff:g id="APP">%s</xliff:g> aktiviert."</string>
<string name="vpn_text" msgid="3011306607126450322">"Zum Verwalten des Netzwerks berühren"</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Verbunden mit <xliff:g id="SESSION">%s</xliff:g>. Zum Verwalten des Netzwerks berühren"</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Datei auswählen"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Keine ausgewählt"</string>
<string name="reset" msgid="2448168080964209908">"Zurücksetzen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 82f0c62..75ceaed 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Το VPN ενεργοποιήθηκε από την εφαρμογή <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Αγγίξτε για τη διαχείριση του δικτύου."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Συνδέθηκε με <xliff:g id="SESSION">%s</xliff:g>. Αγγίξτε για τη διαχείριση του δικτύου."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Επιλογή αρχείου"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Δεν έχει επιλεγεί αρχείο"</string>
<string name="reset" msgid="2448168080964209908">"Επαναφορά"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 4f7b087..4fe04fb 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN is activated by <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Touch to manage the network."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Connected to <xliff:g id="SESSION">%s</xliff:g>. Touch to manage the network."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Choose file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No file chosen"</string>
<string name="reset" msgid="2448168080964209908">"Reset"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 00d8617..170e2f1 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -224,8 +224,8 @@
<string name="permdesc_interactAcrossUsers" msgid="364670963623385786">"Permite que la aplicación lleve a cabo acciones entre los diferentes usuarios del dispositivo. Las aplicaciones maliciosas pueden utilizar este permiso para infringir la protección entre usuarios."</string>
<string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"Licencia completa para interactuar con los usuarios"</string>
<string name="permdesc_interactAcrossUsersFull" msgid="376841368395502366">"Permite todas las interacciones posibles con los usuarios."</string>
- <string name="permlab_manageUsers" msgid="1676150911672282428">"Administrar usuarios"</string>
- <string name="permdesc_manageUsers" msgid="8409306667645355638">"Permite a las aplicaciones administrar a los usuarios del dispositivo, incluidas la creación y la eliminación de consultas."</string>
+ <string name="permlab_manageUsers" msgid="1676150911672282428">"administrar usuarios"</string>
+ <string name="permdesc_manageUsers" msgid="8409306667645355638">"Permite a las aplicaciones administrar los usuarios del dispositivo, lo que incluye buscarlos, crearlos y eliminarlos."</string>
<string name="permlab_getDetailedTasks" msgid="6229468674753529501">"recuperar información sobre las aplicaciones en ejecución"</string>
<string name="permdesc_getDetailedTasks" msgid="153824741440717599">"Permite que la aplicación recupere información detallada sobre tareas en ejecución y recientemente ejecutadas. Las aplicaciones malintencionadas pueden hallar información privada sobre otras aplicaciones."</string>
<string name="permlab_reorderTasks" msgid="2018575526934422779">"reorganizar aplicaciones en ejecución"</string>
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN está activado por <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Toca para administrar la red."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toca para administrar la red."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Elegir archivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No se seleccionó un archivo."</string>
<string name="reset" msgid="2448168080964209908">"Restablecer"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 5e63311..02551e3 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN activada por <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Toca para administrar la red."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toca para administrar la red."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Seleccionar archivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Archivo no seleccionado"</string>
<string name="reset" msgid="2448168080964209908">"Restablecer"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 8280b61..c15fae7 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN-i aktiveeris <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Võrgu haldamiseks puudutage."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Ühendatud seansiga <xliff:g id="SESSION">%s</xliff:g>. Võrgu haldamiseks puudutage."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Valige fail"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ühtegi faili pole valitud"</string>
<string name="reset" msgid="2448168080964209908">"Lähtesta"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 0dba1bd..c55eb07 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN توسط <xliff:g id="APP">%s</xliff:g> فعال شده است"</string>
<string name="vpn_text" msgid="3011306607126450322">"برای مدیریت شبکه لمس کنید."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"به <xliff:g id="SESSION">%s</xliff:g> وصل شد. برای مدیریت شبکه لمس کنید."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"انتخاب فایل"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"هیچ فایلی انتخاب نشد"</string>
<string name="reset" msgid="2448168080964209908">"بازنشانی"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index f6ab9f8..ee536a6 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"<xliff:g id="APP">%s</xliff:g> on aktivoinut VPN-yhteyden"</string>
<string name="vpn_text" msgid="3011306607126450322">"Voit hallinnoida verkkoa koskettamalla."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Yhdistetty: <xliff:g id="SESSION">%s</xliff:g>. Hallinnoi verkkoa koskettamalla."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Valitse tiedosto"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ei valittua tiedostoa"</string>
<string name="reset" msgid="2448168080964209908">"Palauta"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index d4d938b..63a19f4 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN activé par <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Appuyez ici pour gérer le réseau."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Connecté à <xliff:g id="SESSION">%s</xliff:g>. Appuyez ici pour gérer le réseau."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Sélectionner un fichier"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Aucun fichier sélectionné"</string>
<string name="reset" msgid="2448168080964209908">"Réinitialiser"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index b491e97..b2ab326 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -468,7 +468,7 @@
<string name="permdesc_factoryTest" product="default" msgid="8136644990319244802">"फ़ोन हार्डवेयर में पूर्ण पहुंच की अनुमति देते हुए निम्‍न-स्‍तर निर्माता परीक्षण के रूप में चलाएं. केवल तभी उपलब्‍ध जब कोई फ़ोन निर्माता परीक्षण मोड में चल रहा हो."</string>
<string name="permlab_setWallpaper" msgid="6627192333373465143">"वॉलपेपर सेट करें"</string>
<string name="permdesc_setWallpaper" msgid="7373447920977624745">"एप्‍लिकेशन को सिस्‍टम वॉलपेपर सेट करने देता है."</string>
- <string name="permlab_setWallpaperHints" msgid="3278608165977736538">"अपने वॉलपेपर का आकार समायोजित करें"</string>
+ <string name="permlab_setWallpaperHints" msgid="3278608165977736538">"अपने वॉलपेपर का आकार एडजस्ट करें"</string>
<string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"एप्‍लिकेशन को सिस्‍टम वॉलपेपर आकार संकेत सेट करने देता है."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"फ़ैक्‍ट्री डिफ़ॉल्‍ट पर सिस्‍टम रीसेट करें"</string>
<string name="permdesc_masterClear" msgid="3665380492633910226">"एप्लिकेशन को सभी डेटा, कॉन्फ़िगरेशन, और इंस्टॉल एप्लिकेशन मिटाकर, सिस्टम को पूरी तरह उसकी फ़ैक्टरी सेटिंग पर रीसेट करने देता है."</string>
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN को <xliff:g id="APP">%s</xliff:g> द्वारा सक्रिय किया गया है"</string>
<string name="vpn_text" msgid="3011306607126450322">"नेटवर्क प्रबंधित करने के लिए स्‍पर्श करें."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"<xliff:g id="SESSION">%s</xliff:g> से कनेक्‍ट किया गया. नेटवर्क प्रबंधित करने के लिए स्‍पर्श करें."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"फ़ाइल चुनें"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"कोई फ़ाइल चुनी नहीं गई"</string>
<string name="reset" msgid="2448168080964209908">"रीसेट करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index aef3bdf..2dd59e0 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Aplikacija <xliff:g id="APP">%s</xliff:g> aktivirala je VPN"</string>
<string name="vpn_text" msgid="3011306607126450322">"Dodirnite za upravljanje mrežom."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Povezan sa sesijom <xliff:g id="SESSION">%s</xliff:g>. Dodirnite za upravljanje mrežom."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Odaberite datoteku"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nema odabranih datoteka"</string>
<string name="reset" msgid="2448168080964209908">"Ponovo postavi"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 90bd85e..69dae8a 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"A(z) <xliff:g id="APP">%s</xliff:g> aktiválta a VPN-t"</string>
<string name="vpn_text" msgid="3011306607126450322">"Érintse meg a hálózat kezeléséhez."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Csatlakozva ide: <xliff:g id="SESSION">%s</xliff:g>. Érintse meg a hálózat kezeléséhez."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Fájl kiválasztása"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nincs fájl kiválasztva"</string>
<string name="reset" msgid="2448168080964209908">"Alaphelyzet"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 34e6a96..f8e8fc1 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN diaktifkan oleh <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Sentuh untuk mengelola jaringan."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Tersambung ke <xliff:g id="SESSION">%s</xliff:g>. Sentuh untuk mengelola jaringan."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Pilih file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Tidak ada file yang dipilih"</string>
<string name="reset" msgid="2448168080964209908">"Setel ulang"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index a693dc8..9a7c439 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN attivata da <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Tocca per gestire la rete."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Collegata a <xliff:g id="SESSION">%s</xliff:g>. Tocca per gestire la rete."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Scegli file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nessun file è stato scelto"</string>
<string name="reset" msgid="2448168080964209908">"Reimposta"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index a318907..2ae5206 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN מופעל על ידי <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"גע כדי לנהל את הרשת."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"מחובר אל <xliff:g id="SESSION">%s</xliff:g>. גע כדי לנהל את הרשת."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"בחר קובץ"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"לא נבחר קובץ"</string>
<string name="reset" msgid="2448168080964209908">"איפוס"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index b7a9c3b..e94ebe7 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPNが<xliff:g id="APP">%s</xliff:g>により有効化されました"</string>
<string name="vpn_text" msgid="3011306607126450322">"タップしてネットワークを管理します。"</string>
<string name="vpn_text_long" msgid="6407351006249174473">"<xliff:g id="SESSION">%s</xliff:g>に接続しました。ネットワークを管理するにはタップしてください。"</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"ファイルを選択"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ファイルが選択されていません"</string>
<string name="reset" msgid="2448168080964209908">"リセット"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 0193ca2..4609a80 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -794,7 +794,7 @@
<string name="autofill_department" msgid="5343279462564453309">"지역"</string>
<string name="autofill_prefecture" msgid="2028499485065800419">"현"</string>
<string name="autofill_parish" msgid="8202206105468820057">"군"</string>
- <string name="autofill_area" msgid="3547409050889952423">"구역"</string>
+ <string name="autofill_area" msgid="3547409050889952423">"주소"</string>
<string name="autofill_emirate" msgid="2893880978835698818">"에미리트"</string>
<string name="permlab_readHistoryBookmarks" msgid="3775265775405106983">"웹 북마크 및 기록 읽기"</string>
<string name="permdesc_readHistoryBookmarks" msgid="8462378226600439658">"앱이 브라우저가 방문한 모든 URL의 기록과 모든 브라우저 북마크를 읽을 수 있도록 허용합니다. 참고: 이 권한은 타사 브라우저 또는 브라우저 기능을 가진 기타 애플리케이션에 적용되지 않습니다."</string>
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN이 <xliff:g id="APP">%s</xliff:g>에 의해 활성화됨"</string>
<string name="vpn_text" msgid="3011306607126450322">"네트워크를 관리하려면 터치하세요."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"<xliff:g id="SESSION">%s</xliff:g>에 연결되어 있습니다. 네트워크를 관리하려면 터치하세요."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"파일 선택"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"파일을 선택하지 않았습니다."</string>
<string name="reset" msgid="2448168080964209908">"초기화"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index b778d03..ad2e4cd 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN suaktyvino „<xliff:g id="APP">%s</xliff:g>“"</string>
<string name="vpn_text" msgid="3011306607126450322">"Palieskite, kad valdytumėte tinklą."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Prisijungta prie <xliff:g id="SESSION">%s</xliff:g>. Jei norite valdyti tinklą, palieskite."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Pasirinkti failą"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nepasirinktas joks failas"</string>
<string name="reset" msgid="2448168080964209908">"Atstatyti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 4b78bd4..5a5c14b 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -225,7 +225,7 @@
<string name="permlab_interactAcrossUsersFull" msgid="2567734285545074105">"pilna licence ar atļauju darboties visos lietotāju kontos"</string>
<string name="permdesc_interactAcrossUsersFull" msgid="376841368395502366">"Ļauj veikt jebkādas darbības visos lietotāju kontos."</string>
<string name="permlab_manageUsers" msgid="1676150911672282428">"Lietotāju pārvaldība"</string>
- <string name="permdesc_manageUsers" msgid="8409306667645355638">"Ļauj lietotnēm pārvaldīt ierīces lietotājus, tostarp izveidot un dzēst lietotājus vai veidot vaicājumus."</string>
+ <string name="permdesc_manageUsers" msgid="8409306667645355638">"Ļauj lietotnēm pārvaldīt ierīces lietotājus, tostarp izveidot un dzēst lietotājus un veidot vaicājumus."</string>
<string name="permlab_getDetailedTasks" msgid="6229468674753529501">"Informācijas izguve par izmantotajām lietotnēm"</string>
<string name="permdesc_getDetailedTasks" msgid="153824741440717599">"Ļauj lietotnei izgūt informāciju par šobrīd un nesen veiktajiem uzdevumiem. Ļaunprātīgas lietotnes var atklāt privātu informāciju par citām lietotnēm."</string>
<string name="permlab_reorderTasks" msgid="2018575526934422779">"pārkārtot izmantotās lietotnes"</string>
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Lietojumprogramma <xliff:g id="APP">%s</xliff:g> aktivizēja VPN."</string>
<string name="vpn_text" msgid="3011306607126450322">"Pieskarieties, lai pārvaldītu tīklu."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Ir izveidots savienojums ar <xliff:g id="SESSION">%s</xliff:g>. Pieskarieties, lai pārvaldītu tīklu."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Izvēlēties failu"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Neviens fails nav izvēlēts"</string>
<string name="reset" msgid="2448168080964209908">"Atiestatīt"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 782c2a7..2ac167f 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN diaktifkan oleh <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Sentuh untuk mengurus rangkaian."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Bersambung ke <xliff:g id="SESSION">%s</xliff:g>. Sentuh untuk mengurus rangkaian."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Pilih fail"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Tiada fail dipilih"</string>
<string name="reset" msgid="2448168080964209908">"Tetapkan semula"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 3a4212b..7012793 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN er aktivert av <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Trykk for å administrere nettverket."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Koblet til <xliff:g id="SESSION">%s</xliff:g>. Trykk for å administrere nettverket."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Velg fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
<string name="reset" msgid="2448168080964209908">"Tilbakestill"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 89e52a9..8178980 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN wordt geactiveerd door <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Raak aan om het netwerk te beheren."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Verbonden met <xliff:g id="SESSION">%s</xliff:g>. Tik om het netwerk te beheren."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Bestand kiezen"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Geen bestand geselecteerd"</string>
<string name="reset" msgid="2448168080964209908">"Opnieuw instellen"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index f0dcc2f..c907fe6 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Obsługa sieci VPN została włączona przez aplikację <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Dotknij, aby zarządzać siecią."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Nawiązano połączenie z: <xliff:g id="SESSION">%s</xliff:g>. Dotknij, aby zarządzać siecią."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Wybierz plik"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nie wybrano pliku"</string>
<string name="reset" msgid="2448168080964209908">"Resetuj"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 18c25a3..7541db8 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"A VPN foi ativada pelo <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Toque para gerir a rede."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Ligado a <xliff:g id="SESSION">%s</xliff:g>. Toque para gerir a rede."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Escolher ficheiro"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Não foi selecionado nenhum ficheiro"</string>
<string name="reset" msgid="2448168080964209908">"Repor"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 794af68..d8a5bba 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"A VPN está ativada por <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Toque para gerenciar a rede."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Conectado a <xliff:g id="SESSION">%s</xliff:g>. Toque para gerenciar a rede."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Escolher arquivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nenhum arquivo escolhido"</string>
<string name="reset" msgid="2448168080964209908">"Redefinir"</string>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index a093bfb..9ffa164 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -1799,6 +1799,14 @@
<skip />
<!-- no translation found for vpn_text_long (6407351006249174473) -->
<skip />
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Tscherner ina datoteca"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nagina datoteca tschernida"</string>
<string name="reset" msgid="2448168080964209908">"Reinizialisar"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 6f6272d..23c1491 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN este activată de <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Atingeţi pentru a gestiona reţeaua."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Conectat la <xliff:g id="SESSION">%s</xliff:g>. Atingeţi pentru a gestiona reţeaua."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Alegeţi un fişier"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nu au fost găsite fişiere"</string>
<string name="reset" msgid="2448168080964209908">"Resetaţi"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index a9c0701..98dc250 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Сеть VPN активирована приложением <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Нажмите, чтобы открыть настройки."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Сеть VPN подключена: <xliff:g id="SESSION">%s</xliff:g>. Нажмите, чтобы открыть настройки."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Выбрать файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Не выбран файл"</string>
<string name="reset" msgid="2448168080964209908">"Сбросить"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 6240be3..37395a8 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Aplikáciu <xliff:g id="APP">%s</xliff:g> aktivovala sieť VPN"</string>
<string name="vpn_text" msgid="3011306607126450322">"Dotykom môžete spravovať sieť."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Pripojené k relácii <xliff:g id="SESSION">%s</xliff:g>. Po dotyku môžete sieť spravovať."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Zvoliť súbor"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nie je vybratý žiadny súbor"</string>
<string name="reset" msgid="2448168080964209908">"Obnoviť"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 1ede6b7..48fa35d 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN je aktiviral program <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Dotaknite se, če želite upravljati omrežje."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Vzpostavljena povezava s sejo <xliff:g id="SESSION">%s</xliff:g>. Dotaknite se, če želite upravljati omrežje."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Izberi datoteko"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nobena datoteka ni izbrana"</string>
<string name="reset" msgid="2448168080964209908">"Ponastavi"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 4ddde21..259cb15 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Апликација <xliff:g id="APP">%s</xliff:g> је активирала VPN"</string>
<string name="vpn_text" msgid="3011306607126450322">"Додирните да бисте управљали мрежом."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Повезано са сесијом <xliff:g id="SESSION">%s</xliff:g>. Додирните да бисте управљали мрежом."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Одабери датотеку"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Није изабрана ниједна датотека"</string>
<string name="reset" msgid="2448168080964209908">"Поново постави"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 493a0bb..10bdd18 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN aktiveras av <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Tryck om du vill hantera nätverket."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Ansluten till <xliff:g id="SESSION">%s</xliff:g>. Knacka lätt om du vill hantera nätverket."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Välj fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil har valts"</string>
<string name="reset" msgid="2448168080964209908">"Återställ"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 759000d..5f20617 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN imeamilishwa na <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Gusa ili kudhibiti mtandao."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Imeunganishwa kwa <xliff:g id="SESSION">%s</xliff:g>. Gusa ili kudhibiti mtandao."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Chagua faili"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Hakuna faili iliyochaguliwa"</string>
<string name="reset" msgid="2448168080964209908">"Weka upya"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index efb1d6f..40d4eb6 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"เปิดใช้งาน VPN โดย <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"แตะเพื่อจัดการเครือข่าย"</string>
<string name="vpn_text_long" msgid="6407351006249174473">"เชื่อมต่อกับ <xliff:g id="SESSION">%s</xliff:g> แตะเพื่อจัดการเครือข่าย"</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"เลือกไฟล์"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ไม่ได้เลือกไฟล์ไว้"</string>
<string name="reset" msgid="2448168080964209908">"รีเซ็ต"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 261a0b9..036a30e 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Isinaaktibo ang VPN ng <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Pindutin upang pamahalaan ang network."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Nakakonekta sa <xliff:g id="SESSION">%s</xliff:g>. Pindutin upang pamahalaan ang network."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Pumili ng file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Walang napiling file"</string>
<string name="reset" msgid="2448168080964209908">"I-reset"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index c60535f..2848eea 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN, <xliff:g id="APP">%s</xliff:g> tarafından etkinleştirildi"</string>
<string name="vpn_text" msgid="3011306607126450322">"Ağı yönetmek için dokunun."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"<xliff:g id="SESSION">%s</xliff:g> oturumuna bağlandı. Ağı yönetmek için dokunun."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Dosya seç"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Seçili dosya yok"</string>
<string name="reset" msgid="2448168080964209908">"Sıfırla"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index a8633e8..ee3f8da 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"Мережу VPN активовано програмою <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Торкніться, щоб керувати мережею."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Під’єднано до сеансу <xliff:g id="SESSION">%s</xliff:g>. Торкніться, щоб керувати мережею."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Виберіть файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Не вибрано файл"</string>
<string name="reset" msgid="2448168080964209908">"Віднов."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 012a47a..b5c17ae 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"VPN được <xliff:g id="APP">%s</xliff:g> kích hoạt"</string>
<string name="vpn_text" msgid="3011306607126450322">"Chạm để quản lý mạng."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Đã kết nối với <xliff:g id="SESSION">%s</xliff:g>. Chạm để quản lý mạng."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Chọn tệp"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Không có tệp nào được chọn"</string>
<string name="reset" msgid="2448168080964209908">"Đặt lại"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index e982c75..0caef06 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"“<xliff:g id="APP">%s</xliff:g>”已激活 VPN"</string>
<string name="vpn_text" msgid="3011306607126450322">"触摸可管理网络。"</string>
<string name="vpn_text_long" msgid="6407351006249174473">"已连接到“<xliff:g id="SESSION">%s</xliff:g>”。触摸可管理网络。"</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"选择文件"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"未选定任何文件"</string>
<string name="reset" msgid="2448168080964209908">"重置"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 36eba9f..2935256 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"<xliff:g id="APP">%s</xliff:g> 已啟用 VPN"</string>
<string name="vpn_text" msgid="3011306607126450322">"輕觸即可管理網路。"</string>
<string name="vpn_text_long" msgid="6407351006249174473">"已連線至 <xliff:g id="SESSION">%s</xliff:g>,輕觸即可管理網路。"</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"未選擇任何檔案"</string>
<string name="reset" msgid="2448168080964209908">"重設"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 78b287c..8360097 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1167,6 +1167,14 @@
<string name="vpn_title_long" msgid="6400714798049252294">"i-VPN ivuswe ngu <xliff:g id="APP">%s</xliff:g>"</string>
<string name="vpn_text" msgid="3011306607126450322">"Thinta ukuze wengamele inethiwekhi."</string>
<string name="vpn_text_long" msgid="6407351006249174473">"Ixhumeke ku-.<xliff:g id="SESSION">%s</xliff:g> Thinta ukuze ulawule inethiwekhi."</string>
+ <!-- no translation found for vpn_lockdown_connecting (6443438964440960745) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_connected (8202679674819213931) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_error (6009249814034708175) -->
+ <skip />
+ <!-- no translation found for vpn_lockdown_reset (5365010427963548932) -->
+ <skip />
<string name="upload_file" msgid="2897957172366730416">"Khetha ifayela"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ayikho ifayela ekhethiwe"</string>
<string name="reset" msgid="2448168080964209908">"Setha kabusha"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5d8d397..209fff0 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5706,4 +5706,22 @@
<attr name="minHeight" />
</declare-styleable>
+ <!-- PagedView specific attributes. These attributes are used to customize
+ a PagedView view in XML files. -->
+ <declare-styleable name="PagedView">
+ <!-- A spacing override for the icons within a page -->
+ <attr name="pageLayoutWidthGap" format="dimension" />
+ <attr name="pageLayoutHeightGap" format="dimension" />
+ <!-- The padding of the pages that are dynamically created per page -->
+ <attr name="pageLayoutPaddingTop" format="dimension" />
+ <attr name="pageLayoutPaddingBottom" format="dimension" />
+ <attr name="pageLayoutPaddingLeft" format="dimension" />
+ <attr name="pageLayoutPaddingRight" format="dimension" />
+ <!-- The space between adjacent pages of the PagedView. -->
+ <attr name="pageSpacing" format="dimension" />
+ <!-- The padding for the scroll indicator area -->
+ <attr name="scrollIndicatorPaddingLeft" format="dimension" />
+ <attr name="scrollIndicatorPaddingRight" format="dimension" />
+ </declare-styleable>
+
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index f30943a..372a1ee 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -237,4 +237,20 @@
<dimen name="notification_title_text_size">18dp</dimen>
<!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
<dimen name="notification_subtext_size">12dp</dimen>
+
+ <!-- Keyguard dimensions -->
+ <!-- Width of security view in keyguard. -->
+ <dimen name="kg_security_view_width">500dp</dimen>
+
+ <!-- Height of security view in keyguard. -->
+ <dimen name="kg_security_view_height">0dp</dimen>
+
+ <!-- Width of widget view in keyguard. -->
+ <dimen name="kg_widget_view_width">0dp</dimen>
+
+ <!-- Height of widget view in keyguard. -->
+ <dimen name="kg_widget_view_height">0dp</dimen>
+
+ <!-- Padding surrounding each widget page -->
+ <dimen name="kg_widget_page_padding">10dp</dimen>
</resources>
diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml
new file mode 100644
index 0000000..603fd7e
--- /dev/null
+++ b/core/res/res/values/integers.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+ <integer name="flip_duration">300</integer>
+</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d761980..6414df8 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -120,6 +120,8 @@
<java-symbol type="id" name="old_app_action" />
<java-symbol type="id" name="old_app_description" />
<java-symbol type="id" name="old_app_icon" />
+ <java-symbol type="id" name="overlay_display_window_texture" />
+ <java-symbol type="id" name="overlay_display_window_title" />
<java-symbol type="id" name="package_label" />
<java-symbol type="id" name="packages_list" />
<java-symbol type="id" name="pause" />
@@ -479,7 +481,9 @@
<java-symbol type="string" name="decline" />
<java-symbol type="string" name="default_text_encoding" />
<java-symbol type="string" name="description_target_unlock_tablet" />
- <java-symbol type="string" name="display_manager_built_in_display" />
+ <java-symbol type="string" name="display_manager_built_in_display_name" />
+ <java-symbol type="string" name="display_manager_overlay_display_name" />
+ <java-symbol type="string" name="display_manager_overlay_display_title" />
<java-symbol type="string" name="double_tap_toast" />
<java-symbol type="string" name="elapsed_time_short_format_h_mm_ss" />
<java-symbol type="string" name="elapsed_time_short_format_mm_ss" />
@@ -1093,6 +1097,7 @@
<java-symbol type="layout" name="list_menu_item_radio" />
<java-symbol type="layout" name="locale_picker_item" />
<java-symbol type="layout" name="media_controller" />
+ <java-symbol type="layout" name="overlay_display_window" />
<java-symbol type="layout" name="preference" />
<java-symbol type="layout" name="preference_header_item" />
<java-symbol type="layout" name="preference_list_content" />
@@ -1203,6 +1208,8 @@
<java-symbol type="anim" name="dock_left_exit" />
<java-symbol type="anim" name="dock_right_enter" />
<java-symbol type="anim" name="dock_right_exit" />
+ <java-symbol type="anim" name="keyguard_security_animate_in" />
+ <java-symbol type="anim" name="keyguard_security_animate_out" />
<java-symbol type="array" name="config_keyboardTapVibePattern" />
<java-symbol type="array" name="config_longPressVibePattern" />
<java-symbol type="array" name="config_safeModeDisabledVibePattern" />
@@ -1230,6 +1237,7 @@
<java-symbol type="dimen" name="navigation_bar_height_landscape" />
<java-symbol type="dimen" name="navigation_bar_width" />
<java-symbol type="dimen" name="status_bar_height" />
+ <java-symbol type="dimen" name="kg_widget_page_padding" />
<java-symbol type="drawable" name="ic_jog_dial_sound_off" />
<java-symbol type="drawable" name="ic_jog_dial_sound_on" />
<java-symbol type="drawable" name="ic_jog_dial_unlock" />
@@ -1247,6 +1255,8 @@
<java-symbol type="drawable" name="jog_tab_target_yellow" />
<java-symbol type="drawable" name="menu_background" />
<java-symbol type="drawable" name="stat_sys_secure" />
+ <java-symbol type="drawable" name="kg_widget_overscroll_layer_left" />
+ <java-symbol type="drawable" name="kg_widget_overscroll_layer_right" />
<java-symbol type="id" name="action_mode_bar_stub" />
<java-symbol type="id" name="alarm_status" />
<java-symbol type="id" name="backspace" />
@@ -1299,6 +1309,28 @@
<java-symbol type="id" name="two" />
<java-symbol type="id" name="unlock_widget" />
<java-symbol type="id" name="zero" />
+ <java-symbol type="id" name="message_area" />
+ <java-symbol type="id" name="keyguard_selector_view" />
+ <java-symbol type="id" name="keyguard_pattern_view" />
+ <java-symbol type="id" name="keyguard_password_view" />
+ <java-symbol type="id" name="keyguard_face_unlock_view" />
+ <java-symbol type="id" name="keyguard_sim_pin_view" />
+ <java-symbol type="id" name="keyguard_sim_puk_view" />
+ <java-symbol type="id" name="keyguard_account_view" />
+ <java-symbol type="id" name="app_widget_container" />
+ <java-symbol type="id" name="view_flipper" />
+ <java-symbol type="id" name="emergency_call_button" />
+ <java-symbol type="id" name="keyguard_host_view" />
+ <java-symbol type="id" name="delete_button" />
+ <java-symbol type="id" name="lockPatternView" />
+ <java-symbol type="id" name="forgot_password_button" />
+ <java-symbol type="id" name="glow_pad_view" />
+ <java-symbol type="id" name="sim_pin_entry" />
+ <java-symbol type="id" name="delete_button" />
+ <java-symbol type="id" name="sim_puk_entry" />
+ <java-symbol type="id" name="sim_pin_entry" />
+ <java-symbol type="id" name="puk_delete_button" />
+ <java-symbol type="id" name="pin_delete_button" />
<java-symbol type="integer" name="config_carDockRotation" />
<java-symbol type="integer" name="config_defaultUiModeType" />
<java-symbol type="integer" name="config_deskDockRotation" />
@@ -1328,6 +1360,7 @@
<java-symbol type="layout" name="screen_simple_overlay_action_mode" />
<java-symbol type="layout" name="screen_title" />
<java-symbol type="layout" name="screen_title_icons" />
+ <java-symbol type="layout" name="keyguard_host_view" />
<java-symbol type="string" name="abbrev_wday_month_day_no_year" />
<java-symbol type="string" name="android_upgrading_title" />
<java-symbol type="string" name="bugreport_title" />
@@ -1381,6 +1414,33 @@
<java-symbol type="style" name="Animation.LockScreen" />
<java-symbol type="style" name="Theme.Dialog.RecentApplications" />
<java-symbol type="style" name="Theme.ExpandedMenu" />
+ <java-symbol type="string" name="kg_emergency_call_label" />
+ <java-symbol type="string" name="kg_forgot_pattern_button_text" />
+ <java-symbol type="string" name="kg_wrong_pattern" />
+ <java-symbol type="string" name="kg_wrong_password" />
+ <java-symbol type="string" name="kg_wrong_pin" />
+ <java-symbol type="string" name="kg_too_many_failed_attempts_countdown" />
+ <java-symbol type="string" name="kg_pattern_instructions" />
+ <java-symbol type="string" name="kg_sim_pin_instructions" />
+ <java-symbol type="string" name="kg_pin_instructions" />
+ <java-symbol type="string" name="kg_password_instructions" />
+ <java-symbol type="string" name="kg_puk_enter_puk_hint" />
+ <java-symbol type="string" name="kg_puk_enter_pin_hint" />
+ <java-symbol type="string" name="kg_sim_unlock_progress_dialog_message" />
+ <java-symbol type="string" name="kg_password_wrong_pin_code" />
+ <java-symbol type="string" name="kg_invalid_sim_pin_hint" />
+ <java-symbol type="string" name="kg_invalid_sim_puk_hint" />
+ <java-symbol type="string" name="kg_sim_puk_recovery_hint" />
+ <java-symbol type="string" name="kg_invalid_puk" />
+ <java-symbol type="string" name="kg_login_too_many_attempts" />
+ <java-symbol type="string" name="kg_login_instructions" />
+ <java-symbol type="string" name="kg_login_username_hint" />
+ <java-symbol type="string" name="kg_login_password_hint" />
+ <java-symbol type="string" name="kg_login_submit_button" />
+ <java-symbol type="string" name="kg_login_invalid_input" />
+ <java-symbol type="string" name="kg_login_account_recovery_hint" />
+ <java-symbol type="string" name="kg_login_checking_password" />
+
<!-- From services -->
<java-symbol type="anim" name="screen_rotate_0_enter" />
@@ -3692,5 +3752,5 @@
<public type="attr" name="listPreferredItemPaddingStart" />
<public type="attr" name="listPreferredItemPaddingEnd" />
<public type="attr" name="singleUser" />
-
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e77dde7..3178af0 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1616,6 +1616,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
<string name="permdesc_mediaStorageWrite" product="default">Allows the app to modify the contents of the internal media storage.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] -->
+ <string name="permlab_sdcardAccessAll">access external storage of all users</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_sdcardAccessAll">Allows the app to access external storage for all users.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_cache_filesystem">access the cache filesystem</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -3651,6 +3656,42 @@
<!-- Display manager service -->
<!-- Name of the built-in display. [CHAR LIMIT=50] -->
- <string name="display_manager_built_in_display">Built-in Screen</string>
+ <string name="display_manager_built_in_display_name">Built-in Screen</string>
+
+ <!-- Name of the N'th overlay display for testing. [CHAR LIMIT=50] -->
+ <string name="display_manager_overlay_display_name">Overlay #<xliff:g id="id">%1$d</xliff:g></string>
+
+ <!-- Title text to show within the overlay. [CHAR LIMIT=50] -->
+ <string name="display_manager_overlay_display_title">Overlay #<xliff:g id="id">%1$d</xliff:g>: <xliff:g id="width">%2$d</xliff:g>x<xliff:g id="height">%3$d</xliff:g>, <xliff:g id="dpi">%4$d</xliff:g> dpi</string>
+
+ <!-- Keyguard strings -->
+ <string name="kg_emergency_call_label">Emergency call</string>
+ <string name="kg_forgot_pattern_button_text">Forgot Pattern</string>
+ <string name="kg_wrong_pattern">Wrong Pattern</string>
+ <string name="kg_wrong_password">Wrong Password</string>
+ <string name="kg_wrong_pin">Wrong PIN</string>
+ <string name="kg_too_many_failed_attempts_countdown">Too many attempts</string>
+ <string name="kg_pattern_instructions">Draw your pattern</string>
+ <string name="kg_sim_pin_instructions">Enter SIM PIN</string>
+ <string name="kg_pin_instructions">Enter PIN</string>
+ <string name="kg_password_instructions">Enter Password</string>
+ <string name="kg_puk_enter_puk_hint">PUK code</string>
+ <string name="kg_puk_enter_pin_hint">New PIN code</string>
+ <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+ <string name="kg_password_wrong_pin_code">Incorrect PIN code.</string>
+ <string name="kg_invalid_sim_pin_hint">Type a PIN that is 4 to 8 numbers.</string>
+ <string name="kg_invalid_sim_puk_hint">Type a PUK that is 8 numbers or longer.</string>
+ <string name="kg_sim_puk_recovery_hint">Type PUK and new PIN code</string>
+ <string name="kg_invalid_puk">The PUK you typed isn\'t correct.</string>
+
+ <string name="kg_login_too_many_attempts">Too many pattern attempts</string>
+ <string name="kg_login_instructions">To unlock, sign in with your Google account.</string>
+ <string name="kg_login_username_hint">Username (email)</string>
+ <string name="kg_login_password_hint">Password</string>
+ <string name="kg_login_submit_button">Sign in</string>
+ <string name="kg_login_invalid_input">Invalid username or password.</string>
+ <string name="kg_login_account_recovery_hint">Forgot your username or password\?\nVisit <b>google.com/accounts/recovery</b>.</string>
+ <string name="kg_login_checking_password">Checking\u2026</string>
+ <string name="kg_temp_back_string"> &lt; </string> <!-- TODO: remove this -->
</resources>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 1b69daf..a19b9b4 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -173,7 +173,10 @@
<assign-permission name="android.permission.SET_SCREEN_COMPATIBILITY" uid="shell" />
<assign-permission name="android.permission.READ_EXTERNAL_STORAGE" uid="shell" />
<assign-permission name="android.permission.WRITE_EXTERNAL_STORAGE" uid="shell" />
-
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="shell" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="shell" />
+ <assign-permission name="android.permission.MANAGE_USERS" uid="shell" />
+
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
<assign-permission name="android.permission.ACCESS_DRM" uid="media" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index a073c1a..2109a01 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -230,7 +230,7 @@ nContextSetSurface(JNIEnv *_env, jobject _this, RsContext con, jint width, jint
if (wnd == NULL) {
} else {
- window = android_Surface_getNativeWindow(_env, wnd).get();
+ window = android_view_Surface_getNativeWindow(_env, wnd).get();
}
rsContextSetSurface(con, width, height, window);
@@ -494,7 +494,7 @@ nAllocationSetSurface(JNIEnv *_env, jobject _this, RsContext con, RsAllocation a
sp<Surface> s;
if (sur != 0) {
- s = Surface_getSurface(_env, sur);
+ s = android_view_Surface_getSurface(_env, sur);
}
rsAllocationSetSurface(con, alloc, static_cast<ANativeWindow *>(s.get()));
diff --git a/include/android_runtime/android_view_Surface.h b/include/android_runtime/android_view_Surface.h
index fb0b057..e50186d 100644
--- a/include/android_runtime/android_view_Surface.h
+++ b/include/android_runtime/android_view_Surface.h
@@ -25,12 +25,15 @@ namespace android {
class Surface;
-extern sp<ANativeWindow> android_Surface_getNativeWindow(
- JNIEnv* env, jobject clazz);
-extern bool android_Surface_isInstanceOf(JNIEnv* env, jobject obj);
+/* Gets the underlying ANativeWindow for a Surface. */
+extern sp<ANativeWindow> android_view_Surface_getNativeWindow(
+ JNIEnv* env, jobject surfaceObj);
+
+/* Returns true if the object is an instance of Surface. */
+extern bool android_view_Surface_isInstanceOf(JNIEnv* env, jobject obj);
/* Gets the underlying Surface from a Surface Java object. */
-extern sp<Surface> Surface_getSurface(JNIEnv* env, jobject thiz);
+extern sp<Surface> android_view_Surface_getSurface(JNIEnv* env, jobject surfaceObj);
} // namespace android
diff --git a/include/android_runtime/android_view_SurfaceSession.h b/include/android_runtime/android_view_SurfaceSession.h
new file mode 100644
index 0000000..3748f6c
--- /dev/null
+++ b/include/android_runtime/android_view_SurfaceSession.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_VIEW_SURFACE_SESSION_H
+#define _ANDROID_VIEW_SURFACE_SESSION_H
+
+#include "jni.h"
+
+namespace android {
+
+class SurfaceComposerClient;
+
+/* Gets the underlying SurfaceComposerClient for a SurfaceSession. */
+extern sp<SurfaceComposerClient> android_view_SurfaceSession_getClient(
+ JNIEnv* env, jobject surfaceSessionObj);
+
+} // namespace android
+
+#endif // _ANDROID_VIEW_SURFACE_SESSION_H
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 27e198c..6b08e7f 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -328,19 +328,19 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float
glyph->mCacheTexture);
}
-CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) {
+CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching) {
CachedGlyphInfo* cachedGlyph = NULL;
ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
if (index >= 0) {
cachedGlyph = mCachedGlyphs.valueAt(index);
} else {
- cachedGlyph = cacheGlyph(paint, textUnit);
+ cachedGlyph = cacheGlyph(paint, textUnit, precaching);
}
// Is the glyph still in texture cache?
if (!cachedGlyph->mIsValid) {
const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit);
- updateGlyphCache(paint, skiaGlyph, cachedGlyph);
+ updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching);
}
return cachedGlyph;
@@ -438,7 +438,7 @@ void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
break;
}
- CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
+ CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph, true);
glyphsCount++;
}
@@ -529,7 +529,8 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len
}
}
-void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) {
+void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph,
+ bool precaching) {
glyph->mAdvanceX = skiaGlyph.fAdvanceX;
glyph->mAdvanceY = skiaGlyph.fAdvanceY;
glyph->mBitmapLeft = skiaGlyph.fLeft;
@@ -542,7 +543,7 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp
// Get the bitmap for the glyph
paint->findImage(skiaGlyph);
- mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY);
+ mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching);
if (!glyph->mIsValid) {
return;
@@ -567,7 +568,7 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp
mState->mUploadTexture = true;
}
-CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph) {
+CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) {
CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
@@ -575,7 +576,7 @@ CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph) {
newGlyph->mGlyphIndex = skiaGlyph.fID;
newGlyph->mIsValid = false;
- updateGlyphCache(paint, skiaGlyph, newGlyph);
+ updateGlyphCache(paint, skiaGlyph, newGlyph, precaching);
return newGlyph;
}
@@ -762,7 +763,7 @@ CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
}
void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
- uint32_t* retOriginX, uint32_t* retOriginY) {
+ uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
checkInit();
cachedGlyph->mIsValid = false;
// If the glyph is too tall, don't cache it
@@ -779,15 +780,16 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp
CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
- // If the new glyph didn't fit, flush the state so far and invalidate everything
if (!cacheTexture) {
- flushAllAndInvalidate();
-
- // Try to fit it again
- cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
+ if (!precaching) {
+ // If the new glyph didn't fit and we are not just trying to precache it,
+ // clear out the cache and try again
+ flushAllAndInvalidate();
+ cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
+ }
- // if we still don't fit, something is wrong and we shouldn't draw
if (!cacheTexture) {
+ // either the glyph didn't fit or we're precaching and will cache it when we draw
return;
}
}
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index febae17..8d0d21d 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -243,8 +243,9 @@ protected:
void invalidateTextureCache(CacheTexture *cacheTexture = NULL);
- CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph);
- void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph);
+ CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching);
+ void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph,
+ bool precaching);
void measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
@@ -258,7 +259,7 @@ protected:
void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
SkPathMeasure& measure, SkPoint* position, SkVector* tangent);
- CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit);
+ CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching = false);
static glyph_t nextGlyph(const uint16_t** srcPtr) {
const uint16_t* src = *srcPtr;
@@ -364,7 +365,7 @@ protected:
void initTextTexture();
CacheTexture* createCacheTexture(int width, int height, bool allocate);
void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
- uint32_t *retOriginX, uint32_t *retOriginY);
+ uint32_t *retOriginX, uint32_t *retOriginY, bool precaching);
CacheTexture* cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY);
void flushAllAndInvalidate();
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 726b57c7..2e4e349 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -217,10 +217,12 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions,
float amount = (pos - start) / distance;
float oppAmount = 1.0f - amount;
- *p++ = uint8_t(startR * oppAmount + endR * amount);
- *p++ = uint8_t(startG * oppAmount + endG * amount);
- *p++ = uint8_t(startB * oppAmount + endB * amount);
- *p++ = uint8_t(startA * oppAmount + endA * amount);
+ const float alpha = startA * oppAmount + endA * amount;
+ const float a = alpha / 255.0f;
+ *p++ = uint8_t(a * (startR * oppAmount + endR * amount));
+ *p++ = uint8_t(a * (startG * oppAmount + endG * amount));
+ *p++ = uint8_t(a * (startB * oppAmount + endB * amount));
+ *p++ = uint8_t(alpha);
}
for (int i = 1; i < GRADIENT_TEXTURE_HEIGHT; i++) {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 849c556..8da9f66 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -440,7 +440,7 @@ int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
mode = SkXfermode::kSrcOver_Mode;
}
- createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags, previousFbo);
+ createLayer(left, top, right, bottom, alpha, mode, flags, previousFbo);
}
return count;
@@ -508,44 +508,56 @@ int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bot
* buffer is left untouched until the first drawing operation. Only when
* something actually gets drawn are the layers regions cleared.
*/
-bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
- float right, float bottom, int alpha, SkXfermode::Mode mode,
- int flags, GLuint previousFbo) {
+bool OpenGLRenderer::createLayer(float left, float top, float right, float bottom,
+ int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo) {
LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
// Window coordinates of the layer
+ Rect clip;
Rect bounds(left, top, right, bottom);
- if (!fboLayer) {
- mSnapshot->transform->mapRect(bounds);
-
- // Layers only make sense if they are in the framebuffer's bounds
- if (bounds.intersect(*snapshot->clipRect)) {
- // We cannot work with sub-pixels in this case
- bounds.snapToPixelBoundaries();
-
- // When the layer is not an FBO, we may use glCopyTexImage so we
- // need to make sure the layer does not extend outside the bounds
- // of the framebuffer
- if (!bounds.intersect(snapshot->previous->viewport)) {
- bounds.setEmpty();
- }
- } else {
+ Rect untransformedBounds(bounds);
+ mSnapshot->transform->mapRect(bounds);
+
+ // Layers only make sense if they are in the framebuffer's bounds
+ if (bounds.intersect(*mSnapshot->clipRect)) {
+ // We cannot work with sub-pixels in this case
+ bounds.snapToPixelBoundaries();
+
+ // When the layer is not an FBO, we may use glCopyTexImage so we
+ // need to make sure the layer does not extend outside the bounds
+ // of the framebuffer
+ if (!bounds.intersect(mSnapshot->previous->viewport)) {
bounds.setEmpty();
+ } else if (fboLayer) {
+ clip.set(bounds);
+ mat4 inverse;
+ inverse.loadInverse(*mSnapshot->transform);
+ inverse.mapRect(clip);
+ clip.snapToPixelBoundaries();
+ if (clip.intersect(untransformedBounds)) {
+ clip.translate(-left, -top);
+ bounds.set(untransformedBounds);
+ } else {
+ clip.setEmpty();
+ }
}
+ } else {
+ bounds.setEmpty();
}
if (bounds.isEmpty() || bounds.getWidth() > mCaches.maxTextureSize ||
- bounds.getHeight() > mCaches.maxTextureSize) {
- snapshot->empty = fboLayer;
+ bounds.getHeight() > mCaches.maxTextureSize ||
+ (fboLayer && clip.isEmpty())) {
+ mSnapshot->empty = fboLayer;
} else {
- snapshot->invisible = snapshot->invisible || (alpha <= ALPHA_THRESHOLD && fboLayer);
+ mSnapshot->invisible = mSnapshot->invisible || (alpha <= ALPHA_THRESHOLD && fboLayer);
}
// Bail out if we won't draw in this snapshot
- if (snapshot->invisible || snapshot->empty) {
+ if (mSnapshot->invisible || mSnapshot->empty) {
return false;
}
@@ -563,23 +575,23 @@ bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
layer->setBlend(true);
// Save the layer in the snapshot
- snapshot->flags |= Snapshot::kFlagIsLayer;
- snapshot->layer = layer;
+ mSnapshot->flags |= Snapshot::kFlagIsLayer;
+ mSnapshot->layer = layer;
if (fboLayer) {
- return createFboLayer(layer, bounds, snapshot, previousFbo);
+ return createFboLayer(layer, bounds, clip, previousFbo);
} else {
// Copy the framebuffer into the layer
layer->bindTexture();
if (!bounds.isEmpty()) {
if (layer->isEmpty()) {
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
- bounds.left, snapshot->height - bounds.bottom,
+ bounds.left, mSnapshot->height - bounds.bottom,
layer->getWidth(), layer->getHeight(), 0);
layer->setEmpty(false);
} else {
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left,
- snapshot->height - bounds.bottom, bounds.getWidth(), bounds.getHeight());
+ mSnapshot->height - bounds.bottom, bounds.getWidth(), bounds.getHeight());
}
// Enqueue the buffer coordinates to clear the corresponding region later
@@ -590,35 +602,20 @@ bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
return true;
}
-bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, sp<Snapshot> snapshot,
- GLuint previousFbo) {
+bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLuint previousFbo) {
layer->setFbo(mCaches.fboCache.get());
- snapshot->region = &snapshot->layer->region;
- snapshot->flags |= Snapshot::kFlagFboTarget;
-
- Rect clip(bounds);
- snapshot->transform->mapRect(clip);
- clip.intersect(*snapshot->clipRect);
- clip.snapToPixelBoundaries();
- clip.intersect(snapshot->previous->viewport);
+ mSnapshot->region = &mSnapshot->layer->region;
+ mSnapshot->flags |= Snapshot::kFlagFboTarget;
- mat4 inverse;
- inverse.loadInverse(*mSnapshot->transform);
-
- inverse.mapRect(clip);
- clip.snapToPixelBoundaries();
- clip.intersect(bounds);
- clip.translate(-bounds.left, -bounds.top);
-
- snapshot->flags |= Snapshot::kFlagIsFboLayer;
- snapshot->fbo = layer->getFbo();
- snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
- snapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
- snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
- snapshot->height = bounds.getHeight();
- snapshot->flags |= Snapshot::kFlagDirtyOrtho;
- snapshot->orthoMatrix.load(mOrthoMatrix);
+ mSnapshot->flags |= Snapshot::kFlagIsFboLayer;
+ mSnapshot->fbo = layer->getFbo();
+ mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
+ mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
+ mSnapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+ mSnapshot->height = bounds.getHeight();
+ mSnapshot->flags |= Snapshot::kFlagDirtyOrtho;
+ mSnapshot->orthoMatrix.load(mOrthoMatrix);
// Bind texture to FBO
glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo());
@@ -1403,10 +1400,6 @@ void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords,
int boundaryWidthSlot = mCaches.currentProgram->getUniform("boundaryWidth");
glUniform1f(boundaryWidthSlot, boundaryWidthProportion);
-
- // Setting the inverse value saves computations per-fragment in the shader
- int inverseBoundaryWidthSlot = mCaches.currentProgram->getUniform("inverseBoundaryWidth");
- glUniform1f(inverseBoundaryWidthSlot, 1.0f / boundaryWidthProportion);
}
void OpenGLRenderer::finishDrawAALine(const int widthSlot, const int lengthSlot) {
@@ -1810,15 +1803,13 @@ void OpenGLRenderer::drawAARect(float left, float top, float right, float bottom
float width = right - left;
float height = bottom - top;
- float boundaryWidthProportion = (width != 0) ? (2 * boundarySizeX) / width : 0;
- float boundaryHeightProportion = (height != 0) ? (2 * boundarySizeY) / height : 0;
+ float boundaryWidthProportion = .5 - ((width != 0) ? (2 * boundarySizeX) / width : 0);
+ float boundaryHeightProportion = .5 - ((height != 0) ? (2 * boundarySizeY) / height : 0);
setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords,
boundaryWidthProportion, widthSlot, lengthSlot);
int boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength");
- int inverseBoundaryLengthSlot = mCaches.currentProgram->getUniform("inverseBoundaryLength");
glUniform1f(boundaryLengthSlot, boundaryHeightProportion);
- glUniform1f(inverseBoundaryLengthSlot, (1.0f / boundaryHeightProportion));
AAVertex::set(aaVertices++, left, bottom, 1, 1);
AAVertex::set(aaVertices++, left, top, 1, 0);
@@ -1955,9 +1946,7 @@ status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
Vertex* prevVertex = NULL;
int boundaryLengthSlot = -1;
- int inverseBoundaryLengthSlot = -1;
int boundaryWidthSlot = -1;
- int inverseBoundaryWidthSlot = -1;
for (int i = 0; i < count; i += 4) {
// a = start point, b = end point
@@ -2060,22 +2049,16 @@ status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
if (boundaryWidthSlot < 0) {
boundaryWidthSlot =
mCaches.currentProgram->getUniform("boundaryWidth");
- inverseBoundaryWidthSlot =
- mCaches.currentProgram->getUniform("inverseBoundaryWidth");
}
glUniform1f(boundaryWidthSlot, boundaryWidthProportion);
- glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidthProportion));
}
if (boundaryLengthSlot < 0) {
boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength");
- inverseBoundaryLengthSlot =
- mCaches.currentProgram->getUniform("inverseBoundaryLength");
}
glUniform1f(boundaryLengthSlot, boundaryLengthProportion);
- glUniform1f(inverseBoundaryLengthSlot, (1 / boundaryLengthProportion));
if (prevAAVertex != NULL) {
// Issue two repeat vertices to create degenerate triangles to bridge
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index d3b98a4..2369f47 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -380,7 +380,7 @@ private:
*
* @return True if the layer was successfully created, false otherwise
*/
- bool createLayer(sp<Snapshot> snapshot, float left, float top, float right, float bottom,
+ bool createLayer(float left, float top, float right, float bottom,
int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo);
/**
@@ -391,8 +391,7 @@ private:
* @param bounds The bounds of the layer
* @param previousFbo The name of the current framebuffer
*/
- bool createFboLayer(Layer* layer, Rect& bounds, sp<Snapshot> snapshot,
- GLuint previousFbo);
+ bool createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLuint previousFbo);
/**
* Compose the specified layer as a region.
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index d67bfbe..8a9a2ac 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -129,9 +129,7 @@ const char* gFS_Uniforms_Color =
"uniform vec4 color;\n";
const char* gFS_Uniforms_AA =
"uniform float boundaryWidth;\n"
- "uniform float inverseBoundaryWidth;\n"
- "uniform float boundaryLength;\n"
- "uniform float inverseBoundaryLength;\n";
+ "uniform float boundaryLength;\n";
const char* gFS_Header_Uniforms_PointHasBitmap =
"uniform vec2 textureDimension;\n"
"uniform float pointSize;\n";
@@ -242,16 +240,9 @@ const char* gFS_Main_ModulateColor =
const char* gFS_Main_ModulateColor_ApplyGamma =
" fragColor *= pow(color.a, gamma);\n";
const char* gFS_Main_AccountForAA =
- " if (widthProportion < boundaryWidth) {\n"
- " fragColor *= (widthProportion * inverseBoundaryWidth);\n"
- " } else if (widthProportion > (1.0 - boundaryWidth)) {\n"
- " fragColor *= ((1.0 - widthProportion) * inverseBoundaryWidth);\n"
- " }\n"
- " if (lengthProportion < boundaryLength) {\n"
- " fragColor *= (lengthProportion * inverseBoundaryLength);\n"
- " } else if (lengthProportion > (1.0 - boundaryLength)) {\n"
- " fragColor *= ((1.0 - lengthProportion) * inverseBoundaryLength);\n"
- " }\n";
+ " fragColor *= (1.0 - smoothstep(boundaryWidth, 0.5, abs(0.5 - widthProportion)))\n"
+ " * (1.0 - smoothstep(boundaryLength, 0.5, abs(0.5 - lengthProportion)));\n";
+
const char* gFS_Main_FetchTexture[2] = {
// Don't modulate
" fragColor = texture2D(sampler, outTexCoords);\n",
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 8916efd..9013fd5 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -46,11 +46,12 @@ static inline bool isPowerOfTwo(unsigned int n) {
}
static inline void bindUniformColor(int slot, uint32_t color) {
+ const float a = ((color >> 24) & 0xff) / 255.0f;
glUniform4f(slot,
- ((color >> 16) & 0xff) / 255.0f,
- ((color >> 8) & 0xff) / 255.0f,
- ((color ) & 0xff) / 255.0f,
- ((color >> 24) & 0xff) / 255.0f);
+ a * ((color >> 16) & 0xff) / 255.0f,
+ a * ((color >> 8) & 0xff) / 255.0f,
+ a * ((color ) & 0xff) / 255.0f,
+ a);
}
///////////////////////////////////////////////////////////////////////////////
@@ -154,10 +155,6 @@ void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView,
// Uniforms
bindTexture(texture, mWrapS, mWrapT);
- // Assume linear here; we should really check the transform in
- // ::updateTransforms() but we don't have the texture object
- // available at that point. The optimization is not worth the
- // effort for now.
texture->setFilter(GL_LINEAR);
glUniform1i(program->getUniform("bitmapSampler"), textureSlot);
@@ -166,14 +163,6 @@ void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView,
glUniform2f(program->getUniform("textureDimension"), 1.0f / width, 1.0f / height);
}
-void SkiaBitmapShader::updateTransforms(Program* program, const mat4& modelView,
- const Snapshot& snapshot) {
- mat4 textureTransform;
- computeScreenSpaceMatrix(textureTransform, modelView);
- glUniformMatrix4fv(program->getUniform("textureTransform"), 1,
- GL_FALSE, &textureTransform.data[0]);
-}
-
///////////////////////////////////////////////////////////////////////////////
// Linear gradient shader
///////////////////////////////////////////////////////////////////////////////
@@ -257,13 +246,6 @@ void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelV
glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
}
-void SkiaLinearGradientShader::updateTransforms(Program* program, const mat4& modelView,
- const Snapshot& snapshot) {
- mat4 screenSpace;
- computeScreenSpaceMatrix(screenSpace, modelView);
- glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
-}
-
///////////////////////////////////////////////////////////////////////////////
// Circular gradient shader
///////////////////////////////////////////////////////////////////////////////
@@ -384,13 +366,6 @@ void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelVi
glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
}
-void SkiaSweepGradientShader::updateTransforms(Program* program, const mat4& modelView,
- const Snapshot& snapshot) {
- mat4 screenSpace;
- computeScreenSpaceMatrix(screenSpace, modelView);
- glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
-}
-
///////////////////////////////////////////////////////////////////////////////
// Compose shader
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index a710b86..2687592 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -82,10 +82,6 @@ struct SkiaShader {
mGradientCache = gradientCache;
}
- virtual void updateTransforms(Program* program, const mat4& modelView,
- const Snapshot& snapshot) {
- }
-
uint32_t getGenerationId() {
return mGenerationId;
}
@@ -148,7 +144,6 @@ struct SkiaBitmapShader: public SkiaShader {
void describe(ProgramDescription& description, const Extensions& extensions);
void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
GLuint* textureUnit);
- void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);
private:
SkiaBitmapShader() {
@@ -172,7 +167,6 @@ struct SkiaLinearGradientShader: public SkiaShader {
void describe(ProgramDescription& description, const Extensions& extensions);
void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
GLuint* textureUnit);
- void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);
private:
SkiaLinearGradientShader() {
@@ -197,7 +191,6 @@ struct SkiaSweepGradientShader: public SkiaShader {
virtual void describe(ProgramDescription& description, const Extensions& extensions);
void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
GLuint* textureUnit);
- void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);
protected:
SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors, float* positions,
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 84fb0dd..c98fcd3 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -3294,7 +3294,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
// sent if none of these devices is connected.
int mBecomingNoisyIntentDevices =
AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
- AudioSystem.DEVICE_OUT_ALL_A2DP;
+ AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_AUX_DIGITAL |
+ AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
+ AudioSystem.DEVICE_OUT_ALL_USB;
// must be called before removing the device from mConnectedDevices
private int checkSendBecomingNoisyIntent(int device, int state) {
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 4941ae5..f91c9a0 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -370,7 +370,7 @@ static void android_media_MediaCodec_native_configure(
sp<ISurfaceTexture> surfaceTexture;
if (jsurface != NULL) {
- sp<Surface> surface(Surface_getSurface(env, jsurface));
+ sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
if (surface != NULL) {
surfaceTexture = surface->getSurfaceTexture();
} else {
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index c2a6889..04ba348 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -271,7 +271,7 @@ setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlaye
sp<ISurfaceTexture> new_st;
if (jsurface) {
- sp<Surface> surface(Surface_getSurface(env, jsurface));
+ sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
if (surface != NULL) {
new_st = surface->getSurfaceTexture();
new_st->incStrong(thiz);
diff --git a/native/android/native_window.cpp b/native/android/native_window.cpp
index 99c0fd3..ca0c902 100644
--- a/native/android/native_window.cpp
+++ b/native/android/native_window.cpp
@@ -25,7 +25,7 @@
using namespace android;
ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) {
- sp<ANativeWindow> win = android_Surface_getNativeWindow(env, surface);
+ sp<ANativeWindow> win = android_view_Surface_getNativeWindow(env, surface);
if (win != NULL) {
win->incStrong((void*)ANativeWindow_acquire);
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 9a80090..875d2c9 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -46,6 +46,8 @@
<bool name="def_netstats_enabled">true</bool>
<bool name="def_usb_mass_storage_enabled">true</bool>
<bool name="def_wifi_on">false</bool>
+ <!-- 0 == default, 1 == never while plugged, 2 == never -->
+ <integer name="def_wifi_sleep_policy">0</integer>
<bool name="def_networks_available_notification_on">true</bool>
<bool name="def_backup_enabled">false</bool>
@@ -158,7 +160,7 @@
<!-- Whether the feature activates when docked (SCREENSAVER_ACTIVATE_ON_DOCK) -->
<bool name="def_screensaver_activate_on_dock">true</bool>
<!-- Whether the feature activates when docked (SCREENSAVER_ACTIVATE_ON_SLEEP) -->
- <bool name="def_screensaver_activate_on_sleep">true</bool>
+ <bool name="def_screensaver_activate_on_sleep">false</bool>
<!-- ComponentName of the default screen saver (Settings.Secure.SCREENSAVER_COMPONENT) -->
<string name="def_screensaver_component">com.google.android.deskclock/com.android.deskclock.Screensaver</string>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index db81786..2785991 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1568,6 +1568,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
loadIntegerSetting(stmt, Settings.System.POINTER_SPEED,
R.integer.def_pointer_speed);
+ loadIntegerSetting(stmt, Settings.System.WIFI_SLEEP_POLICY,
+ R.integer.def_wifi_sleep_policy);
} finally {
if (stmt != null) stmt.close();
}
diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml
index 67e13eb..fbbd7e5 100644
--- a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml
+++ b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml
@@ -143,8 +143,13 @@
<com.android.systemui.statusbar.policy.DeadZone
android:id="@+id/deadzone"
- android:layout_height="@dimen/navigation_bar_deadzone_size"
+ android:layout_height="match_parent"
android:layout_width="match_parent"
+ systemui:minSize="@dimen/navigation_bar_deadzone_size"
+ systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+ systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+ systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+ systemui:orientation="horizontal"
android:layout_gravity="top"
/>
</FrameLayout>
@@ -269,8 +274,13 @@
<com.android.systemui.statusbar.policy.DeadZone
android:id="@+id/deadzone"
- android:layout_height="@dimen/navigation_bar_deadzone_size"
+ android:layout_height="match_parent"
android:layout_width="match_parent"
+ systemui:minSize="@dimen/navigation_bar_deadzone_size"
+ systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+ systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+ systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+ systemui:orientation="vertical"
android:layout_gravity="top"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index d41040d..33b5dbb 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -147,8 +147,13 @@
<com.android.systemui.statusbar.policy.DeadZone
android:id="@+id/deadzone"
- android:layout_height="@dimen/navigation_bar_deadzone_size"
+ android:layout_height="match_parent"
android:layout_width="match_parent"
+ systemui:minSize="@dimen/navigation_bar_deadzone_size"
+ systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+ systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+ systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+ systemui:orientation="horizontal"
android:layout_gravity="top"
/>
</FrameLayout>
@@ -276,9 +281,14 @@
<com.android.systemui.statusbar.policy.DeadZone
android:id="@+id/deadzone"
- android:layout_width="@dimen/navigation_bar_deadzone_size"
android:layout_height="match_parent"
- android:layout_gravity="left"
+ android:layout_width="match_parent"
+ systemui:minSize="@dimen/navigation_bar_deadzone_size"
+ systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"
+ systemui:holdTime="@integer/navigation_bar_deadzone_hold"
+ systemui:decayTime="@integer/navigation_bar_deadzone_decay"
+ systemui:orientation="vertical"
+ android:layout_gravity="top"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index f34ed8e..36b52e3 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -60,7 +60,7 @@
<string name="always_use_accessory" msgid="1210954576979621596">"ለእዚህ USB ተቀጥላ በነባሪነት ተጠቀም"</string>
<string name="usb_debugging_title" msgid="1114766024068112429">"የUSB ማረሚያ ይፈቀድ?"</string>
<string name="usb_debugging_message" msgid="719863946976291180">"የUSB ማረም ከዚህ ኮምፒውተር ይፈቀድ?"\n"የእርስዎ RSA ቁልፍ ጣት አሻራ "\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g> ነው"</string>
- <string name="usb_debugging_always" msgid="4253099426793114693">"ለእዚህ ኮምፒውተር ሁልጊዜ ፍቀድ"</string>
+ <string name="usb_debugging_always" msgid="4253099426793114693">"ለዚህ ኮምፒውተር ሁልጊዜ ፍቀድ"</string>
<string name="compat_mode_on" msgid="6623839244840638213">"ማያ እንዲሞላ አጉላ"</string>
<string name="compat_mode_off" msgid="4434467572461327898">"ማያ ለመሙለት ሳብ"</string>
<string name="compat_mode_help_header" msgid="7969493989397529910">"የተኳኋኝነት አጉላ"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index e0aa3f9..87b0088 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -59,7 +59,7 @@
<string name="always_use_device" msgid="1450287437017315906">"Se usa de forma predeterminada para este dispositivo USB."</string>
<string name="always_use_accessory" msgid="1210954576979621596">"Se usa de forma predeterminada para este accesorio USB."</string>
<string name="usb_debugging_title" msgid="1114766024068112429">"¿Permitir la depuración de USB?"</string>
- <string name="usb_debugging_message" msgid="719863946976291180">"¿Quieres permitir la depuración de USB desde esta computadora?"\n"La huella digital de tu clave RSA es"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>."</string>
+ <string name="usb_debugging_message" msgid="719863946976291180">"¿Quieres permitir la depuración de USB desde esta computadora?"\n"La huella digital de tu clave RSA es:"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
<string name="usb_debugging_always" msgid="4253099426793114693">"Permitir el uso de esta computadora siempre"</string>
<string name="compat_mode_on" msgid="6623839244840638213">"Zoom para ocupar la pantalla"</string>
<string name="compat_mode_off" msgid="4434467572461327898">"Estirar p/ ocupar la pantalla"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 16e1443..cfc0aec 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -58,8 +58,8 @@
<string name="label_view" msgid="6304565553218192990">"देखें"</string>
<string name="always_use_device" msgid="1450287437017315906">"इस USB उपकरण के लिए डिफ़ॉल्‍ट रूप से उपयोग करें"</string>
<string name="always_use_accessory" msgid="1210954576979621596">"इस USB एसेसरी के लिए डिफ़ॉल्‍ट रूप से उपयोग करें"</string>
- <string name="usb_debugging_title" msgid="1114766024068112429">"USB डीबग करने दें?"</string>
- <string name="usb_debugging_message" msgid="719863946976291180">"इस कंप्यूटर से USB डीबग करने दें?"\n"आपका RSA कुंजी फ़िंगरप्रिंट यह है:"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
+ <string name="usb_debugging_title" msgid="1114766024068112429">"USB डीबगिंग करने दें?"</string>
+ <string name="usb_debugging_message" msgid="719863946976291180">"इस कंप्यूटर से USB डीबगिंग करने दें?"\n"आपका RSA कुंजी फ़िंगरप्रिंट यह है:"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
<string name="usb_debugging_always" msgid="4253099426793114693">"इस कंप्यूटर को हमेशा अनुमति दें"</string>
<string name="compat_mode_on" msgid="6623839244840638213">"स्‍क्रीन भरने हेतु ज़ूम करें"</string>
<string name="compat_mode_off" msgid="4434467572461327898">"स्‍क्रीन को भरने के लिए खींचें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 830b73c..7150c94 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -58,8 +58,8 @@
<string name="label_view" msgid="6304565553218192990">"Prikaži"</string>
<string name="always_use_device" msgid="1450287437017315906">"Koristi se prema zadanim postavkama za ovaj USB uređaj"</string>
<string name="always_use_accessory" msgid="1210954576979621596">"Koristi se prema zadanim postavkama za ovaj USB pribor"</string>
- <string name="usb_debugging_title" msgid="1114766024068112429">"Omogućiti rješavanje programske pogreške na USB-u?"</string>
- <string name="usb_debugging_message" msgid="719863946976291180">"Omogućiti rješavanje programske pogreške na USB-u na ovom računalu?"\n"Vaš je otisak prsta RSA ključa"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
+ <string name="usb_debugging_title" msgid="1114766024068112429">"Omogućiti USB Debugging?"</string>
+ <string name="usb_debugging_message" msgid="719863946976291180">"Omogućiti USB Debugging na ovom računalu?"\n"Vaš je otisak prsta RSA ključa"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
<string name="usb_debugging_always" msgid="4253099426793114693">"Uvijek dopusti ovom računalu"</string>
<string name="compat_mode_on" msgid="6623839244840638213">"Zumiraj i ispuni zaslon"</string>
<string name="compat_mode_off" msgid="4434467572461327898">"Rastegni i ispuni zaslon"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 59730af..f820464 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -58,8 +58,8 @@
<string name="label_view" msgid="6304565553218192990">"הצג"</string>
<string name="always_use_device" msgid="1450287437017315906">"השתמש כברירת מחדל עבור מכשיר USB זה"</string>
<string name="always_use_accessory" msgid="1210954576979621596">"השתמש כברירת מחדל עבור אביזר USB זה"</string>
- <string name="usb_debugging_title" msgid="1114766024068112429">"האם לאפשר ניקוי באגים ב-USB?"</string>
- <string name="usb_debugging_message" msgid="719863946976291180">"האם להרשות ניקוי באגים ב-USB ממחשב זה?"\n"טביעת האצבע של מפתח ה-RSA שלך היא"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
+ <string name="usb_debugging_title" msgid="1114766024068112429">"האם לאפשר ניפוי באגים ב-USB?"</string>
+ <string name="usb_debugging_message" msgid="719863946976291180">"האם להרשות ניפוי באגים ב-USB ממחשב זה?"\n"טביעת האצבע של מפתח ה-RSA שלך היא"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
<string name="usb_debugging_always" msgid="4253099426793114693">"הרשה תמיד במחשב זה"</string>
<string name="compat_mode_on" msgid="6623839244840638213">"הגדל תצוגה כדי למלא את המסך"</string>
<string name="compat_mode_off" msgid="4434467572461327898">"מתח כדי למלא את המסך"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index a6fec13..768b1c8 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -58,7 +58,7 @@
<string name="always_use_accessory" msgid="1210954576979621596">"Tumia kama chaguo-msingi ya kifuasi hiki cha USB"</string>
<string name="usb_debugging_title" msgid="1114766024068112429">"Ruhusu Utatuaji USB?"</string>
<string name="usb_debugging_message" msgid="719863946976291180">"Ruhusu Utatuaji wa USB kutoka kwenye kompyuta hii?"\n"Kitufe chako RSA cha alama ya kidole ni "\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
- <string name="usb_debugging_always" msgid="4253099426793114693">"Kila wakati ruhusu kompyuta hii"</string>
+ <string name="usb_debugging_always" msgid="4253099426793114693">"Ruhusu kompyuta hii kila wakati"</string>
<string name="compat_mode_on" msgid="6623839244840638213">"Kuza ili kujaza skrini"</string>
<string name="compat_mode_off" msgid="4434467572461327898">"Tanua ili kujaza skrini"</string>
<string name="compat_mode_help_header" msgid="7969493989397529910">"Kukuza kwa Utangamanifu"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index c24fa36..fb06e1a 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -59,7 +59,7 @@
<string name="always_use_device" msgid="1450287437017315906">"ใช้ค่าเริ่มต้นสำหรับอุปกรณ์ USB นี้"</string>
<string name="always_use_accessory" msgid="1210954576979621596">"ใช้ค่าเริ่มต้นสำหรับอุปกรณ์เสริม USB นี้"</string>
<string name="usb_debugging_title" msgid="1114766024068112429">"อนุญาตการแก้ไขข้อบกพร่องของ USB หรือไม่"</string>
- <string name="usb_debugging_message" msgid="719863946976291180">"อนุญาตการแก้ไขข้อบกพร่องของ USB จากคอมพิวเตอร์เครื่องนี้หรือไ่ม่"\n"ลายนิ้วมือคีย์ RSA ของคุณคือ"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
+ <string name="usb_debugging_message" msgid="719863946976291180">"อนุญาตการแก้ไขข้อบกพร่องของ USB จากคอมพิวเตอร์เครื่องนี้หรือไม่"\n"ลายนิ้วมือคีย์ RSA ของคุณคือ"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
<string name="usb_debugging_always" msgid="4253099426793114693">"อนุญาตคอมพิวเตอร์เครื่องนี้เสมอ"</string>
<string name="compat_mode_on" msgid="6623839244840638213">"ขยายจนเต็มหน้าจอ"</string>
<string name="compat_mode_off" msgid="4434467572461327898">"ยืดจนเต็มหน้าจอ"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index c642c87..f9d2b3c 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -59,7 +59,7 @@
<string name="always_use_device" msgid="1450287437017315906">"Sử dụng theo mặc định cho thiết bị USB này"</string>
<string name="always_use_accessory" msgid="1210954576979621596">"Sử dụng theo mặc định cho phụ kiện USB này"</string>
<string name="usb_debugging_title" msgid="1114766024068112429">"Cho phép gỡ lỗi USB?"</string>
- <string name="usb_debugging_message" msgid="719863946976291180">"Cho phép gỡ lỗi USB từ máy tính này?"\n"Tệp tham chiếu chính của RSA của bạn là"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
+ <string name="usb_debugging_message" msgid="719863946976291180">"Cho phép gỡ lỗi USB từ máy tính này?"\n"Dấu tay khóa RSA của bạn là"\n"<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
<string name="usb_debugging_always" msgid="4253099426793114693">"Luôn cho phép máy tính này"</string>
<string name="compat_mode_on" msgid="6623839244840638213">"T.phóng để lấp đầy m.hình"</string>
<string name="compat_mode_off" msgid="4434467572461327898">"Giãn ra để lấp đầy m.hình"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 48fb21f..047570f 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -35,5 +35,17 @@
<declare-styleable name="RecentsPanelView">
<attr name="recentItemLayout" format="reference" />
</declare-styleable>
+ <declare-styleable name="DeadZone">
+ <attr name="minSize" format="dimension" />
+ <attr name="maxSize" format="dimension" />
+ <attr name="holdTime" format="integer" />
+ <attr name="decayTime" format="integer" />
+ <attr name="orientation" />
+ </declare-styleable>
+
+ <attr name="orientation">
+ <enum name="horizontal" value="0" />
+ <enum name="vertical" value="1" />
+ </attr>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1cd7904..13622e6 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -72,5 +72,11 @@
<!-- Whether we're using the tablet-optimized recents interface (we use this
value at runtime for some things) -->
<integer name="status_bar_recents_bg_gradient_degrees">90</integer>
+
+ <!-- decay duration (from size_max -> size), in ms -->
+ <integer name="navigation_bar_deadzone_hold">333</integer>
+ <integer name="navigation_bar_deadzone_decay">333</integer>
+
+ <bool name="config_dead_zone_flash">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 94465e2..0d7cdb1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -63,6 +63,8 @@
<!-- thickness (height) of the dead zone at the top of the navigation bar,
reducing false presses on navbar buttons; approx 2mm -->
<dimen name="navigation_bar_deadzone_size">12dp</dimen>
+ <!-- size of the dead zone when touches have recently occurred elsewhere on screen -->
+ <dimen name="navigation_bar_deadzone_size_max">32dp</dimen>
<!-- Height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
index 4281ccf..2a225d9 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
@@ -337,8 +337,7 @@ public class RecentTasksLoader implements View.OnTouchListener {
mContext.getSystemService(Context.ACTIVITY_SERVICE);
final List<ActivityManager.RecentTaskInfo> recentTasks =
- am.getRecentTasksForUser(MAX_TASKS,
- ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.USER_CURRENT);
+ am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
int numTasks = recentTasks.size();
ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 33973b6..dcc2e57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -45,13 +45,12 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.DelegateViewHelper;
+import com.android.systemui.statusbar.policy.DeadZone;
public class NavigationBarView extends LinearLayout {
final static boolean DEBUG = false;
final static String TAG = "PhoneStatusBar/NavigationBarView";
- final static boolean DEBUG_DEADZONE = false;
-
final static boolean NAVBAR_ALWAYS_AT_RIGHT = true;
final static boolean ANIMATE_HIDE_TRANSITION = false; // turned off because it introduces unsightly delay when videos goes to full screen
@@ -71,6 +70,7 @@ public class NavigationBarView extends LinearLayout {
private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon;
private DelegateViewHelper mDelegateHelper;
+ private DeadZone mDeadZone;
// workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
final static boolean WORKAROUND_INVALID_LAYOUT = true;
@@ -109,10 +109,14 @@ public class NavigationBarView extends LinearLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
+ if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ mDeadZone.poke(event);
+ }
if (mDelegateHelper != null) {
- mDelegateHelper.onInterceptTouchEvent(event);
+ boolean ret = mDelegateHelper.onInterceptTouchEvent(event);
+ if (ret) return true;
}
- return true;
+ return super.onTouchEvent(event);
}
@Override
@@ -335,15 +339,13 @@ public class NavigationBarView extends LinearLayout {
mCurrentView = mRotatedViews[rot];
mCurrentView.setVisibility(View.VISIBLE);
+ mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
+
// force the low profile & disabled states into compliance
setLowProfile(mLowProfile, false, true /* force */);
setDisabledFlags(mDisabledFlags, true /* force */);
setMenuVisibility(mShowMenu, true /* force */);
- if (DEBUG_DEADZONE) {
- mCurrentView.findViewById(R.id.deadzone).setBackgroundColor(0x808080FF);
- }
-
if (DEBUG) {
Slog.d(TAG, "reorient(): rot=" + mDisplay.getRotation());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 9d2678a..9b4ee38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -639,6 +639,7 @@ public class PhoneStatusBar extends BaseStatusBar {
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.OPAQUE);
// this will allow the navbar to run in an overlay on devices that support this
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
index 19fbe96..e5ef5fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
@@ -16,26 +16,150 @@
package com.android.systemui.statusbar.policy;
+import android.animation.ObjectAnimator;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
import com.android.systemui.R;
public class DeadZone extends View {
+ public static final String TAG = "DeadZone";
+
+ public static final boolean DEBUG = false;
+ public static final int HORIZONTAL = 0;
+ public static final int VERTICAL = 1;
+
+ private static final boolean CHATTY = true; // print to logcat when we eat a click
+
+ private boolean mShouldFlash;
+ private float mFlashFrac = 0f;
+
+ private int mSizeMax;
+ private int mSizeMin;
+ // Upon activity elsewhere in the UI, the dead zone will hold steady for
+ // mHold ms, then move back over the course of mDecay ms
+ private int mHold, mDecay;
+ private boolean mVertical;
+ private long mLastPokeTime;
+
+ private final Runnable mDebugFlash = new Runnable() {
+ @Override
+ public void run() {
+ ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start();
+ }
+ };
+
public DeadZone(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DeadZone(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DeadZone,
+ defStyle, 0);
+
+ mHold = a.getInteger(R.styleable.DeadZone_holdTime, 0);
+ mDecay = a.getInteger(R.styleable.DeadZone_decayTime, 0);
+
+ mSizeMin = a.getDimensionPixelSize(R.styleable.DeadZone_minSize, 0);
+ mSizeMax = a.getDimensionPixelSize(R.styleable.DeadZone_maxSize, 0);
+
+ int index = a.getInt(R.styleable.DeadZone_orientation, -1);
+ mVertical = (index == VERTICAL);
+
+ if (DEBUG)
+ Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold
+ + (mVertical ? " vertical" : " horizontal"));
+
+ setFlashOnTouchCapture(context.getResources().getBoolean(R.bool.config_dead_zone_flash));
+ }
+
+ static float lerp(float a, float b, float f) {
+ return (b - a) * f + a;
+ }
+
+ private float getSize(long now) {
+ if (mSizeMax == 0)
+ return 0;
+ long dt = (now - mLastPokeTime);
+ if (dt > mHold + mDecay)
+ return mSizeMin;
+ if (dt < mHold)
+ return mSizeMax;
+ return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay);
}
- // I made you a touch event
+ public void setFlashOnTouchCapture(boolean dbg) {
+ mShouldFlash = dbg;
+ mFlashFrac = 0f;
+ postInvalidate();
+ }
+
+ // I made you a touch event...
@Override
- public boolean onTouchEvent (MotionEvent event) {
- return true; // but I eated it
+ public boolean onTouchEvent(MotionEvent event) {
+ if (DEBUG) {
+ Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction()));
+ }
+
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_OUTSIDE) {
+ poke(event);
+ } else if (action == MotionEvent.ACTION_DOWN) {
+ if (DEBUG) {
+ Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY());
+ }
+ int size = (int) getSize(event.getEventTime());
+ if ((mVertical && event.getX() < size) || event.getY() < size) {
+ if (CHATTY) {
+ Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")");
+ }
+ if (mShouldFlash) {
+ post(mDebugFlash);
+ postInvalidate();
+ }
+ return true; // ...but I eated it
+ }
+ }
+ return false;
+ }
+
+ public void poke(MotionEvent event) {
+ mLastPokeTime = event.getEventTime();
+ if (DEBUG)
+ Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime));
+ postInvalidate();
+ }
+
+ public void setFlash(float f) {
+ mFlashFrac = f;
+ postInvalidate();
}
-}
+ public float getFlash() {
+ return mFlashFrac;
+ }
+
+ @Override
+ public void onDraw(Canvas can) {
+ if (!mShouldFlash || mFlashFrac <= 0f) {
+ return;
+ }
+
+ final int size = (int) getSize(SystemClock.uptimeMillis());
+ can.clipRect(0, 0, mVertical ? size : can.getWidth(), mVertical ? can.getHeight() : size);
+ final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac;
+ can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA);
+
+ if (DEBUG && size > mSizeMin)
+ // crazy aggressive redrawing here, for debugging only
+ postInvalidateDelayed(100);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 8365d08..d94c6b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -988,6 +988,8 @@ public class NetworkController extends BroadcastReceiver {
combinedActivityIconId = mMobileActivityIconId;
combinedSignalIconId = mDataSignalIconId; // set by updateDataIcon()
mContentDescriptionCombinedSignal = mContentDescriptionDataType;
+ } else {
+ mMobileActivityIconId = 0;
}
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 209ad38..2b6e856 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -61,6 +61,8 @@ import android.provider.Settings;
import com.android.internal.R;
import com.android.internal.policy.PolicyManager;
+import com.android.internal.policy.impl.keyguard.KeyguardViewManager;
+import com.android.internal.policy.impl.keyguard.KeyguardViewMediator;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.telephony.ITelephony;
import com.android.internal.widget.PointerLocationView;
@@ -108,6 +110,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVE
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER;
@@ -218,14 +221,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static final int NAVIGATION_BAR_PANEL_LAYER = 20;
// system-level error dialogs
static final int SYSTEM_ERROR_LAYER = 21;
+ // used to simulate secondary display devices
+ static final int DISPLAY_OVERLAY_LAYER = 22;
// the drag layer: input for drag-and-drop is associated with this window,
// which sits above all other focusable windows
- static final int DRAG_LAYER = 22;
- static final int SECURE_SYSTEM_OVERLAY_LAYER = 23;
- static final int BOOT_PROGRESS_LAYER = 24;
+ static final int DRAG_LAYER = 23;
+ static final int SECURE_SYSTEM_OVERLAY_LAYER = 24;
+ static final int BOOT_PROGRESS_LAYER = 25;
// the (mouse) pointer layer
- static final int POINTER_LAYER = 25;
- static final int HIDDEN_NAV_CONSUMER_LAYER = 26;
+ static final int POINTER_LAYER = 26;
+ static final int HIDDEN_NAV_CONSUMER_LAYER = 27;
static final int APPLICATION_MEDIA_SUBLAYER = -2;
static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1;
@@ -861,7 +866,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
if (!mHeadless) {
// don't create KeyguardViewMediator if headless
- mKeyguardMediator = new KeyguardViewMediator(context, this);
+ mKeyguardMediator = new KeyguardViewMediator(context, null);
}
mHandler = new PolicyHandler();
mOrientationListener = new MyOrientationListener(mContext);
@@ -1327,6 +1332,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return SCREENSAVER_LAYER;
case TYPE_UNIVERSE_BACKGROUND:
return UNIVERSE_BACKGROUND_LAYER;
+ case TYPE_DISPLAY_OVERLAY:
+ return DISPLAY_OVERLAY_LAYER;
}
Log.e(TAG, "Unknown window type: " + type);
return APPLICATION_LAYER;
@@ -3134,7 +3141,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public void onServiceDisconnected(ComponentName name) {}
};
- if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
+ if (mContext.bindService(
+ intent, conn, Context.BIND_AUTO_CREATE, UserHandle.USER_CURRENT)) {
mScreenshotConnection = conn;
mHandler.postDelayed(mScreenshotTimeout, 10000);
}
diff --git a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java
index f476f82..39afaa2 100644
--- a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard;
import android.view.View;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java
new file mode 100644
index 0000000..31d138b
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import com.android.internal.policy.IFaceLockCallback;
+import com.android.internal.policy.IFaceLockInterface;
+import com.android.internal.widget.LockPatternUtils;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+
+public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "FULLockscreen";
+
+ private final Context mContext;
+ private final LockPatternUtils mLockPatternUtils;
+
+ // TODO: is mServiceRunning needed or can we just use mIsRunning or check if mService is null?
+ private boolean mServiceRunning = false;
+ // TODO: now that the code has been restructure to do almost all operations from a handler, this
+ // lock may no longer be necessary.
+ private final Object mServiceRunningLock = new Object();
+ private IFaceLockInterface mService;
+ private boolean mBoundToService = false;
+ private View mFaceUnlockView;
+
+ private Handler mHandler;
+ private final int MSG_SHOW_FACE_UNLOCK_VIEW = 0;
+ private final int MSG_HIDE_FACE_UNLOCK_VIEW = 1;
+ private final int MSG_SERVICE_CONNECTED = 2;
+ private final int MSG_SERVICE_DISCONNECTED = 3;
+ private final int MSG_UNLOCK = 4;
+ private final int MSG_CANCEL = 5;
+ private final int MSG_REPORT_FAILED_ATTEMPT = 6;
+ private final int MSG_EXPOSE_FALLBACK = 7;
+ private final int MSG_POKE_WAKELOCK = 8;
+
+ // TODO: This was added for the purpose of adhering to what the biometric interface expects
+ // the isRunning() function to return. However, it is probably not necessary to have both
+ // mRunning and mServiceRunning. I'd just rather wait to change that logic.
+ private volatile boolean mIsRunning = false;
+
+ // Long enough to stay visible while the service starts
+ // Short enough to not have to wait long for backup if service fails to start or crashes
+ // The service can take a couple of seconds to start on the first try after boot
+ private final int SERVICE_STARTUP_VIEW_TIMEOUT = 3000;
+
+ // So the user has a consistent amount of time when brought to the backup method from Face
+ // Unlock
+ private final int BACKUP_LOCK_TIMEOUT = 5000;
+
+ KeyguardSecurityCallback mKeyguardScreenCallback;
+
+ /**
+ * Stores some of the structures that Face Unlock will need to access and creates the handler
+ * will be used to execute messages on the UI thread.
+ */
+ public FaceUnlock(Context context, KeyguardSecurityCallback keyguardScreenCallback) {
+ mContext = context;
+ mLockPatternUtils = new LockPatternUtils(context);
+ mKeyguardScreenCallback = keyguardScreenCallback;
+ mHandler = new Handler(this);
+ }
+
+ /**
+ * Stores and displays the view that Face Unlock is allowed to draw within.
+ * TODO: since the layout object will eventually be shared by multiple biometric unlock
+ * methods, we will have to add our other views (background, cancel button) here.
+ */
+ public void initializeView(View biometricUnlockView) {
+ Log.d(TAG, "initializeView()");
+ mFaceUnlockView = biometricUnlockView;
+ }
+
+ /**
+ * Indicates whether Face Unlock is currently running.
+ */
+ public boolean isRunning() {
+ return mIsRunning;
+ }
+
+ /**
+ * Sets the Face Unlock view to visible, hiding it after the specified amount of time. If
+ * timeoutMillis is 0, no hide is performed. Called on the UI thread.
+ */
+ public void show(long timeoutMillis) {
+ if (DEBUG) Log.d(TAG, "show()");
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ Log.e(TAG, "show() called off of the UI thread");
+ }
+
+ removeDisplayMessages();
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.VISIBLE);
+ }
+ if (timeoutMillis > 0) {
+ mHandler.sendEmptyMessageDelayed(MSG_HIDE_FACE_UNLOCK_VIEW, timeoutMillis);
+ }
+ }
+
+ /**
+ * Hides the Face Unlock view.
+ */
+ public void hide() {
+ if (DEBUG) Log.d(TAG, "hide()");
+ // Remove messages to prevent a delayed show message from undo-ing the hide
+ removeDisplayMessages();
+ mHandler.sendEmptyMessage(MSG_HIDE_FACE_UNLOCK_VIEW);
+ }
+
+ /**
+ * Binds to the Face Unlock service. Face Unlock will be started when the bind completes. The
+ * Face Unlock view is displayed to hide the backup lock while the service is starting up.
+ * Called on the UI thread.
+ */
+ public boolean start() {
+ if (DEBUG) Log.d(TAG, "start()");
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ Log.e(TAG, "start() called off of the UI thread");
+ }
+
+ if (mIsRunning) {
+ Log.w(TAG, "start() called when already running");
+ }
+
+ // Show Face Unlock view, but only for a little bit so lockpattern will become visible if
+ // Face Unlock fails to start or crashes
+ // This must show before bind to guarantee that Face Unlock has a place to display
+ show(SERVICE_STARTUP_VIEW_TIMEOUT);
+ if (!mBoundToService) {
+ Log.d(TAG, "Binding to Face Unlock service");
+ mContext.bindService(new Intent(IFaceLockInterface.class.getName()),
+ mConnection,
+ Context.BIND_AUTO_CREATE,
+ mLockPatternUtils.getCurrentUser());
+ mBoundToService = true;
+ } else {
+ Log.w(TAG, "Attempt to bind to Face Unlock when already bound");
+ }
+
+ mIsRunning = true;
+ return true;
+ }
+
+ /**
+ * Stops Face Unlock and unbinds from the service. Called on the UI thread.
+ */
+ public boolean stop() {
+ if (DEBUG) Log.d(TAG, "stop()");
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ Log.e(TAG, "stop() called off of the UI thread");
+ }
+
+ boolean mWasRunning = mIsRunning;
+ stopUi();
+
+ if (mBoundToService) {
+ if (mService != null) {
+ try {
+ mService.unregisterCallback(mFaceUnlockCallback);
+ } catch (RemoteException e) {
+ // Not much we can do
+ }
+ }
+ Log.d(TAG, "Unbinding from Face Unlock service");
+ mContext.unbindService(mConnection);
+ mBoundToService = false;
+ } else {
+ // This is usually not an error when this happens. Sometimes we will tell it to
+ // unbind multiple times because it's called from both onWindowFocusChanged and
+ // onDetachedFromWindow.
+ if (DEBUG) Log.d(TAG, "Attempt to unbind from Face Unlock when not bound");
+ }
+ mIsRunning = false;
+ return mWasRunning;
+ }
+
+ /**
+ * Frees up resources used by Face Unlock and stops it if it is still running.
+ */
+ public void cleanUp() {
+ if (DEBUG) Log.d(TAG, "cleanUp()");
+ if (mService != null) {
+ try {
+ mService.unregisterCallback(mFaceUnlockCallback);
+ } catch (RemoteException e) {
+ // Not much we can do
+ }
+ stopUi();
+ mService = null;
+ }
+ }
+
+ /**
+ * Returns the Device Policy Manager quality for Face Unlock, which is BIOMETRIC_WEAK.
+ */
+ public int getQuality() {
+ return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+ }
+
+ /**
+ * Handles messages such that everything happens on the UI thread in a deterministic order.
+ * Calls from the Face Unlock service come from binder threads. Calls from lockscreen typically
+ * come from the UI thread. This makes sure there are no race conditions between those calls.
+ */
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SHOW_FACE_UNLOCK_VIEW:
+ handleShowFaceUnlockView();
+ break;
+ case MSG_HIDE_FACE_UNLOCK_VIEW:
+ handleHideFaceUnlockView();
+ break;
+ case MSG_SERVICE_CONNECTED:
+ handleServiceConnected();
+ break;
+ case MSG_SERVICE_DISCONNECTED:
+ handleServiceDisconnected();
+ break;
+ case MSG_UNLOCK:
+ handleUnlock();
+ break;
+ case MSG_CANCEL:
+ handleCancel();
+ break;
+ case MSG_REPORT_FAILED_ATTEMPT:
+ handleReportFailedAttempt();
+ break;
+ case MSG_EXPOSE_FALLBACK:
+ handleExposeFallback();
+ break;
+ case MSG_POKE_WAKELOCK:
+ handlePokeWakelock(msg.arg1);
+ break;
+ default:
+ Log.e(TAG, "Unhandled message");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sets the Face Unlock view to visible, thus covering the backup lock.
+ */
+ void handleShowFaceUnlockView() {
+ if (DEBUG) Log.d(TAG, "handleShowFaceUnlockView()");
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.VISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleShowFaceUnlockView()");
+ }
+ }
+
+ /**
+ * Sets the Face Unlock view to invisible, thus exposing the backup lock.
+ */
+ void handleHideFaceUnlockView() {
+ if (DEBUG) Log.d(TAG, "handleHideFaceUnlockView()");
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.INVISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleHideFaceUnlockView()");
+ }
+ }
+
+ /**
+ * Tells the service to start its UI via an AIDL interface. Called when the
+ * onServiceConnected() callback is received.
+ */
+ void handleServiceConnected() {
+ Log.d(TAG, "handleServiceConnected()");
+
+ // It is possible that an unbind has occurred in the time between the bind and when this
+ // function is reached. If an unbind has already occurred, proceeding on to call startUi()
+ // can result in a fatal error. Note that the onServiceConnected() callback is
+ // asynchronous, so this possibility would still exist if we executed this directly in
+ // onServiceConnected() rather than using a handler.
+ if (!mBoundToService) {
+ Log.d(TAG, "Dropping startUi() in handleServiceConnected() because no longer bound");
+ return;
+ }
+
+ try {
+ mService.registerCallback(mFaceUnlockCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString());
+ mService = null;
+ mBoundToService = false;
+ mIsRunning = false;
+ return;
+ }
+
+ if (mFaceUnlockView != null) {
+ IBinder windowToken = mFaceUnlockView.getWindowToken();
+ if (windowToken != null) {
+ // When switching between portrait and landscape view while Face Unlock is running,
+ // the screen will eventually go dark unless we poke the wakelock when Face Unlock
+ // is restarted.
+ mKeyguardScreenCallback.userActivity(0);
+
+ int[] position;
+ position = new int[2];
+ mFaceUnlockView.getLocationInWindow(position);
+ startUi(windowToken, position[0], position[1], mFaceUnlockView.getWidth(),
+ mFaceUnlockView.getHeight());
+ } else {
+ Log.e(TAG, "windowToken is null in handleServiceConnected()");
+ }
+ }
+ }
+
+ /**
+ * Called when the onServiceDisconnected() callback is received. This should not happen during
+ * normal operation. It indicates an error has occurred.
+ */
+ void handleServiceDisconnected() {
+ Log.e(TAG, "handleServiceDisconnected()");
+ // TODO: this lock may no longer be needed now that everything is being called from a
+ // handler
+ synchronized (mServiceRunningLock) {
+ mService = null;
+ mServiceRunning = false;
+ }
+ mBoundToService = false;
+ mIsRunning = false;
+ }
+
+ /**
+ * Stops the Face Unlock service and tells the device to grant access to the user. Shows the
+ * Face Unlock view to keep the backup lock covered while the device unlocks.
+ */
+ void handleUnlock() {
+ if (DEBUG) Log.d(TAG, "handleUnlock()");
+ removeDisplayMessages();
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.VISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleUnlock()");
+ }
+ stop();
+ mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
+ mKeyguardScreenCallback.dismiss(true);
+ }
+
+ /**
+ * Stops the Face Unlock service and exposes the backup lock.
+ */
+ void handleCancel() {
+ if (DEBUG) Log.d(TAG, "handleCancel()");
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.INVISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleCancel()");
+ }
+ stop();
+ mKeyguardScreenCallback.userActivity(BACKUP_LOCK_TIMEOUT);
+ }
+
+ /**
+ * Increments the number of failed Face Unlock attempts.
+ */
+ void handleReportFailedAttempt() {
+ if (DEBUG) Log.d(TAG, "handleReportFailedAttempt()");
+ mKeyguardScreenCallback.reportFailedUnlockAttempt();
+ }
+
+ /**
+ * Hides the Face Unlock view to expose the backup lock. Called when the Face Unlock service UI
+ * is started, indicating there is no need to continue displaying the underlying view because
+ * the service UI is now covering the backup lock.
+ */
+ void handleExposeFallback() {
+ if (DEBUG) Log.d(TAG, "handleExposeFallback()");
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.INVISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleExposeFallback()");
+ }
+ }
+
+ /**
+ * Pokes the wakelock to keep the screen alive and active for a specific amount of time.
+ */
+ void handlePokeWakelock(int millis) {
+ mKeyguardScreenCallback.userActivity(millis);
+ }
+
+ /**
+ * Removes show and hide messages from the message queue. Called to prevent delayed show/hide
+ * messages from undoing a new message.
+ */
+ private void removeDisplayMessages() {
+ mHandler.removeMessages(MSG_SHOW_FACE_UNLOCK_VIEW);
+ mHandler.removeMessages(MSG_HIDE_FACE_UNLOCK_VIEW);
+ }
+
+ /**
+ * Implements service connection methods.
+ */
+ private ServiceConnection mConnection = new ServiceConnection() {
+ /**
+ * Called when the Face Unlock service connects after calling bind().
+ */
+ public void onServiceConnected(ComponentName className, IBinder iservice) {
+ Log.d(TAG, "Connected to Face Unlock service");
+ mService = IFaceLockInterface.Stub.asInterface(iservice);
+ mHandler.sendEmptyMessage(MSG_SERVICE_CONNECTED);
+ }
+
+ /**
+ * Called if the Face Unlock service unexpectedly disconnects. This indicates an error.
+ */
+ public void onServiceDisconnected(ComponentName className) {
+ Log.e(TAG, "Unexpected disconnect from Face Unlock service");
+ mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED);
+ }
+ };
+
+ /**
+ * Tells the Face Unlock service to start displaying its UI and start processing.
+ */
+ private void startUi(IBinder windowToken, int x, int y, int w, int h) {
+ if (DEBUG) Log.d(TAG, "startUi()");
+ synchronized (mServiceRunningLock) {
+ if (!mServiceRunning) {
+ Log.d(TAG, "Starting Face Unlock");
+ try {
+ mService.startUi(windowToken, x, y, w, h,
+ mLockPatternUtils.isBiometricWeakLivelinessEnabled());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught exception starting Face Unlock: " + e.toString());
+ return;
+ }
+ mServiceRunning = true;
+ } else {
+ Log.w(TAG, "startUi() attempted while running");
+ }
+ }
+ }
+
+ /**
+ * Tells the Face Unlock service to stop displaying its UI and stop processing.
+ */
+ private void stopUi() {
+ if (DEBUG) Log.d(TAG, "stopUi()");
+ // Note that attempting to stop Face Unlock when it's not running is not an issue.
+ // Face Unlock can return, which stops it and then we try to stop it when the
+ // screen is turned off. That's why we check.
+ synchronized (mServiceRunningLock) {
+ if (mServiceRunning) {
+ Log.d(TAG, "Stopping Face Unlock");
+ try {
+ mService.stopUi();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught exception stopping Face Unlock: " + e.toString());
+ }
+ mServiceRunning = false;
+ } else {
+ // This is usually not an error when this happens. Sometimes we will tell it to
+ // stop multiple times because it's called from both onWindowFocusChanged and
+ // onDetachedFromWindow.
+ if (DEBUG) Log.d(TAG, "stopUi() attempted while not running");
+ }
+ }
+ }
+
+ /**
+ * Implements the AIDL biometric unlock service callback interface.
+ */
+ private final IFaceLockCallback mFaceUnlockCallback = new IFaceLockCallback.Stub() {
+ /**
+ * Called when Face Unlock wants to grant access to the user.
+ */
+ public void unlock() {
+ if (DEBUG) Log.d(TAG, "unlock()");
+ mHandler.sendEmptyMessage(MSG_UNLOCK);
+ }
+
+ /**
+ * Called when Face Unlock wants to go to the backup.
+ */
+ public void cancel() {
+ if (DEBUG) Log.d(TAG, "cancel()");
+ mHandler.sendEmptyMessage(MSG_CANCEL);
+ }
+
+ /**
+ * Called when Face Unlock wants to increment the number of failed attempts.
+ */
+ public void reportFailedAttempt() {
+ if (DEBUG) Log.d(TAG, "reportFailedAttempt()");
+ mHandler.sendEmptyMessage(MSG_REPORT_FAILED_ATTEMPT);
+ }
+
+ /**
+ * Called when the Face Unlock service starts displaying the UI, indicating that the backup
+ * unlock can be exposed because the Face Unlock service is now covering the backup with its
+ * UI.
+ **/
+ public void exposeFallback() {
+ if (DEBUG) Log.d(TAG, "exposeFallback()");
+ mHandler.sendEmptyMessage(MSG_EXPOSE_FALLBACK);
+ }
+
+ /**
+ * Called when Face Unlock wants to keep the screen alive and active for a specific amount
+ * of time.
+ */
+ public void pokeWakelock(int millis) {
+ if (DEBUG) Log.d(TAG, "pokeWakelock() for " + millis + "ms");
+ Message message = mHandler.obtainMessage(MSG_POKE_WAKELOCK, millis, -1);
+ mHandler.sendMessage(message);
+ }
+
+ };
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java
new file mode 100644
index 0000000..1e73c5b
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.LoginFilter;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.R;
+
+import java.io.IOException;
+
+/**
+ * When the user forgets their password a bunch of times, we fall back on their
+ * account's login/password to unlock the phone (and reset their lock pattern).
+ */
+public class KeyguardAccountView extends LinearLayout implements KeyguardSecurityView,
+ View.OnClickListener, TextWatcher {
+ private static final int AWAKE_POKE_MILLIS = 30000;
+ private static final String LOCK_PATTERN_PACKAGE = "com.android.settings";
+ private static final String LOCK_PATTERN_CLASS = LOCK_PATTERN_PACKAGE + ".ChooseLockGeneric";
+
+ private KeyguardSecurityCallback mCallback;
+ private LockPatternUtils mLockPatternUtils;
+ private EditText mLogin;
+ private EditText mPassword;
+ private Button mOk;
+ public boolean mEnableFallback;
+ private KeyguardNavigationManager mNavigationManager;
+
+ /**
+ * Shown while making asynchronous check of password.
+ */
+ private ProgressDialog mCheckingDialog;
+
+ public KeyguardAccountView(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardAccountView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardAccountView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mNavigationManager = new KeyguardNavigationManager(this);
+
+ mLogin = (EditText) findViewById(R.id.login);
+ mLogin.setFilters(new InputFilter[] { new LoginFilter.UsernameFilterGeneric() } );
+ mLogin.addTextChangedListener(this);
+
+ mPassword = (EditText) findViewById(R.id.password);
+ mPassword.addTextChangedListener(this);
+
+ mOk = (Button) findViewById(R.id.ok);
+ mOk.setOnClickListener(this);
+ reset();
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+
+ public void afterTextChanged(Editable s) {
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ if (mCallback != null) {
+ mCallback.userActivity(AWAKE_POKE_MILLIS);
+ }
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction,
+ Rect previouslyFocusedRect) {
+ // send focus to the login field
+ return mLogin.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ public boolean needsInput() {
+ return true;
+ }
+
+ public void reset() {
+ // start fresh
+ mLogin.setText("");
+ mPassword.setText("");
+ mLogin.requestFocus();
+ mNavigationManager.setMessage(mLockPatternUtils.isPermanentlyLocked() ?
+ R.string.kg_login_too_many_attempts : R.string.kg_login_instructions);
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ if (mCheckingDialog != null) {
+ mCheckingDialog.hide();
+ }
+ mCallback = null;
+ mLockPatternUtils = null;
+ }
+
+ public void onClick(View v) {
+ mCallback.userActivity(0);
+ if (v == mOk) {
+ asyncCheckPassword();
+ }
+ }
+
+ private void postOnCheckPasswordResult(final boolean success) {
+ // ensure this runs on UI thread
+ mLogin.post(new Runnable() {
+ public void run() {
+ if (success) {
+ // clear out forgotten password
+ mLockPatternUtils.setPermanentlyLocked(false);
+ mLockPatternUtils.setLockPatternEnabled(false);
+ mLockPatternUtils.saveLockPattern(null);
+
+ // launch the 'choose lock pattern' activity so
+ // the user can pick a new one if they want to
+ Intent intent = new Intent();
+ intent.setClassName(LOCK_PATTERN_PACKAGE, LOCK_PATTERN_CLASS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ mCallback.reportSuccessfulUnlockAttempt();
+
+ // dismiss keyguard
+ mCallback.dismiss(true);
+ } else {
+ mNavigationManager.setMessage(R.string.kg_login_invalid_input);
+ mPassword.setText("");
+ mCallback.reportFailedUnlockAttempt();
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (mLockPatternUtils.isPermanentlyLocked()) {
+ mCallback.dismiss(false);
+ } else {
+ // TODO: mCallback.forgotPattern(false);
+ }
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ /**
+ * Given the string the user entered in the 'username' field, find
+ * the stored account that they probably intended. Prefer, in order:
+ *
+ * - an exact match for what was typed, or
+ * - a case-insensitive match for what was typed, or
+ * - if they didn't include a domain, an exact match of the username, or
+ * - if they didn't include a domain, a case-insensitive
+ * match of the username.
+ *
+ * If there is a tie for the best match, choose neither --
+ * the user needs to be more specific.
+ *
+ * @return an account name from the database, or null if we can't
+ * find a single best match.
+ */
+ private Account findIntendedAccount(String username) {
+ Account[] accounts = AccountManager.get(mContext).getAccountsByType("com.google");
+
+ // Try to figure out which account they meant if they
+ // typed only the username (and not the domain), or got
+ // the case wrong.
+
+ Account bestAccount = null;
+ int bestScore = 0;
+ for (Account a: accounts) {
+ int score = 0;
+ if (username.equals(a.name)) {
+ score = 4;
+ } else if (username.equalsIgnoreCase(a.name)) {
+ score = 3;
+ } else if (username.indexOf('@') < 0) {
+ int i = a.name.indexOf('@');
+ if (i >= 0) {
+ String aUsername = a.name.substring(0, i);
+ if (username.equals(aUsername)) {
+ score = 2;
+ } else if (username.equalsIgnoreCase(aUsername)) {
+ score = 1;
+ }
+ }
+ }
+ if (score > bestScore) {
+ bestAccount = a;
+ bestScore = score;
+ } else if (score == bestScore) {
+ bestAccount = null;
+ }
+ }
+ return bestAccount;
+ }
+
+ private void asyncCheckPassword() {
+ mCallback.userActivity(AWAKE_POKE_MILLIS);
+ final String login = mLogin.getText().toString();
+ final String password = mPassword.getText().toString();
+ Account account = findIntendedAccount(login);
+ if (account == null) {
+ postOnCheckPasswordResult(false);
+ return;
+ }
+ getProgressDialog().show();
+ Bundle options = new Bundle();
+ options.putString(AccountManager.KEY_PASSWORD, password);
+ AccountManager.get(mContext).confirmCredentials(account, options, null /* activity */,
+ new AccountManagerCallback<Bundle>() {
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ mCallback.userActivity(AWAKE_POKE_MILLIS);
+ final Bundle result = future.getResult();
+ final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ postOnCheckPasswordResult(verified);
+ } catch (OperationCanceledException e) {
+ postOnCheckPasswordResult(false);
+ } catch (IOException e) {
+ postOnCheckPasswordResult(false);
+ } catch (AuthenticatorException e) {
+ postOnCheckPasswordResult(false);
+ } finally {
+ mLogin.post(new Runnable() {
+ public void run() {
+ getProgressDialog().hide();
+ }
+ });
+ }
+ }
+ }, null /* handler */);
+ }
+
+ private Dialog getProgressDialog() {
+ if (mCheckingDialog == null) {
+ mCheckingDialog = new ProgressDialog(mContext);
+ mCheckingDialog.setMessage(
+ mContext.getString(R.string.kg_login_checking_password));
+ mCheckingDialog.setIndeterminate(true);
+ mCheckingDialog.setCancelable(false);
+ mCheckingDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ return mCheckingDialog;
+ }
+
+ @Override
+ public void onPause() {
+
+ }
+
+ @Override
+ public void onResume() {
+ reset();
+ }
+
+}
+
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
new file mode 100644
index 0000000..843151b
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+
+public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecurityView {
+
+ // Long enough to stay visible while dialer comes up
+ // Short enough to not be visible if the user goes back immediately
+ private static final int BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT = 1000;
+ private KeyguardSecurityCallback mKeyguardSecurityCallback;
+ private LockPatternUtils mLockPatternUtils;
+
+ public KeyguardFaceUnlockView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardFaceUnlockView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mKeyguardSecurityCallback = callback;
+ }
+
+ @Override
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ @Override
+ public void reset() {
+
+ }
+
+ @Override
+ public void onPause() {
+
+ }
+
+ @Override
+ public void onResume() {
+
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mKeyguardSecurityCallback;
+ }
+
+ // TODO
+ // public void onRefreshBatteryInfo(BatteryStatus status) {
+ // // When someone plugs in or unplugs the device, we hide the biometric sensor area and
+ // // suppress its startup for the next onScreenTurnedOn(). Since plugging/unplugging
+ // // causes the screen to turn on, the biometric unlock would start if it wasn't
+ // // suppressed.
+ // //
+ // // However, if the biometric unlock is already running, we do not want to interrupt it.
+ // final boolean pluggedIn = status.isPluggedIn();
+ // if (mBiometricUnlock != null && mPluggedIn != pluggedIn
+ // && !mBiometricUnlock.isRunning()) {
+ // mBiometricUnlock.stop();
+ // mBiometricUnlock.hide();
+ // mSuppressBiometricUnlock = true;
+ // }
+ // mPluggedIn = pluggedIn;
+ // }
+
+ // We need to stop the biometric unlock when a phone call comes in
+ // @Override
+ // public void onPhoneStateChanged(int phoneState) {
+ // if (DEBUG) Log.d(TAG, "phone state: " + phoneState);
+ // if (phoneState == TelephonyManager.CALL_STATE_RINGING) {
+ // mSuppressBiometricUnlock = true;
+ // mBiometricUnlock.stop();
+ // mBiometricUnlock.hide();
+ // }
+ // }
+
+ // @Override
+ // public void onUserSwitched(int userId) {
+ // if (mBiometricUnlock != null) {
+ // mBiometricUnlock.stop();
+ // }
+ // mLockPatternUtils.setCurrentUser(userId);
+ // updateScreen(getInitialMode(), true);
+ // }
+
+ // /**
+ // * This returns false if there is any condition that indicates that the biometric unlock should
+ // * not be used before the next time the unlock screen is recreated. In other words, if this
+ // * returns false there is no need to even construct the biometric unlock.
+ // */
+ // private boolean useBiometricUnlock() {
+ // final ShowingMode unlockMode = getUnlockMode();
+ // final boolean backupIsTimedOut = (mUpdateMonitor.getFailedAttempts() >=
+ // LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
+ // return (mLockPatternUtils.usingBiometricWeak() &&
+ // mLockPatternUtils.isBiometricWeakInstalled() &&
+ // !mUpdateMonitor.getMaxBiometricUnlockAttemptsReached() &&
+ // !backupIsTimedOut &&
+ // (unlockMode == ShowingMode.Pattern || unlockMode == ShowingMode.Password));
+ // }
+
+ // private void initializeBiometricUnlockView(View view) {
+ // boolean restartBiometricUnlock = false;
+ //
+ // if (mBiometricUnlock != null) {
+ // restartBiometricUnlock = mBiometricUnlock.stop();
+ // }
+ //
+ // // Prevents biometric unlock from coming up immediately after a phone call or if there
+ // // is a dialog on top of lockscreen. It is only updated if the screen is off because if the
+ // // screen is on it's either because of an orientation change, or when it first boots.
+ // // In both those cases, we don't want to override the current value of
+ // // mSuppressBiometricUnlock and instead want to use the previous value.
+ // if (!mScreenOn) {
+ // mSuppressBiometricUnlock =
+ // mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE
+ // || mHasDialog;
+ // }
+ //
+ // // If the biometric unlock is not being used, we don't bother constructing it. Then we can
+ // // simply check if it is null when deciding whether we should make calls to it.
+ // mBiometricUnlock = null;
+ // if (useBiometricUnlock()) {
+ // // TODO: make faceLockAreaView a more general biometricUnlockView
+ // // We will need to add our Face Unlock specific child views programmatically in
+ // // initializeView rather than having them in the XML files.
+ // View biometricUnlockView = view.findViewById(
+ // com.android.internal.R.id.faceLockAreaView);
+ // if (biometricUnlockView != null) {
+ // mBiometricUnlock = new FaceUnlock(mContext, mUpdateMonitor, mLockPatternUtils,
+ // mKeyguardScreenCallback);
+ // mBiometricUnlock.initializeView(biometricUnlockView);
+ //
+ // // If this is being called because the screen turned off, we want to cover the
+ // // backup lock so it is covered when the screen turns back on.
+ // if (!mScreenOn) mBiometricUnlock.show(0);
+ // } else {
+ // Log.w(TAG, "Couldn't find biometric unlock view");
+ // }
+ // }
+ //
+ // if (mBiometricUnlock != null && restartBiometricUnlock) {
+ // maybeStartBiometricUnlock();
+ // }
+ // }
+
+ // /**
+ // * Starts the biometric unlock if it should be started based on a number of factors including
+ // * the mSuppressBiometricUnlock flag. If it should not be started, it hides the biometric
+ // * unlock area.
+ // */
+ // private void maybeStartBiometricUnlock() {
+ // if (mBiometricUnlock != null) {
+ // final boolean backupIsTimedOut = (mUpdateMonitor.getFailedAttempts() >=
+ // LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
+ // if (!mSuppressBiometricUnlock
+ // && mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
+ // && !mUpdateMonitor.getMaxBiometricUnlockAttemptsReached()
+ // && !backupIsTimedOut) {
+ // mBiometricUnlock.start();
+ // } else {
+ // mBiometricUnlock.hide();
+ // }
+ // }
+ //}
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
new file mode 100644
index 0000000..d74a5e7
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.telephony.TelephonyManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.ViewFlipper;
+import android.widget.RemoteViews.OnClickHandler;
+
+import com.android.internal.policy.impl.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.R;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class KeyguardHostView extends KeyguardViewBase {
+ // Use this to debug all of keyguard
+ public static boolean DEBUG;
+
+ static final int APPWIDGET_HOST_ID = 0x4B455947;
+ private static final String KEYGUARD_WIDGET_PREFS = "keyguard_widget_prefs";
+
+ // time after launching EmergencyDialer before the screen goes blank.
+ private static final int EMERGENCY_CALL_TIMEOUT = 10000;
+
+ // intent action for launching emergency dialer activity.
+ static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
+
+ private static final String TAG = "KeyguardViewHost";
+
+ private static final int SECURITY_SELECTOR_ID = R.id.keyguard_selector_view;
+ private static final int SECURITY_PATTERN_ID = R.id.keyguard_pattern_view;
+ private static final int SECURITY_PASSWORD_ID = R.id.keyguard_password_view;
+ private static final int SECURITY_BIOMETRIC_ID = R.id.keyguard_face_unlock_view;
+ private static final int SECURITY_SIM_PIN_ID = R.id.keyguard_sim_pin_view;
+ private static final int SECURITY_SIM_PUK_ID = R.id.keyguard_sim_puk_view;
+ private static final int SECURITY_ACCOUNT_ID = R.id.keyguard_account_view;
+
+ private AppWidgetHost mAppWidgetHost;
+ private KeyguardWidgetPager mAppWidgetContainer;
+ private ViewFlipper mViewFlipper;
+ private Button mEmergencyDialerButton;
+ private boolean mEnableMenuKey;
+ private boolean mScreenOn;
+ private boolean mIsVerifyUnlockOnly;
+ private int mCurrentSecurityId = SECURITY_SELECTOR_ID;
+
+ // KeyguardSecurityViews
+ final private int [] mViewIds = {
+ SECURITY_SELECTOR_ID,
+ SECURITY_PATTERN_ID,
+ SECURITY_PASSWORD_ID,
+ SECURITY_BIOMETRIC_ID,
+ SECURITY_SIM_PIN_ID,
+ SECURITY_SIM_PUK_ID,
+ SECURITY_ACCOUNT_ID,
+ };
+
+ private ArrayList<View> mViews = new ArrayList<View>(mViewIds.length);
+
+ protected Runnable mLaunchRunnable;
+
+ protected int mFailedAttempts;
+ private LockPatternUtils mLockPatternUtils;
+
+ private KeyguardSecurityModel mSecurityModel;
+
+ public KeyguardHostView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardHostView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAppWidgetHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID, mOnClickHandler);
+ mSecurityModel = new KeyguardSecurityModel(mContext);
+
+ // The following enables the MENU key to work for testing automation
+ mEnableMenuKey = shouldEnableMenuKey();
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ mCallback.keyguardDoneDrawing();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container);
+ mAppWidgetContainer.setVisibility(VISIBLE);
+
+ // View Flipper
+ mViewFlipper = (ViewFlipper) findViewById(R.id.view_flipper);
+ mViewFlipper.setInAnimation(AnimationUtils.loadAnimation(mContext,
+ R.anim.keyguard_security_animate_in));
+ mViewFlipper.setOutAnimation(AnimationUtils.loadAnimation(mContext,
+ R.anim.keyguard_security_animate_out));
+
+ // Initialize all security views
+ for (int i = 0; i < mViewIds.length; i++) {
+ View view = findViewById(mViewIds[i]);
+ mViews.add(view);
+ if (view != null) {
+ ((KeyguardSecurityView) view).setKeyguardCallback(mCallback);
+ } else {
+ Log.v("*********", "Can't find view id " + mViewIds[i]);
+ }
+ }
+
+ // Enable emergency dialer button
+ mEmergencyDialerButton = (Button) findViewById(R.id.emergency_call_button);
+ mEmergencyDialerButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ takeEmergencyCallAction();
+ }
+ });
+ }
+
+ void setLockPatternUtils(LockPatternUtils utils) {
+ mSecurityModel.setLockPatternUtils(utils);
+ mLockPatternUtils = utils;
+ for (int i = 0; i < mViews.size(); i++) {
+ KeyguardSecurityView ksv = (KeyguardSecurityView) mViews.get(i);
+ if (ksv != null) {
+ ksv.setLockPatternUtils(utils);
+ } else {
+ Log.w(TAG, "**** ksv was null at " + i);
+ }
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mAppWidgetHost.startListening();
+ populateWidgets();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mAppWidgetHost.stopListening();
+ }
+
+ AppWidgetHost getAppWidgetHost() {
+ return mAppWidgetHost;
+ }
+
+ void addWidget(AppWidgetHostView view) {
+ mAppWidgetContainer.addWidget(view);
+ }
+
+ private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() {
+
+ public void userActivity(long timeout) {
+ mViewMediatorCallback.pokeWakelock(timeout);
+ }
+
+ public void dismiss(boolean authenticated) {
+ showNextSecurityScreenOrFinish(authenticated);
+ }
+
+ public boolean isVerifyUnlockOnly() {
+ // TODO
+ return false;
+ }
+
+ public void reportSuccessfulUnlockAttempt() {
+ KeyguardUpdateMonitor.getInstance(mContext).clearFailedAttempts();
+ }
+
+ public void reportFailedUnlockAttempt() {
+ // TODO: handle biometric attempt differently.
+ KeyguardUpdateMonitor.getInstance(mContext).reportFailedAttempt();
+ }
+
+ public int getFailedAttempts() {
+ return KeyguardUpdateMonitor.getInstance(mContext).getFailedAttempts();
+ }
+
+ public void showBackupUnlock() {
+ // TODO
+ }
+
+ public void keyguardDoneDrawing() {
+ mViewMediatorCallback.keyguardDoneDrawing();
+ }
+
+ @Override
+ public void setOnDismissRunnable(Runnable runnable) {
+ KeyguardHostView.this.setOnDismissRunnable(runnable);
+ }
+
+ };
+
+ public void takeEmergencyCallAction() {
+ mCallback.userActivity(EMERGENCY_CALL_TIMEOUT);
+ if (TelephonyManager.getDefault().getCallState()
+ == TelephonyManager.CALL_STATE_OFFHOOK) {
+ mLockPatternUtils.resumeCall();
+ } else {
+ Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ getContext().startActivity(intent);
+ }
+ }
+
+ protected void showNextSecurityScreenOrFinish(boolean authenticated) {
+ boolean finish = false;
+ if (SECURITY_SELECTOR_ID == mCurrentSecurityId) {
+ int realSecurityId = getSecurityViewIdForMode(mSecurityModel.getSecurityMode());
+ if (realSecurityId == mCurrentSecurityId) {
+ finish = true; // no security required
+ } else {
+ showSecurityScreen(realSecurityId); // switch to the "real" security view
+ }
+ } else if (authenticated) {
+ if ((mCurrentSecurityId == SECURITY_PATTERN_ID
+ || mCurrentSecurityId == SECURITY_PASSWORD_ID
+ || mCurrentSecurityId == SECURITY_ACCOUNT_ID)) {
+ finish = true;
+ }
+ } else {
+ // Not authenticated but we were asked to dismiss so go back to selector screen.
+ showSecurityScreen(SECURITY_SELECTOR_ID);
+ }
+ if (finish) {
+ // If there's a pending runnable because the user interacted with a widget
+ // and we're leaving keyguard, then run it.
+ if (mLaunchRunnable != null) {
+ mLaunchRunnable.run();
+ mViewFlipper.setDisplayedChild(0);
+ mLaunchRunnable = null;
+ }
+ mViewMediatorCallback.keyguardDone(true);
+ }
+ }
+
+ private OnClickHandler mOnClickHandler = new OnClickHandler() {
+ @Override
+ public boolean onClickHandler(final View view,
+ final android.app.PendingIntent pendingIntent,
+ final Intent fillInIntent) {
+ if (pendingIntent.isActivity()) {
+ setOnDismissRunnable(new Runnable() {
+ public void run() {
+ try {
+ // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
+ Context context = view.getContext();
+ ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view,
+ 0, 0,
+ view.getMeasuredWidth(), view.getMeasuredHeight());
+ context.startIntentSender(
+ pendingIntent.getIntentSender(), fillInIntent,
+ Intent.FLAG_ACTIVITY_NEW_TASK,
+ Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle());
+ } catch (IntentSender.SendIntentException e) {
+ android.util.Log.e(TAG, "Cannot send pending intent: ", e);
+ } catch (Exception e) {
+ android.util.Log.e(TAG, "Cannot send pending intent due to " +
+ "unknown exception: ", e);
+ }
+ }
+ });
+
+ mCallback.dismiss(false);
+ return true;
+ } else {
+ return super.onClickHandler(view, pendingIntent, fillInIntent);
+ }
+ };
+ };
+
+ @Override
+ public void reset() {
+ requestFocus();
+ }
+
+ /**
+ * Sets a runnable to run when keyguard is dismissed
+ * @param runnable
+ */
+ protected void setOnDismissRunnable(Runnable runnable) {
+ mLaunchRunnable = runnable;
+ }
+
+ private KeyguardSecurityView getSecurityView(int securitySelectorId) {
+ final int children = mViewFlipper.getChildCount();
+ for (int child = 0; child < children; child++) {
+ if (mViewFlipper.getChildAt(child).getId() == securitySelectorId) {
+ return ((KeyguardSecurityView)mViewFlipper.getChildAt(child));
+ }
+ }
+ return null;
+ }
+
+ private void showSecurityScreen(int securityViewId) {
+
+ if (securityViewId == mCurrentSecurityId) return;
+
+ KeyguardSecurityView oldView = getSecurityView(mCurrentSecurityId);
+ KeyguardSecurityView newView = getSecurityView(securityViewId);
+
+ // Emulate Activity life cycle
+ oldView.onPause();
+ newView.onResume();
+
+ mViewMediatorCallback.setNeedsInput(newView.needsInput());
+ mCurrentSecurityId = securityViewId;
+
+ // Find and show this child.
+ final int childCount = mViewFlipper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (securityViewId == mViewFlipper.getChildAt(i).getId()) {
+ mViewFlipper.setDisplayedChild(i);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onScreenTurnedOn() {
+ if (DEBUG) Log.d(TAG, "screen on");
+ mScreenOn = true;
+ showSecurityScreen(mCurrentSecurityId);
+ }
+
+ @Override
+ public void onScreenTurnedOff() {
+ if (DEBUG) Log.d(TAG, "screen off");
+ mScreenOn = false;
+ showSecurityScreen(SECURITY_SELECTOR_ID);
+ }
+
+ @Override
+ public void show() {
+ onScreenTurnedOn();
+ }
+
+ private boolean isSecure() {
+ SecurityMode mode = mSecurityModel.getSecurityMode();
+ switch (mode) {
+ case Pattern:
+ return mLockPatternUtils.isLockPatternEnabled();
+ case Password:
+ return mLockPatternUtils.isLockPasswordEnabled();
+ case SimPin:
+ case SimPuk:
+ case Account:
+ return true;
+ case None:
+ return false;
+ default:
+ throw new IllegalStateException("Unknown security mode " + mode);
+ }
+ }
+
+ @Override
+ public void wakeWhenReadyTq(int keyCode) {
+ if (DEBUG) Log.d(TAG, "onWakeKey");
+ if (keyCode == KeyEvent.KEYCODE_MENU && isSecure()) {
+ if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU");
+ showSecurityScreen(SECURITY_SELECTOR_ID);
+ mViewMediatorCallback.pokeWakelock();
+ } else {
+ if (DEBUG) Log.d(TAG, "poking wake lock immediately");
+ mViewMediatorCallback.pokeWakelock();
+ }
+ }
+
+ @Override
+ public void verifyUnlock() {
+ SecurityMode securityMode = mSecurityModel.getSecurityMode();
+ if (securityMode == KeyguardSecurityModel.SecurityMode.None) {
+ mViewMediatorCallback.keyguardDone(true);
+ } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern
+ && securityMode != KeyguardSecurityModel.SecurityMode.Password) {
+ // can only verify unlock when in pattern/password mode
+ mViewMediatorCallback.keyguardDone(false);
+ } else {
+ // otherwise, go to the unlock screen, see if they can verify it
+ mIsVerifyUnlockOnly = true;
+ showSecurityScreen(getSecurityViewIdForMode(securityMode));
+ }
+ }
+
+ private int getSecurityViewIdForMode(SecurityMode securityMode) {
+ switch (securityMode) {
+ case None: return SECURITY_SELECTOR_ID;
+ case Pattern: return SECURITY_PATTERN_ID;
+ case Password: return SECURITY_PASSWORD_ID;
+ case Biometric: return SECURITY_BIOMETRIC_ID;
+ case Account: return SECURITY_ACCOUNT_ID;
+ case SimPin: return SECURITY_SIM_PIN_ID;
+ case SimPuk: return SECURITY_SIM_PUK_ID;
+ }
+ return 0;
+ }
+
+ private void addWidget(int appId) {
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+ AppWidgetProviderInfo appWidgetInfo = appWidgetManager.getAppWidgetInfo(appId);
+ AppWidgetHostView view = getAppWidgetHost().createView(mContext, appId, appWidgetInfo);
+ addWidget(view);
+ }
+
+ private void populateWidgets() {
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ KEYGUARD_WIDGET_PREFS, Context.MODE_PRIVATE);
+ for (String key : prefs.getAll().keySet()) {
+ int appId = prefs.getInt(key, -1);
+ if (appId != -1) {
+ Log.w(TAG, "populate: adding " + key);
+ addWidget(appId);
+ } else {
+ Log.w(TAG, "populate: can't find " + key);
+ }
+ }
+ }
+
+ @Override
+ public void cleanUp() {
+
+ }
+
+ /**
+ * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
+ * some cases where we wish to disable it, notably when the menu button placement or technology
+ * is prone to false positives.
+ *
+ * @return true if the menu key should be enabled
+ */
+ private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
+ private boolean shouldEnableMenuKey() {
+ final Resources res = getResources();
+ final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
+ final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
+ final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
+ return !configDisabled || isTestHarness || fileOverride;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_MENU && mEnableMenuKey) {
+ showNextSecurityScreenOrFinish(false);
+ return true;
+ } else {
+ return super.onKeyDown(keyCode, event);
+ }
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
new file mode 100644
index 0000000..d3feced
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+public class KeyguardNavigationManager {
+
+ private TextView mMessageArea;
+ private KeyguardSecurityView mKeyguardSecurityView;
+
+ public KeyguardNavigationManager(KeyguardSecurityView view) {
+ mKeyguardSecurityView = view;
+ mMessageArea = (TextView) ((View) view).findViewById(R.id.message_area);
+ mMessageArea.setSelected(true); // Make marquee work
+ mMessageArea.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mKeyguardSecurityView.getCallback().dismiss(false);
+ }
+ });
+ }
+
+ public void setMessage(CharSequence msg) {
+ mMessageArea.setText(msg);
+ }
+
+ public void setMessage(int resId) {
+ if (resId != 0) {
+ mMessageArea.setText(resId);
+ } else {
+ mMessageArea.setText("");
+ }
+ }
+
+ public void setMessage(int resId, Object... formatArgs) {
+ if (resId != 0) {
+ mMessageArea.setText(mMessageArea.getContext().getString(resId, formatArgs));
+ } else {
+ mMessageArea.setText("");
+ }
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java
new file mode 100644
index 0000000..6938561
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+import java.util.List;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.internal.widget.PasswordEntryKeyboardView;
+
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.security.KeyStore;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.text.method.DigitsKeyListener;
+import android.text.method.TextKeyListener;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.widget.PasswordEntryKeyboardHelper;
+/**
+ * Displays a dialer-like interface or alphanumeric (latin-1) key entry for the user to enter
+ * an unlock password
+ */
+
+public class KeyguardPasswordView extends LinearLayout
+ implements KeyguardSecurityView, OnEditorActionListener {
+ private KeyguardSecurityCallback mCallback;
+ private EditText mPasswordEntry;
+ private LockPatternUtils mLockPatternUtils;
+ private PasswordEntryKeyboardView mKeyboardView;
+ private PasswordEntryKeyboardHelper mKeyboardHelper;
+ private boolean mIsAlpha;
+ private KeyguardNavigationManager mNavigationManager;
+
+ // To avoid accidental lockout due to events while the device in in the pocket, ignore
+ // any passwords with length less than or equal to this length.
+ private static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
+
+ public KeyguardPasswordView(Context context) {
+ super(context);
+ }
+
+ public KeyguardPasswordView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ public void reset() {
+ // start fresh
+ mPasswordEntry.setText("");
+ mPasswordEntry.requestFocus();
+
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ } else {
+ mNavigationManager.setMessage(
+ mIsAlpha ? R.string.kg_password_instructions : R.string.kg_pin_instructions);
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mLockPatternUtils = new LockPatternUtils(mContext); // TODO: use common one
+
+ mNavigationManager = new KeyguardNavigationManager(this);
+
+ final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality();
+ mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality
+ || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality
+ || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == quality;
+
+ mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
+ mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
+ mPasswordEntry.setOnEditorActionListener(this);
+
+ mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false);
+ mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ boolean imeOrDeleteButtonVisible = false;
+ if (mIsAlpha) {
+ // We always use the system IME for alpha keyboard, so hide lockscreen's soft keyboard
+ mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
+ mKeyboardView.setVisibility(View.GONE);
+ } else {
+ // Use lockscreen's numeric keyboard if the physical keyboard isn't showing
+ mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+ mKeyboardView.setVisibility(getResources().getConfiguration().hardKeyboardHidden
+ == Configuration.HARDKEYBOARDHIDDEN_NO ? View.INVISIBLE : View.VISIBLE);
+
+ // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts,
+ // not a separate view
+ View pinDelete = findViewById(R.id.delete_button);
+ if (pinDelete != null) {
+ pinDelete.setVisibility(View.VISIBLE);
+ imeOrDeleteButtonVisible = true;
+ pinDelete.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mKeyboardHelper.handleBackspace();
+ }
+ });
+ }
+ }
+
+ mPasswordEntry.requestFocus();
+
+ // This allows keyboards with overlapping qwerty/numeric keys to choose just numeric keys.
+ if (mIsAlpha) {
+ mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
+ mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ } else {
+ mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance());
+ mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER
+ | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+ }
+
+ // Poke the wakelock any time the text is selected or modified
+ mPasswordEntry.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.userActivity(0); // TODO: customize timeout for text?
+ }
+ });
+
+ mPasswordEntry.addTextChangedListener(new TextWatcher() {
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void afterTextChanged(Editable s) {
+ mCallback.userActivity(0);
+ }
+ });
+
+ // If there's more than one IME, enable the IME switcher button
+ View switchImeButton = findViewById(R.id.switch_ime_button);
+ final InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ if (mIsAlpha && switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) {
+ switchImeButton.setVisibility(View.VISIBLE);
+ imeOrDeleteButtonVisible = true;
+ switchImeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.userActivity(0); // Leave the screen on a bit longer
+ imm.showInputMethodPicker();
+ }
+ });
+ }
+
+ // If no icon is visible, reset the left margin on the password field so the text is
+ // still centered.
+ if (!imeOrDeleteButtonVisible) {
+ android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
+ if (params instanceof MarginLayoutParams) {
+ ((MarginLayoutParams)params).leftMargin = 0;
+ mPasswordEntry.setLayoutParams(params);
+ }
+ }
+ }
+
+ /**
+ * Method adapted from com.android.inputmethod.latin.Utils
+ *
+ * @param imm The input method manager
+ * @param shouldIncludeAuxiliarySubtypes
+ * @return true if we have multiple IMEs to choose from
+ */
+ private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
+ final boolean shouldIncludeAuxiliarySubtypes) {
+ final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
+
+ // Number of the filtered IMEs
+ int filteredImisCount = 0;
+
+ for (InputMethodInfo imi : enabledImis) {
+ // We can return true immediately after we find two or more filtered IMEs.
+ if (filteredImisCount > 1) return true;
+ final List<InputMethodSubtype> subtypes =
+ imm.getEnabledInputMethodSubtypeList(imi, true);
+ // IMEs that have no subtypes should be counted.
+ if (subtypes.isEmpty()) {
+ ++filteredImisCount;
+ continue;
+ }
+
+ int auxCount = 0;
+ for (InputMethodSubtype subtype : subtypes) {
+ if (subtype.isAuxiliary()) {
+ ++auxCount;
+ }
+ }
+ final int nonAuxCount = subtypes.size() - auxCount;
+
+ // IMEs that have one or more non-auxiliary subtypes should be counted.
+ // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+ // subtypes should be counted as well.
+ if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+ ++filteredImisCount;
+ continue;
+ }
+ }
+
+ return filteredImisCount > 1
+ // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
+ // input method subtype (The current IME should be LatinIME.)
+ || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ // send focus to the password field
+ return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ private void verifyPasswordAndUnlock() {
+ String entry = mPasswordEntry.getText().toString();
+ boolean wrongPassword = true;
+ if (mLockPatternUtils.checkPassword(entry)) {
+ mCallback.reportSuccessfulUnlockAttempt();
+ KeyStore.getInstance().password(entry);
+ mCallback.dismiss(true);
+ wrongPassword = false;
+ } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT ) {
+ // to avoid accidental lockout, only count attempts that are long enough to be a
+ // real password. This may require some tweaking.
+ mCallback.reportFailedUnlockAttempt();
+ if (0 == (mCallback.getFailedAttempts()
+ % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
+ handleAttemptLockout(deadline);
+ }
+ }
+ mNavigationManager.setMessage(wrongPassword ?
+ (mIsAlpha ? R.string.kg_wrong_password : R.string.kg_wrong_pin) : 0);
+ mPasswordEntry.setText("");
+ }
+
+ // Prevent user from using the PIN/Password entry until scheduled deadline.
+ private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ mPasswordEntry.setEnabled(false);
+ mKeyboardView.setEnabled(false);
+ long elapsedRealtime = SystemClock.elapsedRealtime();
+ new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ int secondsRemaining = (int) (millisUntilFinished / 1000);
+ mNavigationManager.setMessage(
+ R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
+ }
+
+ @Override
+ public void onFinish() {
+ mPasswordEntry.setEnabled(true);
+ mKeyboardView.setEnabled(true);
+ }
+ }.start();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ mCallback.userActivity(0);
+ return false;
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // Check if this was the result of hitting the enter key
+ if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
+ verifyPasswordAndUnlock();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean needsInput() {
+ return mIsAlpha;
+ }
+
+ @Override
+ public void onPause() {
+
+ }
+
+ @Override
+ public void onResume() {
+ reset();
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
+
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
new file mode 100644
index 0000000..a95cfcb
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.security.KeyStore;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.R;
+
+import java.io.IOException;
+import java.util.List;
+
+public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView {
+
+ private static final String TAG = "SecurityPatternView";
+ private static final boolean DEBUG = false;
+
+ // how long before we clear the wrong pattern
+ private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+ // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
+ private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
+
+ // how long we stay awake after the user hits the first dot.
+ private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000;
+
+ // how many cells the user has to cross before we poke the wakelock
+ private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
+
+ private int mFailedPatternAttemptsSinceLastTimeout = 0;
+ private int mTotalFailedPatternAttempts = 0;
+ private CountDownTimer mCountdownTimer = null;
+ private LockPatternUtils mLockPatternUtils;
+ private LockPatternView mLockPatternView;
+ private Button mForgotPatternButton;
+ private KeyguardSecurityCallback mCallback;
+ private boolean mEnableFallback;
+ private KeyguardNavigationManager mNavigationManager;
+
+ /**
+ * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
+ * Initialized to something guaranteed to make us poke the wakelock when the user starts
+ * drawing the pattern.
+ * @see #dispatchTouchEvent(android.view.MotionEvent)
+ */
+ private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
+
+ /**
+ * Useful for clearing out the wrong pattern after a delay
+ */
+ private Runnable mCancelPatternRunnable = new Runnable() {
+ public void run() {
+ mLockPatternView.clearPattern();
+ }
+ };
+
+ enum FooterMode {
+ Normal,
+ ForgotLockPattern,
+ VerifyUnlocked
+ }
+
+ public KeyguardPatternView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardPatternView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mNavigationManager = new KeyguardNavigationManager(this);
+ mLockPatternUtils = mLockPatternUtils == null
+ ? new LockPatternUtils(mContext) : mLockPatternUtils;
+
+ mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView);
+ mLockPatternView.setSaveEnabled(false);
+ mLockPatternView.setFocusable(false);
+ mLockPatternView.setOnPatternListener(new UnlockPatternListener());
+
+ // stealth mode will be the same for the life of this screen
+ mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled());
+
+ // vibrate mode will be the same for the life of this screen
+ mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ mForgotPatternButton = (Button) findViewById(R.id.forgot_password_button);
+ mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text);
+ mForgotPatternButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.showBackupUnlock();
+ }
+ });
+
+ setFocusableInTouchMode(true);
+
+ maybeEnableFallback(mContext);
+ }
+
+ private void updateFooter(FooterMode mode) {
+ switch (mode) {
+ case Normal:
+ if (DEBUG) Log.d(TAG, "mode normal");
+ mForgotPatternButton.setVisibility(View.GONE);
+ break;
+ case ForgotLockPattern:
+ if (DEBUG) Log.d(TAG, "mode ForgotLockPattern");
+ mForgotPatternButton.setVisibility(View.VISIBLE);
+ break;
+ case VerifyUnlocked:
+ if (DEBUG) Log.d(TAG, "mode VerifyUnlocked");
+ mForgotPatternButton.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ final boolean result = super.dispatchTouchEvent(ev);
+ // as long as the user is entering a pattern (i.e sending a touch event that was handled
+ // by this screen), keep poking the wake lock so that the screen will stay on.
+ final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
+ if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
+ mLastPokeTime = SystemClock.elapsedRealtime();
+ }
+ return result;
+ }
+
+ public void reset() {
+ // reset lock pattern
+ mLockPatternView.enableInput();
+ mLockPatternView.setEnabled(true);
+ mLockPatternView.clearPattern();
+
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ } else {
+ mNavigationManager.setMessage(R.string.kg_pattern_instructions);
+ }
+
+ // the footer depends on how many total attempts the user has failed
+ if (mCallback.isVerifyUnlockOnly()) {
+ updateFooter(FooterMode.VerifyUnlocked);
+ } else if (mEnableFallback &&
+ (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
+ updateFooter(FooterMode.ForgotLockPattern);
+ } else {
+ updateFooter(FooterMode.Normal);
+ }
+
+ }
+
+ /** TODO: hook this up */
+ public void cleanUp() {
+ if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
+ mLockPatternUtils = null;
+ mLockPatternView.setOnPatternListener(null);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus) {
+ // when timeout dialog closes we want to update our state
+ reset();
+ }
+ }
+
+ private class UnlockPatternListener implements LockPatternView.OnPatternListener {
+
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+ }
+
+ public void onPatternCleared() {
+ }
+
+ public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
+ // To guard against accidental poking of the wakelock, look for
+ // the user actually trying to draw a pattern of some minimal length.
+ if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
+ mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
+ } else {
+ // Give just a little extra time if they hit one of the first few dots
+ mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS);
+ }
+ }
+
+ public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+ if (mLockPatternUtils.checkPattern(pattern)) {
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
+ mCallback.dismiss(true); // keyguardDone(true)
+ KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern));
+ } else {
+ if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
+ mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
+ }
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
+ mTotalFailedPatternAttempts++;
+ mFailedPatternAttemptsSinceLastTimeout++;
+ mCallback.reportFailedUnlockAttempt();
+ }
+ if (mFailedPatternAttemptsSinceLastTimeout
+ >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
+ handleAttemptLockout(deadline);
+ } else {
+ mNavigationManager.setMessage(R.string.kg_wrong_pattern);
+ mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+ }
+ }
+ }
+ }
+
+ private void maybeEnableFallback(Context context) {
+ // Ask the account manager if we have an account that can be used as a
+ // fallback in case the user forgets his pattern.
+ AccountAnalyzer accountAnalyzer = new AccountAnalyzer(AccountManager.get(context));
+ accountAnalyzer.start();
+ }
+
+ private class AccountAnalyzer implements AccountManagerCallback<Bundle> {
+ private final AccountManager mAccountManager;
+ private final Account[] mAccounts;
+ private int mAccountIndex;
+
+ private AccountAnalyzer(AccountManager accountManager) {
+ mAccountManager = accountManager;
+ mAccounts = accountManager.getAccountsByType("com.google");
+ }
+
+ private void next() {
+ // if we are ready to enable the fallback or if we depleted the list of accounts
+ // then finish and get out
+ if (mAccountIndex >= mAccounts.length) {
+ mEnableFallback = true;
+ return;
+ }
+
+ // lookup the confirmCredentials intent for the current account
+ mAccountManager.confirmCredentials(mAccounts[mAccountIndex], null, null, this, null);
+ }
+
+ public void start() {
+ mEnableFallback = false;
+ mAccountIndex = 0;
+ next();
+ }
+
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ Bundle result = future.getResult();
+ if (result.getParcelable(AccountManager.KEY_INTENT) != null) {
+ mEnableFallback = true;
+ }
+ } catch (OperationCanceledException e) {
+ // just skip the account if we are unable to query it
+ } catch (IOException e) {
+ // just skip the account if we are unable to query it
+ } catch (AuthenticatorException e) {
+ // just skip the account if we are unable to query it
+ } finally {
+ mAccountIndex++;
+ next();
+ }
+ }
+ }
+
+ private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ mLockPatternView.clearPattern();
+ mLockPatternView.setEnabled(false);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ final int secondsRemaining = (int) (millisUntilFinished / 1000);
+ mNavigationManager.setMessage(
+ R.string.kg_too_many_failed_attempts_countdown,secondsRemaining);
+ }
+
+ @Override
+ public void onFinish() {
+ mLockPatternView.setEnabled(true);
+ mNavigationManager.setMessage(R.string.kg_pattern_instructions);
+ // TODO mUnlockIcon.setVisibility(View.VISIBLE);
+ mFailedPatternAttemptsSinceLastTimeout = 0;
+ if (mEnableFallback) {
+ updateFooter(FooterMode.ForgotLockPattern);
+ } else {
+ updateFooter(FooterMode.Normal);
+ }
+ }
+
+ }.start();
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public void onPause() {
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ mCountdownTimer = null;
+ }
+ }
+
+ @Override
+ public void onResume() {
+ reset();
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
+
+
+
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
new file mode 100644
index 0000000..36342a5
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard;
+
+public interface KeyguardSecurityCallback {
+
+ /**
+ * Dismiss the given security screen.
+ * @param securityVerified true if the user correctly entered credentials for the given screen.
+ */
+ void dismiss(boolean securityVerified);
+
+ /**
+ * Manually report user activity to keep the device awake. If timeout is 0,
+ * uses user-defined timeout.
+ * @param timeout
+ */
+ void userActivity(long timeout);
+
+ /**
+ * Checks if keyguard is in "verify credentials" mode.
+ * @return true if user has been asked to verify security.
+ */
+ boolean isVerifyUnlockOnly();
+
+ /**
+ * Call when user correctly enters their credentials
+ */
+ void reportSuccessfulUnlockAttempt();
+
+ /**
+ * Call when the user incorrectly enters their credentials
+ */
+ void reportFailedUnlockAttempt();
+
+ /**
+ * Gets the number of attempts thus far as reported by {@link #reportFailedUnlockAttempt()}
+ * @return number of failed attempts
+ */
+ int getFailedAttempts();
+
+ /**
+ * Shows the backup unlock for the current method. If none available, this call is a NOP.
+ */
+ void showBackupUnlock();
+
+ /**
+ * Used to inform keyguard when the current view is done drawing.
+ */
+ void keyguardDoneDrawing();
+
+ /**
+ * Sets a runnable to launch after the user enters their
+ * @param runnable
+ */
+ void setOnDismissRunnable(Runnable runnable);
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
new file mode 100644
index 0000000..d041dd3
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.widget.LockPatternUtils;
+
+public class KeyguardSecurityModel {
+ /**
+ * The different types of security available for {@link Mode#UnlockScreen}.
+ * @see com.android.internal.policy.impl.LockPatternKeyguardView#getUnlockMode()
+ */
+ enum SecurityMode {
+ None, // No security enabled
+ Pattern, // Unlock by drawing a pattern.
+ Password, // Unlock by entering a password or PIN
+ Biometric, // Unlock with a biometric key (e.g. finger print or face unlock)
+ Account, // Unlock by entering an account's login and password.
+ SimPin, // Unlock by entering a sim pin.
+ SimPuk // Unlock by entering a sim puk
+ }
+
+ private Context mContext;
+ private LockPatternUtils mLockPatternUtils;
+
+ KeyguardSecurityModel(Context context) {
+ mContext = context;
+ mLockPatternUtils = new LockPatternUtils(context);
+ }
+
+ void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ SecurityMode getSecurityMode() {
+ KeyguardUpdateMonitor mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ final IccCardConstants.State simState = mUpdateMonitor.getSimState();
+ if (simState == IccCardConstants.State.PIN_REQUIRED) {
+ return SecurityMode.SimPin;
+ } else if (simState == IccCardConstants.State.PUK_REQUIRED) {
+ return SecurityMode.SimPuk;
+ } else {
+ final int mode = mLockPatternUtils.getKeyguardStoredPasswordQuality();
+ switch (mode) {
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+ return mLockPatternUtils.isLockPasswordEnabled() ?
+ SecurityMode.Password : SecurityMode.None;
+
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+ case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
+ if (mLockPatternUtils.isLockPatternEnabled()) {
+ return mLockPatternUtils.isPermanentlyLocked() ?
+ SecurityMode.Account : SecurityMode.Pattern;
+ } else {
+ return SecurityMode.None;
+ }
+ default:
+ throw new IllegalStateException("Unknown unlock mode:" + mode);
+ }
+ }
+ }
+
+ SecurityMode getBackupFor(SecurityMode mode) {
+ return SecurityMode.None; // TODO: handle biometric unlock, etc.
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java
new file mode 100644
index 0000000..d80c1db
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard;
+
+import com.android.internal.widget.LockPatternUtils;
+
+public interface KeyguardSecurityView {
+ /**
+ * Interface back to keyguard to tell it when security
+ * @param callback
+ */
+ void setKeyguardCallback(KeyguardSecurityCallback callback);
+
+ /**
+ * Set {@link LockPatternUtils} object. Useful for providing a mock interface.
+ * @param utils
+ */
+ void setLockPatternUtils(LockPatternUtils utils);
+
+ /**
+ * Reset the view and prepare to take input. This should do things like clearing the
+ * password or pattern and clear error messages.
+ */
+ void reset();
+
+ /**
+ * Emulate activity life cycle within the view. When called, the view should clean up
+ * and prepare to be removed.
+ */
+ void onPause();
+
+ /**
+ * Emulate activity life cycle within this view. When called, the view should prepare itself
+ * to be shown.
+ */
+ void onResume();
+
+ /**
+ * Inquire whether this view requires IME (keyboard) interaction.
+ *
+ * @return true if IME interaction is required.
+ */
+ boolean needsInput();
+
+ /**
+ * Get {@link KeyguardSecurityCallback} for the given object
+ * @return KeyguardSecurityCallback
+ */
+ KeyguardSecurityCallback getCallback();
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
new file mode 100644
index 0000000..a38b247
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard;
+
+import android.animation.ObjectAnimator;
+import android.app.ActivityManagerNative;
+import android.app.SearchManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.MediaStore;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.multiwaveview.GlowPadView;
+import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
+import com.android.internal.R;
+
+public class KeyguardSelectorView extends LinearLayout implements KeyguardSecurityView {
+ private static final boolean DEBUG = KeyguardHostView.DEBUG;
+ private static final String TAG = "SecuritySelectorView";
+ private static final String ASSIST_ICON_METADATA_NAME =
+ "com.android.systemui.action_assist_icon";
+ private KeyguardSecurityCallback mCallback;
+ private GlowPadView mGlowPadView;
+ private Button mEmergencyCallButton;
+ private ObjectAnimator mAnim;
+ private boolean mCameraDisabled;
+ private boolean mSearchDisabled;
+ private LockPatternUtils mLockPatternUtils;
+
+ OnTriggerListener mOnTriggerListener = new OnTriggerListener() {
+
+ public void onTrigger(View v, int target) {
+ final int resId = mGlowPadView.getResourceIdForTarget(target);
+ switch (resId) {
+ case com.android.internal.R.drawable.ic_action_assist_generic:
+ Intent assistIntent =
+ ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, UserHandle.USER_CURRENT);
+ if (assistIntent != null) {
+ launchActivity(assistIntent, false);
+ } else {
+ Log.w(TAG, "Failed to get intent for assist activity");
+ }
+ mCallback.userActivity(0);
+ break;
+
+ case com.android.internal.R.drawable.ic_lockscreen_camera:
+ launchCamera();
+ mCallback.userActivity(0);
+ break;
+
+ case com.android.internal.R.drawable.ic_lockscreen_unlock_phantom:
+ case com.android.internal.R.drawable.ic_lockscreen_unlock:
+ mCallback.dismiss(false);
+ break;
+ }
+ }
+
+ public void onReleased(View v, int handle) {
+ doTransition(mEmergencyCallButton, 1.0f);
+ }
+
+ public void onGrabbed(View v, int handle) {
+ doTransition(mEmergencyCallButton, 0.0f);
+ }
+
+ public void onGrabbedStateChange(View v, int handle) {
+
+ }
+
+ public void onFinishFinalAnimation() {
+
+ }
+
+ };
+
+ KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+
+ private boolean mEmergencyDialerDisableBecauseSimLocked;
+
+ @Override
+ public void onDevicePolicyManagerStateChanged() {
+ updateTargets();
+ }
+
+ @Override
+ public void onSimStateChanged(IccCardConstants.State simState) {
+ // Some carriers aren't capable of handling emergency calls while the SIM is locked
+ mEmergencyDialerDisableBecauseSimLocked = KeyguardUpdateMonitor.isSimLocked(simState)
+ && !mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked();
+ updateTargets();
+ }
+
+ void onPhoneStateChanged(int phoneState) {
+ if (mEmergencyCallButton != null) {
+ mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked();
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton,
+ phoneState, !mEmergencyDialerDisableBecauseSimLocked);
+ }
+ };
+ };
+
+ public KeyguardSelectorView(Context context) {
+ this(context, null);
+ }
+
+ protected void launchCamera() {
+ if (mLockPatternUtils.isSecure()) {
+ // Launch the secure version of the camera
+ launchActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE), true);
+ } else {
+ // Launch the normal camera
+ launchActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA), false);
+ }
+ }
+
+ public KeyguardSelectorView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view);
+ mGlowPadView.setOnTriggerListener(mOnTriggerListener);
+ mEmergencyCallButton = (Button) findViewById(R.id.emergency_call_button);
+ KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mInfoCallback);
+ updateTargets();
+ }
+
+ public boolean isTargetPresent(int resId) {
+ return mGlowPadView.getTargetPosition(resId) != -1;
+ }
+
+ private void updateTargets() {
+ boolean disabledByAdmin = mLockPatternUtils.getDevicePolicyManager()
+ .getCameraDisabled(null);
+ final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(getContext());
+ boolean disabledBySimState = monitor.isSimLocked();
+ boolean cameraTargetPresent =
+ isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_camera);
+ boolean searchTargetPresent =
+ isTargetPresent(com.android.internal.R.drawable.ic_action_assist_generic);
+
+ if (disabledByAdmin) {
+ Log.v(TAG, "Camera disabled by Device Policy");
+ } else if (disabledBySimState) {
+ Log.v(TAG, "Camera disabled by Sim State");
+ }
+ boolean searchActionAvailable =
+ ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, UserHandle.USER_CURRENT) != null;
+ mCameraDisabled = disabledByAdmin || disabledBySimState || !cameraTargetPresent;
+ mSearchDisabled = disabledBySimState || !searchActionAvailable || !searchTargetPresent;
+ updateResources();
+ }
+
+ public void updateResources() {
+ // Update the search icon with drawable from the search .apk
+ if (!mSearchDisabled) {
+ Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, UserHandle.USER_CURRENT);
+ if (intent != null) {
+ // XXX Hack. We need to substitute the icon here but haven't formalized
+ // the public API. The "_google" metadata will be going away, so
+ // DON'T USE IT!
+ ComponentName component = intent.getComponent();
+ boolean replaced = mGlowPadView.replaceTargetDrawablesIfPresent(component,
+ ASSIST_ICON_METADATA_NAME + "_google",
+ com.android.internal.R.drawable.ic_action_assist_generic);
+
+ if (!replaced && !mGlowPadView.replaceTargetDrawablesIfPresent(component,
+ ASSIST_ICON_METADATA_NAME,
+ com.android.internal.R.drawable.ic_action_assist_generic)) {
+ Slog.w(TAG, "Couldn't grab icon from package " + component);
+ }
+ }
+ }
+
+ mGlowPadView.setEnableTarget(com.android.internal.R.drawable
+ .ic_lockscreen_camera, !mCameraDisabled);
+ mGlowPadView.setEnableTarget(com.android.internal.R.drawable
+ .ic_action_assist_generic, !mSearchDisabled);
+ }
+
+ void doTransition(Object v, float to) {
+ if (mAnim != null) {
+ mAnim.cancel();
+ }
+ mAnim = ObjectAnimator.ofFloat(mEmergencyCallButton, "alpha", to);
+ mAnim.start();
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ /**
+ * Launches the said intent for the current foreground user.
+ * @param intent
+ * @param showsWhileLocked true if the activity can be run on top of keyguard.
+ * See {@link WindowManager#FLAG_SHOW_WHEN_LOCKED}
+ */
+ private void launchActivity(final Intent intent, boolean showsWhileLocked) {
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ boolean isSecure = mLockPatternUtils.isSecure();
+ if (!isSecure || showsWhileLocked) {
+ if (!isSecure) try {
+ ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
+ } catch (RemoteException e) {
+ Log.w(TAG, "can't dismiss keyguard on launch");
+ }
+ try {
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity not found for intent + " + intent.getAction());
+ }
+ } else {
+ // Create a runnable to start the activity and ask the user to enter their
+ // credentials.
+ mCallback.setOnDismissRunnable(new Runnable() {
+ @Override
+ public void run() {
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ }
+ });
+ mCallback.dismiss(false);
+ }
+ }
+
+ @Override
+ public void reset() {
+ mGlowPadView.reset(false);
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public void onPause() {
+ KeyguardUpdateMonitor.getInstance(getContext()).removeCallback(mInfoCallback);
+ }
+
+ @Override
+ public void onResume() {
+ KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mInfoCallback);
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java
new file mode 100644
index 0000000..294ea5c
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.PasswordEntryKeyboardHelper;
+import com.android.internal.widget.PasswordEntryKeyboardView;
+import com.android.internal.R;
+
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+/**
+ * Displays a dialer like interface to unlock the SIM PIN.
+ */
+public class KeyguardSimPinView extends LinearLayout
+ implements KeyguardSecurityView, OnEditorActionListener {
+
+ private static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
+
+ private EditText mPinEntry;
+ private ProgressDialog mSimUnlockProgressDialog = null;
+ private KeyguardSecurityCallback mCallback;
+ private PasswordEntryKeyboardView mKeyboardView;
+ private PasswordEntryKeyboardHelper mKeyboardHelper;
+ private LockPatternUtils mLockPatternUtils;
+ private KeyguardNavigationManager mNavigationManager;
+
+ public KeyguardSimPinView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardSimPinView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mNavigationManager = new KeyguardNavigationManager(this);
+
+ mPinEntry = (EditText) findViewById(R.id.sim_pin_entry);
+ mPinEntry.setOnEditorActionListener(this);
+
+ mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
+ mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false);
+ mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+ mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ final View deleteButton = findViewById(R.id.delete_button);
+ if (deleteButton != null) {
+ deleteButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mKeyboardHelper.handleBackspace();
+ }
+ });
+ }
+
+ setFocusableInTouchMode(true);
+ reset();
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ return mPinEntry.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ public void reset() {
+ // start fresh
+ mNavigationManager.setMessage(R.string.kg_sim_pin_instructions);
+
+ // make sure that the number of entered digits is consistent when we
+ // erase the SIM unlock code, including orientation changes.
+ mPinEntry.setText("");
+ mPinEntry.requestFocus();
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ // dismiss the dialog.
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.dismiss();
+ mSimUnlockProgressDialog = null;
+ }
+ }
+
+ /**
+ * Since the IPC can block, we want to run the request in a separate thread
+ * with a callback.
+ */
+ private abstract class CheckSimPin extends Thread {
+ private final String mPin;
+
+ protected CheckSimPin(String pin) {
+ mPin = pin;
+ }
+
+ abstract void onSimLockChangedResponse(boolean success);
+
+ @Override
+ public void run() {
+ try {
+ final boolean result = ITelephony.Stub.asInterface(ServiceManager
+ .checkService("phone")).supplyPin(mPin);
+ post(new Runnable() {
+ public void run() {
+ onSimLockChangedResponse(result);
+ }
+ });
+ } catch (RemoteException e) {
+ post(new Runnable() {
+ public void run() {
+ onSimLockChangedResponse(false);
+ }
+ });
+ }
+ }
+ }
+
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // Check if this was the result of hitting the enter key
+ mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS);
+ if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
+ checkPin();
+ return true;
+ }
+ return false;
+ }
+
+ private Dialog getSimUnlockProgressDialog() {
+ if (mSimUnlockProgressDialog == null) {
+ mSimUnlockProgressDialog = new ProgressDialog(mContext);
+ mSimUnlockProgressDialog.setMessage(
+ mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
+ mSimUnlockProgressDialog.setIndeterminate(true);
+ mSimUnlockProgressDialog.setCancelable(false);
+ if (!(mContext instanceof Activity)) {
+ mSimUnlockProgressDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ }
+ return mSimUnlockProgressDialog;
+ }
+
+ private void checkPin() {
+ if (mPinEntry.getText().length() < 4) {
+ // otherwise, display a message to the user, and don't submit.
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint);
+ mPinEntry.setText("");
+ mCallback.userActivity(0);
+ return;
+ }
+
+ getSimUnlockProgressDialog().show();
+
+ new CheckSimPin(mPinEntry.getText().toString()) {
+ void onSimLockChangedResponse(final boolean success) {
+ post(new Runnable() {
+ public void run() {
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ if (success) {
+ // before closing the keyguard, report back that the sim is unlocked
+ // so it knows right away.
+ KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked();
+ mCallback.dismiss(false); //
+ } else {
+ mNavigationManager.setMessage(R.string.kg_password_wrong_pin_code);
+ mPinEntry.setText("");
+ }
+ mCallback.userActivity(0);
+ }
+ });
+ }
+ }.start();
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ public boolean needsInput() {
+ return false; // This view provides its own keypad
+ }
+
+ public void onPause() {
+
+ }
+
+ public void onResume() {
+ reset();
+ }
+
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java
new file mode 100644
index 0000000..801dfc3
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.Editable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.PasswordEntryKeyboardHelper;
+import com.android.internal.widget.PasswordEntryKeyboardView;
+import com.android.internal.R;
+
+public class KeyguardSimPukView extends LinearLayout implements View.OnClickListener,
+ View.OnFocusChangeListener, KeyguardSecurityView, OnEditorActionListener {
+
+ private static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
+
+ private TextView mPukText;
+ private TextView mPinText;
+ private TextView mFocusedEntry;
+
+ private View mDelPukButton;
+ private View mDelPinButton;
+
+ private ProgressDialog mSimUnlockProgressDialog = null;
+ private KeyguardSecurityCallback mCallback;
+
+ private KeyguardNavigationManager mNavigationManager;
+
+ private PasswordEntryKeyboardView mKeyboardView;
+
+ private PasswordEntryKeyboardHelper mKeyboardHelper;
+
+ private LockPatternUtils mLockPatternUtils;
+
+ public KeyguardSimPukView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardSimPukView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mNavigationManager = new KeyguardNavigationManager(this);
+
+ mPukText = (TextView) findViewById(R.id.sim_puk_entry);
+ mPukText.setOnEditorActionListener(this);
+ mPinText = (TextView) findViewById(R.id.sim_pin_entry);
+ mPinText.setOnEditorActionListener(this);
+ mDelPukButton = findViewById(R.id.puk_delete_button);
+ mDelPukButton.setOnClickListener(this);
+ mDelPinButton = findViewById(R.id.pin_delete_button);
+ mDelPinButton.setOnClickListener(this);
+
+ mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
+ mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false);
+ mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+ mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ mNavigationManager.setMessage(R.string.kg_sim_puk_recovery_hint);
+
+ mPinText.setFocusableInTouchMode(true);
+ mPinText.setOnFocusChangeListener(this);
+ mPukText.setFocusableInTouchMode(true);
+ mPukText.setOnFocusChangeListener(this);
+
+ setFocusableInTouchMode(true);
+
+ reset();
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ return mPukText.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ public boolean needsInput() {
+ return false; // This view provides its own keypad
+ }
+
+ public void onPause() {
+
+ }
+
+ public void onResume() {
+ reset();
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ // dismiss the dialog.
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.dismiss();
+ mSimUnlockProgressDialog = null;
+ }
+ }
+
+ /**
+ * Since the IPC can block, we want to run the request in a separate thread
+ * with a callback.
+ */
+ private abstract class CheckSimPuk extends Thread {
+
+ private final String mPin, mPuk;
+
+ protected CheckSimPuk(String puk, String pin) {
+ mPuk = puk;
+ mPin = pin;
+ }
+
+ abstract void onSimLockChangedResponse(boolean success);
+
+ @Override
+ public void run() {
+ try {
+ final boolean result = ITelephony.Stub.asInterface(ServiceManager
+ .checkService("phone")).supplyPuk(mPuk, mPin);
+
+ post(new Runnable() {
+ public void run() {
+ onSimLockChangedResponse(result);
+ }
+ });
+ } catch (RemoteException e) {
+ post(new Runnable() {
+ public void run() {
+ onSimLockChangedResponse(false);
+ }
+ });
+ }
+ }
+ }
+
+ public void onClick(View v) {
+ if (v == mDelPukButton) {
+ if (mFocusedEntry != mPukText)
+ mPukText.requestFocus();
+ final Editable digits = mPukText.getEditableText();
+ final int len = digits.length();
+ if (len > 0) {
+ digits.delete(len-1, len);
+ }
+ } else if (v == mDelPinButton) {
+ if (mFocusedEntry != mPinText)
+ mPinText.requestFocus();
+ final Editable digits = mPinText.getEditableText();
+ final int len = digits.length();
+ if (len > 0) {
+ digits.delete(len-1, len);
+ }
+ }
+ mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS);
+ }
+
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ if (hasFocus)
+ mFocusedEntry = (TextView) view;
+ }
+
+ private Dialog getSimUnlockProgressDialog() {
+ if (mSimUnlockProgressDialog == null) {
+ mSimUnlockProgressDialog = new ProgressDialog(mContext);
+ mSimUnlockProgressDialog.setMessage(mContext.getString(
+ R.string.kg_sim_unlock_progress_dialog_message));
+ mSimUnlockProgressDialog.setIndeterminate(true);
+ mSimUnlockProgressDialog.setCancelable(false);
+ if (!(mContext instanceof Activity)) {
+ mSimUnlockProgressDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ }
+ return mSimUnlockProgressDialog;
+ }
+
+ private void checkPuk() {
+ // make sure the puk is at least 8 digits long.
+ if (mPukText.getText().length() < 8) {
+ // otherwise, display a message to the user, and don't submit.
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_puk_hint);
+ mPukText.setText("");
+ return;
+ }
+
+ // make sure the PIN is between 4 and 8 digits
+ if (mPinText.getText().length() < 4
+ || mPinText.getText().length() > 8) {
+ // otherwise, display a message to the user, and don't submit.
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint);
+ mPinText.setText("");
+ return;
+ }
+
+ getSimUnlockProgressDialog().show();
+
+ new CheckSimPuk(mPukText.getText().toString(),
+ mPinText.getText().toString()) {
+ void onSimLockChangedResponse(final boolean success) {
+ mPinText.post(new Runnable() {
+ public void run() {
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ if (success) {
+ mCallback.dismiss(true);
+ } else {
+ mNavigationManager.setMessage(R.string.kg_invalid_puk);
+ mPukText.setText("");
+ mPinText.setText("");
+ }
+ }
+ });
+ }
+ }.start();
+ }
+
+ @Override
+ public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
+ // Check if this was the result of hitting the enter key
+ mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS);
+ if (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
+ if (view == mPukText && mPukText.getText().length() < 8) {
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_puk_hint);
+ mPukText.setText("");
+ mPukText.requestFocus();
+ return true;
+ } else if (view == mPinText) {
+ if (mPinText.getText().length() < 4 || mPinText.getText().length() > 8) {
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint);
+ mPinText.setText("");
+ mPinText.requestFocus();
+ } else {
+ checkPuk();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ @Override
+ public void reset() {
+ mNavigationManager.setMessage(R.string.kg_sim_puk_recovery_hint);
+ mPinText.setText("");
+ mPukText.setText("");
+ mPukText.requestFocus();
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java
new file mode 100644
index 0000000..d6ce967
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.GridLayout;
+
+public class KeyguardStatusView extends GridLayout {
+ public KeyguardStatusView(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardStatusView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ // StatusView manages all of the widgets in this view.
+ new KeyguardStatusViewManager(this);
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
new file mode 100644
index 0000000..06ed88a
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
@@ -0,0 +1,570 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import com.android.internal.R;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.widget.DigitalClock;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.Date;
+
+import libcore.util.MutableInt;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+/***
+ * Manages a number of views inside of LockScreen layouts. See below for a list of widgets
+ */
+class KeyguardStatusViewManager {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "KeyguardStatusView";
+
+ public static final int LOCK_ICON = 0; // R.drawable.ic_lock_idle_lock;
+ public static final int ALARM_ICON = R.drawable.ic_lock_idle_alarm;
+ public static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging;
+ public static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery;
+
+ private static final int INSTRUCTION_TEXT = 10;
+ private static final int CARRIER_TEXT = 11;
+ private static final int CARRIER_HELP_TEXT = 12;
+ private static final int HELP_MESSAGE_TEXT = 13;
+ private static final int OWNER_INFO = 14;
+ private static final int BATTERY_INFO = 15;
+
+ private StatusMode mStatus;
+ private String mDateFormatString;
+
+ // Views that this class controls.
+ // NOTE: These may be null in some LockScreen screens and should protect from NPE
+ private TextView mCarrierView;
+ private TextView mDateView;
+ private TextView mStatus1View;
+ private TextView mOwnerInfoView;
+ private TextView mAlarmStatusView;
+
+ // Top-level container view for above views
+ private View mContainer;
+
+ // are we showing battery information?
+ private boolean mShowingBatteryInfo = false;
+
+ // last known plugged in state
+ private boolean mPluggedIn = false;
+
+ // last known battery level
+ private int mBatteryLevel = 100;
+
+ // last known SIM state
+ protected IccCardConstants.State mSimState;
+
+ private LockPatternUtils mLockPatternUtils;
+ private KeyguardUpdateMonitor mUpdateMonitor;
+
+ // Shadowed text values
+ private CharSequence mCarrierText;
+ private CharSequence mCarrierHelpText;
+ private String mHelpMessageText;
+ private String mInstructionText;
+ private CharSequence mOwnerInfoText;
+ private boolean mShowingStatus;
+ private CharSequence mPlmn;
+ private CharSequence mSpn;
+ protected int mPhoneState;
+ private DigitalClock mDigitalClock;
+ protected boolean mBatteryCharged;
+ protected boolean mBatteryIsLow;
+ private boolean mEmergencyButtonEnabledBecauseSimLocked;
+ private Button mEmergencyCallButton;
+ private boolean mEmergencyCallButtonEnabledInScreen;
+
+ /**
+ *
+ * @param view the containing view of all widgets
+ * @param updateMonitor the update monitor to use
+ * @param lockPatternUtils lock pattern util object
+ * @param callback used to invoke emergency dialer
+ * @param emergencyButtonEnabledInScreen whether emergency button is enabled by default
+ */
+ public KeyguardStatusViewManager(View view) {
+ if (DEBUG) Log.v(TAG, "KeyguardStatusViewManager()");
+ mContainer = view;
+ mDateFormatString = getContext().getString(R.string.abbrev_wday_month_day_no_year);
+ mLockPatternUtils = new LockPatternUtils(view.getContext());
+ mUpdateMonitor = KeyguardUpdateMonitor.getInstance(view.getContext());
+
+ mCarrierView = (TextView) findViewById(R.id.carrier);
+ mDateView = (TextView) findViewById(R.id.date);
+ mStatus1View = (TextView) findViewById(R.id.status1);
+ mAlarmStatusView = (TextView) findViewById(R.id.alarm_status);
+ mOwnerInfoView = (TextView) findViewById(R.id.propertyOf);
+ mDigitalClock = (DigitalClock) findViewById(R.id.time);
+
+ // Registering this callback immediately updates the battery state, among other things.
+ mUpdateMonitor.registerCallback(mInfoCallback);
+
+ resetStatusInfo();
+ refreshDate();
+ updateOwnerInfo();
+
+ // Required to get Marquee to work.
+ final View scrollableViews[] = { mCarrierView, mDateView, mStatus1View, mOwnerInfoView,
+ mAlarmStatusView };
+ for (View v : scrollableViews) {
+ if (v != null) {
+ v.setSelected(true);
+ }
+ }
+ }
+
+ void setInstructionText(String string) {
+ mInstructionText = string;
+ update(INSTRUCTION_TEXT, string);
+ }
+
+ void setCarrierText(CharSequence string) {
+ mCarrierText = string;
+ update(CARRIER_TEXT, string);
+ }
+
+ void setOwnerInfo(CharSequence string) {
+ mOwnerInfoText = string;
+ update(OWNER_INFO, string);
+ }
+
+ /**
+ * Sets the carrier help text message, if view is present. Carrier help text messages are
+ * typically for help dealing with SIMS and connectivity.
+ *
+ * @param resId resource id of the message
+ */
+ public void setCarrierHelpText(int resId) {
+ mCarrierHelpText = getText(resId);
+ update(CARRIER_HELP_TEXT, mCarrierHelpText);
+ }
+
+ private CharSequence getText(int resId) {
+ return resId == 0 ? null : getContext().getText(resId);
+ }
+
+ /**
+ * Unlock help message. This is typically for help with unlock widgets, e.g. "wrong password"
+ * or "try again."
+ *
+ * @param textResId
+ * @param lockIcon
+ */
+ public void setHelpMessage(int textResId, int lockIcon) {
+ final CharSequence tmp = getText(textResId);
+ mHelpMessageText = tmp == null ? null : tmp.toString();
+ update(HELP_MESSAGE_TEXT, mHelpMessageText);
+ }
+
+ private void update(int what, CharSequence string) {
+ updateStatusLines(mShowingStatus);
+ }
+
+ public void onPause() {
+ if (DEBUG) Log.v(TAG, "onPause()");
+ mUpdateMonitor.removeCallback(mInfoCallback);
+ }
+
+ /** {@inheritDoc} */
+ public void onResume() {
+ if (DEBUG) Log.v(TAG, "onResume()");
+
+ // First update the clock, if present.
+ if (mDigitalClock != null) {
+ mDigitalClock.updateTime();
+ }
+
+ mUpdateMonitor.registerCallback(mInfoCallback);
+ resetStatusInfo();
+ }
+
+ void resetStatusInfo() {
+ mInstructionText = null;
+ updateStatusLines(true);
+ }
+
+ /**
+ * Update the status lines based on these rules:
+ * AlarmStatus: Alarm state always gets it's own line.
+ * Status1 is shared between help, battery status and generic unlock instructions,
+ * prioritized in that order.
+ * @param showStatusLines status lines are shown if true
+ */
+ void updateStatusLines(boolean showStatusLines) {
+ if (DEBUG) Log.v(TAG, "updateStatusLines(" + showStatusLines + ")");
+ mShowingStatus = showStatusLines;
+ updateAlarmInfo();
+ updateOwnerInfo();
+ updateStatus1();
+ updateCarrierText();
+ }
+
+ private void updateAlarmInfo() {
+ if (mAlarmStatusView != null) {
+ String nextAlarm = mLockPatternUtils.getNextAlarm();
+ boolean showAlarm = mShowingStatus && !TextUtils.isEmpty(nextAlarm);
+ mAlarmStatusView.setText(nextAlarm);
+ mAlarmStatusView.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0);
+ mAlarmStatusView.setVisibility(showAlarm ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private void updateOwnerInfo() {
+ final ContentResolver res = getContext().getContentResolver();
+ final boolean ownerInfoEnabled = Settings.Secure.getInt(res,
+ Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0;
+ mOwnerInfoText = ownerInfoEnabled ?
+ Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO) : null;
+ if (mOwnerInfoView != null) {
+ mOwnerInfoView.setText(mOwnerInfoText);
+ mOwnerInfoView.setVisibility(TextUtils.isEmpty(mOwnerInfoText) ? View.GONE:View.VISIBLE);
+ }
+ }
+
+ private void updateStatus1() {
+ if (mStatus1View != null) {
+ MutableInt icon = new MutableInt(0);
+ CharSequence string = getPriorityTextMessage(icon);
+ mStatus1View.setText(string);
+ mStatus1View.setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0);
+ mStatus1View.setVisibility(mShowingStatus ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private void updateCarrierText() {
+ mCarrierView.setText(mCarrierText);
+ }
+
+ private CharSequence getAltTextMessage(MutableInt icon) {
+ // If we have replaced the status area with a single widget, then this code
+ // prioritizes what to show in that space when all transient messages are gone.
+ CharSequence string = null;
+ if (mShowingBatteryInfo) {
+ // Battery status
+ if (mPluggedIn) {
+ // Charging, charged or waiting to charge.
+ string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged
+ :R.string.lockscreen_plugged_in, mBatteryLevel);
+ icon.value = CHARGING_ICON;
+ } else if (mBatteryIsLow) {
+ // Battery is low
+ string = getContext().getString(R.string.lockscreen_low_battery);
+ icon.value = BATTERY_LOW_ICON;
+ }
+ } else {
+ string = mCarrierText;
+ }
+ return string;
+ }
+
+ private CharSequence getPriorityTextMessage(MutableInt icon) {
+ CharSequence string = null;
+ if (!TextUtils.isEmpty(mInstructionText)) {
+ // Instructions only
+ string = mInstructionText;
+ icon.value = LOCK_ICON;
+ } else if (mShowingBatteryInfo) {
+ // Battery status
+ if (mPluggedIn) {
+ // Charging, charged or waiting to charge.
+ string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged
+ :R.string.lockscreen_plugged_in, mBatteryLevel);
+ icon.value = CHARGING_ICON;
+ } else if (mBatteryIsLow) {
+ // Battery is low
+ string = getContext().getString(R.string.lockscreen_low_battery);
+ icon.value = BATTERY_LOW_ICON;
+ }
+ } else if (mOwnerInfoView == null && mOwnerInfoText != null) {
+ string = mOwnerInfoText;
+ }
+ return string;
+ }
+
+ void refreshDate() {
+ if (mDateView != null) {
+ mDateView.setText(DateFormat.format(mDateFormatString, new Date()));
+ }
+ }
+
+ /**
+ * Determine the current status of the lock screen given the sim state and other stuff.
+ */
+ public StatusMode getStatusForIccState(IccCardConstants.State simState) {
+ // Since reading the SIM may take a while, we assume it is present until told otherwise.
+ if (simState == null) {
+ return StatusMode.Normal;
+ }
+
+ final boolean missingAndNotProvisioned = (!mUpdateMonitor.isDeviceProvisioned()
+ && (simState == IccCardConstants.State.ABSENT ||
+ simState == IccCardConstants.State.PERM_DISABLED));
+
+ // Assume we're NETWORK_LOCKED if not provisioned
+ simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
+ switch (simState) {
+ case ABSENT:
+ return StatusMode.SimMissing;
+ case NETWORK_LOCKED:
+ return StatusMode.SimMissingLocked;
+ case NOT_READY:
+ return StatusMode.SimMissing;
+ case PIN_REQUIRED:
+ return StatusMode.SimLocked;
+ case PUK_REQUIRED:
+ return StatusMode.SimPukLocked;
+ case READY:
+ return StatusMode.Normal;
+ case PERM_DISABLED:
+ return StatusMode.SimPermDisabled;
+ case UNKNOWN:
+ return StatusMode.SimMissing;
+ }
+ return StatusMode.SimMissing;
+ }
+
+ private Context getContext() {
+ return mContainer.getContext();
+ }
+
+ /**
+ * Update carrier text, carrier help and emergency button to match the current status based
+ * on SIM state.
+ *
+ * @param simState
+ */
+ private void updateCarrierStateWithSimStatus(IccCardConstants.State simState) {
+ if (DEBUG) Log.d(TAG, "updateCarrierTextWithSimStatus(), simState = " + simState);
+
+ CharSequence carrierText = null;
+ int carrierHelpTextId = 0;
+ mEmergencyButtonEnabledBecauseSimLocked = false;
+ mStatus = getStatusForIccState(simState);
+ mSimState = simState;
+ switch (mStatus) {
+ case Normal:
+ carrierText = makeCarierString(mPlmn, mSpn);
+ break;
+
+ case NetworkLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_network_locked_message),
+ mPlmn);
+ carrierHelpTextId = R.string.lockscreen_instructions_when_pattern_disabled;
+ break;
+
+ case SimMissing:
+ // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+ // This depends on mPlmn containing the text "Emergency calls only" when the radio
+ // has some connectivity. Otherwise, it should be null or empty and just show
+ // "No SIM card"
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_missing_sim_message_short),
+ mPlmn);
+ carrierHelpTextId = R.string.lockscreen_missing_sim_instructions_long;
+ break;
+
+ case SimPermDisabled:
+ carrierText = getContext().getText(
+ R.string.lockscreen_permanent_disabled_sim_message_short);
+ carrierHelpTextId = R.string.lockscreen_permanent_disabled_sim_instructions;
+ mEmergencyButtonEnabledBecauseSimLocked = true;
+ break;
+
+ case SimMissingLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_missing_sim_message_short),
+ mPlmn);
+ carrierHelpTextId = R.string.lockscreen_missing_sim_instructions;
+ mEmergencyButtonEnabledBecauseSimLocked = true;
+ break;
+
+ case SimLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_sim_locked_message),
+ mPlmn);
+ mEmergencyButtonEnabledBecauseSimLocked = true;
+ break;
+
+ case SimPukLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_sim_puk_locked_message),
+ mPlmn);
+ if (!mLockPatternUtils.isPukUnlockScreenEnable()) {
+ // This means we're showing the PUK unlock screen
+ mEmergencyButtonEnabledBecauseSimLocked = true;
+ }
+ break;
+ }
+
+ setCarrierText(carrierText);
+ setCarrierHelpText(carrierHelpTextId);
+ updateEmergencyCallButtonState(mPhoneState);
+ }
+
+
+ /*
+ * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
+ */
+ private CharSequence makeCarrierStringOnEmergencyCapable(
+ CharSequence simMessage, CharSequence emergencyCallMessage) {
+ if (mLockPatternUtils.isEmergencyCallCapable()) {
+ return makeCarierString(simMessage, emergencyCallMessage);
+ }
+ return simMessage;
+ }
+
+ private View findViewById(int id) {
+ return mContainer.findViewById(id);
+ }
+
+ /**
+ * The status of this lock screen. Primarily used for widgets on LockScreen.
+ */
+ enum StatusMode {
+ /**
+ * Normal case (sim card present, it's not locked)
+ */
+ Normal(true),
+
+ /**
+ * The sim card is 'network locked'.
+ */
+ NetworkLocked(true),
+
+ /**
+ * The sim card is missing.
+ */
+ SimMissing(false),
+
+ /**
+ * The sim card is missing, and this is the device isn't provisioned, so we don't let
+ * them get past the screen.
+ */
+ SimMissingLocked(false),
+
+ /**
+ * The sim card is PUK locked, meaning they've entered the wrong sim unlock code too many
+ * times.
+ */
+ SimPukLocked(false),
+
+ /**
+ * The sim card is locked.
+ */
+ SimLocked(true),
+
+ /**
+ * The sim card is permanently disabled due to puk unlock failure
+ */
+ SimPermDisabled(false);
+
+ private final boolean mShowStatusLines;
+
+ StatusMode(boolean mShowStatusLines) {
+ this.mShowStatusLines = mShowStatusLines;
+ }
+
+ /**
+ * @return Whether the status lines (battery level and / or next alarm) are shown while
+ * in this state. Mostly dictated by whether this is room for them.
+ */
+ public boolean shouldShowStatusLines() {
+ return mShowStatusLines;
+ }
+ }
+
+ private void updateEmergencyCallButtonState(int phoneState) {
+ if (mEmergencyCallButton != null) {
+ boolean enabledBecauseSimLocked =
+ mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked()
+ && mEmergencyButtonEnabledBecauseSimLocked;
+ boolean shown = mEmergencyCallButtonEnabledInScreen || enabledBecauseSimLocked;
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton,
+ phoneState, shown);
+ }
+ }
+
+ private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
+ mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow();
+ mPluggedIn = status.isPluggedIn();
+ mBatteryLevel = status.level;
+ mBatteryCharged = status.isCharged();
+ mBatteryIsLow = status.isBatteryLow();
+ final MutableInt tmpIcon = new MutableInt(0);
+ update(BATTERY_INFO, getAltTextMessage(tmpIcon));
+ }
+
+ @Override
+ public void onTimeChanged() {
+ refreshDate();
+ }
+
+ @Override
+ public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+ mPlmn = plmn;
+ mSpn = spn;
+ updateCarrierStateWithSimStatus(mSimState);
+ }
+
+ @Override
+ public void onPhoneStateChanged(int phoneState) {
+ mPhoneState = phoneState;
+ updateEmergencyCallButtonState(phoneState);
+ }
+
+ @Override
+ public void onSimStateChanged(IccCardConstants.State simState) {
+ updateCarrierStateWithSimStatus(simState);
+ }
+ };
+
+ /**
+ * Performs concentenation of PLMN/SPN
+ * @param plmn
+ * @param spn
+ * @return
+ */
+ private static CharSequence makeCarierString(CharSequence plmn, CharSequence spn) {
+ final boolean plmnValid = !TextUtils.isEmpty(plmn);
+ final boolean spnValid = !TextUtils.isEmpty(spn);
+ if (plmnValid && spnValid) {
+ return plmn + "|" + spn;
+ } else if (plmnValid) {
+ return plmn;
+ } else if (spnValid) {
+ return spn;
+ } else {
+ return "";
+ }
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
new file mode 100644
index 0000000..dad0dff
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
@@ -0,0 +1,710 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import static android.os.BatteryManager.BATTERY_STATUS_FULL;
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_STATUS;
+import static android.os.BatteryManager.EXTRA_PLUGGED;
+import static android.os.BatteryManager.EXTRA_LEVEL;
+import static android.os.BatteryManager.EXTRA_HEALTH;
+import android.media.AudioManager;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.TelephonyIntents;
+
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import com.android.internal.R;
+import com.google.android.collect.Lists;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Watches for updates that may be interesting to the keyguard, and provides
+ * the up to date information as well as a registration for callbacks that care
+ * to be updated.
+ *
+ * Note: under time crunch, this has been extended to include some stuff that
+ * doesn't really belong here. see {@link #handleBatteryUpdate} where it shutdowns
+ * the device, and {@link #getFailedAttempts()}, {@link #reportFailedAttempt()}
+ * and {@link #clearFailedAttempts()}. Maybe we should rename this 'KeyguardContext'...
+ */
+public class KeyguardUpdateMonitor {
+
+ private static final String TAG = "KeyguardUpdateMonitor";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_SIM_STATES = DEBUG || false;
+ private static final int FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 3;
+ private static final int LOW_BATTERY_THRESHOLD = 20;
+
+ // Callback messages
+ private static final int MSG_TIME_UPDATE = 301;
+ private static final int MSG_BATTERY_UPDATE = 302;
+ private static final int MSG_CARRIER_INFO_UPDATE = 303;
+ private static final int MSG_SIM_STATE_CHANGE = 304;
+ private static final int MSG_RINGER_MODE_CHANGED = 305;
+ private static final int MSG_PHONE_STATE_CHANGED = 306;
+ private static final int MSG_CLOCK_VISIBILITY_CHANGED = 307;
+ private static final int MSG_DEVICE_PROVISIONED = 308;
+ protected static final int MSG_DPM_STATE_CHANGED = 309;
+ protected static final int MSG_USER_SWITCHED = 310;
+ protected static final int MSG_USER_REMOVED = 311;
+
+ private static KeyguardUpdateMonitor sInstance;
+
+ private final Context mContext;
+
+ // Telephony state
+ private IccCardConstants.State mSimState = IccCardConstants.State.READY;
+ private CharSequence mTelephonyPlmn;
+ private CharSequence mTelephonySpn;
+ private int mRingMode;
+ private int mPhoneState;
+
+ // Device provisioning state
+ private boolean mDeviceProvisioned;
+
+ // Battery status
+ private BatteryStatus mBatteryStatus;
+
+ // Password attempts
+ private int mFailedAttempts = 0;
+ private int mFailedBiometricUnlockAttempts = 0;
+
+ private boolean mClockVisible;
+
+ private ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
+ mCallbacks = Lists.newArrayList();
+ private ContentObserver mContentObserver;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TIME_UPDATE:
+ handleTimeUpdate();
+ break;
+ case MSG_BATTERY_UPDATE:
+ handleBatteryUpdate((BatteryStatus) msg.obj);
+ break;
+ case MSG_CARRIER_INFO_UPDATE:
+ handleCarrierInfoUpdate();
+ break;
+ case MSG_SIM_STATE_CHANGE:
+ handleSimStateChange((SimArgs) msg.obj);
+ break;
+ case MSG_RINGER_MODE_CHANGED:
+ handleRingerModeChange(msg.arg1);
+ break;
+ case MSG_PHONE_STATE_CHANGED:
+ handlePhoneStateChanged((String)msg.obj);
+ break;
+ case MSG_CLOCK_VISIBILITY_CHANGED:
+ handleClockVisibilityChanged();
+ break;
+ case MSG_DEVICE_PROVISIONED:
+ handleDeviceProvisioned();
+ break;
+ case MSG_DPM_STATE_CHANGED:
+ handleDevicePolicyManagerStateChanged();
+ break;
+ case MSG_USER_SWITCHED:
+ handleUserSwitched(msg.arg1);
+ break;
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ break;
+ }
+ }
+ };
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "received broadcast " + action);
+
+ if (Intent.ACTION_TIME_TICK.equals(action)
+ || Intent.ACTION_TIME_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_TIME_UPDATE));
+ } else if (TelephonyIntents.SPN_STRINGS_UPDATED_ACTION.equals(action)) {
+ mTelephonyPlmn = getTelephonyPlmnFrom(intent);
+ mTelephonySpn = getTelephonySpnFrom(intent);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_CARRIER_INFO_UPDATE));
+ } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
+ final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);
+ final int level = intent.getIntExtra(EXTRA_LEVEL, 0);
+ final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+ final Message msg = mHandler.obtainMessage(
+ MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health));
+ mHandler.sendMessage(msg);
+ } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
+ if (DEBUG_SIM_STATES) {
+ Log.v(TAG, "action " + action + " state" +
+ intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE));
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MSG_SIM_STATE_CHANGE, SimArgs.fromIntent(intent)));
+ } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED,
+ intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0));
+ } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
+ String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state));
+ } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
+ .equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED));
+ } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED,
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
+ } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_REMOVED,
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
+ }
+ }
+ };
+
+ /**
+ * When we receive a
+ * {@link com.android.internal.telephony.TelephonyIntents#ACTION_SIM_STATE_CHANGED} broadcast,
+ * and then pass a result via our handler to {@link KeyguardUpdateMonitor#handleSimStateChange},
+ * we need a single object to pass to the handler. This class helps decode
+ * the intent and provide a {@link SimCard.State} result.
+ */
+ private static class SimArgs {
+ public final IccCardConstants.State simState;
+
+ SimArgs(IccCardConstants.State state) {
+ simState = state;
+ }
+
+ static SimArgs fromIntent(Intent intent) {
+ IccCardConstants.State state;
+ if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
+ throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
+ }
+ String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+ if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
+ final String absentReason = intent
+ .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
+
+ if (IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED.equals(
+ absentReason)) {
+ state = IccCardConstants.State.PERM_DISABLED;
+ } else {
+ state = IccCardConstants.State.ABSENT;
+ }
+ } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
+ state = IccCardConstants.State.READY;
+ } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
+ final String lockedReason = intent
+ .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
+ if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
+ state = IccCardConstants.State.PIN_REQUIRED;
+ } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
+ state = IccCardConstants.State.PUK_REQUIRED;
+ } else {
+ state = IccCardConstants.State.UNKNOWN;
+ }
+ } else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
+ state = IccCardConstants.State.NETWORK_LOCKED;
+ } else {
+ state = IccCardConstants.State.UNKNOWN;
+ }
+ return new SimArgs(state);
+ }
+
+ public String toString() {
+ return simState.toString();
+ }
+ }
+
+ /* package */ static class BatteryStatus {
+ public final int status;
+ public final int level;
+ public final int plugged;
+ public final int health;
+ public BatteryStatus(int status, int level, int plugged, int health) {
+ this.status = status;
+ this.level = level;
+ this.plugged = plugged;
+ this.health = health;
+ }
+
+ /**
+ * Determine whether the device is plugged in (USB or power).
+ * @return true if the device is plugged in.
+ */
+ boolean isPluggedIn() {
+ return plugged == BatteryManager.BATTERY_PLUGGED_AC
+ || plugged == BatteryManager.BATTERY_PLUGGED_USB;
+ }
+
+ /**
+ * Whether or not the device is charged. Note that some devices never return 100% for
+ * battery level, so this allows either battery level or status to determine if the
+ * battery is charged.
+ * @return true if the device is charged
+ */
+ public boolean isCharged() {
+ return status == BATTERY_STATUS_FULL || level >= 100;
+ }
+
+ /**
+ * Whether battery is low and needs to be charged.
+ * @return true if battery is low
+ */
+ public boolean isBatteryLow() {
+ return level < LOW_BATTERY_THRESHOLD;
+ }
+
+ }
+
+ public static KeyguardUpdateMonitor getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new KeyguardUpdateMonitor(context);
+ }
+ return sInstance;
+ }
+
+ private KeyguardUpdateMonitor(Context context) {
+ mContext = context;
+
+ mDeviceProvisioned = Settings.Secure.getInt(
+ mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+
+ // Since device can't be un-provisioned, we only need to register a content observer
+ // to update mDeviceProvisioned when we are...
+ if (!mDeviceProvisioned) {
+ watchForDeviceProvisioning();
+ }
+
+ // Take a guess at initial SIM state, battery status and PLMN until we get an update
+ mSimState = IccCardConstants.State.NOT_READY;
+ mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0);
+ mTelephonyPlmn = getDefaultPlmn();
+
+ // Watch for interesting updates
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+ filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
+ filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ private void watchForDeviceProvisioning() {
+ mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+ if (mDeviceProvisioned) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED));
+ }
+ if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED),
+ false, mContentObserver);
+
+ // prevent a race condition between where we check the flag and where we register the
+ // observer by grabbing the value once again...
+ boolean provisioned = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+ if (provisioned != mDeviceProvisioned) {
+ mDeviceProvisioned = provisioned;
+ if (mDeviceProvisioned) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED));
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_DPM_STATE_CHANGED}
+ */
+ protected void handleDevicePolicyManagerStateChanged() {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onDevicePolicyManagerStateChanged();
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_USER_SWITCHED}
+ */
+ protected void handleUserSwitched(int userId) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onUserSwitched(userId);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_USER_SWITCHED}
+ */
+ protected void handleUserRemoved(int userId) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onUserRemoved(userId);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_DEVICE_PROVISIONED}
+ */
+ protected void handleDeviceProvisioned() {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onDeviceProvisioned();
+ }
+ }
+ if (mContentObserver != null) {
+ // We don't need the observer anymore...
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ mContentObserver = null;
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_PHONE_STATE_CHANGED}
+ */
+ protected void handlePhoneStateChanged(String newState) {
+ if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
+ if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) {
+ mPhoneState = TelephonyManager.CALL_STATE_IDLE;
+ } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(newState)) {
+ mPhoneState = TelephonyManager.CALL_STATE_OFFHOOK;
+ } else if (TelephonyManager.EXTRA_STATE_RINGING.equals(newState)) {
+ mPhoneState = TelephonyManager.CALL_STATE_RINGING;
+ }
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onPhoneStateChanged(mPhoneState);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_RINGER_MODE_CHANGED}
+ */
+ protected void handleRingerModeChange(int mode) {
+ if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")");
+ mRingMode = mode;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onRingerModeChanged(mode);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_TIME_UPDATE}
+ */
+ private void handleTimeUpdate() {
+ if (DEBUG) Log.d(TAG, "handleTimeUpdate");
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTimeChanged();
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_BATTERY_UPDATE}
+ */
+ private void handleBatteryUpdate(BatteryStatus status) {
+ if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
+ final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
+ mBatteryStatus = status;
+ if (batteryUpdateInteresting) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onRefreshBatteryInfo(status);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_CARRIER_INFO_UPDATE}
+ */
+ private void handleCarrierInfoUpdate() {
+ if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn
+ + ", spn = " + mTelephonySpn);
+
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_SIM_STATE_CHANGE}
+ */
+ private void handleSimStateChange(SimArgs simArgs) {
+ final IccCardConstants.State state = simArgs.simState;
+
+ if (DEBUG) {
+ Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " "
+ + "state resolved to " + state.toString());
+ }
+
+ if (state != IccCardConstants.State.UNKNOWN && state != mSimState) {
+ mSimState = state;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onSimStateChanged(state);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_CLOCK_VISIBILITY_CHANGED}
+ */
+ private void handleClockVisibilityChanged() {
+ if (DEBUG) Log.d(TAG, "handleClockVisibilityChanged()");
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onClockVisibilityChanged();
+ }
+ }
+ }
+
+ private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
+ final boolean nowPluggedIn = current.isPluggedIn();
+ final boolean wasPluggedIn = old.isPluggedIn();
+ final boolean stateChangedWhilePluggedIn =
+ wasPluggedIn == true && nowPluggedIn == true
+ && (old.status != current.status);
+
+ // change in plug state is always interesting
+ if (wasPluggedIn != nowPluggedIn || stateChangedWhilePluggedIn) {
+ return true;
+ }
+
+ // change in battery level while plugged in
+ if (nowPluggedIn && old.level != current.level) {
+ return true;
+ }
+
+ // change where battery needs charging
+ if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param intent The intent with action {@link TelephonyIntents#SPN_STRINGS_UPDATED_ACTION}
+ * @return The string to use for the plmn, or null if it should not be shown.
+ */
+ private CharSequence getTelephonyPlmnFrom(Intent intent) {
+ if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
+ final String plmn = intent.getStringExtra(TelephonyIntents.EXTRA_PLMN);
+ return (plmn != null) ? plmn : getDefaultPlmn();
+ }
+ return null;
+ }
+
+ /**
+ * @return The default plmn (no service)
+ */
+ private CharSequence getDefaultPlmn() {
+ return mContext.getResources().getText(R.string.lockscreen_carrier_default);
+ }
+
+ /**
+ * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION}
+ * @return The string to use for the plmn, or null if it should not be shown.
+ */
+ private CharSequence getTelephonySpnFrom(Intent intent) {
+ if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
+ final String spn = intent.getStringExtra(TelephonyIntents.EXTRA_SPN);
+ if (spn != null) {
+ return spn;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Remove the given observer's callback.
+ *
+ * @param observer The observer to remove
+ */
+ public void removeCallback(Object observer) {
+ mCallbacks.remove(observer);
+ }
+
+ /**
+ * Register to receive notifications about general keyguard information
+ * (see {@link InfoCallback}.
+ * @param callback The callback.
+ */
+ public void registerCallback(KeyguardUpdateMonitorCallback callback) {
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(new WeakReference<KeyguardUpdateMonitorCallback>(callback));
+ // Notify listener of the current state
+ callback.onRefreshBatteryInfo(mBatteryStatus);
+ callback.onTimeChanged();
+ callback.onRingerModeChanged(mRingMode);
+ callback.onPhoneStateChanged(mPhoneState);
+ callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
+ callback.onClockVisibilityChanged();
+ callback.onSimStateChanged(mSimState);
+ } else {
+ if (DEBUG) Log.e(TAG, "Object tried to add another callback",
+ new Exception("Called by"));
+ }
+
+ // Clean up any unused references
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ if (mCallbacks.get(i).get() == null) {
+ mCallbacks.remove(i);
+ }
+ }
+ }
+
+ public void reportClockVisible(boolean visible) {
+ mClockVisible = visible;
+ mHandler.obtainMessage(MSG_CLOCK_VISIBILITY_CHANGED).sendToTarget();
+ }
+
+ public IccCardConstants.State getSimState() {
+ return mSimState;
+ }
+
+ /**
+ * Report that the user successfully entered the SIM PIN or PUK/SIM PIN so we
+ * have the information earlier than waiting for the intent
+ * broadcast from the telephony code.
+ *
+ * NOTE: Because handleSimStateChange() invokes callbacks immediately without going
+ * through mHandler, this *must* be called from the UI thread.
+ */
+ public void reportSimUnlocked() {
+ handleSimStateChange(new SimArgs(IccCardConstants.State.READY));
+ }
+
+ public CharSequence getTelephonyPlmn() {
+ return mTelephonyPlmn;
+ }
+
+ public CharSequence getTelephonySpn() {
+ return mTelephonySpn;
+ }
+
+ /**
+ * @return Whether the device is provisioned (whether they have gone through
+ * the setup wizard)
+ */
+ public boolean isDeviceProvisioned() {
+ return mDeviceProvisioned;
+ }
+
+ public int getFailedAttempts() {
+ return mFailedAttempts;
+ }
+
+ public void clearFailedAttempts() {
+ mFailedAttempts = 0;
+ mFailedBiometricUnlockAttempts = 0;
+ }
+
+ public void reportFailedAttempt() {
+ mFailedAttempts++;
+ }
+
+ public boolean isClockVisible() {
+ return mClockVisible;
+ }
+
+ public int getPhoneState() {
+ return mPhoneState;
+ }
+
+ public void reportFailedBiometricUnlockAttempt() {
+ mFailedBiometricUnlockAttempts++;
+ }
+
+ public boolean getMaxBiometricUnlockAttemptsReached() {
+ return mFailedBiometricUnlockAttempts >= FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP;
+ }
+
+ public boolean isSimLocked() {
+ return isSimLocked(mSimState);
+ }
+
+ public static boolean isSimLocked(IccCardConstants.State state) {
+ return state == IccCardConstants.State.PIN_REQUIRED
+ || state == IccCardConstants.State.PUK_REQUIRED
+ || state == IccCardConstants.State.PERM_DISABLED;
+ }
+
+ public boolean isSimPinSecure() {
+ return isSimPinSecure(mSimState);
+ }
+
+ public static boolean isSimPinSecure(IccCardConstants.State state) {
+ final IccCardConstants.State simState = state;
+ return (simState == IccCardConstants.State.PIN_REQUIRED
+ || simState == IccCardConstants.State.PUK_REQUIRED
+ || simState == IccCardConstants.State.PERM_DISABLED);
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java
index d791419..3d65e68 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java
@@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard;
import android.app.admin.DevicePolicyManager;
import android.media.AudioManager;
-import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus;
import com.android.internal.telephony.IccCardConstants;
/**
@@ -31,7 +30,7 @@ class KeyguardUpdateMonitorCallback {
*
* @param status current battery status
*/
- void onRefreshBatteryInfo(BatteryStatus status) { }
+ void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { }
/**
* Called once per minute or when the time changes.
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java
new file mode 100644
index 0000000..ad5de0e
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java
@@ -0,0 +1,254 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.TelephonyManager;
+import android.view.KeyEvent;
+import android.widget.LinearLayout;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * Base class for keyguard view. {@link #reset} is where you should
+ * reset the state of your view. Use the {@link KeyguardViewCallback} via
+ * {@link #getCallback()} to send information back (such as poking the wake lock,
+ * or finishing the keyguard).
+ *
+ * Handles intercepting of media keys that still work when the keyguard is
+ * showing.
+ */
+public abstract class KeyguardViewBase extends LinearLayout {
+
+ private static final int BACKGROUND_COLOR = 0x70000000;
+ private AudioManager mAudioManager;
+ private TelephonyManager mTelephonyManager = null;
+ protected KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback;
+
+ // Whether the volume keys should be handled by keyguard. If true, then
+ // they will be handled here for specific media types such as music, otherwise
+ // the audio service will bring up the volume dialog.
+ private static final boolean KEYGUARD_MANAGES_VOLUME = true;
+
+ // This is a faster way to draw the background on devices without hardware acceleration
+ private static final Drawable mBackgroundDrawable = new Drawable() {
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+ };
+
+ public KeyguardViewBase(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardViewBase(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ resetBackground();
+ }
+
+ public void resetBackground() {
+ setBackground(mBackgroundDrawable);
+ }
+
+ /**
+ * Called when you need to reset the state of your view.
+ */
+ abstract public void reset();
+
+ /**
+ * Called when the screen turned off.
+ */
+ abstract public void onScreenTurnedOff();
+
+ /**
+ * Called when the screen turned on.
+ */
+ abstract public void onScreenTurnedOn();
+
+ /**
+ * Called when the view needs to be shown.
+ */
+ abstract public void show();
+
+ /**
+ * Called when a key has woken the device to give us a chance to adjust our
+ * state according the the key. We are responsible for waking the device
+ * (by poking the wake lock) once we are ready.
+ *
+ * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}.
+ * Be sure not to take any action that takes a long time; any significant
+ * action should be posted to a handler.
+ *
+ * @param keyCode The wake key, which may be relevant for configuring the
+ * keyguard. May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking for a reason
+ * other than a key press.
+ */
+ abstract public void wakeWhenReadyTq(int keyCode);
+
+ /**
+ * Verify that the user can get past the keyguard securely. This is called,
+ * for example, when the phone disables the keyguard but then wants to launch
+ * something else that requires secure access.
+ *
+ * The result will be propogated back via {@link KeyguardViewCallback#keyguardDone(boolean)}
+ */
+ abstract public void verifyUnlock();
+
+ /**
+ * Called before this view is being removed.
+ */
+ abstract public void cleanUp();
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (interceptMediaKey(event)) {
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ /**
+ * Allows the media keys to work when the keyguard is showing.
+ * The media keys should be of no interest to the actual keyguard view(s),
+ * so intercepting them here should not be of any harm.
+ * @param event The key event
+ * @return whether the event was consumed as a media key.
+ */
+ private boolean interceptMediaKey(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ /* Suppress PLAY/PAUSE toggle when phone is ringing or
+ * in-call to avoid music playback */
+ if (mTelephonyManager == null) {
+ mTelephonyManager = (TelephonyManager) getContext().getSystemService(
+ Context.TELEPHONY_SERVICE);
+ }
+ if (mTelephonyManager != null &&
+ mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
+ return true; // suppress key event
+ }
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ 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: {
+ handleMediaKeyEvent(event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ if (KEYGUARD_MANAGES_VOLUME) {
+ synchronized (this) {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ }
+ }
+ // Volume buttons should only function for music (local or remote).
+ // TODO: Actually handle MUTE.
+ mAudioManager.adjustLocalOrRemoteStreamVolume(
+ AudioManager.STREAM_MUSIC,
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ ? AudioManager.ADJUST_RAISE
+ : AudioManager.ADJUST_LOWER);
+ // Don't execute default volume behavior
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ switch (keyCode) {
+ 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: {
+ handleMediaKeyEvent(event);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void handleMediaKeyEvent(KeyEvent keyEvent) {
+ IAudioService audioService = IAudioService.Stub.asInterface(
+ ServiceManager.checkService(Context.AUDIO_SERVICE));
+ if (audioService != null) {
+ try {
+ audioService.dispatchMediaKeyEvent(keyEvent);
+ } catch (RemoteException e) {
+ Log.e("KeyguardViewBase", "dispatchMediaKeyEvent threw exception " + e);
+ }
+ } else {
+ Slog.w("KeyguardViewBase", "Unable to find IAudioService for media key event");
+ }
+ }
+
+ @Override
+ public void dispatchSystemUiVisibilityChanged(int visibility) {
+ super.dispatchSystemUiVisibilityChanged(visibility);
+ setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
+ }
+
+ public void setViewMediatorCallback(
+ KeyguardViewMediator.ViewMediatorCallback viewMediatorCallback) {
+ mViewMediatorCallback = viewMediatorCallback;
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
new file mode 100644
index 0000000..61003bf
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
@@ -0,0 +1,318 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewManager;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.R;
+
+/**
+ * Manages creating, showing, hiding and resetting the keyguard. Calls back
+ * via {@link com.android.internal.policy.impl.KeyguardViewCallback} to poke
+ * the wake lock and report that the keyguard is done, which is in turn,
+ * reported to this class by the current {@link KeyguardViewBase}.
+ */
+public class KeyguardViewManager {
+ private final static boolean DEBUG = false;
+ private static String TAG = "KeyguardViewManager";
+
+ private final Context mContext;
+ private final ViewManager mViewManager;
+ private final KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback;
+
+ private WindowManager.LayoutParams mWindowLayoutParams;
+ private boolean mNeedsInput = false;
+
+ private FrameLayout mKeyguardHost;
+ private KeyguardHostView mKeyguardView;
+
+ private boolean mScreenOn = false;
+ private LockPatternUtils mLockPatternUtils;
+
+ public interface ShowListener {
+ void onShown(IBinder windowToken);
+ };
+
+ /**
+ * @param context Used to create views.
+ * @param viewManager Keyguard will be attached to this.
+ * @param callback Used to notify of changes.
+ * @param lockPatternUtils
+ */
+ public KeyguardViewManager(Context context, ViewManager viewManager,
+ KeyguardViewMediator.ViewMediatorCallback callback,
+ LockPatternUtils lockPatternUtils) {
+ mContext = context;
+ mViewManager = viewManager;
+ mViewMediatorCallback = callback;
+ mLockPatternUtils = lockPatternUtils;
+ }
+
+ /**
+ * Show the keyguard. Will handle creating and attaching to the view manager
+ * lazily.
+ */
+ public synchronized void show() {
+ if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView);
+
+ boolean enableScreenRotation = shouldEnableScreenRotation();
+
+ maybeCreateKeyguardLocked(enableScreenRotation);
+ maybeEnableScreenRotation(enableScreenRotation);
+
+ // Disable aspects of the system/status/navigation bars that are not appropriate or
+ // useful for the lockscreen but can be re-shown by dialogs or SHOW_WHEN_LOCKED activities.
+ // Other disabled bits are handled by the KeyguardViewMediator talking directly to the
+ // status bar service.
+ int visFlags = View.STATUS_BAR_DISABLE_BACK | View.STATUS_BAR_DISABLE_HOME;
+ if (DEBUG) Log.v(TAG, "KGVM: Set visibility on " + mKeyguardHost + " to " + visFlags);
+ mKeyguardHost.setSystemUiVisibility(visFlags);
+
+ mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+ mKeyguardHost.setVisibility(View.VISIBLE);
+ mKeyguardView.show();
+ mKeyguardView.requestFocus();
+ }
+
+ private boolean shouldEnableScreenRotation() {
+ Resources res = mContext.getResources();
+ return SystemProperties.getBoolean("lockscreen.rot_override",false)
+ || res.getBoolean(com.android.internal.R.bool.config_enableLockScreenRotation);
+ }
+
+ class ViewManagerHost extends FrameLayout {
+ public ViewManagerHost(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ maybeCreateKeyguardLocked(shouldEnableScreenRotation());
+ }
+ }
+
+ private void maybeCreateKeyguardLocked(boolean enableScreenRotation) {
+ final boolean isActivity = (mContext instanceof Activity); // for test activity
+
+ if (mKeyguardHost == null) {
+ if (DEBUG) Log.d(TAG, "keyguard host is null, creating it...");
+
+ mKeyguardHost = new ViewManagerHost(mContext);
+
+ int flags = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER
+ | WindowManager.LayoutParams.FLAG_SLIPPERY;
+
+ if (!mNeedsInput) {
+ flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ }
+ if (ActivityManager.isHighEndGfx()) {
+ flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+
+ final int stretch = ViewGroup.LayoutParams.MATCH_PARENT;
+ final int type = isActivity ? WindowManager.LayoutParams.TYPE_APPLICATION
+ : WindowManager.LayoutParams.TYPE_KEYGUARD;
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ stretch, stretch, type, flags, PixelFormat.TRANSLUCENT);
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen;
+ if (ActivityManager.isHighEndGfx()) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ lp.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
+ }
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY;
+ lp.setTitle(isActivity ? "KeyguardMock" : "Keyguard");
+ mWindowLayoutParams = lp;
+ mViewManager.addView(mKeyguardHost, lp);
+ }
+ inflateKeyguardView();
+ mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+ }
+
+ private void inflateKeyguardView() {
+ if (mKeyguardView != null) {
+ mKeyguardHost.removeView(mKeyguardView);
+ }
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true);
+ mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view);
+ mKeyguardView.setLockPatternUtils(mLockPatternUtils);
+ mKeyguardView.setViewMediatorCallback(mViewMediatorCallback);
+
+ if (mScreenOn) {
+ mKeyguardView.show();
+ }
+ }
+
+ private void maybeEnableScreenRotation(boolean enableScreenRotation) {
+ // TODO: move this outside
+ if (enableScreenRotation) {
+ if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen On!");
+ mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
+ } else {
+ if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen Off!");
+ mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ }
+ mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+ }
+
+ public void setNeedsInput(boolean needsInput) {
+ mNeedsInput = needsInput;
+ if (mWindowLayoutParams != null) {
+ if (needsInput) {
+ mWindowLayoutParams.flags &=
+ ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ } else {
+ mWindowLayoutParams.flags |=
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ }
+
+ try {
+ mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+ } catch (java.lang.IllegalArgumentException e) {
+ // TODO: Ensure this method isn't called on views that are changing...
+ Log.w(TAG,"Can't update input method on " + mKeyguardHost + " window not attached");
+ }
+ }
+ }
+
+ /**
+ * Reset the state of the view.
+ */
+ public synchronized void reset() {
+ if (DEBUG) Log.d(TAG, "reset()");
+ if (mKeyguardView != null) {
+ mKeyguardView.reset();
+ }
+ }
+
+ public synchronized void onScreenTurnedOff() {
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOff()");
+ mScreenOn = false;
+ if (mKeyguardView != null) {
+ mKeyguardView.onScreenTurnedOff();
+ }
+ }
+
+ public synchronized void onScreenTurnedOn(
+ final KeyguardViewManager.ShowListener showListener) {
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOn()");
+ mScreenOn = true;
+ if (mKeyguardView != null) {
+ mKeyguardView.onScreenTurnedOn();
+
+ // Caller should wait for this window to be shown before turning
+ // on the screen.
+ if (mKeyguardHost.getVisibility() == View.VISIBLE) {
+ // Keyguard may be in the process of being shown, but not yet
+ // updated with the window manager... give it a chance to do so.
+ mKeyguardHost.post(new Runnable() {
+ public void run() {
+ if (mKeyguardHost.getVisibility() == View.VISIBLE) {
+ showListener.onShown(mKeyguardHost.getWindowToken());
+ } else {
+ showListener.onShown(null);
+ }
+ }
+ });
+ } else {
+ showListener.onShown(null);
+ }
+ } else {
+ showListener.onShown(null);
+ }
+ }
+
+ public synchronized void verifyUnlock() {
+ if (DEBUG) Log.d(TAG, "verifyUnlock()");
+ show();
+ mKeyguardView.verifyUnlock();
+ }
+
+ /**
+ * A key has woken the device. We use this to potentially adjust the state
+ * of the lock screen based on the key.
+ *
+ * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}.
+ * Be sure not to take any action that takes a long time; any significant
+ * action should be posted to a handler.
+ *
+ * @param keyCode The wake key. May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking
+ * for a reason other than a key press.
+ */
+ public boolean wakeWhenReadyTq(int keyCode) {
+ if (DEBUG) Log.d(TAG, "wakeWhenReady(" + keyCode + ")");
+ if (mKeyguardView != null) {
+ mKeyguardView.wakeWhenReadyTq(keyCode);
+ return true;
+ } else {
+ Log.w(TAG, "mKeyguardView is null in wakeWhenReadyTq");
+ return false;
+ }
+ }
+
+ /**
+ * Hides the keyguard view
+ */
+ public synchronized void hide() {
+ if (DEBUG) Log.d(TAG, "hide()");
+
+ if (mKeyguardHost != null) {
+ mKeyguardHost.setVisibility(View.GONE);
+ // Don't do this right away, so we can let the view continue to animate
+ // as it goes away.
+ if (mKeyguardView != null) {
+ final KeyguardViewBase lastView = mKeyguardView;
+ mKeyguardView = null;
+ mKeyguardHost.postDelayed(new Runnable() {
+ public void run() {
+ synchronized (KeyguardViewManager.this) {
+ lastView.cleanUp();
+ mKeyguardHost.removeView(lastView);
+ }
+ }
+ }, 500);
+ }
+ }
+ }
+
+ /**
+ * @return Whether the keyguard is showing
+ */
+ public synchronized boolean isShowing() {
+ return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE);
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
new file mode 100644
index 0000000..d6733ea
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
@@ -0,0 +1,1342 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.widget.LockPatternUtils;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+
+
+/**
+ * Mediates requests related to the keyguard. This includes queries about the
+ * state of the keyguard, power management events that effect whether the keyguard
+ * should be shown or reset, callbacks to the phone window manager to notify
+ * it of when the keyguard is showing, and events from the keyguard view itself
+ * stating that the keyguard was succesfully unlocked.
+ *
+ * Note that the keyguard view is shown when the screen is off (as appropriate)
+ * so that once the screen comes on, it will be ready immediately.
+ *
+ * Example queries about the keyguard:
+ * - is {movement, key} one that should wake the keygaurd?
+ * - is the keyguard showing?
+ * - are input events restricted due to the state of the keyguard?
+ *
+ * Callbacks to the phone window manager:
+ * - the keyguard is showing
+ *
+ * Example external events that translate to keyguard view changes:
+ * - screen turned off -> reset the keyguard, and show it so it will be ready
+ * next time the screen turns on
+ * - keyboard is slid open -> if the keyguard is not secure, hide it
+ *
+ * Events from the keyguard view:
+ * - user succesfully unlocked keyguard -> hide keyguard view, and no longer
+ * restrict input events.
+ *
+ * Note: in addition to normal power managment events that effect the state of
+ * whether the keyguard should be showing, external apps and services may request
+ * that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When
+ * false, this will override all other conditions for turning on the keyguard.
+ *
+ * Threading and synchronization:
+ * This class is created by the initialization routine of the {@link WindowManagerPolicy},
+ * and runs on its thread. The keyguard UI is created from that thread in the
+ * constructor of this class. The apis may be called from other threads, including the
+ * {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s.
+ * Therefore, methods on this class are synchronized, and any action that is pointed
+ * directly to the keyguard UI is posted to a {@link Handler} to ensure it is taken on the UI
+ * thread of the keyguard.
+ */
+public class KeyguardViewMediator {
+ private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
+ private final static boolean DEBUG = false;
+ private final static boolean DBG_WAKE = false;
+
+ private final static String TAG = "KeyguardViewMediator";
+
+ private static final String DELAYED_KEYGUARD_ACTION =
+ "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD";
+
+ // used for handler messages
+ private static final int TIMEOUT = 1;
+ private static final int SHOW = 2;
+ private static final int HIDE = 3;
+ private static final int RESET = 4;
+ private static final int VERIFY_UNLOCK = 5;
+ private static final int NOTIFY_SCREEN_OFF = 6;
+ private static final int NOTIFY_SCREEN_ON = 7;
+ private static final int WAKE_WHEN_READY = 8;
+ private static final int KEYGUARD_DONE = 9;
+ private static final int KEYGUARD_DONE_DRAWING = 10;
+ private static final int KEYGUARD_DONE_AUTHENTICATING = 11;
+ private static final int SET_HIDDEN = 12;
+ private static final int KEYGUARD_TIMEOUT = 13;
+
+ /**
+ * The default amount of time we stay awake (used for all key input)
+ */
+ protected static final int AWAKE_INTERVAL_DEFAULT_MS = 10000;
+
+ /**
+ * How long to wait after the screen turns off due to timeout before
+ * turning on the keyguard (i.e, the user has this much time to turn
+ * the screen back on without having to face the keyguard).
+ */
+ private static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000;
+
+ /**
+ * How long we'll wait for the {@link KeyguardViewCallback#keyguardDoneDrawing()}
+ * callback before unblocking a call to {@link #setKeyguardEnabled(boolean)}
+ * that is reenabling the keyguard.
+ */
+ private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
+
+ /**
+ * Allow the user to expand the status bar when the keyguard is engaged
+ * (without a pattern or password).
+ */
+ private static final boolean ENABLE_INSECURE_STATUS_BAR_EXPAND = true;
+
+ /** The stream type that the lock sounds are tied to. */
+ private int mMasterStreamType;
+
+ private Context mContext;
+ private AlarmManager mAlarmManager;
+ private AudioManager mAudioManager;
+ private StatusBarManager mStatusBarManager;
+ private boolean mShowLockIcon;
+ private boolean mShowingLockIcon;
+
+ private boolean mSystemReady;
+
+ // Whether the next call to playSounds() should be skipped. Defaults to
+ // true because the first lock (on boot) should be silent.
+ private boolean mSuppressNextLockSound = true;
+
+
+ /** High level access to the power manager for WakeLocks */
+ private PowerManager mPM;
+
+ /**
+ * Used to keep the device awake while the keyguard is showing, i.e for
+ * calls to {@link #pokeWakelock()}
+ */
+ private PowerManager.WakeLock mWakeLock;
+
+ /**
+ * Used to keep the device awake while to ensure the keyguard finishes opening before
+ * we sleep.
+ */
+ private PowerManager.WakeLock mShowKeyguardWakeLock;
+
+ /**
+ * Does not turn on screen, held while a call to {@link KeyguardViewManager#wakeWhenReadyTq(int)}
+ * is called to make sure the device doesn't sleep before it has a chance to poke
+ * the wake lock.
+ * @see #wakeWhenReadyLocked(int)
+ */
+ private PowerManager.WakeLock mWakeAndHandOff;
+
+ private KeyguardViewManager mKeyguardViewManager;
+
+ // these are protected by synchronized (this)
+
+ /**
+ * External apps (like the phone app) can tell us to disable the keygaurd.
+ */
+ private boolean mExternallyEnabled = true;
+
+ /**
+ * Remember if an external call to {@link #setKeyguardEnabled} with value
+ * false caused us to hide the keyguard, so that we need to reshow it once
+ * the keygaurd is reenabled with another call with value true.
+ */
+ private boolean mNeedToReshowWhenReenabled = false;
+
+ // cached value of whether we are showing (need to know this to quickly
+ // answer whether the input should be restricted)
+ private boolean mShowing = false;
+
+ // true if the keyguard is hidden by another window
+ private boolean mHidden = false;
+
+ /**
+ * Helps remember whether the screen has turned on since the last time
+ * it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
+ */
+ private int mDelayedShowingSequence;
+
+ private int mWakelockSequence;
+
+ /**
+ * If the user has disabled the keyguard, then requests to exit, this is
+ * how we'll ultimately let them know whether it was successful. We use this
+ * var being non-null as an indicator that there is an in progress request.
+ */
+ private WindowManagerPolicy.OnKeyguardExitResult mExitSecureCallback;
+
+ // the properties of the keyguard
+
+ private KeyguardUpdateMonitor mUpdateMonitor;
+
+ private boolean mScreenOn;
+
+ // last known state of the cellular connection
+ private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
+
+ /**
+ * we send this intent when the keyguard is dismissed.
+ */
+ private Intent mUserPresentIntent;
+
+ /**
+ * {@link #setKeyguardEnabled} waits on this condition when it reenables
+ * the keyguard.
+ */
+ private boolean mWaitingUntilKeyguardVisible = false;
+ private LockPatternUtils mLockPatternUtils;
+
+ private SoundPool mLockSounds;
+ private int mLockSoundId;
+ private int mUnlockSoundId;
+ private int mLockSoundStreamId;
+
+ /**
+ * The volume applied to the lock/unlock sounds.
+ */
+ private final float mLockSoundVolume;
+
+ /**
+ * The callback used by the keyguard view to tell the {@link KeyguardViewMediator}
+ * various things.
+ */
+ public interface ViewMediatorCallback {
+
+ /**
+ * Request the wakelock to be poked for the default amount of time.
+ */
+ void pokeWakelock();
+
+ /**
+ * Request the wakelock to be poked for a specific amount of time.
+ * @param millis The amount of time in millis.
+ */
+ void pokeWakelock(long millis);
+
+ /**
+ * Report that the keyguard is done.
+ * @param authenticated Whether the user securely got past the keyguard.
+ * the only reason for this to be false is if the keyguard was instructed
+ * to appear temporarily to verify the user is supposed to get past the
+ * keyguard, and the user fails to do so.
+ */
+ void keyguardDone(boolean authenticated);
+
+ /**
+ * Report that the keyguard is done drawing.
+ */
+ void keyguardDoneDrawing();
+
+ /**
+ * Tell ViewMediator that the current view needs IME input
+ * @param needsInput
+ */
+ void setNeedsInput(boolean needsInput);
+ }
+
+ KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onUserSwitched(int userId) {
+ mLockPatternUtils.setCurrentUser(userId);
+ synchronized (KeyguardViewMediator.this) {
+ resetStateLocked();
+ }
+ }
+
+ @Override
+ public void onUserRemoved(int userId) {
+ mLockPatternUtils.removeUser(userId);
+ }
+
+ @Override
+ void onPhoneStateChanged(int phoneState) {
+ synchronized (KeyguardViewMediator.this) {
+ if (TelephonyManager.CALL_STATE_IDLE == phoneState // call ending
+ && !mScreenOn // screen off
+ && mExternallyEnabled) { // not disabled by any app
+
+ // note: this is a way to gracefully reenable the keyguard when the call
+ // ends and the screen is off without always reenabling the keyguard
+ // each time the screen turns off while in call (and having an occasional ugly
+ // flicker while turning back on the screen and disabling the keyguard again).
+ if (DEBUG) Log.d(TAG, "screen is off and call ended, let's make sure the "
+ + "keyguard is showing");
+ doKeyguardLocked();
+ }
+ }
+ };
+
+ @Override
+ public void onClockVisibilityChanged() {
+ adjustStatusBarLocked();
+ }
+
+ @Override
+ public void onDeviceProvisioned() {
+ mContext.sendBroadcast(mUserPresentIntent);
+ }
+
+ @Override
+ public void onSimStateChanged(IccCardConstants.State simState) {
+ if (DEBUG) Log.d(TAG, "onSimStateChanged: " + simState);
+
+ switch (simState) {
+ case NOT_READY:
+ case ABSENT:
+ // only force lock screen in case of missing sim if user hasn't
+ // gone through setup wizard
+ synchronized (this) {
+ if (!mUpdateMonitor.isDeviceProvisioned()) {
+ if (!isShowing()) {
+ if (DEBUG) Log.d(TAG, "ICC_ABSENT isn't showing,"
+ + " we need to show the keyguard since the "
+ + "device isn't provisioned yet.");
+ doKeyguardLocked();
+ } else {
+ resetStateLocked();
+ }
+ }
+ }
+ break;
+ case PIN_REQUIRED:
+ case PUK_REQUIRED:
+ synchronized (this) {
+ if (!isShowing()) {
+ if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
+ + "showing; need to show keyguard so user can enter sim pin");
+ doKeyguardLocked();
+ } else {
+ resetStateLocked();
+ }
+ }
+ break;
+ case PERM_DISABLED:
+ synchronized (this) {
+ if (!isShowing()) {
+ if (DEBUG) Log.d(TAG, "PERM_DISABLED and "
+ + "keygaurd isn't showing.");
+ doKeyguardLocked();
+ } else {
+ if (DEBUG) Log.d(TAG, "PERM_DISABLED, resetStateLocked to"
+ + "show permanently disabled message in lockscreen.");
+ resetStateLocked();
+ }
+ }
+ break;
+ case READY:
+ synchronized (this) {
+ if (isShowing()) {
+ resetStateLocked();
+ }
+ }
+ break;
+ }
+ }
+
+ };
+
+ ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
+ public void pokeWakelock() {
+ KeyguardViewMediator.this.pokeWakelock();
+ }
+
+ public void pokeWakelock(long holdMs) {
+ KeyguardViewMediator.this.pokeWakelock(holdMs);
+ }
+
+ public void keyguardDone(boolean authenticated) {
+ KeyguardViewMediator.this.keyguardDone(authenticated, true);
+ }
+
+ public void keyguardDoneDrawing() {
+ mHandler.sendEmptyMessage(KEYGUARD_DONE_DRAWING);
+ }
+
+ @Override
+ public void setNeedsInput(boolean needsInput) {
+ mKeyguardViewManager.setNeedsInput(needsInput);
+ }
+ };
+
+ public void pokeWakelock() {
+ pokeWakelock(AWAKE_INTERVAL_DEFAULT_MS);
+ }
+
+ public void pokeWakelock(long holdMs) {
+ synchronized (this) {
+ if (DBG_WAKE) Log.d(TAG, "pokeWakelock(" + holdMs + ")");
+ mWakeLock.acquire();
+ mHandler.removeMessages(TIMEOUT);
+ mWakelockSequence++;
+ Message msg = mHandler.obtainMessage(TIMEOUT, mWakelockSequence, 0);
+ mHandler.sendMessageDelayed(msg, holdMs);
+ }
+ }
+
+ /**
+ * Construct a KeyguardViewMediator
+ * @param context
+ * @param lockPatternUtils optional mock interface for LockPatternUtils
+ */
+ public KeyguardViewMediator(Context context, LockPatternUtils lockPatternUtils) {
+ mContext = context;
+ mPM = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = mPM.newWakeLock(
+ PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "keyguard");
+ mWakeLock.setReferenceCounted(false);
+ mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
+ mShowKeyguardWakeLock.setReferenceCounted(false);
+
+ mWakeAndHandOff = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "keyguardWakeAndHandOff");
+ mWakeAndHandOff.setReferenceCounted(false);
+
+ mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION));
+
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+
+ mLockPatternUtils = lockPatternUtils != null
+ ? lockPatternUtils : new LockPatternUtils(mContext);
+
+ WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+
+ mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback,
+ mLockPatternUtils);
+
+ mUserPresentIntent = new Intent(Intent.ACTION_USER_PRESENT);
+ mUserPresentIntent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
+ final ContentResolver cr = mContext.getContentResolver();
+ mShowLockIcon = (Settings.System.getInt(cr, "show_status_bar_lock", 0) == 1);
+
+ mScreenOn = mPM.isScreenOn();
+
+ mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0);
+ String soundPath = Settings.System.getString(cr, Settings.System.LOCK_SOUND);
+ if (soundPath != null) {
+ mLockSoundId = mLockSounds.load(soundPath, 1);
+ }
+ if (soundPath == null || mLockSoundId == 0) {
+ if (DEBUG) Log.d(TAG, "failed to load sound from " + soundPath);
+ }
+ soundPath = Settings.System.getString(cr, Settings.System.UNLOCK_SOUND);
+ if (soundPath != null) {
+ mUnlockSoundId = mLockSounds.load(soundPath, 1);
+ }
+ if (soundPath == null || mUnlockSoundId == 0) {
+ if (DEBUG) Log.d(TAG, "failed to load sound from " + soundPath);
+ }
+ int lockSoundDefaultAttenuation = context.getResources().getInteger(
+ com.android.internal.R.integer.config_lockSoundVolumeDb);
+ mLockSoundVolume = (float)Math.pow(10, (float)lockSoundDefaultAttenuation/20);
+ }
+
+ /**
+ * Let us know that the system is ready after startup.
+ */
+ public void onSystemReady() {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "onSystemReady");
+ mSystemReady = true;
+ mUpdateMonitor.registerCallback(mUpdateCallback);
+ doKeyguardLocked();
+ }
+ }
+
+ /**
+ * Called to let us know the screen was turned off.
+ * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER},
+ * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT} or
+ * {@link WindowManagerPolicy#OFF_BECAUSE_OF_PROX_SENSOR}.
+ */
+ public void onScreenTurnedOff(int why) {
+ synchronized (this) {
+ mScreenOn = false;
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOff(" + why + ")");
+
+ // Lock immediately based on setting if secure (user has a pin/pattern/password).
+ // This also "locks" the device when not secure to provide easy access to the
+ // camera while preventing unwanted input.
+ final boolean lockImmediately =
+ mLockPatternUtils.getPowerButtonInstantlyLocks() || !mLockPatternUtils.isSecure();
+
+ if (mExitSecureCallback != null) {
+ if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
+ mExitSecureCallback.onKeyguardExitResult(false);
+ mExitSecureCallback = null;
+ if (!mExternallyEnabled) {
+ hideLocked();
+ }
+ } else if (mShowing) {
+ notifyScreenOffLocked();
+ resetStateLocked();
+ } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT
+ || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
+ // if the screen turned off because of timeout or the user hit the power button
+ // and we don't need to lock immediately, set an alarm
+ // to enable it a little bit later (i.e, give the user a chance
+ // to turn the screen back on within a certain window without
+ // having to unlock the screen)
+ final ContentResolver cr = mContext.getContentResolver();
+
+ // From DisplaySettings
+ long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT,
+ KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT);
+
+ // From SecuritySettings
+ final long lockAfterTimeout = Settings.Secure.getInt(cr,
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ KEYGUARD_LOCK_AFTER_DELAY_DEFAULT);
+
+ // From DevicePolicyAdmin
+ final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
+ .getMaximumTimeToLock(null);
+
+ long timeout;
+ if (policyTimeout > 0) {
+ // policy in effect. Make sure we don't go beyond policy limit.
+ displayTimeout = Math.max(displayTimeout, 0); // ignore negative values
+ timeout = Math.min(policyTimeout - displayTimeout, lockAfterTimeout);
+ } else {
+ timeout = lockAfterTimeout;
+ }
+
+ if (timeout <= 0) {
+ // Lock now
+ mSuppressNextLockSound = true;
+ doKeyguardLocked();
+ } else {
+ // Lock in the future
+ long when = SystemClock.elapsedRealtime() + timeout;
+ Intent intent = new Intent(DELAYED_KEYGUARD_ACTION);
+ intent.putExtra("seq", mDelayedShowingSequence);
+ PendingIntent sender = PendingIntent.getBroadcast(mContext,
+ 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender);
+ if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = "
+ + mDelayedShowingSequence);
+ }
+ } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) {
+ // Do not enable the keyguard if the prox sensor forced the screen off.
+ } else {
+ doKeyguardLocked();
+ }
+ }
+ }
+
+ /**
+ * Let's us know the screen was turned on.
+ */
+ public void onScreenTurnedOn(KeyguardViewManager.ShowListener showListener) {
+ synchronized (this) {
+ mScreenOn = true;
+ mDelayedShowingSequence++;
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence);
+ if (showListener != null) {
+ notifyScreenOnLocked(showListener);
+ }
+ }
+ }
+
+ /**
+ * Same semantics as {@link WindowManagerPolicy#enableKeyguard}; provide
+ * a way for external stuff to override normal keyguard behavior. For instance
+ * the phone app disables the keyguard when it receives incoming calls.
+ */
+ public void setKeyguardEnabled(boolean enabled) {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")");
+
+ mExternallyEnabled = enabled;
+
+ if (!enabled && mShowing) {
+ if (mExitSecureCallback != null) {
+ if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
+ // we're in the process of handling a request to verify the user
+ // can get past the keyguard. ignore extraneous requests to disable / reenable
+ return;
+ }
+
+ // hiding keyguard that is showing, remember to reshow later
+ if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
+ + "disabling status bar expansion");
+ mNeedToReshowWhenReenabled = true;
+ hideLocked();
+ } else if (enabled && mNeedToReshowWhenReenabled) {
+ // reenabled after previously hidden, reshow
+ if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
+ + "status bar expansion");
+ mNeedToReshowWhenReenabled = false;
+
+ if (mExitSecureCallback != null) {
+ if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting");
+ mExitSecureCallback.onKeyguardExitResult(false);
+ mExitSecureCallback = null;
+ resetStateLocked();
+ } else {
+ showLocked();
+
+ // block until we know the keygaurd is done drawing (and post a message
+ // to unblock us after a timeout so we don't risk blocking too long
+ // and causing an ANR).
+ mWaitingUntilKeyguardVisible = true;
+ mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
+ if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
+ while (mWaitingUntilKeyguardVisible) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
+ }
+ }
+ }
+ }
+
+ /**
+ * @see android.app.KeyguardManager#exitKeyguardSecurely
+ */
+ public void verifyUnlock(WindowManagerPolicy.OnKeyguardExitResult callback) {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "verifyUnlock");
+ if (!mUpdateMonitor.isDeviceProvisioned()) {
+ // don't allow this api when the device isn't provisioned
+ if (DEBUG) Log.d(TAG, "ignoring because device isn't provisioned");
+ callback.onKeyguardExitResult(false);
+ } else if (mExternallyEnabled) {
+ // this only applies when the user has externally disabled the
+ // keyguard. this is unexpected and means the user is not
+ // using the api properly.
+ Log.w(TAG, "verifyUnlock called when not externally disabled");
+ callback.onKeyguardExitResult(false);
+ } else if (mExitSecureCallback != null) {
+ // already in progress with someone else
+ callback.onKeyguardExitResult(false);
+ } else {
+ mExitSecureCallback = callback;
+ verifyUnlockLocked();
+ }
+ }
+ }
+
+ /**
+ * Is the keyguard currently showing?
+ */
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ /**
+ * Is the keyguard currently showing and not being force hidden?
+ */
+ public boolean isShowingAndNotHidden() {
+ return mShowing && !mHidden;
+ }
+
+ /**
+ * Notify us when the keyguard is hidden by another window
+ */
+ public void setHidden(boolean isHidden) {
+ if (DEBUG) Log.d(TAG, "setHidden " + isHidden);
+ mHandler.removeMessages(SET_HIDDEN);
+ Message msg = mHandler.obtainMessage(SET_HIDDEN, (isHidden ? 1 : 0), 0);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Handles SET_HIDDEN message sent by setHidden()
+ */
+ private void handleSetHidden(boolean isHidden) {
+ synchronized (KeyguardViewMediator.this) {
+ if (mHidden != isHidden) {
+ mHidden = isHidden;
+ updateActivityLockScreenState();
+ adjustUserActivityLocked();
+ adjustStatusBarLocked();
+ }
+ }
+ }
+
+ /**
+ * Used by PhoneWindowManager to enable the keyguard due to a user activity timeout.
+ * This must be safe to call from any thread and with any window manager locks held.
+ */
+ public void doKeyguardTimeout() {
+ mHandler.removeMessages(KEYGUARD_TIMEOUT);
+ Message msg = mHandler.obtainMessage(KEYGUARD_TIMEOUT);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Given the state of the keyguard, is the input restricted?
+ * Input is restricted when the keyguard is showing, or when the keyguard
+ * was suppressed by an app that disabled the keyguard or we haven't been provisioned yet.
+ */
+ public boolean isInputRestricted() {
+ return mShowing || mNeedToReshowWhenReenabled || !mUpdateMonitor.isDeviceProvisioned();
+ }
+
+ /**
+ * Enable the keyguard if the settings are appropriate. Return true if all
+ * work that will happen is done; returns false if the caller can wait for
+ * the keyguard to be shown.
+ */
+ private void doKeyguardLocked() {
+ // if another app is disabling us, don't show
+ if (!mExternallyEnabled) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
+
+ // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
+ // for an occasional ugly flicker in this situation:
+ // 1) receive a call with the screen on (no keyguard) or make a call
+ // 2) screen times out
+ // 3) user hits key to turn screen back on
+ // instead, we reenable the keyguard when we know the screen is off and the call
+ // ends (see the broadcast receiver below)
+ // TODO: clean this up when we have better support at the window manager level
+ // for apps that wish to be on top of the keyguard
+ return;
+ }
+
+ // if the keyguard is already showing, don't bother
+ if (mKeyguardViewManager.isShowing()) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
+ return;
+ }
+
+ // if the setup wizard hasn't run yet, don't show
+ final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim",
+ false);
+ final boolean provisioned = mUpdateMonitor.isDeviceProvisioned();
+ final IccCardConstants.State state = mUpdateMonitor.getSimState();
+ final boolean lockedOrMissing = state.isPinLocked()
+ || ((state == IccCardConstants.State.ABSENT
+ || state == IccCardConstants.State.PERM_DISABLED)
+ && requireSim);
+
+ if (!lockedOrMissing && !provisioned) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
+ + " and the sim is not locked or missing");
+ return;
+ }
+
+ if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
+ showLocked();
+ }
+
+ /**
+ * Send message to keyguard telling it to reset its state.
+ * @see #handleReset()
+ */
+ private void resetStateLocked() {
+ if (DEBUG) Log.d(TAG, "resetStateLocked");
+ Message msg = mHandler.obtainMessage(RESET);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Send message to keyguard telling it to verify unlock
+ * @see #handleVerifyUnlock()
+ */
+ private void verifyUnlockLocked() {
+ if (DEBUG) Log.d(TAG, "verifyUnlockLocked");
+ mHandler.sendEmptyMessage(VERIFY_UNLOCK);
+ }
+
+
+ /**
+ * Send a message to keyguard telling it the screen just turned on.
+ * @see #onScreenTurnedOff(int)
+ * @see #handleNotifyScreenOff
+ */
+ private void notifyScreenOffLocked() {
+ if (DEBUG) Log.d(TAG, "notifyScreenOffLocked");
+ mHandler.sendEmptyMessage(NOTIFY_SCREEN_OFF);
+ }
+
+ /**
+ * Send a message to keyguard telling it the screen just turned on.
+ * @see #onScreenTurnedOn()
+ * @see #handleNotifyScreenOn
+ */
+ private void notifyScreenOnLocked(KeyguardViewManager.ShowListener showListener) {
+ if (DEBUG) Log.d(TAG, "notifyScreenOnLocked");
+ Message msg = mHandler.obtainMessage(NOTIFY_SCREEN_ON, showListener);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Send message to keyguard telling it about a wake key so it can adjust
+ * its state accordingly and then poke the wake lock when it is ready.
+ * @param keyCode The wake key.
+ * @see #handleWakeWhenReady
+ * @see #onWakeKeyWhenKeyguardShowingTq(int)
+ */
+ private void wakeWhenReadyLocked(int keyCode) {
+ if (DBG_WAKE) Log.d(TAG, "wakeWhenReadyLocked(" + keyCode + ")");
+
+ /**
+ * acquire the handoff lock that will keep the cpu running. this will
+ * be released once the keyguard has set itself up and poked the other wakelock
+ * in {@link #handleWakeWhenReady(int)}
+ */
+ mWakeAndHandOff.acquire();
+
+ Message msg = mHandler.obtainMessage(WAKE_WHEN_READY, keyCode, 0);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Send message to keyguard telling it to show itself
+ * @see #handleShow()
+ */
+ private void showLocked() {
+ if (DEBUG) Log.d(TAG, "showLocked");
+ // ensure we stay awake until we are finished displaying the keyguard
+ mShowKeyguardWakeLock.acquire();
+ Message msg = mHandler.obtainMessage(SHOW);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Send message to keyguard telling it to hide itself
+ * @see #handleHide()
+ */
+ private void hideLocked() {
+ if (DEBUG) Log.d(TAG, "hideLocked");
+ Message msg = mHandler.obtainMessage(HIDE);
+ mHandler.sendMessage(msg);
+ }
+
+ public boolean isSecure() {
+ return mLockPatternUtils.isSecure()
+ || KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure();
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DELAYED_KEYGUARD_ACTION.equals(intent.getAction())) {
+ final int sequence = intent.getIntExtra("seq", 0);
+ if (DEBUG) Log.d(TAG, "received DELAYED_KEYGUARD_ACTION with seq = "
+ + sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence);
+ synchronized (KeyguardViewMediator.this) {
+ if (mDelayedShowingSequence == sequence) {
+ // Don't play lockscreen SFX if the screen went off due to timeout.
+ mSuppressNextLockSound = true;
+ doKeyguardLocked();
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * When a key is received when the screen is off and the keyguard is showing,
+ * we need to decide whether to actually turn on the screen, and if so, tell
+ * the keyguard to prepare itself and poke the wake lock when it is ready.
+ *
+ * The 'Tq' suffix is per the documentation in {@link WindowManagerPolicy}.
+ * Be sure not to take any action that takes a long time; any significant
+ * action should be posted to a handler.
+ *
+ * @param keyCode The keycode of the key that woke the device
+ * @param isDocked True if the device is in the dock
+ * @return Whether we poked the wake lock (and turned the screen on)
+ */
+ public boolean onWakeKeyWhenKeyguardShowingTq(int keyCode, boolean isDocked) {
+ if (DEBUG) Log.d(TAG, "onWakeKeyWhenKeyguardShowing(" + keyCode + ")");
+
+ if (isWakeKeyWhenKeyguardShowing(keyCode, isDocked)) {
+ // give the keyguard view manager a chance to adjust the state of the
+ // keyguard based on the key that woke the device before poking
+ // the wake lock
+ wakeWhenReadyLocked(keyCode);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * When the keyguard is showing 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 isWakeKeyWhenKeyguardShowing(int keyCode, boolean isDocked) {
+ switch (keyCode) {
+ // ignore volume keys unless docked
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ return isDocked;
+
+ // 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_CAMERA:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * When a wake motion such as an external mouse movement is received when the screen
+ * is off and the keyguard is showing, we need to decide whether to actually turn
+ * on the screen, and if so, tell the keyguard to prepare itself and poke the wake
+ * lock when it is ready.
+ *
+ * The 'Tq' suffix is per the documentation in {@link WindowManagerPolicy}.
+ * Be sure not to take any action that takes a long time; any significant
+ * action should be posted to a handler.
+ *
+ * @return Whether we poked the wake lock (and turned the screen on)
+ */
+ public boolean onWakeMotionWhenKeyguardShowingTq() {
+ if (DEBUG) Log.d(TAG, "onWakeMotionWhenKeyguardShowing()");
+
+ // give the keyguard view manager a chance to adjust the state of the
+ // keyguard based on the key that woke the device before poking
+ // the wake lock
+ wakeWhenReadyLocked(KeyEvent.KEYCODE_UNKNOWN);
+ return true;
+ }
+
+ public void keyguardDone(boolean authenticated, boolean wakeup) {
+ synchronized (this) {
+ EventLog.writeEvent(70000, 2);
+ if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated + ")");
+ Message msg = mHandler.obtainMessage(KEYGUARD_DONE);
+ msg.arg1 = wakeup ? 1 : 0;
+ mHandler.sendMessage(msg);
+
+ if (authenticated) {
+ mUpdateMonitor.clearFailedAttempts();
+ }
+
+ if (mExitSecureCallback != null) {
+ mExitSecureCallback.onKeyguardExitResult(authenticated);
+ mExitSecureCallback = null;
+
+ if (authenticated) {
+ // after succesfully exiting securely, no need to reshow
+ // the keyguard when they've released the lock
+ mExternallyEnabled = true;
+ mNeedToReshowWhenReenabled = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * This handler will be associated with the policy thread, which will also
+ * be the UI thread of the keyguard. Since the apis of the policy, and therefore
+ * this class, can be called by other threads, any action that directly
+ * interacts with the keyguard ui should be posted to this handler, rather
+ * than called directly.
+ */
+ private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case TIMEOUT:
+ handleTimeout(msg.arg1);
+ return ;
+ case SHOW:
+ handleShow();
+ return ;
+ case HIDE:
+ handleHide();
+ return ;
+ case RESET:
+ handleReset();
+ return ;
+ case VERIFY_UNLOCK:
+ handleVerifyUnlock();
+ return;
+ case NOTIFY_SCREEN_OFF:
+ handleNotifyScreenOff();
+ return;
+ case NOTIFY_SCREEN_ON:
+ handleNotifyScreenOn((KeyguardViewManager.ShowListener)msg.obj);
+ return;
+ case WAKE_WHEN_READY:
+ handleWakeWhenReady(msg.arg1);
+ return;
+ case KEYGUARD_DONE:
+ handleKeyguardDone(msg.arg1 != 0);
+ return;
+ case KEYGUARD_DONE_DRAWING:
+ handleKeyguardDoneDrawing();
+ return;
+ case KEYGUARD_DONE_AUTHENTICATING:
+ keyguardDone(true, true);
+ return;
+ case SET_HIDDEN:
+ handleSetHidden(msg.arg1 != 0);
+ break;
+ case KEYGUARD_TIMEOUT:
+ synchronized (KeyguardViewMediator.this) {
+ doKeyguardLocked();
+ }
+ break;
+ }
+ }
+ };
+
+ /**
+ * @see #keyguardDone
+ * @see #KEYGUARD_DONE
+ */
+ private void handleKeyguardDone(boolean wakeup) {
+ if (DEBUG) Log.d(TAG, "handleKeyguardDone");
+ handleHide();
+ if (wakeup) {
+ mPM.wakeUp(SystemClock.uptimeMillis());
+ }
+ mWakeLock.release();
+
+ if (!(mContext instanceof Activity)) {
+ final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser());
+ mContext.sendBroadcastAsUser(mUserPresentIntent, currentUser);
+ }
+ }
+
+ /**
+ * @see #keyguardDoneDrawing
+ * @see #KEYGUARD_DONE_DRAWING
+ */
+ private void handleKeyguardDoneDrawing() {
+ synchronized(this) {
+ if (false) Log.d(TAG, "handleKeyguardDoneDrawing");
+ if (mWaitingUntilKeyguardVisible) {
+ if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing: notifying mWaitingUntilKeyguardVisible");
+ mWaitingUntilKeyguardVisible = false;
+ notifyAll();
+
+ // there will usually be two of these sent, one as a timeout, and one
+ // as a result of the callback, so remove any remaining messages from
+ // the queue
+ mHandler.removeMessages(KEYGUARD_DONE_DRAWING);
+ }
+ }
+ }
+
+ /**
+ * Handles the message sent by {@link #pokeWakelock}
+ * @param seq used to determine if anything has changed since the message
+ * was sent.
+ * @see #TIMEOUT
+ */
+ private void handleTimeout(int seq) {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleTimeout");
+ if (seq == mWakelockSequence) {
+ mWakeLock.release();
+ }
+ }
+ }
+
+ private void playSounds(boolean locked) {
+ // User feedback for keyguard.
+
+ if (mSuppressNextLockSound) {
+ mSuppressNextLockSound = false;
+ return;
+ }
+
+ final ContentResolver cr = mContext.getContentResolver();
+ if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) {
+ final int whichSound = locked
+ ? mLockSoundId
+ : mUnlockSoundId;
+ mLockSounds.stop(mLockSoundStreamId);
+ // Init mAudioManager
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ if (mAudioManager == null) return;
+ mMasterStreamType = mAudioManager.getMasterStreamType();
+ }
+ // If the stream is muted, don't play the sound
+ if (mAudioManager.isStreamMute(mMasterStreamType)) return;
+
+ mLockSoundStreamId = mLockSounds.play(whichSound,
+ mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+ }
+ }
+
+ private void updateActivityLockScreenState() {
+ try {
+ ActivityManagerNative.getDefault().setLockScreenShown(
+ mShowing && !mHidden);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #showLocked}.
+ * @see #SHOW
+ */
+ private void handleShow() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleShow");
+ if (!mSystemReady) return;
+
+ mKeyguardViewManager.show();
+ mShowing = true;
+ updateActivityLockScreenState();
+ adjustUserActivityLocked();
+ adjustStatusBarLocked();
+ try {
+ ActivityManagerNative.getDefault().closeSystemDialogs("lock");
+ } catch (RemoteException e) {
+ }
+
+ // Do this at the end to not slow down display of the keyguard.
+ playSounds(true);
+
+ mShowKeyguardWakeLock.release();
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #hideLocked()}
+ * @see #HIDE
+ */
+ private void handleHide() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleHide");
+ if (mWakeAndHandOff.isHeld()) {
+ Log.w(TAG, "attempt to hide the keyguard while waking, ignored");
+ return;
+ }
+
+ // only play "unlock" noises if not on a call (since the incall UI
+ // disables the keyguard)
+ if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
+ playSounds(false);
+ }
+
+ mKeyguardViewManager.hide();
+ mShowing = false;
+ updateActivityLockScreenState();
+ adjustUserActivityLocked();
+ adjustStatusBarLocked();
+ }
+ }
+
+ private void adjustUserActivityLocked() {
+ // disable user activity if we are shown and not hidden
+ if (DEBUG) Log.d(TAG, "adjustUserActivityLocked mShowing: " + mShowing + " mHidden: " + mHidden);
+ boolean enabled = !mShowing || mHidden;
+ // FIXME: Replace this with a new timeout control mechanism.
+ //mRealPowerManager.enableUserActivity(enabled);
+ if (!enabled && mScreenOn) {
+ // reinstate our short screen timeout policy
+ pokeWakelock();
+ }
+ }
+
+ private void adjustStatusBarLocked() {
+ if (mStatusBarManager == null) {
+ mStatusBarManager = (StatusBarManager)
+ mContext.getSystemService(Context.STATUS_BAR_SERVICE);
+ }
+ if (mStatusBarManager == null) {
+ Log.w(TAG, "Could not get status bar manager");
+ } else {
+ if (mShowLockIcon) {
+ // Give feedback to user when secure keyguard is active and engaged
+ if (mShowing && isSecure()) {
+ if (!mShowingLockIcon) {
+ String contentDescription = mContext.getString(
+ com.android.internal.R.string.status_bar_device_locked);
+ mStatusBarManager.setIcon("secure",
+ com.android.internal.R.drawable.stat_sys_secure, 0,
+ contentDescription);
+ mShowingLockIcon = true;
+ }
+ } else {
+ if (mShowingLockIcon) {
+ mStatusBarManager.removeIcon("secure");
+ mShowingLockIcon = false;
+ }
+ }
+ }
+
+ // Disable aspects of the system/status/navigation bars that must not be re-enabled by
+ // windows that appear on top, ever
+ int flags = StatusBarManager.DISABLE_NONE;
+ if (mShowing) {
+ // disable navigation status bar components (home, recents) if lock screen is up
+ flags |= StatusBarManager.DISABLE_RECENT;
+ if (isSecure() || !ENABLE_INSECURE_STATUS_BAR_EXPAND) {
+ // showing secure lockscreen; disable expanding.
+ flags |= StatusBarManager.DISABLE_EXPAND;
+ }
+ if (isSecure()) {
+ // showing secure lockscreen; disable ticker.
+ flags |= StatusBarManager.DISABLE_NOTIFICATION_TICKER;
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mHidden=" + mHidden
+ + " isSecure=" + isSecure() + " --> flags=0x" + Integer.toHexString(flags));
+ }
+
+ mStatusBarManager.disable(flags);
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #wakeWhenReadyLocked(int)}
+ * @param keyCode The key that woke the device.
+ * @see #WAKE_WHEN_READY
+ */
+ private void handleWakeWhenReady(int keyCode) {
+ synchronized (KeyguardViewMediator.this) {
+ if (DBG_WAKE) Log.d(TAG, "handleWakeWhenReady(" + keyCode + ")");
+
+ // this should result in a call to 'poke wakelock' which will set a timeout
+ // on releasing the wakelock
+ if (!mKeyguardViewManager.wakeWhenReadyTq(keyCode)) {
+ // poke wakelock ourselves if keyguard is no longer active
+ Log.w(TAG, "mKeyguardViewManager.wakeWhenReadyTq did not poke wake lock, so poke it ourselves");
+ pokeWakelock();
+ }
+
+ /**
+ * Now that the keyguard is ready and has poked the wake lock, we can
+ * release the handoff wakelock
+ */
+ mWakeAndHandOff.release();
+
+ if (!mWakeLock.isHeld()) {
+ Log.w(TAG, "mWakeLock not held in mKeyguardViewManager.wakeWhenReadyTq");
+ }
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #resetStateLocked()}
+ * @see #RESET
+ */
+ private void handleReset() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleReset");
+ mKeyguardViewManager.reset();
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #verifyUnlock}
+ * @see #RESET
+ */
+ private void handleVerifyUnlock() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
+ mKeyguardViewManager.verifyUnlock();
+ mShowing = true;
+ updateActivityLockScreenState();
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #notifyScreenOffLocked()}
+ * @see #NOTIFY_SCREEN_OFF
+ */
+ private void handleNotifyScreenOff() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleNotifyScreenOff");
+ mKeyguardViewManager.onScreenTurnedOff();
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #notifyScreenOnLocked()}
+ * @see #NOTIFY_SCREEN_ON
+ */
+ private void handleNotifyScreenOn(KeyguardViewManager.ShowListener showListener) {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleNotifyScreenOn");
+ mKeyguardViewManager.onScreenTurnedOn(showListener);
+ }
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
new file mode 100644
index 0000000..d778129
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+public class KeyguardWidgetFrame extends FrameLayout {
+ private final static PorterDuffXfermode sAddBlendMode =
+ new PorterDuffXfermode(PorterDuff.Mode.ADD);
+ private static int sWidgetPagePadding;
+ private static Drawable sLeftOverscrollDrawable;
+ private static Drawable sRightOverscrollDrawable;
+
+ private Drawable mForegroundDrawable;
+ private final Rect mForegroundRect = new Rect();
+ private int mForegroundAlpha = 0;
+
+ public KeyguardWidgetFrame(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardWidgetFrame(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardWidgetFrame(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (sLeftOverscrollDrawable == null) {
+ Resources res = context.getResources();
+ sLeftOverscrollDrawable = res.getDrawable(
+ com.android.internal.R.drawable.kg_widget_overscroll_layer_left);
+ sRightOverscrollDrawable = res.getDrawable(
+ com.android.internal.R.drawable.kg_widget_overscroll_layer_right);
+ sWidgetPagePadding =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.kg_widget_page_padding);
+ }
+ setPadding(sWidgetPagePadding, sWidgetPagePadding, sWidgetPagePadding, sWidgetPagePadding);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (mForegroundAlpha > 0) {
+ mForegroundDrawable.setBounds(mForegroundRect);
+ Paint p = ((NinePatchDrawable) mForegroundDrawable).getPaint();
+ p.setXfermode(sAddBlendMode);
+ mForegroundDrawable.draw(canvas);
+ p.setXfermode(null);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mForegroundRect.set(sWidgetPagePadding, sWidgetPagePadding,
+ w - sWidgetPagePadding, h - sWidgetPagePadding);
+ }
+
+ void setOverScrollAmount(float r, boolean left) {
+ if (left && mForegroundDrawable != sLeftOverscrollDrawable) {
+ mForegroundDrawable = sLeftOverscrollDrawable;
+ } else if (!left && mForegroundDrawable != sRightOverscrollDrawable) {
+ mForegroundDrawable = sRightOverscrollDrawable;
+ }
+
+ mForegroundAlpha = (int) Math.round((r * 255));
+ mForegroundDrawable.setAlpha(mForegroundAlpha);
+ invalidate();
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java
new file mode 100644
index 0000000..7d077e2
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard;
+
+import android.animation.TimeInterpolator;
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+import android.widget.FrameLayout;
+
+public class KeyguardWidgetPager extends PagedView {
+ ZInterpolator mZInterpolator = new ZInterpolator(0.5f);
+ private static float CAMERA_DISTANCE = 10000;
+ private static float TRANSITION_SCALE_FACTOR = 0.74f;
+ private static float TRANSITION_PIVOT = 0.65f;
+ private static float TRANSITION_MAX_ROTATION = 30;
+ private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
+ private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
+ private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
+
+ public KeyguardWidgetPager(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardWidgetPager(Context context) {
+ this(null, null, 0);
+ }
+
+ public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /*
+ * We wrap widgets in a special frame which handles drawing the overscroll foreground.
+ */
+ public void addWidget(AppWidgetHostView widget) {
+ KeyguardWidgetFrame frame = new KeyguardWidgetFrame(getContext());
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ lp.gravity = Gravity.CENTER;
+ // The framework adds a default padding to AppWidgetHostView. We don't need this padding
+ // for the Keyguard, so we override it to be 0.
+ widget.setPadding(0, 0, 0, 0);
+ frame.addView(widget, lp);
+ addView(frame);
+ }
+
+ /*
+ * This interpolator emulates the rate at which the perceived scale of an object changes
+ * as its distance from a camera increases. When this interpolator is applied to a scale
+ * animation on a view, it evokes the sense that the object is shrinking due to moving away
+ * from the camera.
+ */
+ static class ZInterpolator implements TimeInterpolator {
+ private float focalLength;
+
+ public ZInterpolator(float foc) {
+ focalLength = foc;
+ }
+
+ public float getInterpolation(float input) {
+ return (1.0f - focalLength / (focalLength + input)) /
+ (1.0f - focalLength / (focalLength + 1.0f));
+ }
+ }
+
+ @Override
+ protected void overScroll(float amount) {
+ acceleratedOverScroll(amount);
+ }
+
+ // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
+ @Override
+ protected void screenScrolled(int screenCenter) {
+ super.screenScrolled(screenCenter);
+
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getPageAt(i);
+ if (v != null) {
+ float scrollProgress = getScrollProgress(screenCenter, v, i);
+
+ float interpolatedProgress =
+ mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0)));
+ float scale = (1 - interpolatedProgress) +
+ interpolatedProgress * TRANSITION_SCALE_FACTOR;
+ float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
+
+ float alpha;
+
+ if (scrollProgress < 0) {
+ alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
+ 1 - Math.abs(scrollProgress)) : 1.0f;
+ } else {
+ // On large screens we need to fade the page as it nears its leftmost position
+ alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
+ }
+
+ v.setCameraDistance(mDensity * CAMERA_DISTANCE);
+ int pageWidth = v.getMeasuredWidth();
+ int pageHeight = v.getMeasuredHeight();
+
+ if (PERFORM_OVERSCROLL_ROTATION) {
+ if (i == 0 && scrollProgress < 0) {
+ // Overscroll to the left
+ v.setPivotX(TRANSITION_PIVOT * pageWidth);
+ v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+ if (v instanceof KeyguardWidgetFrame) {
+ ((KeyguardWidgetFrame) v).setOverScrollAmount(Math.abs(scrollProgress),
+ true);
+ }
+ scale = 1.0f;
+ alpha = 1.0f;
+ // On the first page, we don't want the page to have any lateral motion
+ translationX = 0;
+ } else if (i == getChildCount() - 1 && scrollProgress > 0) {
+ // Overscroll to the right
+ v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
+ v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+ scale = 1.0f;
+ alpha = 1.0f;
+ if (v instanceof KeyguardWidgetFrame) {
+ ((KeyguardWidgetFrame) v).setOverScrollAmount(Math.abs(scrollProgress),
+ false);
+ }
+ // On the last page, we don't want the page to have any lateral motion.
+ translationX = 0;
+ } else {
+ v.setPivotY(pageHeight / 2.0f);
+ v.setPivotX(pageWidth / 2.0f);
+ v.setRotationY(0f);
+ }
+ }
+
+ v.setTranslationX(translationX);
+ v.setScaleX(scale);
+ v.setScaleY(scale);
+ v.setAlpha(alpha);
+
+ // If the view has 0 alpha, we set it to be invisible so as to prevent
+ // it from accepting touches
+ if (alpha == 0) {
+ v.setVisibility(INVISIBLE);
+ } else if (v.getVisibility() != VISIBLE) {
+ v.setVisibility(VISIBLE);
+ }
+ }
+ }
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
new file mode 100644
index 0000000..1b46efa
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
@@ -0,0 +1,1704 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * An abstraction of the original Workspace which supports browsing through a
+ * sequential list of "pages"
+ */
+public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
+ private static final String TAG = "WidgetPagedView";
+ private static final boolean DEBUG = false;
+ protected static final int INVALID_PAGE = -1;
+
+ // the min drag distance for a fling to register, to prevent random page shifts
+ private static final int MIN_LENGTH_FOR_FLING = 25;
+
+ protected static final int PAGE_SNAP_ANIMATION_DURATION = 550;
+ protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
+ protected static final float NANOTIME_DIV = 1000000000.0f;
+
+ private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
+ private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
+
+ private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
+ // The page is moved more than halfway, automatically move to the next page on touch up.
+ private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
+
+ // The following constants need to be scaled based on density. The scaled versions will be
+ // assigned to the corresponding member variables below.
+ private static final int FLING_THRESHOLD_VELOCITY = 500;
+ private static final int MIN_SNAP_VELOCITY = 1500;
+ private static final int MIN_FLING_VELOCITY = 250;
+
+ static final int AUTOMATIC_PAGE_SPACING = -1;
+
+ protected int mFlingThresholdVelocity;
+ protected int mMinFlingVelocity;
+ protected int mMinSnapVelocity;
+
+ protected float mDensity;
+ protected float mSmoothingTime;
+ protected float mTouchX;
+
+ protected boolean mFirstLayout = true;
+
+ protected int mCurrentPage;
+ protected int mNextPage = INVALID_PAGE;
+ protected int mMaxScrollX;
+ protected Scroller mScroller;
+ private VelocityTracker mVelocityTracker;
+
+ private float mDownMotionX;
+ protected float mLastMotionX;
+ protected float mLastMotionXRemainder;
+ protected float mLastMotionY;
+ protected float mTotalMotionX;
+ private int mLastScreenCenter = -1;
+ private int[] mChildOffsets;
+ private int[] mChildRelativeOffsets;
+ private int[] mChildOffsetsWithLayoutScale;
+
+ protected final static int TOUCH_STATE_REST = 0;
+ protected final static int TOUCH_STATE_SCROLLING = 1;
+ protected final static int TOUCH_STATE_PREV_PAGE = 2;
+ protected final static int TOUCH_STATE_NEXT_PAGE = 3;
+ protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
+
+ protected int mTouchState = TOUCH_STATE_REST;
+ protected boolean mForceScreenScrolled = false;
+
+ protected OnLongClickListener mLongClickListener;
+
+ protected boolean mAllowLongPress = true;
+
+ protected int mTouchSlop;
+ private int mPagingTouchSlop;
+ private int mMaximumVelocity;
+ private int mMinimumWidth;
+ protected int mPageSpacing;
+ protected int mPageLayoutPaddingTop;
+ protected int mPageLayoutPaddingBottom;
+ protected int mPageLayoutPaddingLeft;
+ protected int mPageLayoutPaddingRight;
+ protected int mPageLayoutWidthGap;
+ protected int mPageLayoutHeightGap;
+ protected int mCellCountX = 0;
+ protected int mCellCountY = 0;
+ protected boolean mCenterPagesVertically;
+ protected boolean mAllowOverScroll = true;
+ protected int mUnboundedScrollX;
+ protected int[] mTempVisiblePagesRange = new int[2];
+ protected boolean mForceDrawAllChildrenNextFrame;
+
+ // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
+ // it is equal to the scaled overscroll position. We use a separate value so as to prevent
+ // the screens from continuing to translate beyond the normal bounds.
+ protected int mOverScrollX;
+
+ // parameter that adjusts the layout to be optimized for pages with that scale factor
+ protected float mLayoutScale = 1.0f;
+
+ protected static final int INVALID_POINTER = -1;
+
+ protected int mActivePointerId = INVALID_POINTER;
+
+ private PageSwitchListener mPageSwitchListener;
+
+ protected ArrayList<Boolean> mDirtyPageContent;
+
+ // If true, syncPages and syncPageItems will be called to refresh pages
+ protected boolean mContentIsRefreshable = true;
+
+ // If true, modify alpha of neighboring pages as user scrolls left/right
+ protected boolean mFadeInAdjacentScreens = true;
+
+ // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
+ // to switch to a new page
+ protected boolean mUsePagingTouchSlop = true;
+
+ // If true, the subclass should directly update scrollX itself in its computeScroll method
+ // (SmoothPagedView does this)
+ protected boolean mDeferScrollUpdate = false;
+
+ protected boolean mIsPageMoving = false;
+
+ // All syncs and layout passes are deferred until data is ready.
+ protected boolean mIsDataReady = true;
+
+ // Scrolling indicator
+ private ValueAnimator mScrollIndicatorAnimator;
+ private View mScrollIndicator;
+ private int mScrollIndicatorPaddingLeft;
+ private int mScrollIndicatorPaddingRight;
+ private boolean mShouldShowScrollIndicator = false;
+ private boolean mShouldShowScrollIndicatorImmediately = false;
+ protected static final int sScrollIndicatorFadeInDuration = 150;
+ protected static final int sScrollIndicatorFadeOutDuration = 650;
+ protected static final int sScrollIndicatorFlashDuration = 650;
+
+ public interface PageSwitchListener {
+ void onPageSwitch(View newPage, int newPageIndex);
+ }
+
+ public PagedView(Context context) {
+ this(context, null);
+ }
+
+ public PagedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.PagedView, defStyle, 0);
+ setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
+ mPageLayoutPaddingTop = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingTop, 0);
+ mPageLayoutPaddingBottom = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingBottom, 0);
+ mPageLayoutPaddingLeft = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingLeft, 0);
+ mPageLayoutPaddingRight = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingRight, 0);
+ mPageLayoutWidthGap = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutWidthGap, 0);
+ mPageLayoutHeightGap = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutHeightGap, 0);
+ mScrollIndicatorPaddingLeft =
+ a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0);
+ mScrollIndicatorPaddingRight =
+ a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
+ a.recycle();
+
+ setHapticFeedbackEnabled(false);
+ init();
+ }
+
+ /**
+ * Initializes various states for this workspace.
+ */
+ protected void init() {
+ mDirtyPageContent = new ArrayList<Boolean>();
+ mDirtyPageContent.ensureCapacity(32);
+ mScroller = new Scroller(getContext(), new ScrollInterpolator());
+ mCurrentPage = 0;
+ mCenterPagesVertically = true;
+
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mDensity = getResources().getDisplayMetrics().density;
+
+ mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
+ mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
+ mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
+ setOnHierarchyChangeListener(this);
+ }
+
+ public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
+ mPageSwitchListener = pageSwitchListener;
+ if (mPageSwitchListener != null) {
+ mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+ }
+ }
+
+ /**
+ * Called by subclasses to mark that data is ready, and that we can begin loading and laying
+ * out pages.
+ */
+ protected void setDataIsReady() {
+ mIsDataReady = true;
+ }
+
+ protected boolean isDataReady() {
+ return mIsDataReady;
+ }
+
+ /**
+ * Returns the index of the currently displayed page.
+ *
+ * @return The index of the currently displayed page.
+ */
+ int getCurrentPage() {
+ return mCurrentPage;
+ }
+
+ int getNextPage() {
+ return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+ }
+
+ int getPageCount() {
+ return getChildCount();
+ }
+
+ View getPageAt(int index) {
+ return getChildAt(index);
+ }
+
+ protected int indexToPage(int index) {
+ return index;
+ }
+
+ /**
+ * Updates the scroll of the current page immediately to its final scroll position. We use this
+ * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
+ * the previous tab page.
+ */
+ protected void updateCurrentPageScroll() {
+ int offset = getChildOffset(mCurrentPage);
+ int relOffset = getRelativeChildOffset(mCurrentPage);
+ int newX = offset - relOffset;
+ scrollTo(newX, 0);
+ mScroller.setFinalX(newX);
+ mScroller.forceFinished(true);
+ }
+
+ /**
+ * Sets the current page.
+ */
+ void setCurrentPage(int currentPage) {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+ // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
+ // the default
+ if (getChildCount() == 0) {
+ return;
+ }
+
+ mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
+ updateCurrentPageScroll();
+ updateScrollingIndicator();
+ notifyPageSwitchListener();
+ invalidate();
+ }
+
+ protected void notifyPageSwitchListener() {
+ if (mPageSwitchListener != null) {
+ mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+ }
+ }
+
+ protected void pageBeginMoving() {
+ if (!mIsPageMoving) {
+ mIsPageMoving = true;
+ onPageBeginMoving();
+ }
+ }
+
+ protected void pageEndMoving() {
+ if (mIsPageMoving) {
+ mIsPageMoving = false;
+ onPageEndMoving();
+ }
+ }
+
+ protected boolean isPageMoving() {
+ return mIsPageMoving;
+ }
+
+ // a method that subclasses can override to add behavior
+ protected void onPageBeginMoving() {
+ }
+
+ // a method that subclasses can override to add behavior
+ protected void onPageEndMoving() {
+ }
+
+ /**
+ * Registers the specified listener on each page contained in this workspace.
+ *
+ * @param l The listener used to respond to long clicks.
+ */
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ mLongClickListener = l;
+ final int count = getPageCount();
+ for (int i = 0; i < count; i++) {
+ getPageAt(i).setOnLongClickListener(l);
+ }
+ }
+
+ @Override
+ public void scrollBy(int x, int y) {
+ scrollTo(mUnboundedScrollX + x, getScrollY() + y);
+ }
+
+ @Override
+ public void scrollTo(int x, int y) {
+ mUnboundedScrollX = x;
+
+ if (x < 0) {
+ super.scrollTo(0, y);
+ if (mAllowOverScroll) {
+ overScroll(x);
+ }
+ } else if (x > mMaxScrollX) {
+ super.scrollTo(mMaxScrollX, y);
+ if (mAllowOverScroll) {
+ overScroll(x - mMaxScrollX);
+ }
+ } else {
+ mOverScrollX = x;
+ super.scrollTo(x, y);
+ }
+
+ mTouchX = x;
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ }
+
+ // we moved this functionality to a helper function so SmoothPagedView can reuse it
+ protected boolean computeScrollHelper() {
+ if (mScroller.computeScrollOffset()) {
+ // Don't bother scrolling if the page does not need to be moved
+ if (getScrollX() != mScroller.getCurrX()
+ || getScrollY() != mScroller.getCurrY()
+ || mOverScrollX != mScroller.getCurrX()) {
+ scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+ }
+ invalidate();
+ return true;
+ } else if (mNextPage != INVALID_PAGE) {
+ mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
+ mNextPage = INVALID_PAGE;
+ notifyPageSwitchListener();
+
+ // We don't want to trigger a page end moving unless the page has settled
+ // and the user has stopped scrolling
+ if (mTouchState == TOUCH_STATE_REST) {
+ pageEndMoving();
+ }
+
+ // Notify the user when the page changes
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (accessibilityManager.isEnabled()) {
+ AccessibilityEvent ev =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ ev.getText().add(getCurrentPageDescription());
+ sendAccessibilityEventUnchecked(ev);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public String getCurrentPageDescription() {
+ return "";
+ }
+
+ @Override
+ public void computeScroll() {
+ computeScrollHelper();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!mIsDataReady || getChildCount() == 0) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ // Return early if we aren't given a proper dimension
+ if (widthSize <= 0 || heightSize <= 0) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
+ * of the All apps view on XLarge displays to not take up more space then it needs. Width
+ * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
+ * each page to have the same width.
+ */
+ final int verticalPadding = getPaddingTop() + getPaddingBottom();
+ final int horizontalPadding = getPaddingLeft() + getPaddingRight();
+
+ // The children are given the same width and height as the workspace
+ // unless they were set to WRAP_CONTENT
+ if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ // disallowing padding in paged view (just pass 0)
+ final View child = getPageAt(i);
+
+ int childWidthMode = MeasureSpec.EXACTLY;
+ int childHeightMode = MeasureSpec.EXACTLY;
+
+ final int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);
+ final int childHeightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+
+ // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
+ // We also wait until we set the measured dimensions before flushing the cache as well, to
+ // ensure that the cache is filled with good values.
+ invalidateCachedOffsets();
+
+ if (childCount > 0) {
+ if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", "
+ + getChildWidth(0));
+
+ // Calculate the variable page spacing if necessary
+ if (mPageSpacing == AUTOMATIC_PAGE_SPACING) {
+ // The gap between pages in the PagedView should be equal to the gap from the page
+ // to the edge of the screen (so it is not visible in the current screen). To
+ // account for unequal padding on each side of the paged view, we take the maximum
+ // of the left/right gap and use that as the gap between each page.
+ int offset = getRelativeChildOffset(0);
+ int spacing = Math.max(offset, widthSize - offset -
+ getChildAt(0).getMeasuredWidth());
+ setPageSpacing(spacing);
+ }
+ }
+
+ updateScrollingIndicatorPosition();
+
+ if (childCount > 0) {
+ mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
+ } else {
+ mMaxScrollX = 0;
+ }
+ }
+
+ public void setPageSpacing(int pageSpacing) {
+ mPageSpacing = pageSpacing;
+ invalidateCachedOffsets();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!mIsDataReady || getChildCount() == 0) {
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
+ final int verticalPadding = getPaddingTop() + getPaddingBottom();
+ final int childCount = getChildCount();
+ int childLeft = getRelativeChildOffset(0);
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = getPageAt(i);
+ if (child.getVisibility() != View.GONE) {
+ final int childWidth = getScaledMeasuredWidth(child);
+ final int childHeight = child.getMeasuredHeight();
+ int childTop = getPaddingTop();
+ if (mCenterPagesVertically) {
+ childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2;
+ }
+
+ if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
+ child.layout(childLeft, childTop,
+ childLeft + child.getMeasuredWidth(), childTop + childHeight);
+ childLeft += childWidth + mPageSpacing;
+ }
+ }
+
+ if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+ setHorizontalScrollBarEnabled(false);
+ updateCurrentPageScroll();
+ setHorizontalScrollBarEnabled(true);
+ mFirstLayout = false;
+ }
+ }
+
+ protected void screenScrolled(int screenCenter) {
+ if (isScrollingIndicatorEnabled()) {
+ updateScrollingIndicator();
+ }
+ boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
+
+ if (mFadeInAdjacentScreens && !isInOverscroll) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child != null) {
+ float scrollProgress = getScrollProgress(screenCenter, child, i);
+ float alpha = 1 - Math.abs(scrollProgress);
+ child.setAlpha(alpha);
+ }
+ }
+ invalidate();
+ }
+ }
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ // This ensures that when children are added, they get the correct transforms / alphas
+ // in accordance with any scroll effects.
+ mForceScreenScrolled = true;
+ invalidate();
+ invalidateCachedOffsets();
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ }
+
+ protected void invalidateCachedOffsets() {
+ int count = getChildCount();
+ if (count == 0) {
+ mChildOffsets = null;
+ mChildRelativeOffsets = null;
+ mChildOffsetsWithLayoutScale = null;
+ return;
+ }
+
+ mChildOffsets = new int[count];
+ mChildRelativeOffsets = new int[count];
+ mChildOffsetsWithLayoutScale = new int[count];
+ for (int i = 0; i < count; i++) {
+ mChildOffsets[i] = -1;
+ mChildRelativeOffsets[i] = -1;
+ mChildOffsetsWithLayoutScale[i] = -1;
+ }
+ }
+
+ protected int getChildOffset(int index) {
+ int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ?
+ mChildOffsets : mChildOffsetsWithLayoutScale;
+
+ if (childOffsets != null && childOffsets[index] != -1) {
+ return childOffsets[index];
+ } else {
+ if (getChildCount() == 0)
+ return 0;
+
+ int offset = getRelativeChildOffset(0);
+ for (int i = 0; i < index; ++i) {
+ offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing;
+ }
+ if (childOffsets != null) {
+ childOffsets[index] = offset;
+ }
+ return offset;
+ }
+ }
+
+ protected int getRelativeChildOffset(int index) {
+ if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) {
+ return mChildRelativeOffsets[index];
+ } else {
+ final int padding = getPaddingLeft() + getPaddingRight();
+ final int offset = getPaddingLeft() +
+ (getMeasuredWidth() - padding - getChildWidth(index)) / 2;
+ if (mChildRelativeOffsets != null) {
+ mChildRelativeOffsets[index] = offset;
+ }
+ return offset;
+ }
+ }
+
+ protected int getScaledMeasuredWidth(View child) {
+ // This functions are called enough times that it actually makes a difference in the
+ // profiler -- so just inline the max() here
+ final int measuredWidth = child.getMeasuredWidth();
+ final int minWidth = mMinimumWidth;
+ final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth;
+ return (int) (maxWidth * mLayoutScale + 0.5f);
+ }
+
+ protected void getVisiblePages(int[] range) {
+ final int pageCount = getChildCount();
+
+ if (pageCount > 0) {
+ final int screenWidth = getMeasuredWidth();
+ int leftScreen = 0;
+ int rightScreen = 0;
+ View currPage = getPageAt(leftScreen);
+ while (leftScreen < pageCount - 1 &&
+ currPage.getX() + currPage.getWidth() -
+ currPage.getPaddingRight() < getScrollX()) {
+ leftScreen++;
+ currPage = getPageAt(leftScreen);
+ }
+ rightScreen = leftScreen;
+ currPage = getPageAt(rightScreen + 1);
+ while (rightScreen < pageCount - 1 &&
+ currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) {
+ rightScreen++;
+ currPage = getPageAt(rightScreen + 1);
+ }
+ range[0] = leftScreen;
+ range[1] = rightScreen;
+ } else {
+ range[0] = -1;
+ range[1] = -1;
+ }
+ }
+
+ protected boolean shouldDrawChild(View child) {
+ return child.getAlpha() > 0;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ int halfScreenSize = getMeasuredWidth() / 2;
+ // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
+ // Otherwise it is equal to the scaled overscroll position.
+ int screenCenter = mOverScrollX + halfScreenSize;
+
+ if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
+ // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
+ // set it for the next frame
+ mForceScreenScrolled = false;
+ screenScrolled(screenCenter);
+ mLastScreenCenter = screenCenter;
+ }
+
+ // Find out which screens are visible; as an optimization we only call draw on them
+ final int pageCount = getChildCount();
+ if (pageCount > 0) {
+ getVisiblePages(mTempVisiblePagesRange);
+ final int leftScreen = mTempVisiblePagesRange[0];
+ final int rightScreen = mTempVisiblePagesRange[1];
+ if (leftScreen != -1 && rightScreen != -1) {
+ final long drawingTime = getDrawingTime();
+ // Clip to the bounds
+ canvas.save();
+ canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
+ getScrollY() + getBottom() - getTop());
+
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final View v = getPageAt(i);
+ if (mForceDrawAllChildrenNextFrame ||
+ (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
+ drawChild(canvas, v, drawingTime);
+ }
+ }
+ mForceDrawAllChildrenNextFrame = false;
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+ int page = indexToPage(indexOfChild(child));
+ if (page != mCurrentPage || !mScroller.isFinished()) {
+ snapToPage(page);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ int focusablePage;
+ if (mNextPage != INVALID_PAGE) {
+ focusablePage = mNextPage;
+ } else {
+ focusablePage = mCurrentPage;
+ }
+ View v = getPageAt(focusablePage);
+ if (v != null) {
+ return v.requestFocus(direction, previouslyFocusedRect);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ if (direction == View.FOCUS_LEFT) {
+ if (getCurrentPage() > 0) {
+ snapToPage(getCurrentPage() - 1);
+ return true;
+ }
+ } else if (direction == View.FOCUS_RIGHT) {
+ if (getCurrentPage() < getPageCount() - 1) {
+ snapToPage(getCurrentPage() + 1);
+ return true;
+ }
+ }
+ return super.dispatchUnhandledMove(focused, direction);
+ }
+
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
+ getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
+ }
+ if (direction == View.FOCUS_LEFT) {
+ if (mCurrentPage > 0) {
+ getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
+ }
+ } else if (direction == View.FOCUS_RIGHT){
+ if (mCurrentPage < getPageCount() - 1) {
+ getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
+ }
+ }
+ }
+
+ /**
+ * If one of our descendant views decides that it could be focused now, only
+ * pass that along if it's on the current page.
+ *
+ * This happens when live folders requery, and if they're off page, they
+ * end up calling requestFocus, which pulls it on page.
+ */
+ @Override
+ public void focusableViewAvailable(View focused) {
+ View current = getPageAt(mCurrentPage);
+ View v = focused;
+ while (true) {
+ if (v == current) {
+ super.focusableViewAvailable(focused);
+ return;
+ }
+ if (v == this) {
+ return;
+ }
+ ViewParent parent = v.getParent();
+ if (parent instanceof View) {
+ v = (View)v.getParent();
+ } else {
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (disallowIntercept) {
+ // We need to make sure to cancel our long press if
+ // a scrollable widget takes over touch events
+ final View currentPage = getPageAt(mCurrentPage);
+ currentPage.cancelLongPress();
+ }
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+
+ /**
+ * Return true if a tap at (x, y) should trigger a flip to the previous page.
+ */
+ protected boolean hitsPreviousPage(float x, float y) {
+ return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing);
+ }
+
+ /**
+ * Return true if a tap at (x, y) should trigger a flip to the next page.
+ */
+ protected boolean hitsNextPage(float x, float y) {
+ return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing));
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onTouchEvent will be called and we do the actual
+ * scrolling there.
+ */
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ // Skip touch handling if there are no pages to swipe
+ if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
+
+ /*
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) &&
+ (mTouchState == TOUCH_STATE_SCROLLING)) {
+ return true;
+ }
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
+ /*
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+ if (mActivePointerId != INVALID_POINTER) {
+ determineScrollingStart(ev);
+ break;
+ }
+ // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
+ // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
+ // i.e. fall through to the next case (don't break)
+ // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
+ // while it's small- this was causing a crash before we checked for INVALID_POINTER)
+ }
+
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ // Remember location of down touch
+ mDownMotionX = x;
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mLastMotionXRemainder = 0;
+ mTotalMotionX = 0;
+ mActivePointerId = ev.getPointerId(0);
+ mAllowLongPress = true;
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
+ final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
+ if (finishedScrolling) {
+ mTouchState = TOUCH_STATE_REST;
+ mScroller.abortAnimation();
+ } else {
+ mTouchState = TOUCH_STATE_SCROLLING;
+ }
+
+ // check if this can be the beginning of a tap on the side of the pages
+ // to scroll the current page
+ if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
+ if (getChildCount() > 0) {
+ if (hitsPreviousPage(x, y)) {
+ mTouchState = TOUCH_STATE_PREV_PAGE;
+ } else if (hitsNextPage(x, y)) {
+ mTouchState = TOUCH_STATE_NEXT_PAGE;
+ }
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mTouchState = TOUCH_STATE_REST;
+ mAllowLongPress = false;
+ mActivePointerId = INVALID_POINTER;
+ releaseVelocityTracker();
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ releaseVelocityTracker();
+ break;
+ }
+
+ /*
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
+ return mTouchState != TOUCH_STATE_REST;
+ }
+
+ protected void determineScrollingStart(MotionEvent ev) {
+ determineScrollingStart(ev, 1.0f);
+ }
+
+ /*
+ * Determines if we should change the touch state to start scrolling after the
+ * user moves their touch point too far.
+ */
+ protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
+ /*
+ * Locally do absolute value. mLastMotionX is set to the y value
+ * of the down event.
+ */
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ return;
+ }
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+ final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
+ boolean xPaged = xDiff > mPagingTouchSlop;
+ boolean xMoved = xDiff > touchSlop;
+ boolean yMoved = yDiff > touchSlop;
+
+ if (xMoved || xPaged || yMoved) {
+ if (mUsePagingTouchSlop ? xPaged : xMoved) {
+ // Scroll if the user moved far enough along the X axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ mTotalMotionX += Math.abs(mLastMotionX - x);
+ mLastMotionX = x;
+ mLastMotionXRemainder = 0;
+ mTouchX = getScrollX();
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ pageBeginMoving();
+ }
+ // Either way, cancel any pending longpress
+ cancelCurrentPageLongPress();
+ }
+ }
+
+ protected void cancelCurrentPageLongPress() {
+ if (mAllowLongPress) {
+ mAllowLongPress = false;
+ // Try canceling the long press. It could also have been scheduled
+ // by a distant descendant, so use the mAllowLongPress flag to block
+ // everything
+ final View currentPage = getPageAt(mCurrentPage);
+ if (currentPage != null) {
+ currentPage.cancelLongPress();
+ }
+ }
+ }
+
+ protected float getScrollProgress(int screenCenter, View v, int page) {
+ final int halfScreenSize = getMeasuredWidth() / 2;
+
+ int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
+ int delta = screenCenter - (getChildOffset(page) -
+ getRelativeChildOffset(page) + halfScreenSize);
+
+ float scrollProgress = delta / (totalDistance * 1.0f);
+ scrollProgress = Math.min(scrollProgress, 1.0f);
+ scrollProgress = Math.max(scrollProgress, -1.0f);
+ return scrollProgress;
+ }
+
+ // This curve determines how the effect of scrolling over the limits of the page dimishes
+ // as the user pulls further and further from the bounds
+ private float overScrollInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
+
+ protected void acceleratedOverScroll(float amount) {
+ int screenSize = getMeasuredWidth();
+
+ // We want to reach the max over scroll effect when the user has
+ // over scrolled half the size of the screen
+ float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
+
+ if (f == 0) return;
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ int overScrollAmount = (int) Math.round(f * screenSize);
+ if (amount < 0) {
+ mOverScrollX = overScrollAmount;
+ super.scrollTo(0, getScrollY());
+ } else {
+ mOverScrollX = mMaxScrollX + overScrollAmount;
+ super.scrollTo(mMaxScrollX, getScrollY());
+ }
+ invalidate();
+ }
+
+ protected void dampedOverScroll(float amount) {
+ int screenSize = getMeasuredWidth();
+
+ float f = (amount / screenSize);
+
+ if (f == 0) return;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
+ if (amount < 0) {
+ mOverScrollX = overScrollAmount;
+ super.scrollTo(0, getScrollY());
+ } else {
+ mOverScrollX = mMaxScrollX + overScrollAmount;
+ super.scrollTo(mMaxScrollX, getScrollY());
+ }
+ invalidate();
+ }
+
+ protected void overScroll(float amount) {
+ dampedOverScroll(amount);
+ }
+
+ protected float maxOverScroll() {
+ // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
+ // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
+ float f = 1.0f;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+ return OVERSCROLL_DAMP_FACTOR * f;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ // Skip touch handling if there are no pages to swipe
+ if (getChildCount() <= 0) return super.onTouchEvent(ev);
+
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ final int action = ev.getAction();
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ // Remember where the motion event started
+ mDownMotionX = mLastMotionX = ev.getX();
+ mLastMotionXRemainder = 0;
+ mTotalMotionX = 0;
+ mActivePointerId = ev.getPointerId(0);
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ pageBeginMoving();
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ // Scroll to follow the motion event
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(pointerIndex);
+ final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
+
+ mTotalMotionX += Math.abs(deltaX);
+
+ // Only scroll and update mLastMotionX if we have moved some discrete amount. We
+ // keep the remainder because we are actually testing if we've moved from the last
+ // scrolled position (which is discrete).
+ if (Math.abs(deltaX) >= 1.0f) {
+ mTouchX += deltaX;
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ if (!mDeferScrollUpdate) {
+ scrollBy((int) deltaX, 0);
+ if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
+ } else {
+ invalidate();
+ }
+ mLastMotionX = x;
+ mLastMotionXRemainder = deltaX - (int) deltaX;
+ } else {
+ awakenScrollBars();
+ }
+ } else {
+ determineScrollingStart(ev);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ final int activePointerId = mActivePointerId;
+ final int pointerIndex = ev.findPointerIndex(activePointerId);
+ final float x = ev.getX(pointerIndex);
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
+ final int deltaX = (int) (x - mDownMotionX);
+ final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));
+ boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
+ SIGNIFICANT_MOVE_THRESHOLD;
+
+ mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
+
+ boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
+ Math.abs(velocityX) > mFlingThresholdVelocity;
+
+ // In the case that the page is moved far to one direction and then is flung
+ // in the opposite direction, we use a threshold to determine whether we should
+ // just return to the starting page, or if we should skip one further.
+ boolean returnToOriginalPage = false;
+ if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
+ Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
+ returnToOriginalPage = true;
+ }
+
+ int finalPage;
+ // We give flings precedence over large moves, which is why we short-circuit our
+ // test for a large move if a fling has been registered. That is, a large
+ // move to the left and fling to the right will register as a fling to the right.
+ if (((isSignificantMove && deltaX > 0 && !isFling) ||
+ (isFling && velocityX > 0)) && mCurrentPage > 0) {
+ finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
+ snapToPageWithVelocity(finalPage, velocityX);
+ } else if (((isSignificantMove && deltaX < 0 && !isFling) ||
+ (isFling && velocityX < 0)) &&
+ mCurrentPage < getChildCount() - 1) {
+ finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
+ snapToPageWithVelocity(finalPage, velocityX);
+ } else {
+ snapToDestination();
+ }
+ } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
+ // at this point we have not moved beyond the touch slop
+ // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+ // we can just page
+ int nextPage = Math.max(0, mCurrentPage - 1);
+ if (nextPage != mCurrentPage) {
+ snapToPage(nextPage);
+ } else {
+ snapToDestination();
+ }
+ } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
+ // at this point we have not moved beyond the touch slop
+ // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+ // we can just page
+ int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
+ if (nextPage != mCurrentPage) {
+ snapToPage(nextPage);
+ } else {
+ snapToDestination();
+ }
+ } else {
+ onUnhandledTap(ev);
+ }
+ mTouchState = TOUCH_STATE_REST;
+ mActivePointerId = INVALID_POINTER;
+ releaseVelocityTracker();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ snapToDestination();
+ }
+ mTouchState = TOUCH_STATE_REST;
+ mActivePointerId = INVALID_POINTER;
+ releaseVelocityTracker();
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_SCROLL: {
+ // Handle mouse (or ext. device) by shifting the page depending on the scroll
+ final float vscroll;
+ final float hscroll;
+ if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
+ vscroll = 0;
+ hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ } else {
+ vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
+ }
+ if (hscroll != 0 || vscroll != 0) {
+ if (hscroll > 0 || vscroll > 0) {
+ scrollRight();
+ } else {
+ scrollLeft();
+ }
+ return true;
+ }
+ }
+ }
+ }
+ return super.onGenericMotionEvent(event);
+ }
+
+ private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ }
+
+ private void releaseVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+ MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ // TODO: Make this decision more intelligent.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
+ mLastMotionY = ev.getY(newPointerIndex);
+ mLastMotionXRemainder = 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ protected void onUnhandledTap(MotionEvent ev) {}
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ int page = indexToPage(indexOfChild(child));
+ if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
+ snapToPage(page);
+ }
+ }
+
+ protected int getChildIndexForRelativeOffset(int relativeOffset) {
+ final int childCount = getChildCount();
+ int left;
+ int right;
+ for (int i = 0; i < childCount; ++i) {
+ left = getRelativeChildOffset(i);
+ right = (left + getScaledMeasuredWidth(getPageAt(i)));
+ if (left <= relativeOffset && relativeOffset <= right) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ protected int getChildWidth(int index) {
+ // This functions are called enough times that it actually makes a difference in the
+ // profiler -- so just inline the max() here
+ final int measuredWidth = getPageAt(index).getMeasuredWidth();
+ final int minWidth = mMinimumWidth;
+ return (minWidth > measuredWidth) ? minWidth : measuredWidth;
+ }
+
+ int getPageNearestToCenterOfScreen() {
+ int minDistanceFromScreenCenter = Integer.MAX_VALUE;
+ int minDistanceFromScreenCenterIndex = -1;
+ int screenCenter = getScrollX() + (getMeasuredWidth() / 2);
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ View layout = (View) getPageAt(i);
+ int childWidth = getScaledMeasuredWidth(layout);
+ int halfChildWidth = (childWidth / 2);
+ int childCenter = getChildOffset(i) + halfChildWidth;
+ int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
+ if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
+ minDistanceFromScreenCenter = distanceFromScreenCenter;
+ minDistanceFromScreenCenterIndex = i;
+ }
+ }
+ return minDistanceFromScreenCenterIndex;
+ }
+
+ protected void snapToDestination() {
+ snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ private static class ScrollInterpolator implements Interpolator {
+ public ScrollInterpolator() {
+ }
+
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t*t*t*t*t + 1;
+ }
+ }
+
+ // We want the duration of the page snap animation to be influenced by the distance that
+ // the screen has to travel, however, we don't want this duration to be effected in a
+ // purely linear fashion. Instead, we use this method to moderate the effect that the distance
+ // of travel has on the overall snap duration.
+ float distanceInfluenceForSnapDuration(float f) {
+ f -= 0.5f; // center the values about 0.
+ f *= 0.3f * Math.PI / 2.0f;
+ return (float) Math.sin(f);
+ }
+
+ protected void snapToPageWithVelocity(int whichPage, int velocity) {
+ whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
+ int halfScreenSize = getMeasuredWidth() / 2;
+
+ if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
+ if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
+ + getMeasuredWidth() + ", " + getChildWidth(whichPage));
+ final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
+ int delta = newX - mUnboundedScrollX;
+ int duration = 0;
+
+ if (Math.abs(velocity) < mMinFlingVelocity) {
+ // If the velocity is low enough, then treat this more as an automatic page advance
+ // as opposed to an apparent physical response to flinging
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+ return;
+ }
+
+ // Here we compute a "distance" that will be used in the computation of the overall
+ // snap duration. This is a function of the actual distance that needs to be traveled;
+ // we keep this value close to half screen size in order to reduce the variance in snap
+ // duration as a function of the distance the page needs to travel.
+ float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
+ float distance = halfScreenSize + halfScreenSize *
+ distanceInfluenceForSnapDuration(distanceRatio);
+
+ velocity = Math.abs(velocity);
+ velocity = Math.max(mMinSnapVelocity, velocity);
+
+ // we want the page's snap velocity to approximately match the velocity at which the
+ // user flings, so we scale the duration by a value near to the derivative of the scroll
+ // interpolator at zero, ie. 5. We use 4 to make it a little slower.
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+
+ snapToPage(whichPage, delta, duration);
+ }
+
+ protected void snapToPage(int whichPage) {
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ protected void snapToPage(int whichPage, int duration) {
+ whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
+
+ if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
+ if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", "
+ + getChildWidth(whichPage));
+ int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
+ final int sX = mUnboundedScrollX;
+ final int delta = newX - sX;
+ snapToPage(whichPage, delta, duration);
+ }
+
+ protected void snapToPage(int whichPage, int delta, int duration) {
+ mNextPage = whichPage;
+
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null && whichPage != mCurrentPage &&
+ focusedChild == getPageAt(mCurrentPage)) {
+ focusedChild.clearFocus();
+ }
+
+ pageBeginMoving();
+ awakenScrollBars(duration);
+ if (duration == 0) {
+ duration = Math.abs(delta);
+ }
+
+ if (!mScroller.isFinished()) mScroller.abortAnimation();
+ mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
+
+ notifyPageSwitchListener();
+ invalidate();
+ }
+
+ public void scrollLeft() {
+ if (mScroller.isFinished()) {
+ if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
+ } else {
+ if (mNextPage > 0) snapToPage(mNextPage - 1);
+ }
+ }
+
+ public void scrollRight() {
+ if (mScroller.isFinished()) {
+ if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
+ } else {
+ if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
+ }
+ }
+
+ public int getPageForView(View v) {
+ int result = -1;
+ if (v != null) {
+ ViewParent vp = v.getParent();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ if (vp == getPageAt(i)) {
+ return i;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return True is long presses are still allowed for the current touch
+ */
+ public boolean allowLongPress() {
+ return mAllowLongPress;
+ }
+
+ /**
+ * Set true to allow long-press events to be triggered, usually checked by
+ * {@link Launcher} to accept or block dpad-initiated long-presses.
+ */
+ public void setAllowLongPress(boolean allowLongPress) {
+ mAllowLongPress = allowLongPress;
+ }
+
+ public static class SavedState extends BaseSavedState {
+ int currentPage = -1;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ currentPage = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(currentPage);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ protected View getScrollingIndicator() {
+ return null;
+ }
+
+ protected boolean isScrollingIndicatorEnabled() {
+ return false;
+ }
+
+ Runnable hideScrollingIndicatorRunnable = new Runnable() {
+ @Override
+ public void run() {
+ hideScrollingIndicator(false);
+ }
+ };
+
+ protected void flashScrollingIndicator(boolean animated) {
+ removeCallbacks(hideScrollingIndicatorRunnable);
+ showScrollingIndicator(!animated);
+ postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration);
+ }
+
+ protected void showScrollingIndicator(boolean immediately) {
+ mShouldShowScrollIndicator = true;
+ mShouldShowScrollIndicatorImmediately = true;
+ if (getChildCount() <= 1) return;
+ if (!isScrollingIndicatorEnabled()) return;
+
+ mShouldShowScrollIndicator = false;
+ getScrollingIndicator();
+ if (mScrollIndicator != null) {
+ // Fade the indicator in
+ updateScrollingIndicatorPosition();
+ mScrollIndicator.setVisibility(View.VISIBLE);
+ cancelScrollingIndicatorAnimations();
+ if (immediately) {
+ mScrollIndicator.setAlpha(1f);
+ } else {
+ mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f);
+ mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration);
+ mScrollIndicatorAnimator.start();
+ }
+ }
+ }
+
+ protected void cancelScrollingIndicatorAnimations() {
+ if (mScrollIndicatorAnimator != null) {
+ mScrollIndicatorAnimator.cancel();
+ }
+ }
+
+ protected void hideScrollingIndicator(boolean immediately) {
+ if (getChildCount() <= 1) return;
+ if (!isScrollingIndicatorEnabled()) return;
+
+ getScrollingIndicator();
+ if (mScrollIndicator != null) {
+ // Fade the indicator out
+ updateScrollingIndicatorPosition();
+ cancelScrollingIndicatorAnimations();
+ if (immediately) {
+ mScrollIndicator.setVisibility(View.INVISIBLE);
+ mScrollIndicator.setAlpha(0f);
+ } else {
+ mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f);
+ mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration);
+ mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
+ private boolean cancelled = false;
+ @Override
+ public void onAnimationCancel(android.animation.Animator animation) {
+ cancelled = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!cancelled) {
+ mScrollIndicator.setVisibility(View.INVISIBLE);
+ }
+ }
+ });
+ mScrollIndicatorAnimator.start();
+ }
+ }
+ }
+
+ /**
+ * To be overridden by subclasses to determine whether the scroll indicator should stretch to
+ * fill its space on the track or not.
+ */
+ protected boolean hasElasticScrollIndicator() {
+ return true;
+ }
+
+ private void updateScrollingIndicator() {
+ if (getChildCount() <= 1) return;
+ if (!isScrollingIndicatorEnabled()) return;
+
+ getScrollingIndicator();
+ if (mScrollIndicator != null) {
+ updateScrollingIndicatorPosition();
+ }
+ if (mShouldShowScrollIndicator) {
+ showScrollingIndicator(mShouldShowScrollIndicatorImmediately);
+ }
+ }
+
+ private void updateScrollingIndicatorPosition() {
+ if (!isScrollingIndicatorEnabled()) return;
+ if (mScrollIndicator == null) return;
+ int numPages = getChildCount();
+ int pageWidth = getMeasuredWidth();
+ int lastChildIndex = Math.max(0, getChildCount() - 1);
+ int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex);
+ int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight;
+ int indicatorWidth = mScrollIndicator.getMeasuredWidth() -
+ mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight();
+
+ float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX));
+ int indicatorSpace = trackWidth / numPages;
+ int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft;
+ if (hasElasticScrollIndicator()) {
+ if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) {
+ mScrollIndicator.getLayoutParams().width = indicatorSpace;
+ mScrollIndicator.requestLayout();
+ }
+ } else {
+ int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2;
+ indicatorPos += indicatorCenterOffset;
+ }
+ mScrollIndicator.setTranslationX(indicatorPos);
+ }
+
+ /* Accessibility */
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setScrollable(getPageCount() > 1);
+ if (getCurrentPage() < getPageCount() - 1) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ }
+ if (getCurrentPage() > 0) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setScrollable(true);
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+ event.setFromIndex(mCurrentPage);
+ event.setToIndex(mCurrentPage);
+ event.setItemCount(getChildCount());
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (super.performAccessibilityAction(action, arguments)) {
+ return true;
+ }
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+ if (getCurrentPage() < getPageCount() - 1) {
+ scrollRight();
+ return true;
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+ if (getCurrentPage() > 0) {
+ scrollLeft();
+ return true;
+ }
+ } break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onHoverEvent(android.view.MotionEvent event) {
+ return true;
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java
index a4baeed..d6a31b8 100644
--- a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
import com.android.internal.widget.LockPatternUtils;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java
new file mode 100644
index 0000000..c38525e
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard_obsolete;
+
+import android.view.View;
+
+interface BiometricSensorUnlock {
+ /**
+ * Initializes the view provided for the biometric unlock UI to work within. The provided area
+ * completely covers the backup unlock mechanism.
+ * @param biometricUnlockView View provided for the biometric unlock UI.
+ */
+ public void initializeView(View biometricUnlockView);
+
+ /**
+ * Indicates whether the biometric unlock is running. Before
+ * {@link BiometricSensorUnlock#start} is called, isRunning() returns false. After a successful
+ * call to {@link BiometricSensorUnlock#start}, isRunning() returns true until the biometric
+ * unlock completes, {@link BiometricSensorUnlock#stop} has been called, or an error has
+ * forced the biometric unlock to stop.
+ * @return whether the biometric unlock is currently running.
+ */
+ public boolean isRunning();
+
+ /**
+ * Covers the backup unlock mechanism by showing the contents of the view initialized in
+ * {@link BiometricSensorUnlock#initializeView(View)}. The view should disappear after the
+ * specified timeout. If the timeout is 0, the interface shows until another event, such as
+ * calling {@link BiometricSensorUnlock#hide()}, causes it to disappear. Called on the UI
+ * thread.
+ * @param timeoutMilliseconds Amount of time in milliseconds to display the view before
+ * disappearing. A value of 0 means the view should remain visible.
+ */
+ public void show(long timeoutMilliseconds);
+
+ /**
+ * Uncovers the backup unlock mechanism by hiding the contents of the view initialized in
+ * {@link BiometricSensorUnlock#initializeView(View)}.
+ */
+ public void hide();
+
+ /**
+ * Binds to the biometric unlock service and starts the unlock procedure. Called on the UI
+ * thread.
+ * @return false if it can't be started or the backup should be used.
+ */
+ public boolean start();
+
+ /**
+ * Stops the biometric unlock procedure and unbinds from the service. Called on the UI thread.
+ * @return whether the biometric unlock was running when called.
+ */
+ public boolean stop();
+
+ /**
+ * Cleans up any resources used by the biometric unlock.
+ */
+ public void cleanUp();
+
+ /**
+ * Gets the Device Policy Manager quality of the biometric unlock sensor
+ * (e.g., PASSWORD_QUALITY_BIOMETRIC_WEAK).
+ * @return biometric unlock sensor quality, as defined by Device Policy Manager.
+ */
+ public int getQuality();
+}
diff --git a/policy/src/com/android/internal/policy/impl/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java
index fda3c9d..e4d9215 100644
--- a/policy/src/com/android/internal/policy/impl/FaceUnlock.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
import com.android.internal.policy.IFaceLockCallback;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java
index bbb6875..ba5b7ff 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
/**
* Common interface of each {@link android.view.View} that is a screen of
@@ -27,7 +27,7 @@ public interface KeyguardScreen {
* keyboard to be displayed.
*/
boolean needsInput();
-
+
/**
* This screen is no longer in front of the user.
*/
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java
index a843603..be505a1 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.res.Configuration;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java
index bb07a1d..409f87b 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.widget.DigitalClock;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.TransportControlView;
-import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus;
import java.util.ArrayList;
import java.util.Date;
@@ -621,8 +620,7 @@ class KeyguardStatusViewManager implements OnClickListener {
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onRefreshBatteryInfo(BatteryStatus status) {
+ public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow();
mPluggedIn = status.isPluggedIn();
mBatteryLevel = status.level;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java
index 5c0cd4f..d990f5f 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java
new file mode 100644
index 0000000..79233e8
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.policy.impl.keyguard_obsolete;
+
+import android.app.admin.DevicePolicyManager;
+import android.media.AudioManager;
+
+import com.android.internal.telephony.IccCardConstants;
+
+/**
+ * Callback for general information relevant to lock screen.
+ */
+class KeyguardUpdateMonitorCallback {
+ /**
+ * Called when the battery status changes, e.g. when plugged in or unplugged, charge
+ * level, etc. changes.
+ *
+ * @param status current battery status
+ */
+ void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { }
+
+ /**
+ * Called once per minute or when the time changes.
+ */
+ void onTimeChanged() { }
+
+ /**
+ * Called when the carrier PLMN or SPN changes.
+ *
+ * @param plmn The operator name of the registered network. May be null if it shouldn't
+ * be displayed.
+ * @param spn The service provider name. May be null if it shouldn't be displayed.
+ */
+ void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { }
+
+ /**
+ * Called when the ringer mode changes.
+ * @param state the current ringer state, as defined in
+ * {@link AudioManager#RINGER_MODE_CHANGED_ACTION}
+ */
+ void onRingerModeChanged(int state) { }
+
+ /**
+ * Called when the phone state changes. String will be one of:
+ * {@link TelephonyManager#EXTRA_STATE_IDLE}
+ * {@link TelephonyManager@EXTRA_STATE_RINGING}
+ * {@link TelephonyManager#EXTRA_STATE_OFFHOOK
+ */
+ void onPhoneStateChanged(int phoneState) { }
+
+ /**
+ * Called when visibility of lockscreen clock changes, such as when
+ * obscured by a widget.
+ */
+ void onClockVisibilityChanged() { }
+
+ /**
+ * Called when the device becomes provisioned
+ */
+ void onDeviceProvisioned() { }
+
+ /**
+ * Called when the device policy changes.
+ * See {@link DevicePolicyManager#ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED}
+ */
+ void onDevicePolicyManagerStateChanged() { }
+
+ /**
+ * Called when the user changes.
+ */
+ void onUserSwitched(int userId) { }
+
+ /**
+ * Called when the SIM state changes.
+ * @param simState
+ */
+ void onSimStateChanged(IccCardConstants.State simState) { }
+
+ /**
+ * Called when a user is removed.
+ */
+ void onUserRemoved(int userId) { }
+}
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java
index 29a5573..f9fe797 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.Context;
import android.content.Intent;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java
index b376d65..4cc0f30 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
/**
- * The callback used by the keyguard view to tell the {@link KeyguardViewMediator}
+ * The callback used by the keyguard view to tell the {@link KeyguardViewMediator}
* various things.
*/
public interface KeyguardViewCallback {
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java
index d521c05..5dbef48 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
@@ -231,7 +231,7 @@ public class KeyguardViewManager implements KeyguardWindowController {
// Keyguard may be in the process of being shown, but not yet
// updated with the window manager... give it a chance to do so.
mKeyguardHost.post(new Runnable() {
- @Override public void run() {
+ public void run() {
if (mKeyguardHost.getVisibility() == View.VISIBLE) {
showListener.onShown(mKeyguardHost.getWindowToken());
} else {
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java
index 236a4ea..641ee88 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import com.android.internal.policy.impl.PhoneWindowManager;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.widget.LockPatternUtils;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java
index 51b7f1e..676574d 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.Context;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java
index 4ad48fb..98e3209 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
/**
* Interface passed to the keyguard view, for it to call up to control
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java
index 32aec10..0ce87b1 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
-import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockScreenWidgetCallback;
@@ -689,7 +688,7 @@ public class LockPatternKeyguardView extends KeyguardViewBase {
KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onRefreshBatteryInfo(BatteryStatus status) {
+ public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
// When someone plugs in or unplugs the device, we hide the biometric sensor area and
// suppress its startup for the next onScreenTurnedOn(). Since plugging/unplugging
// causes the screen to turn on, the biometric unlock would start if it wasn't
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java
index 5066e3c..5d9cc8e 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.widget.LockPatternUtils;
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java
index 91b5ca1..4e9a1f7 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
import com.android.internal.telephony.IccCardConstants;
diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java
index 203f9db..87a7371 100644
--- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import java.util.List;
diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java
index 9a6d2cc..6d5706b 100644
--- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.Context;
import android.content.res.Configuration;
diff --git a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java
index 3b2a473..3c1703a 100644
--- a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.app.Dialog;
import android.app.ProgressDialog;
diff --git a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java
index 80407f5..13c040c 100644
--- a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.app.Dialog;
import android.app.ProgressDialog;
diff --git a/policy/tests/src/com/android/internal/policy/impl/LockPatternKeyguardViewTest.java b/policy/tests/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewTest.java
index 862e683..97c5672 100644
--- a/policy/tests/src/com/android/internal/policy/impl/LockPatternKeyguardViewTest.java
+++ b/policy/tests/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewTest.java
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.Context;
-import com.android.internal.policy.impl.KeyguardViewCallback;
+
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardScreen;
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardUpdateMonitor;
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardViewCallback;
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardWindowController;
+import com.android.internal.policy.impl.keyguard_obsolete.LockPatternKeyguardView;
import com.android.internal.telephony.IccCardConstants;
import android.content.res.Configuration;
import android.test.AndroidTestCase;
diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp
index b15d4c8..1f3d2cf 100644
--- a/services/input/SpriteController.cpp
+++ b/services/input/SpriteController.cpp
@@ -298,7 +298,7 @@ void SpriteController::doUpdateSprites() {
}
if (becomingVisible) {
- status = update.state.surfaceControl->show(surfaceLayer);
+ status = update.state.surfaceControl->show();
if (status) {
ALOGE("Error %d showing sprite surface.", status);
} else {
@@ -369,7 +369,8 @@ sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height
ensureSurfaceComposerClient();
sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
- String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888);
+ String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eHidden);
if (surfaceControl == NULL || !surfaceControl->isValid()
|| !surfaceControl->getSurface()->isValid()) {
ALOGE("Error creating sprite surface.");
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 32ac8e1..9b7be02 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -34,6 +34,7 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.text.TextUtils;
import android.text.format.Time;
@@ -303,7 +304,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
}
}
-
+
public void removeLocked(String packageName) {
removeLocked(mRtcWakeupAlarms, packageName);
removeLocked(mRtcAlarms, packageName);
@@ -327,6 +328,29 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
}
}
+
+ public void removeUserLocked(int userHandle) {
+ removeUserLocked(mRtcWakeupAlarms, userHandle);
+ removeUserLocked(mRtcAlarms, userHandle);
+ removeUserLocked(mElapsedRealtimeWakeupAlarms, userHandle);
+ removeUserLocked(mElapsedRealtimeAlarms, userHandle);
+ }
+
+ private void removeUserLocked(ArrayList<Alarm> alarmList, int userHandle) {
+ if (alarmList.size() <= 0) {
+ return;
+ }
+
+ // iterator over the list removing any it where the intent match
+ Iterator<Alarm> it = alarmList.iterator();
+
+ while (it.hasNext()) {
+ Alarm alarm = it.next();
+ if (UserHandle.getUserId(alarm.operation.getTargetUid()) == userHandle) {
+ it.remove();
+ }
+ }
+ }
public boolean lookForPackageLocked(String packageName) {
return lookForPackageLocked(mRtcWakeupAlarms, packageName)
@@ -822,6 +846,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ sdFilter.addAction(Intent.ACTION_USER_STOPPED);
mContext.registerReceiver(this, sdFilter);
}
@@ -841,6 +866,11 @@ class AlarmManagerService extends IAlarmManager.Stub {
return;
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+ int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userHandle >= 0) {
+ removeUserLocked(userHandle);
+ }
} else {
if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
&& intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 776a1a4..8a1ac10 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -42,6 +42,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.net.CaptivePortalTracker;
import android.net.ConnectivityManager;
import android.net.DummyDataStateTracker;
import android.net.EthernetDataTracker;
@@ -166,6 +167,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
private NetworkStateTracker mNetTrackers[];
+ /* Handles captive portal check on a network */
+ private CaptivePortalTracker mCaptivePortalTracker;
+
/**
* The link properties that define the current links
*/
@@ -1363,8 +1367,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return false;
}
NetworkStateTracker tracker = mNetTrackers[networkType];
+ DetailedState netState = tracker.getNetworkInfo().getDetailedState();
- if (tracker == null || !tracker.getNetworkInfo().isConnected() ||
+ if (tracker == null || (netState != DetailedState.CONNECTED &&
+ netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
tracker.isTeardownRequested()) {
if (VDBG) {
log("requestRouteToHostAddress on down network " +
@@ -1966,32 +1972,29 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
};
+ private boolean isNewNetTypePreferredOverCurrentNetType(int type) {
+ if ((type != mNetworkPreference &&
+ mNetConfigs[mActiveDefaultNetwork].priority >
+ mNetConfigs[type].priority) ||
+ mNetworkPreference == mActiveDefaultNetwork) return false;
+ return true;
+ }
+
private void handleConnect(NetworkInfo info) {
- final int type = info.getType();
+ final int newNetType = info.getType();
- setupDataActivityTracking(type);
+ setupDataActivityTracking(newNetType);
// snapshot isFailover, because sendConnectedBroadcast() resets it
boolean isFailover = info.isFailover();
- final NetworkStateTracker thisNet = mNetTrackers[type];
+ final NetworkStateTracker thisNet = mNetTrackers[newNetType];
final String thisIface = thisNet.getLinkProperties().getInterfaceName();
// if this is a default net and other default is running
// kill the one not preferred
- if (mNetConfigs[type].isDefault()) {
- if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
- if ((type != mNetworkPreference &&
- mNetConfigs[mActiveDefaultNetwork].priority >
- mNetConfigs[type].priority) ||
- mNetworkPreference == mActiveDefaultNetwork) {
- // don't accept this one
- if (VDBG) {
- log("Not broadcasting CONNECT_ACTION " +
- "to torn down network " + info.getTypeName());
- }
- teardown(thisNet);
- return;
- } else {
+ if (mNetConfigs[newNetType].isDefault()) {
+ if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) {
+ if (isNewNetTypePreferredOverCurrentNetType(newNetType)) {
// tear down the other
NetworkStateTracker otherNet =
mNetTrackers[mActiveDefaultNetwork];
@@ -2004,6 +2007,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
teardown(thisNet);
return;
}
+ } else {
+ // don't accept this one
+ if (VDBG) {
+ log("Not broadcasting CONNECT_ACTION " +
+ "to torn down network " + info.getTypeName());
+ }
+ teardown(thisNet);
+ return;
}
}
synchronized (ConnectivityService.this) {
@@ -2017,7 +2028,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
1000);
}
}
- mActiveDefaultNetwork = type;
+ mActiveDefaultNetwork = newNetType;
// this will cause us to come up initially as unconnected and switching
// to connected after our normal pause unless somebody reports us as reall
// disconnected
@@ -2029,19 +2040,47 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
thisNet.setTeardownRequested(false);
updateNetworkSettings(thisNet);
- handleConnectivityChange(type, false);
+ handleConnectivityChange(newNetType, false);
sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
// notify battery stats service about this network
if (thisIface != null) {
try {
- BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, type);
+ BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, newNetType);
} catch (RemoteException e) {
// ignored; service lives in system_server
}
}
}
+ private void handleCaptivePortalTrackerCheck(NetworkInfo info) {
+ if (DBG) log("Captive portal check " + info);
+ int type = info.getType();
+ final NetworkStateTracker thisNet = mNetTrackers[type];
+ if (mNetConfigs[type].isDefault()) {
+ if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
+ if (isNewNetTypePreferredOverCurrentNetType(type)) {
+ if (DBG) log("Captive check on " + info.getTypeName());
+ mCaptivePortalTracker = CaptivePortalTracker.detect(mContext, info,
+ ConnectivityService.this);
+ return;
+ } else {
+ if (DBG) log("Tear down low priority net " + info.getTypeName());
+ teardown(thisNet);
+ return;
+ }
+ }
+ }
+
+ thisNet.captivePortalCheckComplete();
+ }
+
+ /** @hide */
+ public void captivePortalCheckComplete(NetworkInfo info) {
+ mNetTrackers[info.getType()].captivePortalCheckComplete();
+ mCaptivePortalTracker = null;
+ }
+
/**
* Setup data activity tracking for the given network interface.
*
@@ -2630,6 +2669,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (info.getDetailedState() ==
NetworkInfo.DetailedState.FAILED) {
handleConnectionFailure(info);
+ } else if (info.getDetailedState() ==
+ DetailedState.CAPTIVE_PORTAL_CHECK) {
+ handleCaptivePortalTrackerCheck(info);
} else if (state == NetworkInfo.State.DISCONNECTED) {
handleDisconnect(info);
} else if (state == NetworkInfo.State.SUSPENDED) {
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index d6fed39..9b61ec4 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -544,6 +544,11 @@ public class NotificationManagerService extends INotificationManager.Stub
mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(
TelephonyManager.EXTRA_STATE_OFFHOOK));
updateNotificationPulse();
+ } else if (action.equals(Intent.ACTION_USER_STOPPED)) {
+ int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userHandle >= 0) {
+ cancelAllNotificationsUser(userHandle);
+ }
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
// turn off LED when user passes through lock screen
mNotificationLight.turnOff();
@@ -619,6 +624,7 @@ public class NotificationManagerService extends INotificationManager.Stub
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
filter.addAction(Intent.ACTION_USER_PRESENT);
+ filter.addAction(Intent.ACTION_USER_STOPPED);
mContext.registerReceiver(mIntentReceiver, filter);
IntentFilter pkgFilter = new IntentFilter();
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -1249,6 +1255,29 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
+ /**
+ * Cancels all notifications from a given user.
+ */
+ boolean cancelAllNotificationsUser(int userHandle) {
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ boolean canceledSomething = false;
+ for (int i = N-1; i >= 0; --i) {
+ NotificationRecord r = mNotificationList.get(i);
+ if (UserHandle.getUserId(r.uid) != userHandle) {
+ continue;
+ }
+ canceledSomething = true;
+ mNotificationList.remove(i);
+ cancelNotificationLocked(r, false);
+ }
+ if (canceledSomething) {
+ updateLightsLocked();
+ }
+ return canceledSomething;
+ }
+ }
+
@Deprecated
public void cancelNotification(String pkg, int id) {
cancelNotificationWithTag(pkg, null /* tag */, id);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7097891..48b1215 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -28,6 +28,8 @@ import android.content.pm.IPackageManager;
import android.content.res.Configuration;
import android.media.AudioService;
import android.net.wifi.p2p.WifiP2pService;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SchedulingPolicyService;
@@ -147,7 +149,52 @@ class ServerThread extends Thread {
CommonTimeManagementService commonTimeMgmtService = null;
InputManagerService inputManager = null;
+ // Create a shared handler thread for UI within the system server.
+ // This thread is used by at least the following components:
+ // - WindowManagerPolicy
+ // - KeyguardViewManager
+ // - DisplayManagerService
+ HandlerThread uiHandlerThread = new HandlerThread("UI");
+ uiHandlerThread.start();
+ Handler uiHandler = new Handler(uiHandlerThread.getLooper());
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ //Looper.myLooper().setMessageLogging(new LogPrinter(
+ // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM));
+ android.os.Process.setThreadPriority(
+ android.os.Process.THREAD_PRIORITY_FOREGROUND);
+ android.os.Process.setCanSelfBackground(false);
+
+ // For debug builds, log event loop stalls to dropbox for analysis.
+ if (StrictMode.conditionallyEnableDebugLogging()) {
+ Slog.i(TAG, "Enabled StrictMode logging for UI Looper");
+ }
+ }
+ });
+
+ // Create a handler thread just for the window manager to enjoy.
+ HandlerThread wmHandlerThread = new HandlerThread("WindowManager");
+ wmHandlerThread.start();
+ Handler wmHandler = new Handler(wmHandlerThread.getLooper());
+ wmHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ //Looper.myLooper().setMessageLogging(new LogPrinter(
+ // android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM));
+ android.os.Process.setThreadPriority(
+ android.os.Process.THREAD_PRIORITY_DISPLAY);
+ android.os.Process.setCanSelfBackground(false);
+
+ // For debug builds, log event loop stalls to dropbox for analysis.
+ if (StrictMode.conditionallyEnableDebugLogging()) {
+ Slog.i(TAG, "Enabled StrictMode logging for UI Looper");
+ }
+ }
+ });
+
// Critical services...
+ boolean onlyCore = false;
try {
Slog.i(TAG, "Entropy Mixer");
ServiceManager.addService("entropy", new EntropyMixer());
@@ -160,7 +207,7 @@ class ServerThread extends Thread {
context = ActivityManagerService.main(factoryTest);
Slog.i(TAG, "Display Manager");
- display = new DisplayManagerService(context);
+ display = new DisplayManagerService(context, uiHandler);
ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
Slog.i(TAG, "Telephony Registry");
@@ -172,10 +219,14 @@ class ServerThread extends Thread {
AttributeCache.init(context);
+ if (!display.waitForDefaultDisplay()) {
+ reportWtf("Timeout waiting for default display to be initialized.",
+ new Throwable());
+ }
+
Slog.i(TAG, "Package Manager");
// Only run "core" apps if we're encrypting the device.
String cryptState = SystemProperties.get("vold.decrypt");
- boolean onlyCore = false;
if (ENCRYPTING_STATE.equals(cryptState)) {
Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
onlyCore = true;
@@ -244,6 +295,7 @@ class ServerThread extends Thread {
Slog.i(TAG, "Window Manager");
wm = WindowManagerService.main(context, power, display,
+ uiHandler, wmHandler,
factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
!firstBoot, onlyCore);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
@@ -753,6 +805,12 @@ class ServerThread extends Thread {
reportWtf("making Package Manager Service ready", e);
}
+ try {
+ display.systemReady(safeMode, onlyCore);
+ } catch (Throwable e) {
+ reportWtf("making Display Manager Service ready", e);
+ }
+
// These are needed to propagate to the runnable below.
final Context contextF = context;
final BatteryService batteryF = battery;
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index afd7d0e..7ed698b 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -19,6 +19,7 @@ package com.android.server;
import static android.os.FileObserver.*;
import static android.os.ParcelFileDescriptor.*;
+import android.app.AppGlobals;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
import android.app.PendingIntent;
@@ -31,6 +32,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -146,6 +148,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
final Context mContext;
final IWindowManager mIWindowManager;
+ final IPackageManager mIPackageManager;
final MyPackageMonitor mMonitor;
WallpaperData mLastWallpaper;
@@ -389,6 +392,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
mContext = context;
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
+ mIPackageManager = AppGlobals.getPackageManager();
mMonitor = new MyPackageMonitor();
mMonitor.register(context, null, true);
WALLPAPER_BASE_DIR.mkdirs();
@@ -710,8 +714,9 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
if (DEBUG) Slog.v(TAG, "Using image wallpaper");
}
}
- ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName,
- PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS);
+ int serviceUserId = wallpaper.userId;
+ ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
+ PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);
if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
String msg = "Selected service does not require "
+ android.Manifest.permission.BIND_WALLPAPER
@@ -728,8 +733,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
if (componentName != null && !componentName.equals(wallpaper.imageWallpaperComponent)) {
// Make sure the selected service is actually a wallpaper service.
- List<ResolveInfo> ris = mContext.getPackageManager()
- .queryIntentServices(intent, PackageManager.GET_META_DATA);
+ List<ResolveInfo> ris =
+ mIPackageManager.queryIntentServices(intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.GET_META_DATA, serviceUserId);
for (int i=0; i<ris.size(); i++) {
ServiceInfo rsi = ris.get(i).serviceInfo;
if (rsi.name.equals(si.name) &&
@@ -767,7 +774,6 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
intent.setComponent(componentName);
- int serviceUserId = wallpaper.userId;
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.wallpaper_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
@@ -800,8 +806,8 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
}
} catch (RemoteException e) {
}
- } catch (PackageManager.NameNotFoundException e) {
- String msg = "Unknown component " + componentName;
+ } catch (RemoteException e) {
+ String msg = "Remote exception for " + componentName + "\n" + e;
if (fromUser) {
throw new IllegalArgumentException(msg);
}
diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java
index 9edfad6..2c5038e 100644
--- a/services/java/com/android/server/Watchdog.java
+++ b/services/java/com/android/server/Watchdog.java
@@ -26,6 +26,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Build;
import android.os.Debug;
import android.os.Handler;
import android.os.Message;
@@ -39,6 +40,8 @@ import android.util.Log;
import android.util.Slog;
import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
@@ -429,11 +432,10 @@ public class Watchdog extends Thread {
}
// If we got here, that means that the system is most likely hung.
- // First collect stack traces from all threads of the system process.
- // Then kill this process so that the system will restart.
final String name = (mCurrentMonitor != null) ?
mCurrentMonitor.getClass().getName() : "null";
+ Slog.w(TAG, "WATCHDOG PROBLEM IN SYSTEM SERVER: " + name);
EventLog.writeEvent(EventLogTags.WATCHDOG, name);
ArrayList<Integer> pids = new ArrayList<Integer>();
@@ -468,11 +470,15 @@ public class Watchdog extends Thread {
dropboxThread.join(2000); // wait up to 2 seconds for it to return.
} catch (InterruptedException ignored) {}
- // Only kill the process if the debugger is not attached.
+ // Only kill/crash the process if the debugger is not attached.
if (!Debug.isDebuggerConnected()) {
Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + name);
- Process.killProcess(Process.myPid());
- System.exit(10);
+ if (!Build.TYPE.equals("user")) {
+ forceCrashDump();
+ } else {
+ Process.killProcess(Process.myPid());
+ System.exit(10);
+ }
} else {
Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
}
@@ -481,6 +487,50 @@ public class Watchdog extends Thread {
}
}
+ private void forceCrashDump() {
+ /* Sync file system to flash the data which is written just before the
+ * crash.
+ */
+ java.lang.Process p = null;
+ try {
+ p = Runtime.getRuntime().exec("sync");
+ if (p != null) {
+ // It is not necessary to check the exit code, here.
+ // 'sync' command always succeeds, and this function returns 0.
+ p.waitFor();
+ } else {
+ Slog.e(TAG, "Failed to execute 'sync' command. (no process handle)");
+ }
+ } catch (Exception e) {
+ // This code is an emergency path to crash MUT. The system already
+ // caused fatal error, and then calls this function to create a
+ // crash dump. This function must run the code below to force a
+ // crash, even if the sync command failed.
+ // Therefore, ignore all exceptions, here.
+ Slog.e(TAG, "Failed to execute 'sync' command prior to forcing crash: " + e);
+ } finally {
+ if (p != null) {
+ p.destroy();
+ }
+ }
+
+ FileWriter out = null;
+ try {
+ out = new FileWriter("/proc/sysrq-trigger");
+ out.write("c");
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write to sysrq-trigger while triggering crash: " + e);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close sysrq-trigger while triggering crash: " + e);
+ }
+ }
+ }
+ }
+
private File dumpKernelStackTraces() {
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath == null || tracesPath.length() == 0) {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index f483576..6bc5e10 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -240,7 +240,7 @@ public class WifiService extends IWifiManager.Stub {
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- Slog.d(TAG, "New client listening to asynchronous messages");
+ if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
mClients.add((AsyncChannel) msg.obj);
} else {
Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
@@ -249,9 +249,9 @@ public class WifiService extends IWifiManager.Stub {
}
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
- Slog.d(TAG, "Send failed, client connection lost");
+ if (DBG) Slog.d(TAG, "Send failed, client connection lost");
} else {
- Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
+ if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
}
mClients.remove((AsyncChannel) msg.obj);
break;
@@ -412,6 +412,7 @@ public class WifiService extends IWifiManager.Stub {
switch(mNetworkInfo.getDetailedState()) {
case CONNECTED:
case DISCONNECTED:
+ case CAPTIVE_PORTAL_CHECK:
evaluateTrafficStatsPolling();
resetNotification();
break;
@@ -606,6 +607,12 @@ public class WifiService extends IWifiManager.Stub {
"WifiService");
}
+ private void enforceConnectivityInternalPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ "ConnectivityService");
+ }
+
/**
* see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
* @param enable {@code true} to enable, {@code false} to disable.
@@ -910,7 +917,7 @@ public class WifiService extends IWifiManager.Stub {
*
*/
public void startWifi() {
- enforceChangePermission();
+ enforceConnectivityInternalPermission();
/* TODO: may be add permissions for access only to connectivity service
* TODO: if a start issued, keep wifi alive until a stop issued irrespective
* of WifiLock & device idle status unless wifi enabled status is toggled
@@ -920,20 +927,24 @@ public class WifiService extends IWifiManager.Stub {
mWifiStateMachine.reconnectCommand();
}
+ public void captivePortalCheckComplete() {
+ enforceConnectivityInternalPermission();
+ mWifiStateMachine.captivePortalCheckComplete();
+ }
+
/**
* see {@link android.net.wifi.WifiManager#stopWifi}
*
*/
public void stopWifi() {
- enforceChangePermission();
- /* TODO: may be add permissions for access only to connectivity service
+ enforceConnectivityInternalPermission();
+ /*
* TODO: if a stop is issued, wifi is brought up only by startWifi
* unless wifi enabled status is toggled
*/
mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode);
}
-
/**
* see {@link android.net.wifi.WifiManager#addToBlacklist}
*
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index ca7faa2..b0dfa80 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -989,6 +989,17 @@ public class ActiveServices {
// restarting state.
mRestartingServices.remove(r);
+ // Make sure that the user who owns this service is started. If not,
+ // we don't want to allow it to run.
+ if (mAm.mStartedUsers.get(r.userId) == null) {
+ Slog.w(TAG, "Unable to launch app "
+ + r.appInfo.packageName + "/"
+ + r.appInfo.uid + " for service "
+ + r.intent.getIntent() + ": user " + r.userId + " is stopped");
+ bringDownServiceLocked(r, true);
+ return false;
+ }
+
// Service is now being launched, its package can't be stopped.
try {
AppGlobals.getPackageManager().setPackageStoppedState(
@@ -1509,7 +1520,7 @@ public class ActiveServices {
boolean didSomething = false;
ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
for (ServiceRecord service : mServiceMap.getAllServices(userId)) {
- if (service.packageName.equals(name)
+ if ((name == null || service.packageName.equals(name))
&& (service.app == null || evenPersistent || !service.app.persistent)) {
if (!doit) {
return true;
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 2b4f8b1..a061d58 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -46,6 +46,7 @@ import android.app.IInstrumentationWatcher;
import android.app.INotificationManager;
import android.app.IProcessObserver;
import android.app.IServiceConnection;
+import android.app.IStopUserCallback;
import android.app.IThumbnailReceiver;
import android.app.Instrumentation;
import android.app.Notification;
@@ -428,6 +429,11 @@ public final class ActivityManagerService extends ActivityManagerNative
long mPreviousProcessVisibleTime;
/**
+ * Which uses have been started, so are allowed to run code.
+ */
+ final SparseArray<UserStartedState> mStartedUsers = new SparseArray<UserStartedState>();
+
+ /**
* Packages that the user has asked to have run in screen size
* compatibility mode instead of filling the screen.
*/
@@ -791,7 +797,6 @@ public final class ActivityManagerService extends ActivityManagerNative
static ActivityThread mSystemThread;
private int mCurrentUserId;
- private SparseIntArray mLoggedInUsers = new SparseIntArray(5);
private UserManager mUserManager;
private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -1506,6 +1511,9 @@ public final class ActivityManagerService extends ActivityManagerNative
systemDir, "usagestats").toString());
mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
+ // User 0 is the first and only user that runs at boot.
+ mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true));
+
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
@@ -1983,13 +1991,19 @@ public final class ActivityManagerService extends ActivityManagerNative
try {
final PackageManager pm = mContext.getPackageManager();
gids = pm.getPackageGids(app.info.packageName);
+
+ if (Environment.isExternalStorageEmulated()) {
+ 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) {
Slog.w(TAG, "Unable to retrieve gids", e);
}
-
- if (Environment.isExternalStorageEmulated()) {
- mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
- }
}
if (mFactoryTest != SystemServer.FACTORY_TEST_OFF) {
if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL
@@ -2095,7 +2109,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- boolean startHomeActivityLocked(int userId) {
+ boolean startHomeActivityLocked(int userId, UserStartedState startingUser) {
if (mHeadless) {
// Added because none of the other calls to ensureBootCompleted seem to fire
// when running headless.
@@ -2135,6 +2149,9 @@ public final class ActivityManagerService extends ActivityManagerNative
null, null, 0, 0, 0, 0, null, false, null);
}
}
+ if (startingUser != null) {
+ mMainStack.addStartingUserLocked(startingUser);
+ }
return true;
}
@@ -3454,7 +3471,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.w(TAG, "Invalid packageName: " + packageName);
return;
}
- killPackageProcessesLocked(packageName, pkgUid,
+ killPackageProcessesLocked(packageName, pkgUid, -1,
ProcessList.SERVICE_ADJ, false, true, true, false, "kill background");
}
} finally {
@@ -3650,7 +3667,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private void forceStopPackageLocked(final String packageName, int uid) {
- forceStopPackageLocked(packageName, uid, false, false, true, false, UserHandle.getUserId(uid));
+ forceStopPackageLocked(packageName, uid, false, false, true, false,
+ UserHandle.getUserId(uid));
Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
Uri.fromParts("package", packageName, null));
if (!mProcessesReady) {
@@ -3662,16 +3680,27 @@ public final class ActivityManagerService extends ActivityManagerNative
false, false,
MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid));
}
-
+
+ private void forceStopUserLocked(int userId) {
+ forceStopPackageLocked(null, -1, false, false, true, false, userId);
+ Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ broadcastIntentLocked(null, null, intent,
+ null, null, 0, null, null, null,
+ false, false,
+ MY_PID, Process.SYSTEM_UID, userId);
+ }
+
private final boolean killPackageProcessesLocked(String packageName, int uid,
- int minOomAdj, boolean callerWillRestart, boolean allowRestart, boolean doit,
- boolean evenPersistent, String reason) {
+ int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
+ boolean doit, boolean evenPersistent, String reason) {
ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
// Remove all processes this package may have touched: all with the
// same UID (except for the system or root user), and all whose name
// matches the package name.
- final String procNamePrefix = packageName + ":";
+ final String procNamePrefix = packageName != null ? (packageName + ":") : null;
for (SparseArray<ProcessRecord> apps : mProcessNames.getMap().values()) {
final int NA = apps.size();
for (int ia=0; ia<NA; ia++) {
@@ -3684,6 +3713,18 @@ public final class ActivityManagerService extends ActivityManagerNative
if (doit) {
procs.add(app);
}
+ // If no package is specified, we call all processes under the
+ // give user id.
+ } else if (packageName == null) {
+ if (app.userId == userId) {
+ if (app.setAdj >= minOomAdj) {
+ if (!doit) {
+ return true;
+ }
+ app.removed = true;
+ procs.add(app);
+ }
+ }
// If uid is specified and the uid and process name match
// Or, the uid is not specified and the process name matches
} else if (((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid)
@@ -3714,7 +3755,7 @@ public final class ActivityManagerService extends ActivityManagerNative
int i;
int N;
- if (uid < 0) {
+ if (uid < 0 && name != null) {
try {
uid = AppGlobals.getPackageManager().getPackageUid(name, userId);
} catch (RemoteException e) {
@@ -3722,24 +3763,45 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (doit) {
- Slog.i(TAG, "Force stopping package " + name + " uid=" + uid);
+ if (name != null) {
+ Slog.i(TAG, "Force stopping package " + name + " uid=" + uid);
+ } else {
+ Slog.i(TAG, "Force stopping user " + userId);
+ }
Iterator<SparseArray<Long>> badApps = mProcessCrashTimes.getMap().values().iterator();
while (badApps.hasNext()) {
SparseArray<Long> ba = badApps.next();
- if (ba.get(uid) != null) {
+ for (i=ba.size()-1; i>=0; i--) {
+ boolean remove = false;
+ final int entUid = ba.keyAt(i);
+ if (name != null) {
+ if (entUid == uid) {
+ remove = true;
+ }
+ } else if (UserHandle.getUserId(entUid) == userId) {
+ remove = true;
+ }
+ if (remove) {
+ ba.removeAt(i);
+ }
+ }
+ if (ba.size() == 0) {
badApps.remove();
}
}
}
-
- boolean didSomething = killPackageProcessesLocked(name, uid, -100,
- callerWillRestart, false, doit, evenPersistent, "force stop");
+
+ boolean didSomething = killPackageProcessesLocked(name, uid,
+ name == null ? userId : -1 , -100, callerWillRestart, false,
+ doit, evenPersistent,
+ name == null ? ("force stop user " + userId) : ("force stop " + name));
TaskRecord lastTask = null;
for (i=0; i<mMainStack.mHistory.size(); i++) {
ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i);
- final boolean samePackage = r.packageName.equals(name);
+ final boolean samePackage = r.packageName.equals(name)
+ || (name == null && r.userId == userId);
if (r.userId == userId
&& (samePackage || r.task == lastTask)
&& (r.app == null || evenPersistent || !r.app.persistent)) {
@@ -3776,7 +3838,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
for (ContentProviderRecord provider : mProviderMap.getProvidersByClass(userId).values()) {
- if (provider.info.packageName.equals(name)
+ if ((name == null || provider.info.packageName.equals(name))
&& (provider.proc == null || evenPersistent || !provider.proc.persistent)) {
if (!doit) {
return true;
@@ -3792,7 +3854,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (doit) {
- if (purgeCache) {
+ if (purgeCache && name != null) {
AttributeCache ac = AttributeCache.instance();
if (ac != null) {
ac.removePackage(name);
@@ -4197,15 +4259,6 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}, pkgFilter);
- IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- onUserRemoved(intent);
- }
- }, userFilter);
-
synchronized (this) {
// Ensure that any processes we had put on hold are now started
// up.
@@ -4227,13 +4280,17 @@ public final class ActivityManagerService extends ActivityManagerNative
// Tell anyone interested that we are done booting!
SystemProperties.set("sys.boot_completed", "1");
SystemProperties.set("dev.bootcomplete", "1");
- List<UserInfo> users = getUserManager().getUsers();
- for (UserInfo user : users) {
- broadcastIntentLocked(null, null,
- new Intent(Intent.ACTION_BOOT_COMPLETED, null),
- null, null, 0, null, null,
- android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
- false, false, MY_PID, Process.SYSTEM_UID, user.id);
+ for (int i=0; i<mStartedUsers.size(); i++) {
+ UserStartedState uss = mStartedUsers.valueAt(i);
+ if (uss.mState == UserStartedState.STATE_BOOTING) {
+ uss.mState = UserStartedState.STATE_RUNNING;
+ broadcastIntentLocked(null, null,
+ new Intent(Intent.ACTION_BOOT_COMPLETED, null),
+ null, null, 0, null, null,
+ android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
+ false, false, MY_PID, Process.SYSTEM_UID,
+ mStartedUsers.keyAt(i));
+ }
}
}
}
@@ -4430,7 +4487,8 @@ public final class ActivityManagerService extends ActivityManagerNative
PendingIntentRecord.Key key = new PendingIntentRecord.Key(
type, packageName, activity, resultWho,
- requestCode, intents, resolvedTypes, flags, options);
+ requestCode, intents, resolvedTypes, flags, options,
+ UserHandle.getUserId(callingUid));
WeakReference<PendingIntentRecord> ref;
ref = mIntentSenderRecords.get(key);
PendingIntentRecord rec = ref != null ? ref.get() : null;
@@ -6296,6 +6354,16 @@ public final class ActivityManagerService extends ActivityManagerNative
"Attempt to launch content provider before system ready");
}
+ // Make sure that the user who owns this provider is started. If not,
+ // we don't want to allow it to run.
+ if (mStartedUsers.get(userId) == null) {
+ Slog.w(TAG, "Unable to launch app "
+ + cpi.applicationInfo.packageName + "/"
+ + cpi.applicationInfo.uid + " for provider "
+ + name + ": user " + userId + " is stopped");
+ return null;
+ }
+
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
cpr = mProviderMap.getProviderByClass(comp, userId);
final boolean firstClass = cpr == null;
@@ -9047,6 +9115,13 @@ public final class ActivityManagerService extends ActivityManagerNative
}
pw.println();
+ pw.println(" mStartedUsers:");
+ for (int i=0; i<mStartedUsers.size(); i++) {
+ UserStartedState uss = mStartedUsers.valueAt(i);
+ pw.print(" User #"); pw.print(uss.mHandle.getIdentifier());
+ pw.println(":");
+ uss.dump(" ", pw);
+ }
pw.println(" mHomeProcess: " + mHomeProcess);
pw.println(" mPreviousProcess: " + mPreviousProcess);
if (dumpAll) {
@@ -9404,9 +9479,15 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
boolean needSep = false;
-
+ boolean onlyHistory = false;
+
+ if ("history".equals(dumpPackage)) {
+ onlyHistory = true;
+ dumpPackage = null;
+ }
+
pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
- if (dumpAll) {
+ if (!onlyHistory && dumpAll) {
if (mRegisteredReceivers.size() > 0) {
boolean printed = false;
Iterator it = mRegisteredReceivers.values().iterator();
@@ -9439,7 +9520,7 @@ public final class ActivityManagerService extends ActivityManagerNative
needSep = true;
- if (mStickyBroadcasts != null && dumpPackage == null) {
+ if (!onlyHistory && mStickyBroadcasts != null && dumpPackage == null) {
if (needSep) {
pw.println();
}
@@ -9471,7 +9552,7 @@ public final class ActivityManagerService extends ActivityManagerNative
needSep = true;
}
- if (dumpAll) {
+ if (!onlyHistory && dumpAll) {
pw.println();
for (BroadcastQueue queue : mBroadcastQueues) {
pw.println(" mBroadcastsScheduled [" + queue.mQueueName + "]="
@@ -10667,7 +10748,25 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new IllegalArgumentException("File descriptors passed in Intent");
}
- checkValidCaller(Binder.getCallingUid(), userId);
+ if (userId != UserHandle.getCallingUserId()) {
+ // Requesting a different user, make sure that they have permission
+ if (checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Binder.getCallingPid(), Binder.getCallingUid(), -1, true)
+ == PackageManager.PERMISSION_GRANTED) {
+ // Translate to the current user id, if caller wasn't aware
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = mCurrentUserId;
+ }
+ } else {
+ String msg = "Permission Denial: Request to bindService as user " + userId
+ + " but is calling from user " + UserHandle.getCallingUserId()
+ + "; this requires "
+ + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
synchronized(this) {
return mServices.bindServiceLocked(caller, token, service, resolvedType,
@@ -10982,7 +11081,7 @@ public final class ActivityManagerService extends ActivityManagerNative
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, -1, -1, null, receivers, null, 0, null, null,
- false, true, true, false);
+ false, true, true, -1);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
@@ -11075,33 +11174,39 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
}
- boolean onlySendToCaller = false;
-
// If the caller is trying to send this broadcast to a different
// user, verify that is allowed.
if (UserHandle.getUserId(callingUid) != userId) {
if (checkComponentPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- callingPid, callingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED) {
- if (checkComponentPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS,
- callingPid, callingUid, -1, true)
- == PackageManager.PERMISSION_GRANTED) {
- onlySendToCaller = true;
- } else {
- String msg = "Permission Denial: " + intent.getAction()
- + " broadcast from " + callerPackage
- + " asks to send as user " + userId
- + " but is calling from user " + UserHandle.getUserId(callingUid)
- + "; this requires "
- + android.Manifest.permission.INTERACT_ACROSS_USERS;
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED
+ && checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ callingPid, callingUid, -1, true)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: " + intent.getAction()
+ + " broadcast from " + callerPackage
+ + " asks to send as user " + userId
+ + " but is calling from user " + UserHandle.getUserId(callingUid)
+ + "; this requires "
+ + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ } else {
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = mCurrentUserId;
}
}
}
+ // Make sure that the user who is receiving this broadcast is started
+ // If not, we will just skip it.
+ if (mStartedUsers.get(userId) == null) {
+ Slog.w(TAG, "Skipping broadcast of " + intent
+ + ": user " + userId + " is stopped");
+ return ActivityManager.BROADCAST_SUCCESS;
+ }
+
// Handle special intents: if this broadcast is from the package
// manager about a package being removed, we need to remove all of
// its activities from the history stack.
@@ -11288,7 +11393,7 @@ public final class ActivityManagerService extends ActivityManagerNative
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
registeredReceivers, resultTo, resultCode, resultData, map,
- ordered, sticky, false, onlySendToCaller);
+ ordered, sticky, false, userId);
if (DEBUG_BROADCAST) Slog.v(
TAG, "Enqueueing parallel broadcast " + r);
final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
@@ -11378,7 +11483,7 @@ public final class ActivityManagerService extends ActivityManagerNative
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
callerPackage, callingPid, callingUid, requiredPermission,
receivers, resultTo, resultCode, resultData, map, ordered,
- sticky, false, onlySendToCaller);
+ sticky, false, userId);
if (DEBUG_BROADCAST) Slog.v(
TAG, "Enqueueing ordered broadcast " + r
+ ": prev had " + queue.mOrderedBroadcasts.size());
@@ -11644,7 +11749,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.instrumentationProfileFile = null;
app.instrumentationArguments = null;
- forceStopPackageLocked(app.processName, -1, false, false, true, true, app.userId);
+ forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId);
}
public void finishInstrumentation(IApplicationThread target,
@@ -13486,75 +13591,174 @@ public final class ActivityManagerService extends ActivityManagerNative
// Multi-user methods
+ @Override
public boolean switchUser(int userId) {
- final int callingUid = Binder.getCallingUid();
- if (callingUid != 0 && callingUid != Process.myUid()) {
- Slog.e(TAG, "Trying to switch user from unauthorized app");
- return false;
+ if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: switchUser() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
}
- if (mCurrentUserId == userId)
- return true;
-
synchronized (this) {
- // Check if user is already logged in, otherwise check if user exists first before
- // adding to the list of logged in users.
- if (mLoggedInUsers.indexOfKey(userId) < 0) {
- if (!userExists(userId)) {
- return false;
- }
- mLoggedInUsers.append(userId, userId);
+ if (mCurrentUserId == userId) {
+ return true;
+ }
+
+ // If the user we are switching to is not currently started, then
+ // we need to start it now.
+ if (mStartedUsers.get(userId) == null) {
+ mStartedUsers.put(userId, new UserStartedState(new UserHandle(userId), false));
}
mCurrentUserId = userId;
boolean haveActivities = mMainStack.switchUser(userId);
if (!haveActivities) {
- startHomeActivityLocked(userId);
+ startHomeActivityLocked(userId, mStartedUsers.get(userId));
}
-
}
- // Inform of user switch
- Intent addedIntent = new Intent(Intent.ACTION_USER_SWITCHED);
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ // Inform of user switch
+ Intent addedIntent = new Intent(Intent.ACTION_USER_SWITCHED);
+ addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
return true;
}
- @Override
- public UserInfo getCurrentUser() throws RemoteException {
- final int callingUid = Binder.getCallingUid();
- if (callingUid != 0 && callingUid != Process.myUid()) {
- Slog.e(TAG, "Trying to get user from unauthorized app");
- return null;
+ void finishUserSwitch(UserStartedState uss) {
+ synchronized (this) {
+ if (uss.mState == UserStartedState.STATE_BOOTING
+ && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
+ uss.mState = UserStartedState.STATE_RUNNING;
+ broadcastIntentLocked(null, null,
+ new Intent(Intent.ACTION_BOOT_COMPLETED, null),
+ null, null, 0, null, null,
+ android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
+ false, false, MY_PID, Process.SYSTEM_UID, uss.mHandle.getIdentifier());
+ }
}
- return getUserManager().getUserInfo(mCurrentUserId);
}
- private void onUserRemoved(Intent intent) {
- int extraUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (extraUserId < 1) return;
-
- // Kill all the processes for the user
- ArrayList<Pair<String, Integer>> pkgAndUids = new ArrayList<Pair<String,Integer>>();
+ @Override
+ public int stopUser(final int userId, final IStopUserCallback callback) {
+ if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: switchUser() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (userId <= 0) {
+ throw new IllegalArgumentException("Can't stop primary user " + userId);
+ }
synchronized (this) {
- HashMap<String,SparseArray<ProcessRecord>> map = mProcessNames.getMap();
- for (Entry<String, SparseArray<ProcessRecord>> uidMap : map.entrySet()) {
- SparseArray<ProcessRecord> uids = uidMap.getValue();
- for (int i = 0; i < uids.size(); i++) {
- if (UserHandle.getUserId(uids.keyAt(i)) == extraUserId) {
- pkgAndUids.add(new Pair<String,Integer>(uidMap.getKey(), uids.keyAt(i)));
- }
+ if (mCurrentUserId == userId) {
+ return ActivityManager.USER_OP_IS_CURRENT;
+ }
+
+ final UserStartedState uss = mStartedUsers.get(userId);
+ if (uss == null) {
+ // User is not started, nothing to do... but we do need to
+ // callback if requested.
+ if (callback != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ callback.userStopped(userId);
+ } catch (RemoteException e) {
+ }
+ }
+ });
}
+ return ActivityManager.USER_OP_SUCCESS;
+ }
+
+ if (callback != null) {
+ uss.mStopCallbacks.add(callback);
+ }
+
+ if (uss.mState != UserStartedState.STATE_STOPPING) {
+ uss.mState = UserStartedState.STATE_STOPPING;
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ // Inform of user switch
+ Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
+ final IIntentReceiver resultReceiver = new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky) {
+ finishUserStop(uss);
+ }
+ };
+ broadcastIntentLocked(null, null, intent,
+ null, resultReceiver, 0, null, null, null,
+ true, false, MY_PID, Process.SYSTEM_UID, userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ return ActivityManager.USER_OP_SUCCESS;
+ }
+
+ void finishUserStop(UserStartedState uss) {
+ final int userId = uss.mHandle.getIdentifier();
+ boolean stopped;
+ ArrayList<IStopUserCallback> callbacks;
+ synchronized (this) {
+ callbacks = new ArrayList<IStopUserCallback>(uss.mStopCallbacks);
+ if (uss.mState != UserStartedState.STATE_STOPPING
+ || mStartedUsers.get(userId) != uss) {
+ stopped = false;
+ } else {
+ stopped = true;
+ // User can no longer run.
+ mStartedUsers.remove(userId);
+
+ // Clean up all state and processes associated with the user.
+ // Kill all the processes for the user.
+ forceStopUserLocked(userId);
}
+ }
- for (Pair<String,Integer> pkgAndUid : pkgAndUids) {
- forceStopPackageLocked(pkgAndUid.first, pkgAndUid.second,
- false, false, true, true, extraUserId);
+ for (int i=0; i<callbacks.size(); i++) {
+ try {
+ if (stopped) callbacks.get(i).userStopped(userId);
+ else callbacks.get(i).userStopAborted(userId);
+ } catch (RemoteException e) {
}
}
}
+ @Override
+ public UserInfo getCurrentUser() {
+ if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: getCurrentUser() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ synchronized (this) {
+ return getUserManager().getUserInfo(mCurrentUserId);
+ }
+ }
+
private boolean userExists(int userId) {
UserInfo user = getUserManager().getUserInfo(userId);
return user != null;
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index ccea41a..a389edc 100755
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -211,7 +211,10 @@ final class ActivityStack {
*/
final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible
= new ArrayList<IActivityManager.WaitResult>();
-
+
+ final ArrayList<UserStartedState> mStartingUsers
+ = new ArrayList<UserStartedState>();
+
/**
* Set when the system is going to sleep, until we have
* successfully paused the current activity and released our wake lock.
@@ -1397,7 +1400,7 @@ final class ActivityStack {
// Launcher...
if (mMainStack) {
ActivityOptions.abort(options);
- return mService.startHomeActivityLocked(0);
+ return mService.startHomeActivityLocked(0, null);
}
}
@@ -1427,7 +1430,16 @@ final class ActivityStack {
ActivityOptions.abort(options);
return false;
}
-
+
+ // Make sure that the user who owns this activity is started. If not,
+ // we will just leave it as is because someone should be bringing
+ // another user's activities to the top of the stack.
+ if (mService.mStartedUsers.get(next.userId) == null) {
+ Slog.w(TAG, "Skipping resume of top activity " + next
+ + ": user " + next.userId + " is stopped");
+ return false;
+ }
+
// The activity may be waiting for stop, but that is no longer
// appropriate for it.
mStoppingActivities.remove(next);
@@ -1494,7 +1506,7 @@ final class ActivityStack {
Slog.d(TAG, "no-history finish of " + last + " on new resume");
}
requestFinishActivityLocked(last.appToken, Activity.RESULT_CANCELED, null,
- "no-history");
+ "no-history");
}
}
@@ -3414,6 +3426,7 @@ final class ActivityStack {
ArrayList<ActivityRecord> stops = null;
ArrayList<ActivityRecord> finishes = null;
ArrayList<ActivityRecord> thumbnails = null;
+ ArrayList<UserStartedState> startingUsers = null;
int NS = 0;
int NF = 0;
int NT = 0;
@@ -3495,6 +3508,10 @@ final class ActivityStack {
booting = mService.mBooting;
mService.mBooting = false;
}
+ if (mStartingUsers.size() > 0) {
+ startingUsers = new ArrayList<UserStartedState>(mStartingUsers);
+ mStartingUsers.clear();
+ }
}
int i;
@@ -3539,6 +3556,10 @@ final class ActivityStack {
if (booting) {
mService.finishBooting();
+ } else if (startingUsers != null) {
+ for (i=0; i<startingUsers.size(); i++) {
+ mService.finishUserSwitch(startingUsers.get(i));
+ }
}
mService.trimApplications();
@@ -3556,6 +3577,10 @@ final class ActivityStack {
return res;
}
+ final void addStartingUserLocked(UserStartedState uss) {
+ mStartingUsers.add(uss);
+ }
+
/**
* @return Returns true if the activity is being finished, false if for
* some reason it is being left as-is.
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index 7873dd8..34dec3a 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -372,17 +372,7 @@ public class BroadcastQueue {
private final void deliverToRegisteredReceiverLocked(BroadcastRecord r,
BroadcastFilter filter, boolean ordered) {
boolean skip = false;
- if (r.onlySendToCaller) {
- if (!UserHandle.isSameApp(r.callingUid, filter.owningUid)) {
- Slog.w(TAG, "Permission Denial: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid="
- + r.callingPid + ", uid=" + r.callingUid + ")"
- + " not allowed to go to different app " + filter.owningUid);
- skip = true;
- }
- }
- if (!skip && filter.requiredPermission != null) {
+ if (filter.requiredPermission != null) {
int perm = mService.checkComponentPermission(filter.requiredPermission,
r.callingPid, r.callingUid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
@@ -667,18 +657,6 @@ public class BroadcastQueue {
info.activityInfo.name);
boolean skip = false;
- if (r.onlySendToCaller) {
- if (!UserHandle.isSameApp(r.callingUid, info.activityInfo.applicationInfo.uid)) {
- Slog.w(TAG, "Permission Denial: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid="
- + r.callingPid + ", uid=" + r.callingUid + ")"
- + " to " + component.flattenToShortString()
- + " not allowed to go to different app "
- + info.activityInfo.applicationInfo.uid);
- skip = true;
- }
- }
int perm = mService.checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index 799b609..ca6d5f7 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -44,7 +44,7 @@ class BroadcastRecord extends Binder {
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean initialSticky; // initial broadcast from register to sticky?
- final boolean onlySendToCaller; // only allow receipt by sender's components?
+ final int userId; // user id this broadcast was for
final String requiredPermission; // a permission the caller has required
final List receivers; // contains BroadcastFilter and ResolveInfo
IIntentReceiver resultTo; // who receives final result if non-null
@@ -80,7 +80,7 @@ class BroadcastRecord extends Binder {
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.uptimeMillis();
- pw.print(prefix); pw.println(this);
+ pw.print(prefix); pw.print(this); pw.print(" to user "); pw.println(userId);
pw.print(prefix); pw.println(intent);
if (sticky) {
Bundle bundle = intent.getExtras();
@@ -141,14 +141,15 @@ class BroadcastRecord extends Binder {
pw.println(curReceiver.applicationInfo.sourceDir);
}
}
- String stateStr = " (?)";
- switch (state) {
- case IDLE: stateStr=" (IDLE)"; break;
- case APP_RECEIVE: stateStr=" (APP_RECEIVE)"; break;
- case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break;
- case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break;
+ if (state != IDLE) {
+ String stateStr = " (?)";
+ switch (state) {
+ case APP_RECEIVE: stateStr=" (APP_RECEIVE)"; break;
+ case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break;
+ case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break;
+ }
+ pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr);
}
- pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr);
final int N = receivers != null ? receivers.size() : 0;
String p2 = prefix + " ";
PrintWriterPrinter printer = new PrintWriterPrinter(pw);
@@ -168,7 +169,8 @@ class BroadcastRecord extends Binder {
int _callingPid, int _callingUid, String _requiredPermission,
List _receivers, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized,
- boolean _sticky, boolean _initialSticky, boolean _onlySendToCaller) {
+ boolean _sticky, boolean _initialSticky,
+ int _userId) {
queue = _queue;
intent = _intent;
callerApp = _callerApp;
@@ -184,7 +186,7 @@ class BroadcastRecord extends Binder {
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
- onlySendToCaller = _onlySendToCaller;
+ userId = _userId;
nextReceiver = 0;
state = IDLE;
}
diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java
index d3b8510..8e70376 100644
--- a/services/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/java/com/android/server/am/PendingIntentRecord.java
@@ -54,11 +54,12 @@ class PendingIntentRecord extends IIntentSender.Stub {
String[] allResolvedTypes;
final int flags;
final int hashCode;
+ final int userId;
private static final int ODD_PRIME_NUMBER = 37;
Key(int _t, String _p, ActivityRecord _a, String _w,
- int _r, Intent[] _i, String[] _it, int _f, Bundle _o) {
+ int _r, Intent[] _i, String[] _it, int _f, Bundle _o, int _userId) {
type = _t;
packageName = _p;
activity = _a;
@@ -70,10 +71,12 @@ class PendingIntentRecord extends IIntentSender.Stub {
allResolvedTypes = _it;
flags = _f;
options = _o;
-
+ userId = _userId;
+
int hash = 23;
hash = (ODD_PRIME_NUMBER*hash) + _f;
hash = (ODD_PRIME_NUMBER*hash) + _r;
+ hash = (ODD_PRIME_NUMBER*hash) + _userId;
if (_w != null) {
hash = (ODD_PRIME_NUMBER*hash) + _w.hashCode();
}
@@ -102,6 +105,9 @@ class PendingIntentRecord extends IIntentSender.Stub {
if (type != other.type) {
return false;
}
+ if (userId != other.userId){
+ return false;
+ }
if (!packageName.equals(other.packageName)) {
return false;
}
@@ -156,7 +162,7 @@ class PendingIntentRecord extends IIntentSender.Stub {
+ " intent="
+ (requestIntent != null
? requestIntent.toShortString(false, true, false, false) : "<null>")
- + " flags=0x" + Integer.toHexString(flags) + "}";
+ + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}";
}
String typeName() {
diff --git a/services/java/com/android/server/am/UserStartedState.java b/services/java/com/android/server/am/UserStartedState.java
new file mode 100644
index 0000000..3f3ed85
--- /dev/null
+++ b/services/java/com/android/server/am/UserStartedState.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import android.app.IStopUserCallback;
+import android.os.UserHandle;
+
+public class UserStartedState {
+ public final static int STATE_BOOTING = 0;
+ public final static int STATE_RUNNING = 1;
+ public final static int STATE_STOPPING = 2;
+
+ public final UserHandle mHandle;
+ public final ArrayList<IStopUserCallback> mStopCallbacks
+ = new ArrayList<IStopUserCallback>();
+
+ public int mState = STATE_BOOTING;
+
+ public UserStartedState(UserHandle handle, boolean initial) {
+ mHandle = handle;
+ }
+
+ void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mState="); pw.println(mState);
+ }
+}
diff --git a/services/java/com/android/server/display/DisplayAdapter.java b/services/java/com/android/server/display/DisplayAdapter.java
index f9fa7a8..d19fe01 100644
--- a/services/java/com/android/server/display/DisplayAdapter.java
+++ b/services/java/com/android/server/display/DisplayAdapter.java
@@ -16,33 +16,96 @@
package com.android.server.display;
+import android.content.Context;
+import android.os.Handler;
+
+import java.io.PrintWriter;
+
/**
* A display adapter makes zero or more display devices available to the system
* and provides facilities for discovering when displays are connected or disconnected.
* <p>
* For now, all display adapters are registered in the system server but
* in principle it could be done from other processes.
+ * </p><p>
+ * Display devices are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
* </p>
*/
-public abstract class DisplayAdapter {
+public class DisplayAdapter {
+ private final Context mContext;
+ private final String mName;
+ private final Handler mHandler;
+ private Listener mListener;
+
+ public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
+ public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2;
+ public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3;
+
+ public DisplayAdapter(Context context, String name) {
+ mContext = context;
+ mName = name;
+ mHandler = new Handler();
+ }
+
+ public final Context getContext() {
+ return mContext;
+ }
+
+ public final Handler getHandler() {
+ return mHandler;
+ }
+
/**
* Gets the display adapter name for debugging purposes.
*
* @return The display adapter name.
*/
- public abstract String getName();
+ public final String getName() {
+ return mName;
+ }
/**
* Registers the display adapter with the display manager.
- * The display adapter should register any built-in display devices now.
- * Other display devices can be registered dynamically later.
*
- * @param listener The listener for callbacks.
+ * @param listener The listener for callbacks. The listener will
+ * be invoked on the display manager service's handler thread.
+ */
+ public final void register(Listener listener) {
+ mListener = listener;
+ onRegister();
+ }
+
+ /**
+ * Dumps the local state of the display adapter.
+ */
+ public void dump(PrintWriter pw) {
+ }
+
+ /**
+ * Called when the display adapter is registered.
+ *
+ * The display adapter should register any built-in display devices as soon as possible.
+ * The boot process will wait for the default display to be registered.
+ *
+ * Other display devices can be registered dynamically later.
*/
- public abstract void register(Listener listener);
+ protected void onRegister() {
+ }
+
+ /**
+ * Sends a display device event to the display adapter listener asynchronously.
+ */
+ protected void sendDisplayDeviceEvent(final DisplayDevice device, final int event) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayDeviceEvent(device, event);
+ }
+ });
+ }
public interface Listener {
- public void onDisplayDeviceAdded(DisplayDevice device);
- public void onDisplayDeviceRemoved(DisplayDevice device);
+ public void onDisplayDeviceEvent(DisplayDevice device, int event);
}
}
diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java
index 57002ff..c83ce96 100644
--- a/services/java/com/android/server/display/DisplayDevice.java
+++ b/services/java/com/android/server/display/DisplayDevice.java
@@ -16,17 +16,43 @@
package com.android.server.display;
+import android.os.IBinder;
+
/**
* Represents a physical display device such as the built-in display
* an external monitor, or a WiFi display.
+ * <p>
+ * Display devices are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
+ * </p>
*/
public abstract class DisplayDevice {
+ private final DisplayAdapter mDisplayAdapter;
+ private final IBinder mDisplayToken;
+
+ public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) {
+ mDisplayAdapter = displayAdapter;
+ mDisplayToken = displayToken;
+ }
+
/**
- * Gets the display adapter that makes the display device available.
+ * Gets the display adapter that owns the display device.
*
* @return The display adapter.
*/
- public abstract DisplayAdapter getAdapter();
+ public final DisplayAdapter getAdapter() {
+ return mDisplayAdapter;
+ }
+
+ /**
+ * Gets the Surface Flinger display token for this display.
+ *
+ * @return The display token, or null if the display is not being managed
+ * by Surface Flinger.
+ */
+ public final IBinder getDisplayToken() {
+ return mDisplayToken;
+ }
/**
* Gets information about the display device.
@@ -34,4 +60,12 @@ public abstract class DisplayDevice {
* @param outInfo The object to populate with the information.
*/
public abstract void getInfo(DisplayDeviceInfo outInfo);
+
+ // For debugging purposes.
+ @Override
+ public String toString() {
+ DisplayDeviceInfo info = new DisplayDeviceInfo();
+ getInfo(info);
+ return info.toString() + ", owner=\"" + mDisplayAdapter.getName() + "\"";
+ }
}
diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java
index 9c0f964..c7b8c24 100644
--- a/services/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/java/com/android/server/display/DisplayDeviceInfo.java
@@ -20,6 +20,9 @@ package com.android.server.display;
* Describes the characteristics of a physical display device.
*/
public final class DisplayDeviceInfo {
+ public static final int FLAG_DEFAULT_DISPLAY = 1 << 0;
+ public static final int FLAG_SECURE = 1 << 1;
+
/**
* Gets the name of the display device, which may be derived from
* EDID or other sources. The name may be displayed to the user.
@@ -43,6 +46,8 @@ public final class DisplayDeviceInfo {
public float xDpi;
public float yDpi;
+ public int flags;
+
public void copyFrom(DisplayDeviceInfo other) {
name = other.name;
width = other.width;
@@ -51,12 +56,25 @@ public final class DisplayDeviceInfo {
densityDpi = other.densityDpi;
xDpi = other.xDpi;
yDpi = other.yDpi;
+ flags = other.flags;
}
// For debugging purposes
@Override
public String toString() {
return "\"" + name + "\": " + width + " x " + height + ", " + refreshRate + " fps, "
- + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi";
+ + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi"
+ + flagsToString(flags);
+ }
+
+ private static String flagsToString(int flags) {
+ StringBuilder msg = new StringBuilder();
+ if ((flags & FLAG_DEFAULT_DISPLAY) != 0) {
+ msg.append(", FLAG_DEFAULT_DISPLAY");
+ }
+ if ((flags & FLAG_SECURE) != 0) {
+ msg.append(", FLAG_SECURE");
+ }
+ return msg.toString();
}
}
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 2ebad1d..cf835ef 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -16,57 +16,186 @@
package com.android.server.display;
+import com.android.internal.util.IndentingPrintWriter;
+
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IDisplayManager;
+import android.hardware.display.IDisplayManagerCallback;
import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
/**
- * Manages the properties, media routing and power state of attached displays.
+ * Manages attached displays.
* <p>
- * The display manager service does not own or directly control the displays.
- * Instead, other components in the system register their display adapters with the
- * display manager service which acts as a central controller.
+ * The {@link DisplayManagerService} manages the global lifecycle of displays,
+ * decides how to configure logical displays based on the physical display devices currently
+ * attached, sends notifications to the system and to applications when the state
+ * changes, and so on.
+ * </p><p>
+ * The display manager service relies on a collection of {@link DisplayAdapter} components,
+ * for discovering and configuring physical display devices attached to the system.
+ * There are separate display adapters for each manner that devices are attached:
+ * one display adapter for built-in local displays, one for simulated non-functional
+ * displays when the system is headless, one for simulated overlay displays used for
+ * development, one for wifi displays, etc.
+ * </p><p>
+ * Display adapters are only weakly coupled to the display manager service.
+ * Display adapters communicate changes in display device state to the display manager
+ * service asynchronously via a {@link DisplayAdapter.DisplayAdapterListener} registered
+ * by the display manager service. This separation of concerns is important for
+ * two main reasons. First, it neatly encapsulates the responsibilities of these
+ * two classes: display adapters handle individual display devices whereas
+ * the display manager service handles the global state. Second, it eliminates
+ * the potential for deadlocks resulting from asynchronous display device discovery.
+ * </p><p>
+ * To keep things simple, display adapters and display devices are single-threaded
+ * and are only accessed on the display manager's handler thread. Of course, the
+ * display manager must be accessible by multiple thread (especially for
+ * incoming binder calls) so all of the display manager's state is synchronized
+ * and guarded by a lock.
* </p>
*/
public final class DisplayManagerService extends IDisplayManager.Stub {
private static final String TAG = "DisplayManagerService";
+ private static final boolean DEBUG = false;
private static final String SYSTEM_HEADLESS = "ro.config.headless";
+ private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
+
+ private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER = 1;
+ private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
+ private static final int MSG_DELIVER_DISPLAY_EVENT = 3;
private final Object mLock = new Object();
private final Context mContext;
private final boolean mHeadless;
+ private final DisplayManagerHandler mHandler;
+ private final DisplayAdapterListener mDisplayAdapterListener = new DisplayAdapterListener();
+ private final SparseArray<CallbackRecord> mCallbacks =
+ new SparseArray<CallbackRecord>();
+
+ // List of all currently registered display adapters.
private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
- private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
- public DisplayManagerService(Context context) {
+ // List of all currently connected display devices.
+ private final ArrayList<DisplayDevice> mDisplayDevices = new ArrayList<DisplayDevice>();
+
+ // List of all logical displays, indexed by logical display id.
+ private final SparseArray<LogicalDisplay> mLogicalDisplays = new SparseArray<LogicalDisplay>();
+ private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
+
+ // True if in safe mode.
+ // This option may disable certain display adapters.
+ private boolean mSafeMode;
+
+ // True if we are in a special boot mode where only core applications and
+ // services should be started. This option may disable certain display adapters.
+ private boolean mOnlyCore;
+
+ // Temporary callback list, used when sending display events to applications.
+ private ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
+
+ public DisplayManagerService(Context context, Handler uiHandler) {
mContext = context;
mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1");
- registerDefaultDisplayAdapter();
+ mHandler = new DisplayManagerHandler(uiHandler.getLooper());
+ mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
+ }
+
+ /**
+ * Pauses the boot process to wait for the first display to be initialized.
+ */
+ public boolean waitForDefaultDisplay() {
+ synchronized (mLock) {
+ long timeout = SystemClock.uptimeMillis() + WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
+ while (mLogicalDisplays.get(Display.DEFAULT_DISPLAY) == null) {
+ long delay = timeout - SystemClock.uptimeMillis();
+ if (delay <= 0) {
+ return false;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "waitForDefaultDisplay: waiting, timeout=" + delay);
+ }
+ try {
+ mLock.wait(delay);
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Called when the system is ready to go.
+ */
+ public void systemReady(boolean safeMode, boolean onlyCore) {
+ synchronized (mLock) {
+ mSafeMode = safeMode;
+ mOnlyCore = onlyCore;
+ }
+ mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
}
+ // Runs on handler.
private void registerDefaultDisplayAdapter() {
+ // Register default display adapter.
if (mHeadless) {
registerDisplayAdapter(new HeadlessDisplayAdapter(mContext));
} else {
- registerDisplayAdapter(new SurfaceFlingerDisplayAdapter(mContext));
+ registerDisplayAdapter(new LocalDisplayAdapter(mContext));
+ }
+ }
+
+ // Runs on handler.
+ private void registerAdditionalDisplayAdapters() {
+ if (shouldRegisterNonEssentialDisplayAdapters()) {
+ registerDisplayAdapter(new OverlayDisplayAdapter(mContext));
+ }
+ }
+
+ private boolean shouldRegisterNonEssentialDisplayAdapters() {
+ // In safe mode, we disable non-essential display adapters to give the user
+ // an opportunity to fix broken settings or other problems that might affect
+ // system stability.
+ // In only-core mode, we disable non-essential display adapters to minimize
+ // the number of dependencies that are started while in this mode and to
+ // prevent problems that might occur due to the device being encrypted.
+ synchronized (mLock) {
+ return !mSafeMode && !mOnlyCore;
}
}
+ // Runs on handler.
+ private void registerDisplayAdapter(DisplayAdapter adapter) {
+ synchronized (mLock) {
+ mDisplayAdapters.add(adapter);
+ }
+
+ adapter.register(mDisplayAdapterListener);
+ }
+
// FIXME: this isn't the right API for the long term
public void getDefaultExternalDisplayDeviceInfo(DisplayDeviceInfo info) {
// hardcoded assuming 720p touch screen plugged into HDMI and USB
@@ -85,71 +214,224 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
}
/**
- * Save away new DisplayInfo data.
- * @param displayId The local DisplayInfo to store the new data in.
+ * Sets the new logical display orientation.
+ *
+ * @param displayId The logical display id.
+ * @param orientation One of the Surface.ROTATION_* constants.
+ */
+ public void setDisplayOrientation(int displayId, int orientation) {
+ synchronized (mLock) {
+ // TODO: update mirror transforms
+ LogicalDisplay display = mLogicalDisplays.get(displayId);
+ if (display != null && display.mPrimaryDisplayDevice != null) {
+ IBinder displayToken = display.mPrimaryDisplayDevice.getDisplayToken();
+ if (displayToken != null) {
+ Surface.openTransaction();
+ try {
+ Surface.setDisplayOrientation(displayToken, orientation);
+ } finally {
+ Surface.closeTransaction();
+ }
+ }
+
+ display.mBaseDisplayInfo.rotation = orientation;
+ sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ }
+ }
+ }
+
+ /**
+ * Overrides the display information of a particular logical display.
+ * This is used by the window manager to control the size and characteristics
+ * of the default display.
+ *
+ * @param displayId The logical display id.
* @param info The new data to be stored.
*/
- public void setDisplayInfo(int displayId, DisplayInfo info) {
+ public void setDisplayInfoOverrideFromWindowManager(int displayId, DisplayInfo info) {
synchronized (mLock) {
- if (displayId != Display.DEFAULT_DISPLAY) {
- throw new UnsupportedOperationException();
+ LogicalDisplay display = mLogicalDisplays.get(displayId);
+ if (display != null) {
+ if (info != null) {
+ if (display.mOverrideDisplayInfo == null) {
+ display.mOverrideDisplayInfo = new DisplayInfo();
+ }
+ display.mOverrideDisplayInfo.copyFrom(info);
+ } else {
+ display.mOverrideDisplayInfo = null;
+ }
+
+ sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
}
- mDefaultDisplayInfo.copyFrom(info);
}
}
/**
- * Return requested DisplayInfo.
- * @param displayId The data to retrieve.
- * @param outInfo The structure to receive the data.
+ * Returns information about the specified logical display.
+ *
+ * @param displayId The logical display id.
+ * @param The logical display info, or null if the display does not exist.
*/
@Override // Binder call
- public boolean getDisplayInfo(int displayId, DisplayInfo outInfo) {
+ public DisplayInfo getDisplayInfo(int displayId) {
synchronized (mLock) {
- if (displayId != Display.DEFAULT_DISPLAY) {
- return false;
+ LogicalDisplay display = mLogicalDisplays.get(displayId);
+ if (display != null) {
+ if (display.mOverrideDisplayInfo != null) {
+ return new DisplayInfo(display.mOverrideDisplayInfo);
+ }
+ return new DisplayInfo(display.mBaseDisplayInfo);
}
- outInfo.copyFrom(mDefaultDisplayInfo);
- return true;
+ return null;
}
}
- private void registerDisplayAdapter(DisplayAdapter adapter) {
- mDisplayAdapters.add(adapter);
- adapter.register(new DisplayAdapter.Listener() {
- @Override
- public void onDisplayDeviceAdded(DisplayDevice device) {
- DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
- device.getInfo(deviceInfo);
- copyDisplayInfoFromDeviceInfo(mDefaultDisplayInfo, deviceInfo);
+ @Override // Binder call
+ public int[] getDisplayIds() {
+ synchronized (mLock) {
+ final int count = mLogicalDisplays.size();
+ int[] displayIds = new int[count];
+ for (int i = 0; i > count; i++) {
+ displayIds[i] = mLogicalDisplays.keyAt(i);
}
+ return displayIds;
+ }
+ }
- @Override
- public void onDisplayDeviceRemoved(DisplayDevice device) {
+ @Override // Binder call
+ public void registerCallback(IDisplayManagerCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mLock) {
+ int callingPid = Binder.getCallingPid();
+ if (mCallbacks.get(callingPid) != null) {
+ throw new SecurityException("The calling process has already "
+ + "registered an IDisplayManagerCallback.");
}
- });
+
+ CallbackRecord record = new CallbackRecord(callingPid, callback);
+ try {
+ IBinder binder = callback.asBinder();
+ binder.linkToDeath(record, 0);
+ } catch (RemoteException ex) {
+ // give up
+ throw new RuntimeException(ex);
+ }
+
+ mCallbacks.put(callingPid, record);
+ }
+ }
+
+ private void onCallbackDied(int pid) {
+ synchronized (mLock) {
+ mCallbacks.remove(pid);
+ }
+ }
+
+ // Runs on handler.
+ private void handleDisplayDeviceAdded(DisplayDevice device) {
+ synchronized (mLock) {
+ if (mDisplayDevices.contains(device)) {
+ Slog.w(TAG, "Attempted to add already added display device: " + device);
+ return;
+ }
+
+ mDisplayDevices.add(device);
+
+ LogicalDisplay display = new LogicalDisplay(device);
+ display.updateFromPrimaryDisplayDevice();
+
+ boolean isDefault = (display.mPrimaryDisplayDeviceInfo.flags
+ & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
+ if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
+ Slog.w(TAG, "Attempted to add a second default device: " + device);
+ isDefault = false;
+ }
+
+ int displayId = isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
+ mLogicalDisplays.put(displayId, display);
+
+ sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
+
+ // Wake up waitForDefaultDisplay.
+ if (isDefault) {
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ // Runs on handler.
+ private void handleDisplayDeviceChanged(DisplayDevice device) {
+ synchronized (mLock) {
+ if (!mDisplayDevices.contains(device)) {
+ Slog.w(TAG, "Attempted to change non-existent display device: " + device);
+ return;
+ }
+
+ for (int i = mLogicalDisplays.size(); i-- > 0; ) {
+ LogicalDisplay display = mLogicalDisplays.valueAt(i);
+ if (display.mPrimaryDisplayDevice == device) {
+ final int displayId = mLogicalDisplays.keyAt(i);
+ display.updateFromPrimaryDisplayDevice();
+ sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ }
+ }
+ }
+ }
+
+ // Runs on handler.
+ private void handleDisplayDeviceRemoved(DisplayDevice device) {
+ synchronized (mLock) {
+ if (!mDisplayDevices.remove(device)) {
+ Slog.w(TAG, "Attempted to remove non-existent display device: " + device);
+ return;
+ }
+
+ for (int i = mLogicalDisplays.size(); i-- > 0; ) {
+ LogicalDisplay display = mLogicalDisplays.valueAt(i);
+ if (display.mPrimaryDisplayDevice == device) {
+ final int displayId = mLogicalDisplays.keyAt(i);
+ mLogicalDisplays.removeAt(i);
+ sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+ }
+ }
+ }
+ }
+
+ // Posts a message to send a display event at the next opportunity.
+ private void sendDisplayEventLocked(int displayId, int event) {
+ Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+ mHandler.sendMessage(msg);
}
- private void copyDisplayInfoFromDeviceInfo(
- DisplayInfo displayInfo, DisplayDeviceInfo deviceInfo) {
- // Bootstrap the logical display using the physical display.
- displayInfo.appWidth = deviceInfo.width;
- displayInfo.appHeight = deviceInfo.height;
- displayInfo.logicalWidth = deviceInfo.width;
- displayInfo.logicalHeight = deviceInfo.height;
- displayInfo.rotation = Surface.ROTATION_0;
- displayInfo.refreshRate = deviceInfo.refreshRate;
- displayInfo.logicalDensityDpi = deviceInfo.densityDpi;
- displayInfo.physicalXDpi = deviceInfo.xDpi;
- displayInfo.physicalYDpi = deviceInfo.yDpi;
- displayInfo.smallestNominalAppWidth = deviceInfo.width;
- displayInfo.smallestNominalAppHeight = deviceInfo.height;
- displayInfo.largestNominalAppWidth = deviceInfo.width;
- displayInfo.largestNominalAppHeight = deviceInfo.height;
+ // Runs on handler.
+ // This method actually sends display event notifications.
+ // Note that it must be very careful not to be holding the lock while sending
+ // is in progress.
+ private void deliverDisplayEvent(int displayId, int event) {
+ if (DEBUG) {
+ Slog.d(TAG, "Delivering display event: displayId=" + displayId + ", event=" + event);
+ }
+
+ final int count;
+ synchronized (mLock) {
+ count = mCallbacks.size();
+ mTempCallbacks.clear();
+ for (int i = 0; i < count; i++) {
+ mTempCallbacks.add(mCallbacks.valueAt(i));
+ }
+ }
+
+ for (int i = 0; i < count; i++) {
+ mTempCallbacks.get(i).notifyDisplayEventAsync(displayId, event);
+ }
+ mTempCallbacks.clear();
}
@Override // Binder call
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (mContext == null
|| mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -158,19 +440,159 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
return;
}
- pw.println("DISPLAY MANAGER (dumpsys display)\n");
+ pw.println("DISPLAY MANAGER (dumpsys display)");
+ pw.println(" mHeadless=" + mHeadless);
- pw.println("Headless: " + mHeadless);
+ mHandler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ dumpLocal(pw);
+ }
+ });
+ }
+ // Runs on handler.
+ private void dumpLocal(PrintWriter pw) {
synchronized (mLock) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+
+ pw.println();
+ pw.println("Display Adapters: size=" + mDisplayAdapters.size());
for (DisplayAdapter adapter : mDisplayAdapters) {
- pw.println("Adapter: " + adapter.getName());
+ pw.println(" " + adapter.getName());
+ adapter.dump(ipw);
+ }
+
+ pw.println();
+ pw.println("Display Devices: size=" + mDisplayDevices.size());
+ for (DisplayDevice device : mDisplayDevices) {
+ pw.println(" " + device);
+ }
+
+ final int logicalDisplayCount = mLogicalDisplays.size();
+ pw.println();
+ pw.println("Logical Displays: size=" + logicalDisplayCount);
+ for (int i = 0; i < logicalDisplayCount; i++) {
+ int displayId = mLogicalDisplays.keyAt(i);
+ LogicalDisplay display = mLogicalDisplays.valueAt(i);
+ pw.println(" Display " + displayId + ":");
+ pw.println(" mPrimaryDisplayDevice=" + display.mPrimaryDisplayDevice);
+ pw.println(" mBaseDisplayInfo=" + display.mBaseDisplayInfo);
+ pw.println(" mOverrideDisplayInfo="
+ + display.mOverrideDisplayInfo);
+ }
+ }
+ }
+
+ private final class DisplayManagerHandler extends Handler {
+ public DisplayManagerHandler(Looper looper) {
+ super(looper, null, true /*async*/);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER:
+ registerDefaultDisplayAdapter();
+ break;
+
+ case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS:
+ registerAdditionalDisplayAdapters();
+ break;
+
+ case MSG_DELIVER_DISPLAY_EVENT:
+ deliverDisplayEvent(msg.arg1, msg.arg2);
+ break;
+ }
+ }
+ }
+
+ private final class DisplayAdapterListener implements DisplayAdapter.Listener {
+ @Override
+ public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+ switch (event) {
+ case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED:
+ handleDisplayDeviceAdded(device);
+ break;
+
+ case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:
+ handleDisplayDeviceChanged(device);
+ break;
+
+ case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:
+ handleDisplayDeviceRemoved(device);
+ break;
+ }
+ }
+ }
+
+ private final class CallbackRecord implements DeathRecipient {
+ private final int mPid;
+ private final IDisplayManagerCallback mCallback;
+
+ public CallbackRecord(int pid, IDisplayManagerCallback callback) {
+ mPid = pid;
+ mCallback = callback;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DEBUG) {
+ Slog.d(TAG, "Display listener for pid " + mPid + " died.");
+ }
+ onCallbackDied(mPid);
+ }
+
+ public void notifyDisplayEventAsync(int displayId, int event) {
+ try {
+ mCallback.onDisplayEvent(displayId, event);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process "
+ + mPid + " that displays changed, assuming it died.", ex);
+ binderDied();
}
+ }
+ }
- pw.println("Default display info: " + mDefaultDisplayInfo);
+ /**
+ * Each logical display is primarily associated with one display device.
+ * The primary display device is nominally responsible for the basic properties
+ * of the logical display such as its size, refresh rate, and dpi.
+ *
+ * A logical display may be mirrored onto other display devices besides its
+ * primary display device, but it always remains bound to its primary.
+ * Note that the contents of a logical display may not always be visible, even
+ * on its primary display device, such as in the case where the logical display's
+ * primary display device is currently mirroring content from a different logical display.
+ */
+ private final static class LogicalDisplay {
+ public final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
+ public DisplayInfo mOverrideDisplayInfo; // set by the window manager
+
+ public final DisplayDevice mPrimaryDisplayDevice;
+ public final DisplayDeviceInfo mPrimaryDisplayDeviceInfo = new DisplayDeviceInfo();
+
+ public LogicalDisplay(DisplayDevice primaryDisplayDevice) {
+ mPrimaryDisplayDevice = primaryDisplayDevice;
}
- pw.println("Default display: "
- + DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY));
+ public void updateFromPrimaryDisplayDevice() {
+ // Bootstrap the logical display using its associated primary physical display.
+ mPrimaryDisplayDevice.getInfo(mPrimaryDisplayDeviceInfo);
+
+ mBaseDisplayInfo.appWidth = mPrimaryDisplayDeviceInfo.width;
+ mBaseDisplayInfo.appHeight = mPrimaryDisplayDeviceInfo.height;
+ mBaseDisplayInfo.logicalWidth = mPrimaryDisplayDeviceInfo.width;
+ mBaseDisplayInfo.logicalHeight = mPrimaryDisplayDeviceInfo.height;
+ mBaseDisplayInfo.rotation = Surface.ROTATION_0;
+ mBaseDisplayInfo.refreshRate = mPrimaryDisplayDeviceInfo.refreshRate;
+ mBaseDisplayInfo.logicalDensityDpi = mPrimaryDisplayDeviceInfo.densityDpi;
+ mBaseDisplayInfo.physicalXDpi = mPrimaryDisplayDeviceInfo.xDpi;
+ mBaseDisplayInfo.physicalYDpi = mPrimaryDisplayDeviceInfo.yDpi;
+ mBaseDisplayInfo.smallestNominalAppWidth = mPrimaryDisplayDeviceInfo.width;
+ mBaseDisplayInfo.smallestNominalAppHeight = mPrimaryDisplayDeviceInfo.height;
+ mBaseDisplayInfo.largestNominalAppWidth = mPrimaryDisplayDeviceInfo.width;
+ mBaseDisplayInfo.largestNominalAppHeight = mPrimaryDisplayDeviceInfo.height;
+ }
}
}
diff --git a/services/java/com/android/server/display/HeadlessDisplayAdapter.java b/services/java/com/android/server/display/HeadlessDisplayAdapter.java
index 17c2360..f5c78b9 100644
--- a/services/java/com/android/server/display/HeadlessDisplayAdapter.java
+++ b/services/java/com/android/server/display/HeadlessDisplayAdapter.java
@@ -21,42 +21,40 @@ import android.util.DisplayMetrics;
/**
* Provides a fake default display for headless systems.
+ * <p>
+ * Display adapters are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
+ * </p>
*/
public final class HeadlessDisplayAdapter extends DisplayAdapter {
- private final Context mContext;
- private final HeadlessDisplayDevice mDefaultDisplayDevice;
+ private static final String TAG = "HeadlessDisplayAdapter";
public HeadlessDisplayAdapter(Context context) {
- mContext = context;
- mDefaultDisplayDevice = new HeadlessDisplayDevice();
+ super(context, TAG);
}
@Override
- public String getName() {
- return "HeadlessDisplayAdapter";
- }
-
- @Override
- public void register(Listener listener) {
- listener.onDisplayDeviceAdded(mDefaultDisplayDevice);
+ protected void onRegister() {
+ sendDisplayDeviceEvent(new HeadlessDisplayDevice(), DISPLAY_DEVICE_EVENT_ADDED);
}
private final class HeadlessDisplayDevice extends DisplayDevice {
- @Override
- public DisplayAdapter getAdapter() {
- return HeadlessDisplayAdapter.this;
+ public HeadlessDisplayDevice() {
+ super(HeadlessDisplayAdapter.this, null);
}
@Override
public void getInfo(DisplayDeviceInfo outInfo) {
- outInfo.name = mContext.getResources().getString(
- com.android.internal.R.string.display_manager_built_in_display);
+ outInfo.name = getContext().getResources().getString(
+ com.android.internal.R.string.display_manager_built_in_display_name);
outInfo.width = 640;
outInfo.height = 480;
outInfo.refreshRate = 60;
outInfo.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
outInfo.xDpi = 160;
outInfo.yDpi = 160;
+ outInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
+ | DisplayDeviceInfo.FLAG_SECURE;
}
}
}
diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java
new file mode 100644
index 0000000..73544fc
--- /dev/null
+++ b/services/java/com/android/server/display/LocalDisplayAdapter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.view.Surface;
+import android.view.Surface.PhysicalDisplayInfo;
+
+/**
+ * A display adapter for the local displays managed by Surface Flinger.
+ * <p>
+ * Display adapters are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
+ * </p>
+ */
+public final class LocalDisplayAdapter extends DisplayAdapter {
+ private static final String TAG = "LocalDisplayAdapter";
+
+ public LocalDisplayAdapter(Context context) {
+ super(context, TAG);
+ }
+
+ @Override
+ protected void onRegister() {
+ // TODO: listen for notifications from Surface Flinger about
+ // built-in displays being added or removed and rescan as needed.
+ IBinder displayToken = Surface.getBuiltInDisplay(Surface.BUILT_IN_DISPLAY_ID_MAIN);
+ sendDisplayDeviceEvent(new LocalDisplayDevice(displayToken, true),
+ DISPLAY_DEVICE_EVENT_ADDED);
+ }
+
+ private final class LocalDisplayDevice extends DisplayDevice {
+ private final boolean mIsDefault;
+
+ public LocalDisplayDevice(IBinder displayToken, boolean isDefault) {
+ super(LocalDisplayAdapter.this, displayToken);
+ mIsDefault = isDefault;
+ }
+
+ @Override
+ public void getInfo(DisplayDeviceInfo outInfo) {
+ PhysicalDisplayInfo phys = new PhysicalDisplayInfo();
+ Surface.getDisplayInfo(getDisplayToken(), phys);
+
+ outInfo.name = getContext().getResources().getString(
+ com.android.internal.R.string.display_manager_built_in_display_name);
+ outInfo.width = phys.width;
+ outInfo.height = phys.height;
+ outInfo.refreshRate = phys.refreshRate;
+ outInfo.densityDpi = (int)(phys.density * 160 + 0.5f);
+ outInfo.xDpi = phys.xDpi;
+ outInfo.yDpi = phys.yDpi;
+ if (mIsDefault) {
+ outInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
+ | DisplayDeviceInfo.FLAG_SECURE;
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java
new file mode 100644
index 0000000..476d21a
--- /dev/null
+++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.SurfaceTexture;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A display adapter that uses overlay windows to simulate secondary displays
+ * for development purposes. Use Development Settings to enable one or more
+ * overlay displays.
+ * <p>
+ * Display adapters are not thread-safe and must only be accessed
+ * on the display manager service's handler thread.
+ * </p>
+ */
+public final class OverlayDisplayAdapter extends DisplayAdapter {
+ private static final String TAG = "OverlayDisplayAdapter";
+
+ private static final int MIN_WIDTH = 100;
+ private static final int MIN_HEIGHT = 100;
+ private static final int MAX_WIDTH = 4096;
+ private static final int MAX_HEIGHT = 4096;
+
+ private static final Pattern SETTING_PATTERN =
+ Pattern.compile("(\\d+)x(\\d+)/(\\d+)");
+
+ private final ArrayList<Overlay> mOverlays = new ArrayList<Overlay>();
+ private String mCurrentOverlaySetting = "";
+
+ public OverlayDisplayAdapter(Context context) {
+ super(context, TAG);
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
+ pw.println("mOverlays: size=" + mOverlays.size());
+ for (Overlay overlay : mOverlays) {
+ overlay.dump(pw);
+ }
+ }
+
+ @Override
+ protected void onRegister() {
+ getContext().getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES), true,
+ new ContentObserver(getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateOverlayDisplayDevices();
+ }
+ });
+ updateOverlayDisplayDevices();
+ }
+
+ private void updateOverlayDisplayDevices() {
+ String value = Settings.System.getString(getContext().getContentResolver(),
+ Settings.Secure.OVERLAY_DISPLAY_DEVICES);
+ if (value == null) {
+ value = "";
+ }
+
+ if (value.equals(mCurrentOverlaySetting)) {
+ return;
+ }
+ mCurrentOverlaySetting = value;
+
+ if (!mOverlays.isEmpty()) {
+ Slog.i(TAG, "Dismissing all overlay display devices.");
+ for (Overlay overlay : mOverlays) {
+ overlay.dismiss();
+ }
+ mOverlays.clear();
+ }
+
+ int number = 1;
+ for (String part : value.split(";")) {
+ if (number > 4) {
+ Slog.w(TAG, "Too many overlay display devices.");
+ }
+ Matcher matcher = SETTING_PATTERN.matcher(part);
+ if (matcher.matches()) {
+ try {
+ int width = Integer.parseInt(matcher.group(1), 10);
+ int height = Integer.parseInt(matcher.group(2), 10);
+ int densityDpi = Integer.parseInt(matcher.group(3), 10);
+ if (width >= MIN_WIDTH && width <= MAX_WIDTH
+ && height >= MIN_HEIGHT && height <= MAX_HEIGHT
+ && densityDpi >= DisplayMetrics.DENSITY_LOW
+ && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) {
+ Slog.i(TAG, "Showing overlay display device #" + number
+ + ": width=" + width + ", height=" + height
+ + ", densityDpi=" + densityDpi);
+ mOverlays.add(new Overlay(number++, width, height, densityDpi));
+ continue;
+ }
+ } catch (NumberFormatException ex) {
+ }
+ } else if (part.isEmpty()) {
+ continue;
+ }
+ Slog.w(TAG, "Malformed overlay display devices setting: \"" + value + "\"");
+ }
+
+ for (Overlay overlay : mOverlays) {
+ overlay.show();
+ }
+ }
+
+ // Manages an overlay window.
+ private final class Overlay {
+ private final float INITIAL_SCALE = 0.5f;
+ private final float MIN_SCALE = 0.3f;
+ private final float MAX_SCALE = 1.0f;
+ private final float WINDOW_ALPHA = 0.8f;
+
+ // When true, disables support for moving and resizing the overlay.
+ // The window is made non-touchable, which makes it possible to
+ // directly interact with the content underneath.
+ private final boolean DISABLE_MOVE_AND_RESIZE = false;
+
+ private final DisplayManager mDisplayManager;
+ private final WindowManager mWindowManager;
+
+ private final int mNumber;
+ private final int mWidth;
+ private final int mHeight;
+ private final int mDensityDpi;
+
+ private final String mName;
+ private final String mTitle;
+
+ private final Display mDefaultDisplay;
+ private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
+ private final IBinder mDisplayToken;
+ private final OverlayDisplayDevice mDisplayDevice;
+
+ private View mWindowContent;
+ private WindowManager.LayoutParams mWindowParams;
+ private TextureView mTextureView;
+ private TextView mTitleTextView;
+ private ScaleGestureDetector mScaleGestureDetector;
+
+ private boolean mWindowVisible;
+ private int mWindowX;
+ private int mWindowY;
+ private float mWindowScale;
+
+ private int mLiveTranslationX;
+ private int mLiveTranslationY;
+ private float mLiveScale = 1.0f;
+
+ private int mDragPointerId;
+ private float mDragTouchX;
+ private float mDragTouchY;
+
+ public Overlay(int number, int width, int height, int densityDpi) {
+ Context context = getContext();
+ mDisplayManager = (DisplayManager)context.getSystemService(
+ Context.DISPLAY_SERVICE);
+ mWindowManager = (WindowManager)context.getSystemService(
+ Context.WINDOW_SERVICE);
+
+ mNumber = number;
+ mWidth = width;
+ mHeight = height;
+ mDensityDpi = densityDpi;
+
+ mName = context.getResources().getString(
+ com.android.internal.R.string.display_manager_overlay_display_name, number);
+ mTitle = context.getResources().getString(
+ com.android.internal.R.string.display_manager_overlay_display_title,
+ mNumber, mWidth, mHeight, mDensityDpi);
+
+ mDefaultDisplay = mWindowManager.getDefaultDisplay();
+ updateDefaultDisplayInfo();
+
+ mDisplayToken = Surface.createDisplay(mName);
+ mDisplayDevice = new OverlayDisplayDevice(mDisplayToken, mName,
+ mDefaultDisplayInfo.refreshRate, mDensityDpi);
+
+ createWindow();
+ }
+
+ public void show() {
+ if (!mWindowVisible) {
+ mDisplayManager.registerDisplayListener(mDisplayListener, null);
+ if (!updateDefaultDisplayInfo()) {
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ return;
+ }
+
+ clearLiveState();
+ updateWindowParams();
+ mWindowManager.addView(mWindowContent, mWindowParams);
+ mWindowVisible = true;
+ }
+ }
+
+ public void dismiss() {
+ if (mWindowVisible) {
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ mWindowManager.removeView(mWindowContent);
+ mWindowVisible = false;
+ }
+ }
+
+ public void relayout() {
+ if (mWindowVisible) {
+ updateWindowParams();
+ mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
+ }
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println(" #" + mNumber + ": "
+ + mWidth + "x" + mHeight + ", " + mDensityDpi + " dpi");
+ pw.println(" mName=" + mName);
+ pw.println(" mWindowVisible=" + mWindowVisible);
+ pw.println(" mWindowX=" + mWindowX);
+ pw.println(" mWindowY=" + mWindowY);
+ pw.println(" mWindowScale=" + mWindowScale);
+ pw.println(" mWindowParams=" + mWindowParams);
+ pw.println(" mLiveTranslationX=" + mLiveTranslationX);
+ pw.println(" mLiveTranslationY=" + mLiveTranslationY);
+ pw.println(" mLiveScale=" + mLiveScale);
+ }
+
+ private boolean updateDefaultDisplayInfo() {
+ if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
+ Slog.w(TAG, "Cannot show overlay display because there is no "
+ + "default display upon which to show it.");
+ return false;
+ }
+ return true;
+ }
+
+ private void createWindow() {
+ Context context = getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+
+ mWindowContent = inflater.inflate(
+ com.android.internal.R.layout.overlay_display_window, null);
+ mWindowContent.setOnTouchListener(mOnTouchListener);
+
+ mTextureView = (TextureView)mWindowContent.findViewById(
+ com.android.internal.R.id.overlay_display_window_texture);
+ mTextureView.setPivotX(0);
+ mTextureView.setPivotY(0);
+ mTextureView.getLayoutParams().width = mWidth;
+ mTextureView.getLayoutParams().height = mHeight;
+ mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
+
+ mTitleTextView = (TextView)mWindowContent.findViewById(
+ com.android.internal.R.id.overlay_display_window_title);
+ mTitleTextView.setText(mTitle);
+
+ mWindowParams = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY);
+ mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ if (DISABLE_MOVE_AND_RESIZE) {
+ mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ }
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
+ mWindowParams.alpha = WINDOW_ALPHA;
+ mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
+ mWindowParams.setTitle(mTitle);
+
+ mScaleGestureDetector = new ScaleGestureDetector(context, mOnScaleGestureListener);
+
+ // By default, arrange the displays in the four corners.
+ mWindowVisible = false;
+ mWindowScale = INITIAL_SCALE;
+ if (mNumber == 2 || mNumber == 3) {
+ mWindowX = mDefaultDisplayInfo.logicalWidth;
+ } else {
+ mWindowX = 0;
+ }
+ if (mNumber == 2 || mNumber == 4) {
+ mWindowY = mDefaultDisplayInfo.logicalHeight;
+ } else {
+ mWindowY = 0;
+ }
+ }
+
+ private void updateWindowParams() {
+ float scale = mWindowScale * mLiveScale;
+ if (mWidth * scale > mDefaultDisplayInfo.logicalWidth) {
+ scale = mDefaultDisplayInfo.logicalWidth / mWidth;
+ }
+ if (mHeight * scale > mDefaultDisplayInfo.logicalHeight) {
+ scale = mDefaultDisplayInfo.logicalHeight / mHeight;
+ }
+ scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
+
+ float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
+ int width = (int)(mWidth * scale);
+ int height = (int)(mHeight * scale);
+ int x = mWindowX + mLiveTranslationX - (int)(width * offsetScale);
+ int y = mWindowY + mLiveTranslationY - (int)(height * offsetScale);
+ x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width));
+ y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height));
+
+ mTextureView.setScaleX(scale);
+ mTextureView.setScaleY(scale);
+
+ mWindowParams.x = x;
+ mWindowParams.y = y;
+ mWindowParams.width = width;
+ mWindowParams.height = height;
+ }
+
+ private void saveWindowParams() {
+ mWindowX = mWindowParams.x;
+ mWindowY = mWindowParams.y;
+ mWindowScale = mTextureView.getScaleX();
+ clearLiveState();
+ }
+
+ private void clearLiveState() {
+ mLiveTranslationX = 0;
+ mLiveTranslationY = 0;
+ mLiveScale = 1.0f;
+ }
+
+ private final DisplayManager.DisplayListener mDisplayListener =
+ new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == mDefaultDisplay.getDisplayId()) {
+ if (updateDefaultDisplayInfo()) {
+ relayout();
+ } else {
+ dismiss();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ if (displayId == mDefaultDisplay.getDisplayId()) {
+ dismiss();
+ }
+ }
+ };
+
+ private final SurfaceTextureListener mSurfaceTextureListener =
+ new SurfaceTextureListener() {
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ Surface.openTransaction();
+ try {
+ Surface.setDisplaySurface(mDisplayToken, surface);
+ } finally {
+ Surface.closeTransaction();
+ }
+
+ mDisplayDevice.setSize(width, height);
+ sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
+
+ Surface.openTransaction();
+ try {
+ Surface.setDisplaySurface(mDisplayToken, null);
+ } finally {
+ Surface.closeTransaction();
+ }
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ mDisplayDevice.setSize(width, height);
+ sendDisplayDeviceEvent(mDisplayDevice, DISPLAY_DEVICE_EVENT_CHANGED);
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ }
+ };
+
+ private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ // Work in screen coordinates.
+ final float oldX = event.getX();
+ final float oldY = event.getY();
+ event.setLocation(event.getRawX(), event.getRawY());
+
+ mScaleGestureDetector.onTouchEvent(event);
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ resetDrag(event);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (event.getPointerCount() == 1) {
+ int index = event.findPointerIndex(mDragPointerId);
+ if (index < 0) {
+ resetDrag(event);
+ } else {
+ mLiveTranslationX = (int)(event.getX(index) - mDragTouchX);
+ mLiveTranslationY = (int)(event.getY(index) - mDragTouchY);
+ relayout();
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ saveWindowParams();
+ break;
+ }
+
+ // Revert to window coordinates.
+ event.setLocation(oldX, oldY);
+ return true;
+ }
+
+ private void resetDrag(MotionEvent event) {
+ saveWindowParams();
+ mDragPointerId = event.getPointerId(0);
+ mDragTouchX = event.getX();
+ mDragTouchY = event.getY();
+ }
+ };
+
+ private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
+ new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ saveWindowParams();
+ mDragPointerId = -1; // cause drag to be reset
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ mLiveScale = detector.getScaleFactor();
+ relayout();
+ return false;
+ }
+ };
+ }
+
+ private final class OverlayDisplayDevice extends DisplayDevice {
+ private final String mName;
+ private final float mRefreshRate;
+ private final int mDensityDpi;
+ private int mWidth;
+ private int mHeight;
+
+ public OverlayDisplayDevice(IBinder displayToken, String name,
+ float refreshRate, int densityDpi) {
+ super(OverlayDisplayAdapter.this, displayToken);
+ mName = name;
+ mRefreshRate = refreshRate;
+ mDensityDpi = densityDpi;
+ }
+
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public void getInfo(DisplayDeviceInfo outInfo) {
+ outInfo.name = mName;
+ outInfo.width = mWidth;
+ outInfo.height = mHeight;
+ outInfo.refreshRate = mRefreshRate;
+ outInfo.densityDpi = mDensityDpi;
+ outInfo.xDpi = mDensityDpi;
+ outInfo.yDpi = mDensityDpi;
+ outInfo.flags = DisplayDeviceInfo.FLAG_SECURE;
+ }
+ }
+}
diff --git a/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java b/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java
deleted file mode 100644
index 9531acb..0000000
--- a/services/java/com/android/server/display/SurfaceFlingerDisplayAdapter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import android.content.Context;
-
-/**
- * A display adapter for the displays managed by Surface Flinger.
- */
-public final class SurfaceFlingerDisplayAdapter extends DisplayAdapter {
- private final Context mContext;
- private final SurfaceFlingerDisplayDevice mDefaultDisplayDevice;
-
- private static native void nativeGetDefaultDisplayDeviceInfo(DisplayDeviceInfo outInfo);
-
- public SurfaceFlingerDisplayAdapter(Context context) {
- mContext = context;
- mDefaultDisplayDevice = new SurfaceFlingerDisplayDevice();
- }
-
- @Override
- public String getName() {
- return "SurfaceFlingerDisplayAdapter";
- }
-
- @Override
- public void register(Listener listener) {
- listener.onDisplayDeviceAdded(mDefaultDisplayDevice);
- }
-
- private final class SurfaceFlingerDisplayDevice extends DisplayDevice {
- @Override
- public DisplayAdapter getAdapter() {
- return SurfaceFlingerDisplayAdapter.this;
- }
-
- @Override
- public void getInfo(DisplayDeviceInfo outInfo) {
- outInfo.name = mContext.getResources().getString(
- com.android.internal.R.string.display_manager_built_in_display);
- nativeGetDefaultDisplayDeviceInfo(outInfo);
- }
- }
-}
diff --git a/services/java/com/android/server/net/LockdownVpnTracker.java b/services/java/com/android/server/net/LockdownVpnTracker.java
index 541650e..dabcf2f 100644
--- a/services/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/java/com/android/server/net/LockdownVpnTracker.java
@@ -55,6 +55,7 @@ public class LockdownVpnTracker {
private static final int MAX_ERROR_COUNT = 4;
private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
+ private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
private final Context mContext;
private final INetworkManagementService mNetService;
@@ -84,9 +85,9 @@ public class LockdownVpnTracker {
mVpn = Preconditions.checkNotNull(vpn);
mProfile = Preconditions.checkNotNull(profile);
- final Intent intent = new Intent(ACTION_LOCKDOWN_RESET);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mResetIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
+ resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
}
private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
@@ -115,7 +116,7 @@ public class LockdownVpnTracker {
final boolean egressChanged = egressProp == null
|| !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
if (egressDisconnected || egressChanged) {
- clearSourceRules();
+ clearSourceRulesLocked();
mAcceptedEgressIface = null;
mVpn.stopLegacyVpn();
}
@@ -150,7 +151,7 @@ public class LockdownVpnTracker {
showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
try {
- clearSourceRules();
+ clearSourceRulesLocked();
mNetService.setFirewallInterfaceRule(iface, true);
mNetService.setFirewallEgressSourceRule(sourceAddr, true);
@@ -167,7 +168,13 @@ public class LockdownVpnTracker {
}
public void init() {
- Slog.d(TAG, "init()");
+ synchronized (mStateLock) {
+ initLocked();
+ }
+ }
+
+ private void initLocked() {
+ Slog.d(TAG, "initLocked()");
mVpn.setEnableNotifications(false);
@@ -188,7 +195,13 @@ public class LockdownVpnTracker {
}
public void shutdown() {
- Slog.d(TAG, "shutdown()");
+ synchronized (mStateLock) {
+ shutdownLocked();
+ }
+ }
+
+ private void shutdownLocked() {
+ Slog.d(TAG, "shutdownLocked()");
mAcceptedEgressIface = null;
mErrorCount = 0;
@@ -200,7 +213,7 @@ public class LockdownVpnTracker {
} catch (RemoteException e) {
throw new RuntimeException("Problem setting firewall rules", e);
}
- clearSourceRules();
+ clearSourceRulesLocked();
hideNotification();
mContext.unregisterReceiver(mResetReceiver);
@@ -208,15 +221,15 @@ public class LockdownVpnTracker {
}
public void reset() {
- // cycle tracker, reset error count, and trigger retry
- shutdown();
- init();
synchronized (mStateLock) {
+ // cycle tracker, reset error count, and trigger retry
+ shutdownLocked();
+ initLocked();
handleStateChangedLocked();
}
}
- private void clearSourceRules() {
+ private void clearSourceRulesLocked() {
try {
if (mAcceptedIface != null) {
mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index b84e25a..48a4b74 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -710,20 +710,56 @@ public class PackageManagerService extends IPackageManager.Stub {
res.removedInfo.sendBroadcast(false, true);
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, res.uid);
+ // Determine the set of users who are adding this
+ // package for the first time vs. those who are seeing
+ // an update.
+ int[] firstUsers;
+ int[] updateUsers = new int[0];
+ if (res.origUsers == null || res.origUsers.length == 0) {
+ firstUsers = res.newUsers;
+ } else {
+ firstUsers = new int[0];
+ for (int i=0; i<res.newUsers.length; i++) {
+ int user = res.newUsers[i];
+ boolean isNew = true;
+ for (int j=0; j<res.origUsers.length; j++) {
+ if (res.origUsers[j] == user) {
+ isNew = false;
+ break;
+ }
+ }
+ if (isNew) {
+ int[] newFirst = new int[firstUsers.length+1];
+ System.arraycopy(firstUsers, 0, newFirst, 0,
+ firstUsers.length);
+ newFirst[firstUsers.length] = user;
+ firstUsers = newFirst;
+ } else {
+ int[] newUpdate = new int[updateUsers.length+1];
+ System.arraycopy(updateUsers, 0, newUpdate, 0,
+ updateUsers.length);
+ newUpdate[updateUsers.length] = user;
+ updateUsers = newUpdate;
+ }
+ }
+ }
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
+ res.pkg.applicationInfo.packageName,
+ extras, null, null, firstUsers);
final boolean update = res.removedInfo.removedPackage != null;
if (update) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
res.pkg.applicationInfo.packageName,
- extras, null, null, res.users);
+ extras, null, null, updateUsers);
if (update) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
res.pkg.applicationInfo.packageName,
- extras, null, null, res.users);
+ extras, null, null, updateUsers);
sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
null, null,
- res.pkg.applicationInfo.packageName, null, res.users);
+ res.pkg.applicationInfo.packageName, null, updateUsers);
}
if (res.removedInfo.args != null) {
// Remove the replaced package's older resources safely now
@@ -1782,7 +1818,7 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
pkg.applicationInfo.sourceDir = ps.codePathString;
pkg.applicationInfo.dataDir =
- getDataPathForPackage(ps.pkg.packageName, 0).getPath();
+ getDataPathForPackage(packageName, 0).getPath();
pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString;
}
// pkg.mSetEnabled = ps.getEnabled(userId);
@@ -5278,6 +5314,7 @@ public class PackageManagerService extends IPackageManager.Stub {
return pkgs.get(0);
}
}
+ mSettings.mPackagesToBeCleaned.remove(userId);
}
// Move on to the next user to clean.
long ident = Binder.clearCallingIdentity();
@@ -5343,8 +5380,10 @@ public class PackageManagerService extends IPackageManager.Stub {
public void onEvent(int event, String path) {
String removedPackage = null;
int removedUid = -1;
+ int[] removedUsers = null;
String addedPackage = null;
int addedUid = -1;
+ int[] addedUsers = null;
// TODO post a message to the handler to obtain serial ordering
synchronized (mInstallLock) {
@@ -5373,6 +5412,15 @@ public class PackageManagerService extends IPackageManager.Stub {
// reader
synchronized (mPackages) {
p = mAppDirs.get(fullPathStr);
+ if (p != null) {
+ PackageSetting ps = mSettings.mPackages.get(p.applicationInfo.packageName);
+ if (ps != null) {
+ removedUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
+ } else {
+ removedUsers = sUserManager.getUserIds();
+ }
+ }
+ addedUsers = sUserManager.getUserIds();
}
if ((event&REMOVE_EVENTS) != 0) {
if (p != null) {
@@ -5390,7 +5438,7 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageParser.PARSE_CHATTY |
PackageParser.PARSE_MUST_BE_APK,
SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,
- System.currentTimeMillis(), null);
+ System.currentTimeMillis(), UserHandle.ALL);
if (p != null) {
/*
* TODO this seems dangerous as the package may have
@@ -5419,13 +5467,13 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putInt(Intent.EXTRA_UID, removedUid);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false);
sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
- extras, null, null, null);
+ extras, null, null, removedUsers);
}
if (addedPackage != null) {
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, addedUid);
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage,
- extras, null, null, null);
+ extras, null, null, addedUsers);
}
}
@@ -5468,7 +5516,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
} else {
- user = Process.myUserHandle();
+ user = new UserHandle(UserHandle.getUserId(uid));
}
final int filteredFlags;
@@ -5531,6 +5579,10 @@ public class PackageManagerService extends IPackageManager.Stub {
@Override
public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+ "Only package verification agents can verify applications");
+
final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED);
final PackageVerificationResponse response = new PackageVerificationResponse(
verificationCode, Binder.getCallingUid());
@@ -5542,6 +5594,10 @@ public class PackageManagerService extends IPackageManager.Stub {
@Override
public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
long millisecondsToDelay) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+ "Only package verification agents can extend verification timeouts");
+
final PackageVerificationState state = mPendingVerification.get(id);
final PackageVerificationResponse response = new PackageVerificationResponse(
verificationCodeAtTimeout, Binder.getCallingUid());
@@ -7212,7 +7268,10 @@ public class PackageManagerService extends IPackageManager.Stub {
class PackageInstalledInfo {
String name;
int uid;
- int[] users;
+ // The set of users that originally had this package installed.
+ int[] origUsers;
+ // The set of users that now have this package installed.
+ int[] newUsers;
PackageParser.Package pkg;
int returnCode;
PackageRemovedInfo removedInfo;
@@ -7362,7 +7421,7 @@ public class PackageManagerService extends IPackageManager.Stub {
int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE
| SCAN_UPDATE_TIME;
if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode,
- origUpdateTime, user) == null) {
+ origUpdateTime, null) == null) {
Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade");
return;
}
@@ -7511,10 +7570,6 @@ public class PackageManagerService extends IPackageManager.Stub {
UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
? UPDATE_PERMISSIONS_ALL : 0));
res.name = pkgName;
- PackageSetting ps = mSettings.mPackages.get(pkgName);
- if (ps != null) {
- res.users = ps.getInstalledUsers(sUserManager.getUserIds());
- }
res.uid = newPackage.applicationInfo.uid;
res.pkg = newPackage;
mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE);
@@ -7612,6 +7667,7 @@ public class PackageManagerService extends IPackageManager.Stub {
systemApp = (ps.pkg.applicationInfo.flags &
ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ res.origUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
}
}
@@ -7634,12 +7690,12 @@ public class PackageManagerService extends IPackageManager.Stub {
installerPackageName, res);
} else {
installNewPackageLI(pkg, parseFlags, scanMode, args.user,
- installerPackageName,res);
+ installerPackageName, res);
}
synchronized (mPackages) {
- PackageSetting ps = mSettings.mPackages.get(pkgName);
+ final PackageSetting ps = mSettings.mPackages.get(pkgName);
if (ps != null) {
- res.users = ps.getInstalledUsers(sUserManager.getUserIds());
+ res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
}
}
}
@@ -7865,7 +7921,8 @@ public class PackageManagerService extends IPackageManager.Stub {
if (outInfo != null) {
outInfo.removedPackage = packageName;
outInfo.removedUsers = deletedPs != null
- ? deletedPs.getInstalledUsers(sUserManager.getUserIds()) : null;
+ ? deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true)
+ : null;
}
}
if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java
index 6d31f0e..d8f7345 100644
--- a/services/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/java/com/android/server/pm/PackageSettingBase.java
@@ -210,17 +210,17 @@ class PackageSettingBase extends GrantedPermissions {
return false;
}
- int[] getInstalledUsers(int[] users) {
+ int[] queryInstalledUsers(int[] users, boolean installed) {
int num = 0;
for (int user : users) {
- if (getInstalled(user)) {
+ if (getInstalled(user) == installed) {
num++;
}
}
int[] res = new int[num];
num = 0;
for (int user : users) {
- if (getInstalled(user)) {
+ if (getInstalled(user) == installed) {
res[num] = user;
num++;
}
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index 750aa72..fb04d0f 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -23,6 +23,8 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.IStopUserCallback;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -33,6 +35,7 @@ import android.os.FileUtils;
import android.os.IUserManager;
import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.Slog;
@@ -549,13 +552,36 @@ public class UserManagerService extends IUserManager.Stub {
*/
public boolean removeUser(int userHandle) {
checkManageUsersPermission("Only the system can remove users");
+ final UserInfo user;
+ synchronized (mPackagesLock) {
+ user = mUsers.get(userHandle);
+ if (userHandle == 0 || user == null) {
+ return false;
+ }
+ }
+
+ int res;
+ try {
+ res = ActivityManagerNative.getDefault().stopUser(userHandle,
+ new IStopUserCallback.Stub() {
+ @Override
+ public void userStopped(int userId) {
+ finishRemoveUser(userId);
+ }
+ @Override
+ public void userStopAborted(int userId) {
+ }
+ });
+ } catch (RemoteException e) {
+ return false;
+ }
+
+ return res == ActivityManager.USER_OP_SUCCESS;
+ }
+
+ void finishRemoveUser(int userHandle) {
synchronized (mInstallLock) {
synchronized (mPackagesLock) {
- final UserInfo user = mUsers.get(userHandle);
- if (userHandle == 0 || user == null) {
- return false;
- }
-
// Cleanup package manager settings
mPm.cleanUpUserLILPw(userHandle);
@@ -574,7 +600,6 @@ public class UserManagerService extends IUserManager.Stub {
Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS);
- return true;
}
@Override
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
index cd211da..6b6d899 100644
--- a/services/java/com/android/server/power/DisplayPowerController.java
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -45,7 +45,6 @@ import android.view.Display;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
/**
@@ -169,6 +168,9 @@ final class DisplayPowerController {
// The twilight service.
private final TwilightService mTwilight;
+ // The display manager.
+ private final DisplayManager mDisplayManager;
+
// The sensor manager.
private final SensorManager mSensorManager;
@@ -330,6 +332,7 @@ final class DisplayPowerController {
mLights = lights;
mTwilight = twilight;
mSensorManager = new SystemSensorManager(mHandler.getLooper());
+ mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
final Resources resources = context.getResources();
mScreenBrightnessDimConfig = resources.getInteger(
@@ -475,7 +478,7 @@ final class DisplayPowerController {
private void initialize() {
final Executor executor = AsyncTask.THREAD_POOL_EXECUTOR;
- Display display = DisplayManager.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
+ Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
mPowerState = new DisplayPowerState(new ElectronBeam(display),
new PhotonicModulator(executor,
mLights.getLight(LightsService.LIGHT_ID_BACKLIGHT),
@@ -980,7 +983,7 @@ final class DisplayPowerController {
}
};
- public void dump(PrintWriter pw) {
+ public void dump(final PrintWriter pw) {
synchronized (mLock) {
pw.println();
pw.println("Display Controller Locked State:");
@@ -1000,33 +1003,12 @@ final class DisplayPowerController {
pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline);
pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
- if (Looper.myLooper() == mHandler.getLooper()) {
- dumpLocal(pw);
- } else {
- final StringWriter out = new StringWriter();
- final CountDownLatch latch = new CountDownLatch(1);
- Message msg = Message.obtain(mHandler, new Runnable() {
- @Override
- public void run() {
- PrintWriter localpw = new PrintWriter(out);
- try {
- dumpLocal(localpw);
- } finally {
- localpw.flush();
- latch.countDown();
- }
- }
- });
- msg.setAsynchronous(true);
- mHandler.sendMessage(msg);
- try {
- latch.await();
- pw.print(out.toString());
- } catch (InterruptedException ex) {
- pw.println();
- pw.println("Failed to dump thread state due to interrupted exception!");
+ mHandler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ dumpLocal(pw);
}
- }
+ });
}
private void dumpLocal(PrintWriter pw) {
diff --git a/services/java/com/android/server/power/ElectronBeam.java b/services/java/com/android/server/power/ElectronBeam.java
index aad5a9a..0c68997 100644
--- a/services/java/com/android/server/power/ElectronBeam.java
+++ b/services/java/com/android/server/power/ElectronBeam.java
@@ -481,8 +481,8 @@ final class ElectronBeam {
try {
if (mSurface == null) {
try {
- mSurface = new Surface(mSurfaceSession, Process.myPid(),
- "ElectronBeam", mDisplayLayerStack, mDisplayWidth, mDisplayHeight,
+ mSurface = new Surface(mSurfaceSession,
+ "ElectronBeam", mDisplayWidth, mDisplayHeight,
PixelFormat.OPAQUE, Surface.OPAQUE | Surface.HIDDEN);
} catch (Surface.OutOfResourcesException ex) {
Slog.e(TAG, "Unable to create surface.", ex);
@@ -490,6 +490,7 @@ final class ElectronBeam {
}
}
+ mSurface.setLayerStack(mDisplayLayerStack);
mSurface.setSize(mDisplayWidth, mDisplayHeight);
switch (mDisplayRotation) {
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java
index 6d68104..59d0954 100644
--- a/services/java/com/android/server/power/PowerManagerService.java
+++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -1136,7 +1136,8 @@ public final class PowerManagerService extends IPowerManager.Stub
private boolean isItBedTimeYetLocked() {
return mBootCompleted && !mStayOn
&& (mWakeLockSummary
- & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM)) == 0
+ & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM
+ | WAKE_LOCK_PROXIMITY_SCREEN_OFF)) == 0
&& (mUserActivitySummary
& (USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) == 0;
}
diff --git a/services/java/com/android/server/usb/UsbDebuggingManager.java b/services/java/com/android/server/usb/UsbDebuggingManager.java
index a3b45c7..1bb3a2c 100644
--- a/services/java/com/android/server/usb/UsbDebuggingManager.java
+++ b/services/java/com/android/server/usb/UsbDebuggingManager.java
@@ -53,16 +53,15 @@ public class UsbDebuggingManager implements Runnable {
private final int BUFFER_SIZE = 4096;
private final Context mContext;
- private final Thread mThread;
private final Handler mHandler;
private final HandlerThread mHandlerThread;
+ private Thread mThread;
private boolean mAdbEnabled = false;
private String mFingerprints;
private LocalSocket mSocket = null;
private OutputStream mOutputStream = null;
public UsbDebuggingManager(Context context) {
- mThread = new Thread(this);
mHandlerThread = new HandlerThread("UsbDebuggingHandler");
mHandlerThread.start();
mHandler = new UsbDebuggingHandler(mHandlerThread.getLooper());
@@ -165,6 +164,7 @@ public class UsbDebuggingManager implements Runnable {
mAdbEnabled = true;
+ mThread = new Thread(UsbDebuggingManager.this);
mThread.start();
break;
@@ -181,8 +181,10 @@ public class UsbDebuggingManager implements Runnable {
} catch (Exception ex) {
}
+ mThread = null;
mOutputStream = null;
mSocket = null;
+ break;
case MESSAGE_ADB_ALLOW: {
String key = (String)msg.obj;
diff --git a/services/java/com/android/server/wm/BlackFrame.java b/services/java/com/android/server/wm/BlackFrame.java
index 64d2602..5b77b20 100644
--- a/services/java/com/android/server/wm/BlackFrame.java
+++ b/services/java/com/android/server/wm/BlackFrame.java
@@ -43,14 +43,15 @@ public class BlackFrame {
int w = r-l;
int h = b-t;
if (WindowManagerService.DEBUG_SURFACE_TRACE) {
- surface = new WindowStateAnimator.SurfaceTrace(session, 0, "BlackSurface("
- + l + ", " + t + ")", layerStack,
- w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM);
+ surface = new WindowStateAnimator.SurfaceTrace(session, "BlackSurface("
+ + l + ", " + t + ")",
+ w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM | Surface.HIDDEN);
} else {
- surface = new Surface(session, 0, "BlackSurface", layerStack,
- w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM);
+ surface = new Surface(session, "BlackSurface",
+ w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM | Surface.HIDDEN);
}
surface.setAlpha(1);
+ surface.setLayerStack(layerStack);
surface.setLayer(layer);
surface.show();
if (WindowManagerService.SHOW_TRANSACTIONS ||
diff --git a/services/java/com/android/server/wm/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java
index 81daac6..afcf339 100644
--- a/services/java/com/android/server/wm/DimAnimator.java
+++ b/services/java/com/android/server/wm/DimAnimator.java
@@ -43,20 +43,21 @@ class DimAnimator {
if (mDimSurface == null) {
try {
if (WindowManagerService.DEBUG_SURFACE_TRACE) {
- mDimSurface = new WindowStateAnimator.SurfaceTrace(session, 0,
+ mDimSurface = new WindowStateAnimator.SurfaceTrace(session,
"DimAnimator",
- layerStack, 16, 16, PixelFormat.OPAQUE,
- Surface.FX_SURFACE_DIM);
+ 16, 16, PixelFormat.OPAQUE,
+ Surface.FX_SURFACE_DIM | Surface.HIDDEN);
} else {
- mDimSurface = new Surface(session, 0,
- "DimAnimator",
- layerStack, 16, 16, PixelFormat.OPAQUE,
- Surface.FX_SURFACE_DIM);
+ mDimSurface = new Surface(session, "DimAnimator",
+ 16, 16, PixelFormat.OPAQUE,
+ Surface.FX_SURFACE_DIM | Surface.HIDDEN);
}
if (WindowManagerService.SHOW_TRANSACTIONS ||
WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
" DIM " + mDimSurface + ": CREATE");
+ mDimSurface.setLayerStack(layerStack);
mDimSurface.setAlpha(0.0f);
+ mDimSurface.show();
} catch (Exception e) {
Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e);
}
@@ -212,4 +213,4 @@ class DimAnimator {
mDimTarget = dimTarget;
}
}
-} \ No newline at end of file
+}
diff --git a/services/java/com/android/server/wm/DimSurface.java b/services/java/com/android/server/wm/DimSurface.java
index 4ab8ce1..ddbd70d 100644
--- a/services/java/com/android/server/wm/DimSurface.java
+++ b/services/java/com/android/server/wm/DimSurface.java
@@ -34,20 +34,21 @@ class DimSurface {
if (mDimSurface == null) {
try {
if (WindowManagerService.DEBUG_SURFACE_TRACE) {
- mDimSurface = new WindowStateAnimator.SurfaceTrace(session, 0,
+ mDimSurface = new WindowStateAnimator.SurfaceTrace(session,
"DimSurface",
- layerStack, 16, 16, PixelFormat.OPAQUE,
- Surface.FX_SURFACE_DIM);
+ 16, 16, PixelFormat.OPAQUE,
+ Surface.FX_SURFACE_DIM | Surface.HIDDEN);
} else {
- mDimSurface = new Surface(session, 0,
- "DimSurface",
- layerStack, 16, 16, PixelFormat.OPAQUE,
- Surface.FX_SURFACE_DIM);
+ mDimSurface = new Surface(session, "DimSurface",
+ 16, 16, PixelFormat.OPAQUE,
+ Surface.FX_SURFACE_DIM | Surface.HIDDEN);
}
if (WindowManagerService.SHOW_TRANSACTIONS ||
WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
" DIM " + mDimSurface + ": CREATE");
+ mDimSurface.setLayerStack(layerStack);
mDimSurface.setAlpha(0.0f);
+ mDimSurface.show();
} catch (Exception e) {
Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e);
}
diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java
index 7679413..acf3249 100644
--- a/services/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -215,12 +215,12 @@ class ScreenRotationAnimation {
try {
try {
if (WindowManagerService.DEBUG_SURFACE_TRACE) {
- mSurface = new SurfaceTrace(session, 0, "FreezeSurface",
- mDisplay.getLayerStack(), mWidth, mHeight,
+ mSurface = new SurfaceTrace(session, "FreezeSurface",
+ mWidth, mHeight,
PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN);
} else {
- mSurface = new Surface(session, 0, "FreezeSurface",
- mDisplay.getLayerStack(), mWidth, mHeight,
+ mSurface = new Surface(session, "FreezeSurface",
+ mWidth, mHeight,
PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN);
}
if (!mSurface.isValid()) {
@@ -228,6 +228,7 @@ class ScreenRotationAnimation {
mSurface = null;
return;
}
+ mSurface.setLayerStack(mDisplay.getLayerStack());
mSurface.setLayer(FREEZE_LAYER + 1);
mSurface.setAlpha(0);
mSurface.show();
diff --git a/services/java/com/android/server/wm/StrictModeFlash.java b/services/java/com/android/server/wm/StrictModeFlash.java
index 775aa0f..90bbd08 100644
--- a/services/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/java/com/android/server/wm/StrictModeFlash.java
@@ -37,14 +37,16 @@ class StrictModeFlash {
public StrictModeFlash(Display display, SurfaceSession session) {
try {
- mSurface = new Surface(session, 0, "StrictModeFlash", display.getLayerStack(),
- 1, 1, PixelFormat.TRANSLUCENT, 0);
+ mSurface = new Surface(session, "StrictModeFlash",
+ 1, 1, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
} catch (Surface.OutOfResourcesException e) {
return;
}
+ mSurface.setLayerStack(display.getLayerStack());
mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary.
mSurface.setPosition(0, 0);
+ mSurface.show();
mDrawNeeded = true;
}
diff --git a/services/java/com/android/server/wm/Watermark.java b/services/java/com/android/server/wm/Watermark.java
index 5901cc8..ac152c9 100644
--- a/services/java/com/android/server/wm/Watermark.java
+++ b/services/java/com/android/server/wm/Watermark.java
@@ -113,9 +113,9 @@ class Watermark {
mTextPaint.setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor);
try {
- mSurface = new Surface(session, 0,
- "WatermarkSurface", mDisplay.getLayerStack(),
- 1, 1, PixelFormat.TRANSLUCENT, 0);
+ mSurface = new Surface(session, "WatermarkSurface",
+ 1, 1, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+ mSurface.setLayerStack(mDisplay.getLayerStack());
mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100);
mSurface.setPosition(0, 0);
mSurface.show();
@@ -174,4 +174,4 @@ class Watermark {
}
}
}
-} \ No newline at end of file
+}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 42bc7ce..0d9db90 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -739,116 +739,38 @@ public class WindowManagerService extends IWindowManager.Stub
// For example, when this flag is true, there will be no wallpaper service.
final boolean mOnlyCore;
- public static WindowManagerService main(Context context,
- PowerManagerService pm, DisplayManagerService dm,
- boolean haveInputMethods, boolean allowBootMsgs,
- boolean onlyCore) {
- WMThread thr = new WMThread(context, pm, dm, haveInputMethods, allowBootMsgs, onlyCore);
- thr.start();
-
- synchronized (thr) {
- while (thr.mService == null) {
- try {
- thr.wait();
- } catch (InterruptedException e) {
- }
+ public static WindowManagerService main(final Context context,
+ final PowerManagerService pm, final DisplayManagerService dm,
+ final Handler uiHandler, final Handler wmHandler,
+ final boolean haveInputMethods, final boolean showBootMsgs,
+ final boolean onlyCore) {
+ final WindowManagerService[] holder = new WindowManagerService[1];
+ wmHandler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ holder[0] = new WindowManagerService(context, pm, dm,
+ uiHandler, haveInputMethods, showBootMsgs, onlyCore);
}
- return thr.mService;
- }
- }
-
- static class WMThread extends Thread {
- WindowManagerService mService;
-
- private final Context mContext;
- private final PowerManagerService mPM;
- private final DisplayManagerService mDisplayManager;
- private final boolean mHaveInputMethods;
- private final boolean mAllowBootMessages;
- private final boolean mOnlyCore;
-
- public WMThread(Context context, PowerManagerService pm,
- DisplayManagerService dm,
- boolean haveInputMethods, boolean allowBootMsgs, boolean onlyCore) {
- super("WindowManager");
- mContext = context;
- mPM = pm;
- mDisplayManager = dm;
- mHaveInputMethods = haveInputMethods;
- mAllowBootMessages = allowBootMsgs;
- mOnlyCore = onlyCore;
- }
-
- @Override
- public void run() {
- Looper.prepare();
- //Looper.myLooper().setMessageLogging(new LogPrinter(
- // android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM));
- WindowManagerService s = new WindowManagerService(mContext, mPM, mDisplayManager,
- mHaveInputMethods, mAllowBootMessages, mOnlyCore);
- android.os.Process.setThreadPriority(
- android.os.Process.THREAD_PRIORITY_DISPLAY);
- android.os.Process.setCanSelfBackground(false);
-
- synchronized (this) {
- mService = s;
- notifyAll();
- }
-
- // For debug builds, log event loop stalls to dropbox for analysis.
- if (StrictMode.conditionallyEnableDebugLogging()) {
- Slog.i(TAG, "Enabled StrictMode logging for WMThread's Looper");
- }
-
- Looper.loop();
- }
+ });
+ return holder[0];
}
- static class PolicyThread extends Thread {
- private final WindowManagerPolicy mPolicy;
- private final WindowManagerService mService;
- private final Context mContext;
- boolean mRunning = false;
+ private void initPolicy(Handler uiHandler) {
+ uiHandler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
- public PolicyThread(WindowManagerPolicy policy,
- WindowManagerService service, Context context) {
- super("WindowManagerPolicy");
- mPolicy = policy;
- mService = service;
- mContext = context;
- }
-
- @Override
- public void run() {
- Looper.prepare();
- WindowManagerPolicyThread.set(this, Looper.myLooper());
-
- //Looper.myLooper().setMessageLogging(new LogPrinter(
- // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM));
- android.os.Process.setThreadPriority(
- android.os.Process.THREAD_PRIORITY_FOREGROUND);
- android.os.Process.setCanSelfBackground(false);
- mPolicy.init(mContext, mService, mService);
- mService.mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer()
- * TYPE_LAYER_MULTIPLIER
- + TYPE_LAYER_OFFSET;
-
- synchronized (this) {
- mRunning = true;
- notifyAll();
- }
-
- // For debug builds, log event loop stalls to dropbox for analysis.
- if (StrictMode.conditionallyEnableDebugLogging()) {
- Slog.i(TAG, "Enabled StrictMode for PolicyThread's Looper");
+ mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
+ mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer()
+ * TYPE_LAYER_MULTIPLIER
+ + TYPE_LAYER_OFFSET;
}
-
- Looper.loop();
- }
+ });
}
private WindowManagerService(Context context, PowerManagerService pm,
- DisplayManagerService displayManager,
+ DisplayManagerService displayManager, Handler uiHandler,
boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
mContext = context;
mHaveInputMethods = haveInputMethods;
@@ -857,7 +779,7 @@ public class WindowManagerService extends IWindowManager.Stub
mLimitedAlphaCompositing = context.getResources().getBoolean(
com.android.internal.R.bool.config_sf_limitedAlpha);
mDisplayManagerService = displayManager;
- mDisplayManager = DisplayManager.getInstance();
+ mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
mHeadless = displayManager.isHeadless();
mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy);
@@ -895,17 +817,7 @@ public class WindowManagerService extends IWindowManager.Stub
mFxSession = new SurfaceSession();
mAnimator = new WindowAnimator(this);
- PolicyThread thr = new PolicyThread(mPolicy, this, context);
- thr.start();
-
- synchronized (thr) {
- while (!thr.mRunning) {
- try {
- thr.wait();
- } catch (InterruptedException e) {
- }
- }
- }
+ initPolicy(uiHandler);
mInputManager.start();
@@ -5847,7 +5759,8 @@ public class WindowManagerService extends IWindowManager.Stub
updateLayoutToAnimationLocked();
}
}
- Surface.setOrientation(0, rotation);
+ mDisplayManagerService.setDisplayOrientation(
+ displayContent.getDisplayId(), rotation);
} finally {
if (!inTransaction) {
Surface.closeTransaction();
@@ -6561,7 +6474,8 @@ public class WindowManagerService extends IWindowManager.Stub
displayInfo.appHeight = appHeight;
displayInfo.getLogicalMetrics(mRealDisplayMetrics, null);
displayInfo.getAppMetrics(mDisplayMetrics, null);
- mDisplayManagerService.setDisplayInfo(displayContent.getDisplayId(), displayInfo);
+ mDisplayManagerService.setDisplayInfoOverrideFromWindowManager(
+ displayContent.getDisplayId(), displayInfo);
mAnimator.setDisplayDimensions(dw, dh, appWidth, appHeight);
}
@@ -6711,9 +6625,9 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
try {
if (mDragState == null) {
- Surface surface = new Surface(session, callerPid, "drag surface",
- mDefaultDisplay.getLayerStack(),
+ Surface surface = new Surface(session, "drag surface",
width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+ surface.setLayerStack(mDefaultDisplay.getLayerStack());
if (SHOW_TRANSACTIONS) Slog.i(TAG, " DRAG "
+ surface + ": CREATE");
outSurface.copyFrom(surface);
@@ -6913,7 +6827,10 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized(displayContent.mDisplaySizeLock) {
// Bootstrap the default logical display from the display manager.
displayInfo = displayContent.getDisplayInfo();
- mDisplayManagerService.getDisplayInfo(displayId, displayInfo);
+ DisplayInfo newDisplayInfo = mDisplayManagerService.getDisplayInfo(displayId);
+ if (newDisplayInfo != null) {
+ displayInfo.copyFrom(newDisplayInfo);
+ }
displayContent.mInitialDisplayWidth = displayInfo.logicalWidth;
displayContent.mInitialDisplayHeight = displayInfo.logicalHeight;
displayContent.mInitialDisplayDensity = displayInfo.logicalDensityDpi;
@@ -8342,10 +8259,11 @@ public class WindowManagerService extends IWindowManager.Stub
Rect dirty = new Rect(0, 0, mNextAppTransitionThumbnail.getWidth(),
mNextAppTransitionThumbnail.getHeight());
try {
- Surface surface = new Surface(mFxSession, Process.myPid(),
- "thumbnail anim", mDefaultDisplay.getLayerStack(),
+ Surface surface = new Surface(mFxSession,
+ "thumbnail anim",
dirty.width(), dirty.height(),
PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+ surface.setLayerStack(mDefaultDisplay.getLayerStack());
topOpeningApp.mAppAnimator.thumbnail = surface;
if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL "
+ surface + ": CREATE");
@@ -10363,7 +10281,7 @@ public class WindowManagerService extends IWindowManager.Stub
public DisplayContent getDisplayContent(final int displayId) {
DisplayContent displayContent = mDisplayContents.get(displayId);
if (displayContent == null) {
- displayContent = new DisplayContent(mDisplayManager.getRealDisplay(displayId));
+ displayContent = new DisplayContent(mDisplayManager.getDisplay(displayId));
mDisplayContents.put(displayId, displayContent);
}
return displayContent;
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
index 982f60d..1bda22a 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -481,9 +481,9 @@ class WindowStateAnimator {
private String mName;
public SurfaceTrace(SurfaceSession s,
- int pid, String name, int layerStack, int w, int h, int format, int flags)
+ String name, int w, int h, int format, int flags)
throws OutOfResourcesException {
- super(s, pid, name, layerStack, w, h, format, flags);
+ super(s, name, w, h, format, flags);
mName = name != null ? name : "Not named";
mSize.set(w, h);
Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
@@ -608,7 +608,7 @@ class WindowStateAnimator {
mService.makeWindowFreezingScreenIfNeededLocked(mWin);
- int flags = 0;
+ int flags = Surface.HIDDEN;
final WindowManager.LayoutParams attrs = mWin.mAttrs;
if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
@@ -652,14 +652,14 @@ class WindowStateAnimator {
}
if (DEBUG_SURFACE_TRACE) {
mSurface = new SurfaceTrace(
- mSession.mSurfaceSession, mSession.mPid,
+ mSession.mSurfaceSession,
attrs.getTitle().toString(),
- mLayerStack, w, h, format, flags);
+ w, h, format, flags);
} else {
mSurface = new Surface(
- mSession.mSurfaceSession, mSession.mPid,
+ mSession.mSurfaceSession,
attrs.getTitle().toString(),
- mLayerStack, w, h, format, flags);
+ w, h, format, flags);
}
mWin.mHasSurface = true;
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
@@ -701,10 +701,10 @@ class WindowStateAnimator {
mSurfaceY = mWin.mFrame.top + mWin.mYOffset;
mSurface.setPosition(mSurfaceX, mSurfaceY);
mSurfaceLayer = mAnimLayer;
+ mSurface.setLayerStack(mLayerStack);
mSurface.setLayer(mAnimLayer);
mSurface.setAlpha(0);
mSurfaceShown = false;
- mSurface.hide();
} catch (RuntimeException e) {
Slog.w(TAG, "Error creating surface in " + w, e);
mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true);
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index 43e59b2..d097a93 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -4,7 +4,6 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
com_android_server_AlarmManagerService.cpp \
com_android_server_BatteryService.cpp \
- com_android_server_display_SurfaceFlingerDisplayAdapter.cpp \
com_android_server_input_InputApplicationHandle.cpp \
com_android_server_input_InputManagerService.cpp \
com_android_server_input_InputWindowHandle.cpp \
diff --git a/services/jni/com_android_server_display_SurfaceFlingerDisplayAdapter.cpp b/services/jni/com_android_server_display_SurfaceFlingerDisplayAdapter.cpp
deleted file mode 100644
index 05a74d3..0000000
--- a/services/jni/com_android_server_display_SurfaceFlingerDisplayAdapter.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "SurfaceFlingerDisplayAdapter"
-
-#include "JNIHelp.h"
-#include "jni.h"
-#include <android_runtime/AndroidRuntime.h>
-
-#include <gui/SurfaceComposerClient.h>
-#include <ui/DisplayInfo.h>
-
-#include <utils/Log.h>
-
-namespace android {
-
-static struct {
- jfieldID width;
- jfieldID height;
- jfieldID refreshRate;
- jfieldID densityDpi;
- jfieldID xDpi;
- jfieldID yDpi;
-} gDisplayDeviceInfoClassInfo;
-
-
-static void nativeGetDefaultDisplayDeviceInfo(JNIEnv* env, jclass clazz, jobject infoObj) {
- DisplayInfo info;
- status_t err = SurfaceComposerClient::getDisplayInfo(0, &info);
- if (err < 0) {
- jniThrowExceptionFmt(env, "java/lang/RuntimeException",
- "Could not get display info. err=%d", err);
- return;
- }
-
- env->SetIntField(infoObj, gDisplayDeviceInfoClassInfo.width, info.w);
- env->SetIntField(infoObj, gDisplayDeviceInfoClassInfo.height, info.h);
- env->SetFloatField(infoObj, gDisplayDeviceInfoClassInfo.refreshRate, info.fps);
- env->SetIntField(infoObj, gDisplayDeviceInfoClassInfo.densityDpi,
- (jint)((info.density*160) + .5f));
- env->SetFloatField(infoObj, gDisplayDeviceInfoClassInfo.xDpi, info.xdpi);
- env->SetFloatField(infoObj, gDisplayDeviceInfoClassInfo.yDpi, info.ydpi);
-}
-
-
-static JNINativeMethod gSurfaceFlingerDisplayAdapterMethods[] = {
- /* name, signature, funcPtr */
- { "nativeGetDefaultDisplayDeviceInfo",
- "(Lcom/android/server/display/DisplayDeviceInfo;)V",
- (void*) nativeGetDefaultDisplayDeviceInfo },
-};
-
-#define FIND_CLASS(var, className) \
- var = env->FindClass(className); \
- LOG_FATAL_IF(! var, "Unable to find class " className);
-
-#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
- var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
- LOG_FATAL_IF(! var, "Unable to find field " fieldName);
-
-int register_android_server_display_SurfaceFlingerDisplayAdapter(JNIEnv* env) {
- int res = jniRegisterNativeMethods(env,
- "com/android/server/display/SurfaceFlingerDisplayAdapter",
- gSurfaceFlingerDisplayAdapterMethods, NELEM(gSurfaceFlingerDisplayAdapterMethods));
- LOG_FATAL_IF(res < 0, "Unable to register native methods.");
-
- jclass clazz;
- FIND_CLASS(clazz, "com/android/server/display/DisplayDeviceInfo");
- GET_FIELD_ID(gDisplayDeviceInfoClassInfo.width, clazz, "width", "I");
- GET_FIELD_ID(gDisplayDeviceInfoClassInfo.height, clazz, "height", "I");
- GET_FIELD_ID(gDisplayDeviceInfoClassInfo.refreshRate, clazz, "refreshRate", "F");
- GET_FIELD_ID(gDisplayDeviceInfoClassInfo.densityDpi, clazz, "densityDpi", "I");
- GET_FIELD_ID(gDisplayDeviceInfoClassInfo.xDpi, clazz, "xDpi", "F");
- GET_FIELD_ID(gDisplayDeviceInfoClassInfo.yDpi, clazz, "yDpi", "F");
- return 0;
-}
-
-} /* namespace android */
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index 50873fc..423ebd1 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -22,7 +22,6 @@
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_BatteryService(JNIEnv* env);
-int register_android_server_display_SurfaceFlingerDisplayAdapter(JNIEnv* env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
@@ -52,7 +51,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
- register_android_server_display_SurfaceFlingerDisplayAdapter(env);
register_android_server_InputApplicationHandle(env);
register_android_server_InputWindowHandle(env);
register_android_server_InputManager(env);
diff --git a/telephony/java/android/telephony/CellSignalStrength.java b/telephony/java/android/telephony/CellSignalStrength.java
index a80207e..581efc2 100644
--- a/telephony/java/android/telephony/CellSignalStrength.java
+++ b/telephony/java/android/telephony/CellSignalStrength.java
@@ -59,22 +59,16 @@ public abstract class CellSignalStrength implements Parcelable {
/**
* Get signal level as an int from 0..4
- *
- * @hide
*/
public abstract int getLevel();
/**
* Get the signal level as an asu value between 0..31, 99 is unknown
- *
- * @hide
*/
public abstract int getAsuLevel();
/**
* Get the signal strength as dBm
- *
- * @hide
*/
public abstract int getDbm();
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 20f713b..3912629 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -113,9 +113,7 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel
}
/**
- * Get LTE as level 0..4
- *
- * @hide
+ * Get signal level as an int from 0..4
*/
@Override
public int getLevel() {
@@ -140,8 +138,6 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel
/**
* Get the LTE signal level as an asu value between 0..97, 99 is unknown
* Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
- *
- * @hide
*/
@Override
public int getAsuLevel() {
@@ -172,8 +168,6 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel
/**
* Get cdma as level 0..4
- *
- * @hide
*/
public int getCdmaLevel() {
final int cdmaDbm = getCdmaDbm();
@@ -201,8 +195,6 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel
/**
* Get Evdo as level 0..4
- *
- * @hide
*/
public int getEvdoLevel() {
int evdoDbm = getEvdoDbm();
@@ -228,9 +220,7 @@ public class CellSignalStrengthCdma extends CellSignalStrength implements Parcel
}
/**
- * Get as dBm
- *
- * @hide
+ * Get the signal strength as dBm
*/
@Override
public int getDbm() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index a311c3c..30b444b 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -100,9 +100,7 @@ public class CellSignalStrengthGsm extends CellSignalStrength implements Parcela
}
/**
- * Get LTE as level 0..4
- *
- * @hide
+ * Get signal level as an int from 0..4
*/
@Override
public int getLevel() {
@@ -123,9 +121,7 @@ public class CellSignalStrengthGsm extends CellSignalStrength implements Parcela
}
/**
- * Get LTE as dBm
- *
- * @hide
+ * Get the signal strength as dBm
*/
@Override
public int getDbm() {
@@ -145,8 +141,6 @@ public class CellSignalStrengthGsm extends CellSignalStrength implements Parcela
/**
* Get the LTE signal level as an asu value between 0..97, 99 is unknown
* Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
- *
- * @hide
*/
@Override
public int getAsuLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 078699f..7a4d626 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -134,9 +134,7 @@ public class CellSignalStrengthLte extends CellSignalStrength implements Parcela
}
/**
- * Get LTE as level 0..4
- *
- * @hide
+ * Get signal level as an int from 0..4
*/
@Override
public int getLevel() {
@@ -170,9 +168,7 @@ public class CellSignalStrengthLte extends CellSignalStrength implements Parcela
}
/**
- * Get LTE as dBm
- *
- * @hide
+ * Get signal strength as dBm
*/
@Override
public int getDbm() {
@@ -182,8 +178,6 @@ public class CellSignalStrengthLte extends CellSignalStrength implements Parcela
/**
* Get the LTE signal level as an asu value between 0..97, 99 is unknown
* Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
- *
- * @hide
*/
@Override
public int getAsuLevel() {
diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
index f26edc6..e39d53c 100644
--- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
+++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
@@ -54,9 +54,9 @@ public class MemoryUsageTest extends InstrumentationTestCase {
private static final String TAG = "MemoryUsageInstrumentation";
private static final String KEY_APPS = "apps";
- private Map<String, Intent> nameToIntent;
- private Map<String, String> nameToProcess;
- private Map<String, String> nameToResultKey;
+ private Map<String, Intent> mNameToIntent;
+ private Map<String, String> mNameToProcess;
+ private Map<String, String> mNameToResultKey;
public void testMemory() {
MemoryUsageInstrumentation instrumentation =
@@ -67,7 +67,7 @@ public class MemoryUsageTest extends InstrumentationTestCase {
parseArgs(args);
Bundle results = new Bundle();
- for (String app : nameToResultKey.keySet()) {
+ for (String app : mNameToResultKey.keySet()) {
String processName;
try {
processName = startApp(app);
@@ -81,7 +81,7 @@ public class MemoryUsageTest extends InstrumentationTestCase {
}
private void parseArgs(Bundle args) {
- nameToResultKey = new HashMap<String, String>();
+ mNameToResultKey = new HashMap<String, String>();
String appList = args.getString(KEY_APPS);
if (appList == null)
@@ -95,13 +95,13 @@ public class MemoryUsageTest extends InstrumentationTestCase {
fail();
}
- nameToResultKey.put(parts[0], parts[1]);
+ mNameToResultKey.put(parts[0], parts[1]);
}
}
private void createMappings() {
- nameToIntent = new HashMap<String, Intent>();
- nameToProcess = new HashMap<String, String>();
+ mNameToIntent = new HashMap<String, Intent>();
+ mNameToProcess = new HashMap<String, String>();
PackageManager pm = getInstrumentation().getContext()
.getPackageManager();
@@ -120,8 +120,8 @@ public class MemoryUsageTest extends InstrumentationTestCase {
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
startIntent.setClassName(ri.activityInfo.packageName,
ri.activityInfo.name);
- nameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
- nameToProcess.put(ri.loadLabel(pm).toString(),
+ mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
+ mNameToProcess.put(ri.loadLabel(pm).toString(),
ri.activityInfo.processName);
}
}
@@ -130,11 +130,11 @@ public class MemoryUsageTest extends InstrumentationTestCase {
private String startApp(String appName) throws NameNotFoundException {
Log.i(TAG, "Starting " + appName);
- if (!nameToProcess.containsKey(appName))
+ if (!mNameToProcess.containsKey(appName))
throw new NameNotFoundException("Could not find: " + appName);
- String process = nameToProcess.get(appName);
- Intent startIntent = nameToIntent.get(appName);
+ String process = mNameToProcess.get(appName);
+ Intent startIntent = mNameToIntent.get(appName);
getInstrumentation().getContext().startActivity(startIntent);
return process;
}
@@ -154,14 +154,14 @@ public class MemoryUsageTest extends InstrumentationTestCase {
}
pssData.add(pss);
if (iteration >= MIN_ITERATIONS && stabilized(pssData)) {
- results.putInt(nameToResultKey.get(appName), pss);
+ results.putInt(mNameToResultKey.get(appName), pss);
return;
}
iteration++;
}
Log.w(TAG, appName + " memory usage did not stabilize");
- results.putInt(appName, average(pssData));
+ results.putInt(mNameToResultKey.get(appName), average(pssData));
}
private int average(List<Integer> pssData) {
@@ -202,12 +202,12 @@ public class MemoryUsageTest extends InstrumentationTestCase {
continue;
Log.w(TAG, appName + " crashed: " + crash.shortMsg);
- results.putString(nameToResultKey.get(appName), crash.shortMsg);
+ results.putString(mNameToResultKey.get(appName), crash.shortMsg);
return;
}
}
- results.putString(nameToResultKey.get(appName),
+ results.putString(mNameToResultKey.get(appName),
"Crashed for unknown reason");
Log.w(TAG, appName
+ " not found in process list, most likely it is crashed");
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 6b08074..55de065 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -106,5 +106,7 @@ interface IWifiManager
Messenger getWifiStateMachineMessenger();
String getConfigFile();
+
+ void captivePortalCheckComplete();
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 284bee8..aa59158 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1971,4 +1971,11 @@ public class WifiManager {
return false;
}
}
+
+ /** @hide */
+ public void captivePortalCheckComplete() {
+ try {
+ mService.captivePortalCheckComplete();
+ } catch (RemoteException e) {}
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index a447c86..17c930b 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -20,6 +20,8 @@ import android.net.NetworkInfo;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pService;
+import android.net.wifi.p2p.WifiP2pService.P2pStatus;
import android.net.wifi.p2p.WifiP2pProvDiscEvent;
import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
import android.net.wifi.StateChangeResult;
@@ -186,7 +188,7 @@ public class WifiMonitor {
/* P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437
[psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|passphrase="fKG4jMe3"]
- go_dev_addr=fa:7b:7a:42:02:13 */
+ go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT] */
private static final String P2P_GROUP_STARTED_STR = "P2P-GROUP-STARTED";
/* P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED */
@@ -594,7 +596,13 @@ public class WifiMonitor {
if (tokens.length != 2) return;
String[] nameValue = tokens[1].split("=");
if (nameValue.length != 2) return;
- mStateMachine.sendMessage(P2P_INVITATION_RESULT_EVENT, nameValue[1]);
+ P2pStatus err = P2pStatus.UNKNOWN;
+ try {
+ err = P2pStatus.valueOf(Integer.parseInt(nameValue[1]));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ mStateMachine.sendMessage(P2P_INVITATION_RESULT_EVENT, err);
} else if (dataString.startsWith(P2P_PROV_DISC_PBC_REQ_STR)) {
mStateMachine.sendMessage(P2P_PROV_DISC_PBC_REQ_EVENT,
new WifiP2pProvDiscEvent(dataString));
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 1b7e378..b9feb34 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -569,10 +569,9 @@ public class WifiNative {
break;
}
- //TODO: Add persist behavior once the supplicant interaction is fixed for both
- // group and client scenarios
- /* Persist unless there is an explicit request to not do so*/
- //if (config.persist != WifiP2pConfig.Persist.NO) args.add("persistent");
+ if (config.netId == WifiP2pGroup.PERSISTENT_NET_ID) {
+ args.add("persistent");
+ }
if (joinExistingGroup) {
args.add("join");
@@ -614,10 +613,17 @@ public class WifiNative {
return false;
}
- public boolean p2pGroupAdd() {
+ public boolean p2pGroupAdd(boolean persistent) {
+ if (persistent) {
+ return doBooleanCommand("P2P_GROUP_ADD persistent");
+ }
return doBooleanCommand("P2P_GROUP_ADD");
}
+ public boolean p2pGroupAdd(int netId) {
+ return doBooleanCommand("P2P_GROUP_ADD persistent=" + netId);
+ }
+
public boolean p2pGroupRemove(String iface) {
if (TextUtils.isEmpty(iface)) return false;
return doBooleanCommand("P2P_GROUP_REMOVE " + iface);
@@ -646,6 +652,9 @@ public class WifiNative {
return doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + deviceAddress);
}
+ public String p2pGetSsid(String deviceAddress) {
+ return p2pGetParam(deviceAddress, "oper_ssid");
+ }
public String p2pGetDeviceAddress() {
String status = status();
@@ -687,6 +696,24 @@ public class WifiNative {
return doStringCommand("P2P_PEER " + deviceAddress);
}
+ private String p2pGetParam(String deviceAddress, String key) {
+ if (deviceAddress == null) return null;
+
+ String peerInfo = p2pPeer(deviceAddress);
+ if (peerInfo == null) return null;
+ String[] tokens= peerInfo.split("\n");
+
+ key += "=";
+ for (String token : tokens) {
+ if (token.startsWith(key)) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) break;
+ return nameValue[1];
+ }
+ }
+ return null;
+ }
+
public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) {
/*
* P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump>
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index a5322fa..b52250d 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -256,6 +256,8 @@ public class WifiStateMachine extends StateMachine {
static final int CMD_DELAYED_STOP_DRIVER = BASE + 18;
/* A delayed message sent to start driver when it fail to come up */
static final int CMD_DRIVER_START_TIMED_OUT = BASE + 19;
+ /* Ready to switch to network as default */
+ static final int CMD_CAPTIVE_CHECK_COMPLETE = BASE + 20;
/* Start the soft access point */
static final int CMD_START_AP = BASE + 21;
@@ -459,6 +461,8 @@ public class WifiStateMachine extends StateMachine {
private State mObtainingIpState = new ObtainingIpState();
/* Waiting for link quality verification to be complete */
private State mVerifyingLinkState = new VerifyingLinkState();
+ /* Waiting for captive portal check to be complete */
+ private State mCaptivePortalCheckState = new CaptivePortalCheckState();
/* Connected with IP addr */
private State mConnectedState = new ConnectedState();
/* disconnect issued, waiting for network disconnect confirmation */
@@ -695,6 +699,7 @@ public class WifiStateMachine extends StateMachine {
addState(mL2ConnectedState, mConnectModeState);
addState(mObtainingIpState, mL2ConnectedState);
addState(mVerifyingLinkState, mL2ConnectedState);
+ addState(mCaptivePortalCheckState, mL2ConnectedState);
addState(mConnectedState, mL2ConnectedState);
addState(mDisconnectingState, mConnectModeState);
addState(mDisconnectedState, mConnectModeState);
@@ -865,6 +870,10 @@ public class WifiStateMachine extends StateMachine {
}
}
+ public void captivePortalCheckComplete() {
+ sendMessage(obtainMessage(CMD_CAPTIVE_CHECK_COMPLETE));
+ }
+
/**
* TODO: doc
*/
@@ -1616,7 +1625,7 @@ public class WifiStateMachine extends StateMachine {
}
if (state != mNetworkInfo.getDetailedState()) {
- mNetworkInfo.setDetailedState(state, null, null);
+ mNetworkInfo.setDetailedState(state, null, mWifiInfo.getSSID());
}
}
@@ -1663,10 +1672,7 @@ public class WifiStateMachine extends StateMachine {
/* In case we were in middle of DHCP operation
restore back powermode */
handlePostDhcpSetup();
-
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
- mDhcpStateMachine.doQuit();
- mDhcpStateMachine = null;
}
try {
@@ -1919,6 +1925,9 @@ public class WifiStateMachine extends StateMachine {
case CMD_CLEAR_SUSPEND_OPTIMIZATIONS:
case CMD_NO_NETWORKS_PERIODIC_SCAN:
break;
+ case DhcpStateMachine.CMD_ON_QUIT:
+ mDhcpStateMachine = null;
+ break;
case CMD_SET_SUSPEND_OPTIMIZATIONS:
mSuspendWakeLock.release();
break;
@@ -2489,6 +2498,9 @@ public class WifiStateMachine extends StateMachine {
/* Send any reset commands to supplicant before shutting it down */
handleNetworkDisconnect();
+ if (mDhcpStateMachine != null) {
+ mDhcpStateMachine.doQuit();
+ }
if (DBG) log("stopping supplicant");
if (!mWifiNative.stopSupplicant()) {
@@ -3188,8 +3200,11 @@ public class WifiStateMachine extends StateMachine {
if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
//start DHCP
- mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
- mContext, WifiStateMachine.this, mInterfaceName);
+ if (mDhcpStateMachine == null) {
+ mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
+ mContext, WifiStateMachine.this, mInterfaceName);
+
+ }
mDhcpStateMachine.registerForPreDhcpNotification();
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
} else {
@@ -3253,6 +3268,26 @@ public class WifiStateMachine extends StateMachine {
//stay here
break;
case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
+ transitionTo(mCaptivePortalCheckState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class CaptivePortalCheckState extends State {
+ @Override
+ public void enter() {
+ setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CAPTIVE_PORTAL_CHECK);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_CAPTIVE_CHECK_COMPLETE:
try {
mNwService.enableIpv6(mInterfaceName);
} catch (RemoteException re) {
@@ -3260,7 +3295,6 @@ public class WifiStateMachine extends StateMachine {
} catch (IllegalStateException e) {
loge("Failed to enable IPv6: " + e);
}
-
setNetworkDetailedState(DetailedState.CONNECTED);
mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
sendNetworkStateChangeBroadcast(mLastBssid);
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index bfb91e2..a5a2469 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -23,6 +23,7 @@ import android.content.IntentFilter;
import android.net.LinkCapabilities;
import android.net.LinkProperties;
import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
import android.net.NetworkStateTracker;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Handler;
@@ -113,6 +114,14 @@ public class WifiStateTracker implements NetworkStateTracker {
}
/**
+ * Captive check is complete, switch to network
+ */
+ @Override
+ public void captivePortalCheckComplete() {
+ mWifiManager.captivePortalCheckComplete();
+ }
+
+ /**
* Turn the wireless radio off for a network.
* @param turnOn {@code true} to turn the radio on, {@code false}
*/
@@ -235,9 +244,10 @@ public class WifiStateTracker implements NetworkStateTracker {
mLinkCapabilities = new LinkCapabilities();
}
// don't want to send redundent state messages
- // TODO can this be fixed in WifiStateMachine?
+ // but send portal check detailed state notice
NetworkInfo.State state = mNetworkInfo.getState();
- if (mLastState == state) {
+ if (mLastState == state &&
+ mNetworkInfo.getDetailedState() != DetailedState.CAPTIVE_PORTAL_CHECK) {
return;
} else {
mLastState = state;
diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
index 29a53b6..7fa6aac 100644
--- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
@@ -16,20 +16,15 @@
package android.net.wifi;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.NetworkInfo;
-import android.net.Uri;
import android.net.wifi.RssiPacketCountInfo;
import android.os.Message;
import android.os.SystemClock;
@@ -44,10 +39,7 @@ import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
import java.text.DecimalFormat;
/**
@@ -100,8 +92,7 @@ public class WifiWatchdogStateMachine extends StateMachine {
private static final int EVENT_SCREEN_OFF = BASE + 9;
/* Internal messages */
- private static final int CMD_DELAYED_WALLED_GARDEN_CHECK = BASE + 11;
- private static final int CMD_RSSI_FETCH = BASE + 12;
+ private static final int CMD_RSSI_FETCH = BASE + 11;
/* Notifications from/to WifiStateMachine */
static final int POOR_LINK_DETECTED = BASE + 21;
@@ -266,27 +257,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
new MaxAvoidTime( 0 * 60000, -55 ),
};
-
- private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden";
-
- private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000;
-
- /**
- * See http://go/clientsdns for usage approval
- */
- private static final String DEFAULT_WALLED_GARDEN_URL =
- "http://clients3.google.com/generate_204";
- private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000;
-
- /**
- * Some carrier apps might have support captive portal handling. Add some
- * delay to allow app authentication to be done before our test. TODO: This
- * should go away once we provide an API to apps to disable walled garden
- * test for certain SSIDs
- */
- private static final int WALLED_GARDEN_START_DELAY_MS = 3000;
-
-
/* Framework related */
private Context mContext;
private ContentResolver mContentResolver;
@@ -300,13 +270,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
/* System settingss related */
private static boolean sWifiOnly = false;
private boolean mPoorNetworkDetectionEnabled;
- private long mWalledGardenIntervalMs;
- private boolean mWalledGardenTestEnabled;
- private String mWalledGardenUrl;
-
- /* Wall garden detection related */
- private long mLastWalledGardenCheckTime = 0;
- private boolean mWalledGardenNotificationShown;
/* Poor link detection related */
private LruCache<String, BssidStatistics> mBssidCache =
@@ -325,7 +288,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
private NotConnectedState mNotConnectedState = new NotConnectedState();
private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState();
private ConnectedState mConnectedState = new ConnectedState();
- private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState();
private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
private LinkMonitoringState mLinkMonitoringState = new LinkMonitoringState();
private OnlineState mOnlineState = new OnlineState();
@@ -359,7 +321,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
addState(mNotConnectedState, mWatchdogEnabledState);
addState(mVerifyingLinkState, mWatchdogEnabledState);
addState(mConnectedState, mWatchdogEnabledState);
- addState(mWalledGardenCheckState, mConnectedState);
addState(mOnlineWatchState, mConnectedState);
addState(mLinkMonitoringState, mConnectedState);
addState(mOnlineState, mConnectedState);
@@ -379,13 +340,12 @@ public class WifiWatchdogStateMachine extends StateMachine {
Context.CONNECTIVITY_SERVICE);
sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
- // Watchdog is always enabled. Poor network detection & walled garden detection
- // can individually be turned on/off
+ // Watchdog is always enabled. Poor network detection can be seperately turned on/off
// TODO: Remove this setting & clean up state machine since we always have
// watchdog in an enabled state
putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true);
- // disable poor network avoidance, but keep watchdog active for walled garden detection
+ // disable poor network avoidance
if (sWifiOnly) {
logd("Disabling poor network avoidance for wi-fi only device");
putSettingsBoolean(contentResolver,
@@ -458,44 +418,8 @@ public class WifiWatchdogStateMachine extends StateMachine {
};
mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS),
- false, contentObserver);
- mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED),
false, contentObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED),
- false, contentObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL),
- false, contentObserver);
- }
-
- /**
- * DNS based detection techniques do not work at all hotspots. The one sure
- * way to check a walled garden is to see if a URL fetch on a known address
- * fetches the data we expect
- */
- private boolean isWalledGardenConnection() {
- HttpURLConnection urlConnection = null;
- try {
- URL url = new URL(mWalledGardenUrl);
- urlConnection = (HttpURLConnection) url.openConnection();
- urlConnection.setInstanceFollowRedirects(false);
- urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
- urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
- urlConnection.setUseCaches(false);
- urlConnection.getInputStream();
- // we got a valid response, but not from the real google
- return urlConnection.getResponseCode() != 204;
- } catch (IOException e) {
- if (DBG) logd("Walled garden check - probably not a portal: exception " + e);
- return false;
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- }
}
public void dump(PrintWriter pw) {
@@ -504,10 +428,7 @@ public class WifiWatchdogStateMachine extends StateMachine {
pw.println("mWifiInfo: [" + mWifiInfo + "]");
pw.println("mLinkProperties: [" + mLinkProperties + "]");
pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]");
- pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]");
pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]");
- pw.println("mWalledGardenTestEnabled: [" + mWalledGardenTestEnabled + "]");
- pw.println("mWalledGardenUrl: [" + mWalledGardenUrl + "]");
}
private boolean isWatchdogEnabled() {
@@ -521,47 +442,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver,
Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, true);
- mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true);
- mWalledGardenUrl = getSettingsStr(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL,
- DEFAULT_WALLED_GARDEN_URL);
- mWalledGardenIntervalMs = Secure.getLong(mContentResolver,
- Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS,
- DEFAULT_WALLED_GARDEN_INTERVAL_MS);
- }
-
- private void setWalledGardenNotificationVisible(boolean visible) {
- // if it should be hidden and it is already hidden, then noop
- if (!visible && !mWalledGardenNotificationShown) {
- return;
- }
-
- Resources r = Resources.getSystem();
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
-
- if (visible) {
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mWalledGardenUrl));
- intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
-
- CharSequence title = r.getString(R.string.wifi_available_sign_in, 0);
- CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed,
- mWifiInfo.getSSID());
-
- Notification notification = new Notification();
- notification.when = 0;
- notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range;
- notification.flags = Notification.FLAG_AUTO_CANCEL;
- notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
- notification.tickerText = title;
- notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
-
- notificationManager.notify(WALLED_GARDEN_NOTIFICATION_ID, 1, notification);
- } else {
- notificationManager.cancel(WALLED_GARDEN_NOTIFICATION_ID, 1);
- }
- mWalledGardenNotificationShown = visible;
}
/**
@@ -587,7 +467,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
case EVENT_NETWORK_STATE_CHANGE:
case EVENT_SUPPLICANT_STATE_CHANGE:
case EVENT_BSSID_CHANGE:
- case CMD_DELAYED_WALLED_GARDEN_CHECK:
case CMD_RSSI_FETCH:
case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
@@ -685,11 +564,7 @@ public class WifiWatchdogStateMachine extends StateMachine {
}
break;
case CONNECTED:
- if (shouldCheckWalledGarden()) {
- transitionTo(mWalledGardenCheckState);
- } else {
- transitionTo(mOnlineWatchState);
- }
+ transitionTo(mOnlineWatchState);
break;
default:
transitionTo(mNotConnectedState);
@@ -716,7 +591,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
return NOT_HANDLED;
}
- setWalledGardenNotificationVisible(false);
return HANDLED;
}
}
@@ -834,38 +708,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
}
/**
- * Checking for wall garden.
- */
- class WalledGardenCheckState extends State {
- private int mWalledGardenToken = 0;
- @Override
- public void enter() {
- if (DBG) logd(getName());
- sendMessageDelayed(obtainMessage(CMD_DELAYED_WALLED_GARDEN_CHECK,
- ++mWalledGardenToken, 0), WALLED_GARDEN_START_DELAY_MS);
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case CMD_DELAYED_WALLED_GARDEN_CHECK:
- if (msg.arg1 == mWalledGardenToken) {
- mLastWalledGardenCheckTime = SystemClock.elapsedRealtime();
- if (isWalledGardenConnection()) {
- if (DBG) logd("Walled garden detected");
- setWalledGardenNotificationVisible(true);
- }
- transitionTo(mOnlineWatchState);
- }
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- /**
* RSSI is high enough and don't need link monitoring.
*/
class OnlineWatchState extends State {
@@ -1037,22 +879,6 @@ public class WifiWatchdogStateMachine extends StateMachine {
}
}
- private boolean shouldCheckWalledGarden() {
- if (!mWalledGardenTestEnabled) {
- if (DBG) logd("Skipping walled garden check - disabled");
- return false;
- }
-
- long waitTime = (mWalledGardenIntervalMs + mLastWalledGardenCheckTime)
- - SystemClock.elapsedRealtime();
-
- if (mLastWalledGardenCheckTime != 0 && waitTime > 0) {
- if (DBG) logd("Skipping walled garden check - wait " + waitTime + " ms.");
- return false;
- }
- return true;
- }
-
private void updateCurrentBssid(String bssid) {
if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null"));
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 6aea090..100e062 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -46,18 +46,8 @@ public class WifiP2pConfig implements Parcelable {
*/
public int groupOwnerIntent = -1;
- /**
- * Indicates whether the configuration is saved
- * @hide
- */
- public enum Persist {
- SYSTEM_DEFAULT,
- YES,
- NO
- }
-
/** @hide */
- public Persist persist = Persist.SYSTEM_DEFAULT;
+ public int netId = WifiP2pGroup.PERSISTENT_NET_ID;
public WifiP2pConfig() {
//set defaults
@@ -110,7 +100,7 @@ public class WifiP2pConfig implements Parcelable {
sbuf.append("\n address: ").append(deviceAddress);
sbuf.append("\n wps: ").append(wps);
sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent);
- sbuf.append("\n persist: ").append(persist.toString());
+ sbuf.append("\n persist: ").append(netId);
return sbuf.toString();
}
@@ -125,7 +115,7 @@ public class WifiP2pConfig implements Parcelable {
deviceAddress = source.deviceAddress;
wps = new WpsInfo(source.wps);
groupOwnerIntent = source.groupOwnerIntent;
- persist = source.persist;
+ netId = source.netId;
}
}
@@ -134,7 +124,7 @@ public class WifiP2pConfig implements Parcelable {
dest.writeString(deviceAddress);
dest.writeParcelable(wps, flags);
dest.writeInt(groupOwnerIntent);
- dest.writeString(persist.name());
+ dest.writeInt(netId);
}
/** Implement the Parcelable interface */
@@ -145,7 +135,7 @@ public class WifiP2pConfig implements Parcelable {
config.deviceAddress = in.readString();
config.wps = (WpsInfo) in.readParcelable(null);
config.groupOwnerIntent = in.readInt();
- config.persist = Persist.valueOf(in.readString());
+ config.netId = in.readInt();
return config;
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index afdc9be..c86ec8b 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -231,11 +231,26 @@ public class WifiP2pDevice implements Parcelable {
return (deviceCapability & DEVICE_CAPAB_SERVICE_DISCOVERY) != 0;
}
+ /** Returns true if the device is capable of invitation {@hide}*/
+ public boolean isInvitationCapable() {
+ return (deviceCapability & DEVICE_CAPAB_INVITATION_PROCEDURE) != 0;
+ }
+
+ /** Returns true if the device reaches the limit. {@hide}*/
+ public boolean isDeviceLimit() {
+ return (deviceCapability & DEVICE_CAPAB_DEVICE_LIMIT) != 0;
+ }
+
/** Returns true if the device is a group owner */
public boolean isGroupOwner() {
return (groupCapability & GROUP_CAPAB_GROUP_OWNER) != 0;
}
+ /** Returns true if the group reaches the limit. {@hide}*/
+ public boolean isGroupLimit() {
+ return (groupCapability & GROUP_CAPAB_GROUP_LIMIT) != 0;
+ }
+
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index c30cc73..bc492b3 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -33,6 +33,16 @@ import java.util.regex.Matcher;
*/
public class WifiP2pGroup implements Parcelable {
+ /** The temporary network id.
+ * {@hide} */
+ public static final int TEMPORARY_NET_ID = -1;
+
+ /** The persistent network id.
+ * If a matching persistent profile is found, use it.
+ * Otherwise, create a new persistent profile.
+ * {@hide} */
+ public static final int PERSISTENT_NET_ID = -2;
+
/** The network name */
private String mNetworkName;
@@ -50,13 +60,17 @@ public class WifiP2pGroup implements Parcelable {
private String mInterface;
+ /** The network id in the wpa_supplicant */
+ private int mNetId;
+
/** P2P group started string pattern */
private static final Pattern groupStartedPattern = Pattern.compile(
"ssid=\"(.+)\" " +
"freq=(\\d+) " +
"(?:psk=)?([0-9a-fA-F]{64})?" +
"(?:passphrase=)?(?:\"(.{8,63})\")? " +
- "go_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})"
+ "go_dev_addr=((?:[0-9a-f]{2}:){5}[0-9a-f]{2})" +
+ " ?(\\[PERSISTENT\\])?"
);
public WifiP2pGroup() {
@@ -67,13 +81,15 @@ public class WifiP2pGroup implements Parcelable {
*
* P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437
* [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|
- * passphrase="fKG4jMe3"] go_dev_addr=fa:7b:7a:42:02:13
+ * passphrase="fKG4jMe3"] go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT]
*
* P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED
*
* P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13
* bssid=fa:7b:7a:42:82:13 unknown-network
*
+ * P2P-INVITATION-RECEIVED sa=b8:f9:34:2a:c7:9d persistent=0
+ *
* Note: The events formats can be looked up in the wpa_supplicant code
* @hide
*/
@@ -100,16 +116,38 @@ public class WifiP2pGroup implements Parcelable {
//String psk = match.group(3);
mPassphrase = match.group(4);
mOwner = new WifiP2pDevice(match.group(5));
-
+ if (match.group(6) != null) {
+ mNetId = PERSISTENT_NET_ID;
+ } else {
+ mNetId = TEMPORARY_NET_ID;
+ }
} else if (tokens[0].equals("P2P-INVITATION-RECEIVED")) {
+ String sa = null;
+ mNetId = PERSISTENT_NET_ID;
for (String token : tokens) {
String[] nameValue = token.split("=");
if (nameValue.length != 2) continue;
+ if (nameValue[0].equals("sa")) {
+ sa = nameValue[1];
+
+ // set source address into the client list.
+ WifiP2pDevice dev = new WifiP2pDevice();
+ dev.deviceAddress = nameValue[1];
+ mClients.add(dev);
+ continue;
+ }
+
if (nameValue[0].equals("go_dev_addr")) {
mOwner = new WifiP2pDevice(nameValue[1]);
continue;
}
+
+ if (nameValue[0].equals("persistent")) {
+ mOwner = new WifiP2pDevice(sa);
+ mNetId = Integer.parseInt(nameValue[1]);
+ continue;
+ }
}
} else {
throw new IllegalArgumentException("Malformed supplicant event");
@@ -212,6 +250,16 @@ public class WifiP2pGroup implements Parcelable {
return mInterface;
}
+ /** @hide */
+ public int getNetworkId() {
+ return mNetId;
+ }
+
+ /** @hide */
+ public void setNetworkId(int netId) {
+ this.mNetId = netId;
+ }
+
public String toString() {
StringBuffer sbuf = new StringBuffer();
sbuf.append("network: ").append(mNetworkName);
@@ -221,6 +269,7 @@ public class WifiP2pGroup implements Parcelable {
sbuf.append("\n Client: ").append(client);
}
sbuf.append("\n interface: ").append(mInterface);
+ sbuf.append("\n networkId: ").append(mNetId);
return sbuf.toString();
}
@@ -238,6 +287,7 @@ public class WifiP2pGroup implements Parcelable {
for (WifiP2pDevice d : source.getClientList()) mClients.add(d);
mPassphrase = source.getPassphrase();
mInterface = source.getInterface();
+ mNetId = source.getNetworkId();
}
}
@@ -252,6 +302,7 @@ public class WifiP2pGroup implements Parcelable {
}
dest.writeString(mPassphrase);
dest.writeString(mInterface);
+ dest.writeInt(mNetId);
}
/** Implement the Parcelable interface */
@@ -268,6 +319,7 @@ public class WifiP2pGroup implements Parcelable {
}
group.setPassphrase(in.readString());
group.setInterface(in.readString());
+ group.setNetworkId(in.readInt());
return group;
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl
new file mode 100644
index 0000000..3d8a476
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2012, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p;
+
+parcelable WifiP2pGroupList; \ No newline at end of file
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
new file mode 100644
index 0000000..3459a5a
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.wifi.p2p;
+
+import java.util.Collection;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.LruCache;
+
+
+/**
+ * A class representing a Wi-Fi P2p group list
+ *
+ * {@see WifiP2pManager}
+ * @hide
+ */
+public class WifiP2pGroupList implements Parcelable {
+
+ private static final int CREDENTIAL_MAX_NUM = 32;
+
+ private LruCache<Integer, WifiP2pGroup> mGroups;
+ private GroupDeleteListener mListener;
+ private boolean isClearCalled = false;
+
+ public interface GroupDeleteListener {
+ public void onDeleteGroup(int netId);
+ }
+
+ WifiP2pGroupList() {
+ this(null);
+ }
+
+ WifiP2pGroupList(GroupDeleteListener listener) {
+ mListener = listener;
+ mGroups = new LruCache<Integer, WifiP2pGroup>(CREDENTIAL_MAX_NUM) {
+ @Override
+ protected void entryRemoved(boolean evicted, Integer netId,
+ WifiP2pGroup oldValue, WifiP2pGroup newValue) {
+ if (mListener != null && !isClearCalled) {
+ mListener.onDeleteGroup(oldValue.getNetworkId());
+ }
+ }
+ };
+ }
+
+ /**
+ * Return the list of p2p group.
+ *
+ * @return the list of p2p group.
+ */
+ public Collection<WifiP2pGroup> getGroupList() {
+ return mGroups.snapshot().values();
+ }
+
+ /**
+ * Add the specified group to this group list.
+ *
+ * @param group
+ */
+ void add(WifiP2pGroup group) {
+ mGroups.put(group.getNetworkId(), group);
+ }
+
+ /**
+ * Remove the group with the specified network id from this group list.
+ *
+ * @param netId
+ */
+ void remove(int netId) {
+ mGroups.remove(netId);
+ }
+
+ /**
+ * Remove the group with the specified device address from this group list.
+ *
+ * @param deviceAddress
+ */
+ void remove(String deviceAddress) {
+ remove(getNetworkId(deviceAddress));
+ }
+
+ /**
+ * Clear the group.
+ */
+ boolean clear() {
+ if (mGroups.size() == 0) return false;
+ isClearCalled = true;
+ mGroups.evictAll();
+ isClearCalled = false;
+ return true;
+ }
+
+ /**
+ * Return the network id of the group owner profile with the specified p2p device
+ * address.
+ * If more than one persistent group of the same address is present in the list,
+ * return the first one.
+ *
+ * @param deviceAddress p2p device address.
+ * @return the network id. if not found, return -1.
+ */
+ int getNetworkId(String deviceAddress) {
+ if (deviceAddress == null) return -1;
+
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ for (WifiP2pGroup grp: groups) {
+ if (deviceAddress.equalsIgnoreCase(grp.getOwner().deviceAddress)) {
+ // update cache ordered.
+ mGroups.get(grp.getNetworkId());
+ return grp.getNetworkId();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Return the network id of the group with the specified p2p device address
+ * and the ssid.
+ *
+ * @param deviceAddress p2p device address.
+ * @param ssid ssid.
+ * @return the network id. if not found, return -1.
+ */
+ int getNetworkId(String deviceAddress, String ssid) {
+ if (deviceAddress == null || ssid == null) {
+ return -1;
+ }
+
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ for (WifiP2pGroup grp: groups) {
+ if (deviceAddress.equalsIgnoreCase(grp.getOwner().deviceAddress) &&
+ ssid.equals(grp.getNetworkName())) {
+ // update cache ordered.
+ mGroups.get(grp.getNetworkId());
+ return grp.getNetworkId();
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Return the group owner address of the group with the specified network id
+ *
+ * @param netId network id.
+ * @return the address. if not found, return null.
+ */
+ String getOwnerAddr(int netId) {
+ WifiP2pGroup grp = mGroups.get(netId);
+ if (grp != null) {
+ return grp.getOwner().deviceAddress;
+ }
+ return null;
+ }
+
+ /**
+ * Return true if this group list contains the specified network id.
+ * This function does NOT update LRU information.
+ * It means the internal queue is NOT reordered.
+ *
+ * @param netId network id.
+ * @return true if the specified network id is present in this group list.
+ */
+ boolean contains(int netId) {
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ for (WifiP2pGroup grp: groups) {
+ if (netId == grp.getNetworkId()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ for (WifiP2pGroup grp: groups) {
+ sbuf.append(grp).append("\n");
+ }
+ return sbuf.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ final Collection<WifiP2pGroup> groups = mGroups.snapshot().values();
+ dest.writeInt(groups.size());
+ for(WifiP2pGroup group : groups) {
+ dest.writeParcelable(group, flags);
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<WifiP2pGroupList> CREATOR =
+ new Creator<WifiP2pGroupList>() {
+ public WifiP2pGroupList createFromParcel(Parcel in) {
+ WifiP2pGroupList grpList = new WifiP2pGroupList();
+
+ int deviceCount = in.readInt();
+ for (int i = 0; i < deviceCount; i++) {
+ grpList.add((WifiP2pGroup)in.readParcelable(null));
+ }
+ return grpList;
+ }
+
+ public WifiP2pGroupList[] newArray(int size) {
+ return new WifiP2pGroupList[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 2c25e9d..96d3a7f 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -34,10 +34,10 @@ import android.os.IBinder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Messenger;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.WorkSource;
-import android.os.Messenger;
import android.util.Log;
import com.android.internal.util.AsyncChannel;
@@ -267,6 +267,13 @@ public class WifiP2pManager {
public static final String EXTRA_WIFI_P2P_DEVICE = "wifiP2pDevice";
/**
+ * Broadcast intent action indicating that remembered persistent groups have changed.
+ * @hide
+ */
+ public static final String WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION =
+ "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED";
+
+ /**
* The lookup key for a {@link #String} object.
* Retrieve with {@link android.os.Bundle#getString(String)}.
* @hide
@@ -436,6 +443,18 @@ public class WifiP2pManager {
/** @hide */
public static final int SHOW_PIN_REQUESTED = BASE + 58;
+ /** @hide */
+ public static final int DELETE_PERSISTENT_GROUP = BASE + 59;
+ /** @hide */
+ public static final int DELETE_PERSISTENT_GROUP_FAILED = BASE + 60;
+ /** @hide */
+ public static final int DELETE_PERSISTENT_GROUP_SUCCEEDED = BASE + 61;
+
+ /** @hide */
+ public static final int REQUEST_PERSISTENT_GROUP_INFO = BASE + 62;
+ /** @hide */
+ public static final int RESPONSE_PERSISTENT_GROUP_INFO = BASE + 63;
+
/**
* Create a new WifiP2pManager instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -657,6 +676,15 @@ public class WifiP2pManager {
public void onDetached(int reason);
}
+ /** Interface for callback invocation when stored group info list is available {@hide}*/
+ public interface PersistentGroupInfoListener {
+ /**
+ * The requested stored p2p group info list is available
+ * @param groups Wi-Fi p2p group info list
+ */
+ public void onPersistentGroupInfoAvailable(WifiP2pGroupList groups);
+ }
+
/**
* A channel that connects the application to the Wifi p2p framework.
* Most p2p operations require a Channel as an argument. An instance of Channel is obtained
@@ -713,6 +741,7 @@ public class WifiP2pManager {
case WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED:
case WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED:
case WifiP2pManager.SET_DEVICE_NAME_FAILED:
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP_FAILED:
if (listener != null) {
((ActionListener) listener).onFailure(message.arg1);
}
@@ -732,6 +761,7 @@ public class WifiP2pManager {
case WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED:
case WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED:
case WifiP2pManager.SET_DEVICE_NAME_SUCCEEDED:
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED:
if (listener != null) {
((ActionListener) listener).onSuccess();
}
@@ -786,6 +816,13 @@ public class WifiP2pManager {
mDialogListener = null;
}
break;
+ case WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO:
+ WifiP2pGroupList groups = (WifiP2pGroupList) message.obj;
+ if (listener != null) {
+ ((PersistentGroupInfoListener) listener).
+ onPersistentGroupInfoAvailable(groups);
+ }
+ break;
default:
Log.d(TAG, "Ignored " + message);
break;
@@ -995,7 +1032,8 @@ public class WifiP2pManager {
*/
public void createGroup(Channel c, ActionListener listener) {
checkChannel(c);
- c.mAsyncChannel.sendMessage(CREATE_GROUP, 0, c.putListener(listener));
+ c.mAsyncChannel.sendMessage(CREATE_GROUP, WifiP2pGroup.PERSISTENT_NET_ID,
+ c.putListener(listener));
}
/**
@@ -1297,6 +1335,40 @@ public class WifiP2pManager {
}
/**
+ * Delete a stored persistent group from the system settings.
+ *
+ * <p> The function call immediately returns after sending a persistent group removal request
+ * to the framework. The application is notified of a success or failure to initiate
+ * group removal through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p>The persistent p2p group list stored in the system can be obtained by
+ * {@link #requestPersistentGroupInfo(Channel, PersistentGroupInfoListener)} and
+ * a network id can be obtained by {@link WifiP2pGroup#getNetworkId()}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param netId he network id of the p2p group.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void deletePersistentGroup(Channel c, int netId, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(DELETE_PERSISTENT_GROUP, netId, c.putListener(listener));
+ }
+
+ /**
+ * Request a list of all the persistent p2p groups stored in system.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callback when persistent group info list is available. Can be null.
+ * @hide
+ */
+ public void requestPersistentGroupInfo(Channel c, PersistentGroupInfoListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(REQUEST_PERSISTENT_GROUP_INFO, 0, c.putListener(listener));
+ }
+
+ /**
* Get a reference to WifiP2pService handler. This is used to establish
* an AsyncChannel communication with WifiService
*
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 6978084..8b8077ec 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -45,6 +45,7 @@ import android.net.wifi.WifiMonitor;
import android.net.wifi.WifiNative;
import android.net.wifi.WifiStateMachine;
import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.WifiP2pGroupList.GroupDeleteListener;
import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
@@ -118,6 +119,13 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
private static final Boolean JOIN_GROUP = true;
private static final Boolean FORM_GROUP = false;
+ private static final Boolean TRY_REINVOCATION = true;;
+ private static final Boolean NO_REINVOCATION = false;
+
+ private static final int CONNECT_FAILURE = -1;
+ private static final int CONNECT_SUCCESS = 0;
+ private static final int NEEDS_PROVISION_REQ = 1;
+
/* Two minutes comes from the wpa_supplicant setting */
private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000;
private static int mGroupCreatingTimeoutIndex = 0;
@@ -191,6 +199,84 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"};
private static final String SERVER_ADDRESS = "192.168.49.1";
+ /**
+ * Error code definition.
+ * see the Table.8 in the WiFi Direct specification for the detail.
+ */
+ public static enum P2pStatus {
+ /* Success. */
+ SUCCESS,
+
+ /* The target device is currently unavailable. */
+ INFORMATION_IS_CURRENTLY_UNAVAILABLE,
+
+ /* Protocol error. */
+ INCOMPATIBLE_PARAMETERS,
+
+ /* The target device reached the limit of the number of the connectable device.
+ * For example, device limit or group limit is set. */
+ LIMIT_REACHED,
+
+ /* Protocol error. */
+ INVALID_PARAMETER,
+
+ /* Unable to accommodate request. */
+ UNABLE_TO_ACCOMMODATE_REQUEST,
+
+ /* Previous protocol error, or disruptive behavior. */
+ PREVIOUS_PROTOCOL_ERROR,
+
+ /* There is no common channels the both devices can use. */
+ NO_COMMON_CHANNE,
+
+ /* Unknown p2p group. For example, Device A tries to invoke the previous persistent group,
+ * but device B has removed the specified credential already. */
+ UNKNOWN_P2P_GROUP,
+
+ /* Both p2p devices indicated an intent of 15 in group owner negotiation. */
+ BOTH_GO_INTENT_15,
+
+ /* Incompatible provisioning method. */
+ INCOMPATIBLE_PROVISIONING_METHOD,
+
+ /* Rejected by user */
+ REJECTED_BY_USER,
+
+ /* Unknown error */
+ UNKNOWN;
+
+ public static P2pStatus valueOf(int error) {
+ switch(error) {
+ case 0 :
+ return SUCCESS;
+ case 1:
+ return INFORMATION_IS_CURRENTLY_UNAVAILABLE;
+ case 2:
+ return INCOMPATIBLE_PARAMETERS;
+ case 3:
+ return LIMIT_REACHED;
+ case 4:
+ return INVALID_PARAMETER;
+ case 5:
+ return UNABLE_TO_ACCOMMODATE_REQUEST;
+ case 6:
+ return PREVIOUS_PROTOCOL_ERROR;
+ case 7:
+ return NO_COMMON_CHANNE;
+ case 8:
+ return UNKNOWN_P2P_GROUP;
+ case 9:
+ return BOTH_GO_INTENT_15;
+ case 10:
+ return INCOMPATIBLE_PROVISIONING_METHOD;
+ case 11:
+ return REJECTED_BY_USER;
+ default:
+ return UNKNOWN;
+ }
+ }
+ }
+
public WifiP2pService(Context context) {
mContext = context;
@@ -273,6 +359,16 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
private WifiMonitor mWifiMonitor = new WifiMonitor(this, mWifiNative);
private WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
+ private WifiP2pGroupList mGroups = new WifiP2pGroupList(
+ new GroupDeleteListener() {
+ @Override
+ public void onDeleteGroup(int netId) {
+ if (DBG) logd("called onDeleteGroup() netId=" + netId);
+ mWifiNative.removeNetwork(netId);
+ mWifiNative.saveConfig();
+ sendP2pPersistentGroupsChangedBroadcast();
+ }
+ });
private WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
private WifiP2pGroup mGroup;
@@ -395,6 +491,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
WifiP2pManager.BUSY);
break;
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+ replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
+ WifiP2pManager.BUSY);
+ break;
case WifiP2pManager.REQUEST_PEERS:
replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers);
break;
@@ -404,6 +504,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
case WifiP2pManager.REQUEST_GROUP_INFO:
replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO, mGroup);
break;
+ case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
+ replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO,
+ mGroups);
+ break;
case WifiP2pManager.SET_DIALOG_LISTENER:
String appPkgName = (String)message.getData().getString(
WifiP2pManager.APP_PKG_BUNDLE_KEY);
@@ -520,6 +624,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+ replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
default:
return NOT_HANDLED;
}
@@ -626,6 +734,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
break;
case WifiStateMachine.CMD_DISABLE_P2P:
if (mPeers.clear()) sendP2pPeersChangedBroadcast();
+ if (mGroups.clear()) sendP2pPersistentGroupsChangedBroadcast();
+
mWifiNative.closeSupplicantConnection();
transitionTo(mP2pDisablingState);
break;
@@ -734,6 +844,11 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
sendServiceResponse(resp);
}
break;
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+ if (DBG) logd(getName() + " delete persistent group");
+ mGroups.remove(message.arg1);
+ replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED);
+ break;
default:
return NOT_HANDLED;
}
@@ -768,47 +883,35 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
/* Update group capability before connect */
int gc = mWifiNative.getGroupCapability(config.deviceAddress);
mPeers.updateGroupCapability(config.deviceAddress, gc);
-
- if (mSavedPeerConfig != null && config.deviceAddress.equals(
- mSavedPeerConfig.deviceAddress)) {
- mSavedPeerConfig = config;
-
- //Stop discovery before issuing connect
- mWifiNative.p2pStopFind();
- if (mPeers.isGroupOwner(mSavedPeerConfig.deviceAddress)) {
- p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP);
- } else {
- p2pConnectWithPinDisplay(mSavedPeerConfig, FORM_GROUP);
- }
- transitionTo(mGroupNegotiationState);
- } else {
- mSavedPeerConfig = config;
- int netId = configuredNetworkId(mSavedPeerConfig.deviceAddress);
- if (netId >= 0) {
- //TODO: if failure, remove config and do a regular p2pConnect()
- mWifiNative.p2pReinvoke(netId, mSavedPeerConfig.deviceAddress);
- } else {
- //Stop discovery before issuing connect
- mWifiNative.p2pStopFind();
- //If peer is a GO, we do not need to send provisional discovery,
- //the supplicant takes care of it.
- if (mPeers.isGroupOwner(mSavedPeerConfig.deviceAddress)) {
- if (DBG) logd("Sending join to GO");
- p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP);
- transitionTo(mGroupNegotiationState);
- } else {
- if (DBG) logd("Sending prov disc");
- transitionTo(mProvisionDiscoveryState);
- }
- }
+ int connectRet = connect(config, TRY_REINVOCATION);
+ if (connectRet == CONNECT_FAILURE) {
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
+ break;
}
mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
sendP2pPeersChangedBroadcast();
replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
+ if (connectRet == NEEDS_PROVISION_REQ) {
+ if (DBG) logd("Sending prov disc");
+ transitionTo(mProvisionDiscoveryState);
+ break;
+ }
+ transitionTo(mGroupNegotiationState);
+ break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ if (mWifiNative.p2pStopFind()) {
+ // When discovery stops in inactive state, flush to clear
+ // state peer data
+ mWifiNative.p2pFlush();
+ mServiceDiscReqId = null;
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.ERROR);
+ }
break;
case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
mSavedPeerConfig = (WifiP2pConfig) message.obj;
-
mAutonomousGroup = false;
mJoinExistingGroup = false;
if (!sendConnectNoticeToApp(mPeers.get(mSavedPeerConfig.deviceAddress),
@@ -848,13 +951,6 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
transitionTo(mUserAuthorizingInvitationState);
}
break;
- case WifiMonitor.P2P_FIND_STOPPED_EVENT:
- // When discovery stops in inactive state, flush to clear
- // state peer data
- mWifiNative.p2pFlush();
- mServiceDiscReqId = null;
- sendP2pDiscoveryChangedBroadcast(false);
- break;
case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
@@ -865,16 +961,44 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
break;
case WifiP2pManager.CREATE_GROUP:
mAutonomousGroup = true;
- if (mWifiNative.p2pGroupAdd()) {
- replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED);
+ int netId = message.arg1;
+ boolean ret = false;
+ if (netId == WifiP2pGroup.PERSISTENT_NET_ID) {
+ // check if the go persistent group is present.
+ netId = mGroups.getNetworkId(mThisDevice.deviceAddress);
+ if (netId != -1) {
+ ret = mWifiNative.p2pGroupAdd(netId);
+ } else {
+ ret = mWifiNative.p2pGroupAdd(true);
+ }
+ } else {
+ ret = mWifiNative.p2pGroupAdd(false);
+ }
+
+ if (ret) {
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED);
+ transitionTo(mGroupNegotiationState);
+ } else {
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
+ WifiP2pManager.ERROR);
+ // remain at this state.
+ }
+ break;
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ mGroup = (WifiP2pGroup) message.obj;
+ if (DBG) logd(getName() + " group started");
+
+ if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
+ // This is an invocation case.
+ mAutonomousGroup = false;
+ deferMessage(message);
+ transitionTo(mGroupNegotiationState);
} else {
- replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
- WifiP2pManager.ERROR);
+ return NOT_HANDLED;
}
- transitionTo(mGroupNegotiationState);
break;
default:
- return NOT_HANDLED;
+ return NOT_HANDLED;
}
return HANDLED;
}
@@ -941,10 +1065,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
@Override
public void enter() {
if (DBG) logd(getName());
- if (!sendConnectNoticeToApp(mPeers.get(mSavedPeerConfig.deviceAddress),
- mSavedPeerConfig)) {
- notifyInvitationReceived();
- }
+ notifyInvitationReceived();
}
@Override
@@ -953,11 +1074,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
boolean ret = HANDLED;
switch (message.what) {
case PEER_CONNECTION_USER_ACCEPT:
- //TODO: handle persistence
- if (mJoinExistingGroup) {
- p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP);
- } else {
- p2pConnectWithPinDisplay(mSavedPeerConfig, FORM_GROUP);
+ if (connect(mSavedPeerConfig, TRY_REINVOCATION) == CONNECT_FAILURE) {
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ break;
}
mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
sendP2pPeersChangedBroadcast();
@@ -1000,7 +1120,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
if (DBG) logd("Found a match " + mSavedPeerConfig);
- mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP);
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
transitionTo(mGroupNegotiationState);
}
break;
@@ -1013,11 +1133,14 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
if (DBG) logd("Found a match " + mSavedPeerConfig);
/* we already have the pin */
if (!TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) {
- mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP);
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
transitionTo(mGroupNegotiationState);
} else {
mJoinExistingGroup = false;
- transitionTo(mUserAuthorizingInvitationState);
+ if (!sendConnectNoticeToApp(mPeers.get(mSavedPeerConfig.deviceAddress),
+ mSavedPeerConfig)) {
+ transitionTo(mUserAuthorizingInvitationState);
+ }
}
}
break;
@@ -1029,7 +1152,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
if (mSavedPeerConfig.wps.setup == WpsInfo.DISPLAY) {
if (DBG) logd("Found a match " + mSavedPeerConfig);
mSavedPeerConfig.wps.pin = provDisc.pin;
- mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP);
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
if (!sendShowPinReqToFrontApp(provDisc.pin)) {
notifyInvitationSent(provDisc.pin, device.deviceAddress);
}
@@ -1062,6 +1185,17 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
case WifiMonitor.P2P_GROUP_STARTED_EVENT:
mGroup = (WifiP2pGroup) message.obj;
if (DBG) logd(getName() + " group started");
+
+ if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
+ /*
+ * update cache information and set network id to mGroup.
+ */
+ updatePersistentNetworks();
+ String devAddr = mGroup.getOwner().deviceAddress;
+ mGroup.setNetworkId(mGroups.getNetworkId(devAddr,
+ mGroup.getNetworkName()));
+ }
+
if (mGroup.isGroupOwner()) {
startDhcpServer(mGroup.getInterface());
} else {
@@ -1090,6 +1224,29 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
// failure causes supplicant issues. Ignore right now.
case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
break;
+ case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
+ P2pStatus status = (P2pStatus)message.obj;
+ if (status == P2pStatus.SUCCESS) {
+ // invocation was succeeded.
+ // wait P2P_GROUP_STARTED_EVENT.
+ break;
+ } else if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
+ // target device has already removed the credential.
+ // So, remove this credential accordingly.
+ int netId = mSavedPeerConfig.netId;
+ if (netId >= 0) {
+ if (DBG) logd("Remove unknown client from the list");
+ removeClientFromList(netId, mSavedPeerConfig.deviceAddress, true);
+ }
+ }
+
+ // invocation is failed or deferred. Try another way to connect.
+ mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ if (connect(mSavedPeerConfig, NO_REINVOCATION) == CONNECT_FAILURE) {
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ }
+ break;
default:
return NOT_HANDLED;
}
@@ -1152,7 +1309,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
}
}
sendP2pPeersChangedBroadcast();
- if (DBG) loge(getName() + " ap sta disconnected");
+ if (DBG) logd(getName() + " ap sta disconnected");
} else {
loge("Disconnect on unknown device: " + device);
}
@@ -1172,7 +1329,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
}
break;
case WifiP2pManager.REMOVE_GROUP:
- if (DBG) loge(getName() + " remove group");
+ if (DBG) logd(getName() + " remove group");
if (mWifiNative.p2pGroupRemove(mGroup.getInterface())) {
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
} else {
@@ -1181,7 +1338,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
}
break;
case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
- if (DBG) loge(getName() + " group removed");
+ if (DBG) logd(getName() + " group removed");
Collection <WifiP2pDevice> devices = mGroup.getClientList();
boolean changed = false;
for (WifiP2pDevice d : mPeers.getDeviceList()) {
@@ -1252,6 +1409,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
} else {
logd("Inviting device : " + config.deviceAddress);
+ mSavedPeerConfig = config;
if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) {
mPeers.updateStatus(config.deviceAddress, WifiP2pDevice.INVITED);
sendP2pPeersChangedBroadcast();
@@ -1263,6 +1421,29 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
}
// TODO: figure out updating the status to declined when invitation is rejected
break;
+ case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
+ P2pStatus status = (P2pStatus)message.obj;
+ logd("===> INVITATION RESULT EVENT : " + status);
+ if (status == P2pStatus.SUCCESS) {
+ // invocation was succeeded.
+ break;
+ } else if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
+ // target device has already removed the credential.
+ // So, remove this credential accordingly.
+ int netId = mGroup.getNetworkId();
+ if (netId >= 0) {
+ if (DBG) logd("Remove unknown client from the list");
+ if (!removeClientFromList(netId,
+ mSavedPeerConfig.deviceAddress, false)) {
+ // not found the client on the list
+ Slog.e(TAG, "Already removed the client, ignore");
+ break;
+ }
+ // try invitation.
+ sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
+ }
+ }
+ break;
case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
@@ -1304,7 +1485,6 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
@Override
public void enter() {
if (DBG) logd(getName());
-
notifyInvitationReceived();
}
@@ -1394,6 +1574,13 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
mContext.sendStickyBroadcast(intent);
}
+ private void sendP2pPersistentGroupsChangedBroadcast() {
+ if (DBG) logd("sending p2p persistent groups changed broadcast");
+ Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendStickyBroadcast(intent);
+ }
+
private void startDhcpServer(String intf) {
InterfaceConfiguration ifcg = null;
try {
@@ -1521,11 +1708,246 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
dialog.show();
}
- //TODO: implement when wpa_supplicant is fixed
- private int configuredNetworkId(String deviceAddress) {
+ /**
+ * Synchronize the persistent group list between
+ * wpa_supplicant and mGroups.
+ */
+ private void updatePersistentNetworks() {
+ String listStr = mWifiNative.listNetworks();
+
+ boolean isSaveRequired = false;
+ String[] lines = listStr.split("\n");
+ // Skip the first line, which is a header
+ for (int i = 1; i < lines.length; i++) {
+ String[] result = lines[i].split("\t");
+ if (result == null || result.length < 4) {
+ continue;
+ }
+ // network-id | ssid | bssid | flags
+ int netId = -1;
+ String ssid = result[1];
+ String bssid = result[2];
+ String flags = result[3];
+ try {
+ netId = Integer.parseInt(result[0]);
+ } catch(NumberFormatException e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ if (flags.indexOf("[CURRENT]") != -1) {
+ continue;
+ }
+ if (flags.indexOf("[P2P-PERSISTENT]") == -1) {
+ /*
+ * The unused profile is sometimes remained when the p2p group formation is failed.
+ * So, we clean up the p2p group here.
+ */
+ if (DBG) logd("clean up the unused persistent group. netId=" + netId);
+ mWifiNative.removeNetwork(netId);
+ isSaveRequired = true;
+ continue;
+ }
+
+ if (mGroups.contains(netId)) {
+ continue;
+ }
+
+ WifiP2pGroup group = new WifiP2pGroup();
+ group.setNetworkId(netId);
+ group.setNetworkName(ssid);
+ String mode = mWifiNative.getNetworkVariable(netId, "mode");
+ if (mode != null && mode.equals("3")) {
+ group.setIsGroupOwner(true);
+ }
+ if (bssid.equalsIgnoreCase(mThisDevice.deviceAddress)) {
+ group.setOwner(mThisDevice);
+ } else {
+ WifiP2pDevice device = new WifiP2pDevice();
+ device.deviceAddress = bssid;
+ group.setOwner(device);
+ }
+ mGroups.add(group);
+ isSaveRequired = true;
+ }
+
+ if (isSaveRequired) {
+ sendP2pPersistentGroupsChangedBroadcast();
+ mWifiNative.saveConfig();
+ }
+ }
+
+ /**
+ * Try to connect to the target device.
+ *
+ * Use the persistent credential if it has been stored.
+ *
+ * @param config
+ * @param tryInvocation if true, try to invoke. Otherwise, never try to invoke.
+ * @return
+ */
+ private int connect(WifiP2pConfig config, boolean tryInvocation) {
+
+ if (config == null) {
+ loge("invalid argument.");
+ return CONNECT_FAILURE;
+ }
+
+ boolean isResp = (mSavedPeerConfig != null &&
+ config.deviceAddress.equals(mSavedPeerConfig.deviceAddress));
+ mSavedPeerConfig = config;
+
+ WifiP2pDevice dev = mPeers.get(config.deviceAddress);
+ if (dev == null) {
+ loge("target device is not found.");
+ return CONNECT_FAILURE;
+ }
+
+ boolean join = dev.isGroupOwner();
+ String ssid = mWifiNative.p2pGetSsid(dev.deviceAddress);
+ if (DBG) logd("target ssid is " + ssid + " join:" + join);
+
+ if (join && dev.isGroupLimit()) {
+ if (DBG) logd("target device reaches group limit.");
+
+ // if the target group has reached the limit,
+ // try group formation.
+ join = false;
+ } else if (join) {
+ int netId = mGroups.getNetworkId(dev.deviceAddress, ssid);
+ if (netId >= 0) {
+ // Skip WPS and start 4way handshake immediately.
+ if (!mWifiNative.p2pGroupAdd(netId)) {
+ return CONNECT_FAILURE;
+ }
+ return CONNECT_SUCCESS;
+ }
+ }
+
+ if (!join && dev.isDeviceLimit()) {
+ loge("target device reaches the device limit.");
+ return CONNECT_FAILURE;
+ }
+
+ if (!join && tryInvocation && dev.isInvitationCapable()) {
+ int netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ if (config.netId >= 0) {
+ if (config.deviceAddress.equals(mGroups.getOwnerAddr(config.netId))) {
+ netId = config.netId;
+ }
+ } else {
+ netId = mGroups.getNetworkId(dev.deviceAddress);
+ }
+ if (netId < 0) {
+ netId = getNetworkIdFromClientList(dev.deviceAddress);
+ }
+ if (DBG) logd("netId related with " + dev.deviceAddress + " = " + netId);
+ if (netId >= 0) {
+
+ // Invoke the persistent group.
+ if (!mWifiNative.p2pReinvoke(netId, dev.deviceAddress)) {
+ loge("p2pReinvoke() failed");
+ return CONNECT_FAILURE;
+ }
+ // Save network id. It'll be used when an invitation result event is received.
+ mSavedPeerConfig.netId = netId;
+ return CONNECT_SUCCESS;
+ }
+ }
+
+ //Stop discovery before issuing connect
+ mWifiNative.p2pStopFind();
+
+ if (!isResp) {
+ return NEEDS_PROVISION_REQ;
+ }
+
+ p2pConnectWithPinDisplay(config);
+ return CONNECT_SUCCESS;
+ }
+
+ /**
+ * Return the network id of the group owner profile which has the p2p client with
+ * the specified device address in it's client list.
+ * If more than one persistent group of the same address is present in its client
+ * lists, return the first one.
+ *
+ * @param deviceAddress p2p device address.
+ * @return the network id. if not found, return -1.
+ */
+ private int getNetworkIdFromClientList(String deviceAddress) {
+ if (deviceAddress == null) return -1;
+
+ Collection<WifiP2pGroup> groups = mGroups.getGroupList();
+ for (WifiP2pGroup group : groups) {
+ int netId = group.getNetworkId();
+ String[] p2pClientList = getClientList(netId);
+ if (p2pClientList == null) continue;
+ for (String client : p2pClientList) {
+ if (deviceAddress.equalsIgnoreCase(client)) {
+ return netId;
+ }
+ }
+ }
return -1;
}
+ /**
+ * Return p2p client list associated with the specified network id.
+ * @param netId network id.
+ * @return p2p client list. if not found, return null.
+ */
+ private String[] getClientList(int netId) {
+ String p2pClients = mWifiNative.getNetworkVariable(netId, "p2p_client_list");
+ if (p2pClients == null) {
+ return null;
+ }
+ return p2pClients.split(" ");
+ }
+
+ /**
+ * Remove the specified p2p client from the specified profile.
+ * @param netId network id of the profile.
+ * @param addr p2p client address to be removed.
+ * @param isRemovable if true, remove the specified profile if its client list becomes empty.
+ * @return whether removing the specified p2p client is successful or not.
+ */
+ private boolean removeClientFromList(int netId, String addr, boolean isRemovable) {
+ StringBuilder modifiedClientList = new StringBuilder();
+ String[] currentClientList = getClientList(netId);
+ boolean isClientRemoved = false;
+ if (currentClientList != null) {
+ for (String client : currentClientList) {
+ if (!client.equalsIgnoreCase(addr)) {
+ modifiedClientList.append(" ");
+ modifiedClientList.append(client);
+ } else {
+ isClientRemoved = true;
+ }
+ }
+ }
+ if (modifiedClientList.length() == 0 && isRemovable) {
+ // the client list is empty. so remove it.
+ if (DBG) logd("Remove unknown network");
+ mGroups.remove(netId);
+ return true;
+ }
+
+ if (!isClientRemoved) {
+ // specified p2p client is not found. already removed.
+ return false;
+ }
+
+ if (DBG) logd("Modified client list: " + modifiedClientList);
+ if (modifiedClientList.length() == 0) {
+ modifiedClientList.append("\"\"");
+ }
+ mWifiNative.setNetworkVariable(netId,
+ "p2p_client_list", modifiedClientList.toString());
+ mWifiNative.saveConfig();
+ return true;
+ }
+
private void setWifiP2pInfoOnGroupFormation(String serverAddress) {
mWifiP2pInfo.groupFormed = true;
mWifiP2pInfo.isGroupOwner = mGroup.isGroupOwner();
@@ -1547,8 +1969,14 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
return deviceAddress;
}
- private void p2pConnectWithPinDisplay(WifiP2pConfig config, boolean join) {
- String pin = mWifiNative.p2pConnect(config, join);
+ private void p2pConnectWithPinDisplay(WifiP2pConfig config) {
+ WifiP2pDevice dev = mPeers.get(config.deviceAddress);
+ if (dev == null) {
+ loge("target device is not found " + config.deviceAddress);
+ return;
+ }
+
+ String pin = mWifiNative.p2pConnect(config, dev.isGroupOwner());
try {
Integer.parseInt(pin);
if (!sendShowPinReqToFrontApp(pin)) {
@@ -1611,6 +2039,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
mWifiNative.p2pServiceFlush();
mServiceTransactionId = 0;
mServiceDiscReqId = null;
+
+ updatePersistentNetworks();
}
private void updateThisDevice(int status) {
@@ -1739,7 +2169,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
//Application does not have transaction id information
//go through stored requests to remove
boolean removed = false;
- for (int i=0; i < clientInfo.mReqList.size(); i++) {
+ for (int i=0; i<clientInfo.mReqList.size(); i++) {
if (req.equals(clientInfo.mReqList.valueAt(i))) {
removed = true;
clientInfo.mReqList.removeAt(i);
@@ -2078,5 +2508,4 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
mServList = new ArrayList<WifiP2pServiceInfo>();
}
}
-
}