summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.xml731
-rw-r--r--cmds/stagefright/recordvideo.cpp92
-rw-r--r--core/java/android/animation/LayoutTransition.java109
-rw-r--r--core/java/android/app/ActivityManagerNative.java29
-rw-r--r--core/java/android/app/ActivityThread.java7
-rw-r--r--core/java/android/app/FragmentManager.java16
-rw-r--r--core/java/android/app/FragmentTransaction.java8
-rw-r--r--core/java/android/app/IActivityManager.java6
-rw-r--r--core/java/android/content/ClipboardManager.java2
-rw-r--r--core/java/android/content/IClipboard.aidl2
-rw-r--r--core/java/android/content/SyncManager.java932
-rw-r--r--core/java/android/content/SyncOperation.java73
-rw-r--r--core/java/android/content/SyncQueue.java91
-rw-r--r--core/java/android/content/SyncStorageEngine.java102
-rw-r--r--core/java/android/content/pm/ActivityInfo.java40
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java13
-rw-r--r--core/java/android/content/pm/PackageParser.java11
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java21
-rw-r--r--core/java/android/net/LinkAddress.aidl21
-rw-r--r--core/java/android/net/LinkAddress.java149
-rw-r--r--core/java/android/net/LinkProperties.java65
-rw-r--r--core/java/android/net/MobileDataStateTracker.java2
-rw-r--r--core/java/android/nfc/NdefMessage.java89
-rw-r--r--core/java/android/nfc/NdefRecord.java231
-rw-r--r--core/java/android/os/BatteryManager.java10
-rw-r--r--core/java/android/os/Debug.java9
-rw-r--r--core/java/android/os/Environment.java36
-rw-r--r--core/java/android/preference/PreferenceActivity.java19
-rw-r--r--core/java/android/view/DragEvent.java9
-rw-r--r--core/java/android/view/GLES20Canvas.java4
-rw-r--r--core/java/android/view/HardwareRenderer.java2
-rw-r--r--core/java/android/view/IWindow.aidl2
-rw-r--r--core/java/android/view/SurfaceView.java6
-rw-r--r--core/java/android/view/View.java138
-rw-r--r--core/java/android/view/ViewDebug.java11
-rw-r--r--core/java/android/view/ViewGroup.java191
-rw-r--r--core/java/android/view/ViewRoot.java59
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java59
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java10
-rwxr-xr-xcore/java/android/webkit/DeviceOrientationService.java8
-rw-r--r--core/java/android/webkit/WebSettings.java1
-rw-r--r--core/java/android/webkit/WebViewCore.java8
-rw-r--r--core/java/android/widget/Adapter.java2
-rw-r--r--core/java/android/widget/AdapterView.java4
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java67
-rw-r--r--core/java/android/widget/AdapterViewFlipper.java17
-rw-r--r--core/java/android/widget/ExpandableListView.java13
-rw-r--r--core/java/android/widget/ListView.java16
-rw-r--r--core/java/android/widget/PopupWindow.java5
-rw-r--r--core/java/android/widget/StackView.java4
-rw-r--r--core/java/android/widget/TextView.java94
-rw-r--r--core/java/com/android/internal/app/AlertController.java13
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl1
-rw-r--r--core/java/com/android/internal/view/InputMethodAndSubtypeEnabler.java319
-rw-r--r--core/jni/android_database_SQLiteDatabase.cpp2
-rw-r--r--core/jni/android_view_GLES20Canvas.cpp23
-rw-r--r--core/res/AndroidManifest.xml5
-rw-r--r--core/res/res/anim/fragment_close_enter.xml14
-rw-r--r--core/res/res/anim/fragment_close_exit.xml14
-rw-r--r--core/res/res/anim/fragment_next_enter.xml32
-rw-r--r--core/res/res/anim/fragment_next_exit.xml32
-rw-r--r--core/res/res/anim/fragment_open_enter.xml14
-rw-r--r--core/res/res/anim/fragment_open_exit.xml14
-rw-r--r--core/res/res/anim/fragment_prev_enter.xml32
-rw-r--r--core/res/res/anim/fragment_prev_exit.xml32
-rw-r--r--core/res/res/drawable-hdpi/ic_dialog_menu_generic.pngbin5541 -> 0 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_gray.9.pngbin3069 -> 5652 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.pngbin4534 -> 5892 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_red.9.pngbin4510 -> 5773 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_yellow.9.pngbin4561 -> 5747 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.pngbin4389 -> 5683 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.pngbin4211 -> 5656 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_gray.9.pngbin3274 -> 5868 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_green.9.pngbin4926 -> 6106 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.pngbin4952 -> 6029 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_yellow.9.pngbin4864 -> 5964 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.pngbin4511 -> 5711 bytes
-rw-r--r--core/res/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.pngbin4267 -> 5754 bytes
-rw-r--r--core/res/res/drawable-hdpi/quickcontact_badge_pressed.9.pngbin670 -> 3093 bytes
-rw-r--r--core/res/res/drawable-hdpi/quickcontact_badge_small_pressed.9.pngbin633 -> 3150 bytes
-rw-r--r--core/res/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.pngbin627 -> 3184 bytes
-rw-r--r--core/res/res/drawable-hdpi/quickcontact_badge_unpressed.9.pngbin709 -> 3127 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_battery_100.pngbin953 -> 1807 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_battery_40.pngbin889 -> 1670 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_battery_60.pngbin895 -> 1706 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_battery_80.pngbin892 -> 1704 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.pngbin1105 -> 1941 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.pngbin1155 -> 1956 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.pngbin1187 -> 1914 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.pngbin1235 -> 2023 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_download_anim1.pngbin803 -> 1192 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_download_anim2.pngbin827 -> 1222 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_download_anim3.pngbin803 -> 1332 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_download_anim4.pngbin767 -> 1360 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_download_anim5.pngbin796 -> 1263 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_upload_anim0.pngbin815 -> 1097 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_upload_anim1.pngbin830 -> 1130 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_upload_anim2.pngbin842 -> 1183 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_upload_anim3.pngbin820 -> 1206 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_upload_anim4.pngbin749 -> 1328 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/stat_sys_upload_anim5.pngbin784 -> 1164 bytes
-rw-r--r--core/res/res/drawable-hdpi/status_bar_close_on.9.pngbin2368 -> 0 bytes
-rwxr-xr-xcore/res/res/drawable-mdpi/ic_dialog_menu_generic.pngbin1187 -> 0 bytes
-rw-r--r--core/res/res/drawable-mdpi/status_bar_close_on.9.pngbin646 -> 0 bytes
-rwxr-xr-xcore/res/res/values/attrs.xml6
-rw-r--r--core/res/res/values/attrs_manifest.xml22
-rw-r--r--core/res/res/values/config.xml9
-rw-r--r--core/res/res/values/public.xml5
-rwxr-xr-xcore/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/styles.xml18
-rw-r--r--core/tests/coretests/src/android/app/DownloadManagerBaseTest.java20
-rw-r--r--core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java5
-rw-r--r--core/tests/coretests/src/android/content/SyncQueueTest.java164
-rw-r--r--core/tests/coretests/src/android/content/SyncStorageEngineTest.java3
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java6
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java2
-rw-r--r--core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java6
-rw-r--r--docs/html/guide/appendix/market-filters.jd2
-rw-r--r--docs/html/guide/practices/design/performance.jd42
-rw-r--r--docs/html/guide/practices/design/responsiveness.jd71
-rw-r--r--docs/html/guide/practices/design/seamlessness.jd35
-rw-r--r--docs/html/guide/practices/screens_support.jd414
-rw-r--r--docs/html/guide/practices/ui_guidelines/activity_task_design.jd2
-rw-r--r--docs/html/guide/practices/ui_guidelines/icon_design.jd25
-rw-r--r--docs/html/guide/practices/ui_guidelines/icon_design_1.jd11
-rw-r--r--docs/html/guide/practices/ui_guidelines/menu_design.jd2
-rw-r--r--docs/html/guide/practices/ui_guidelines/widget_design.jd4
-rw-r--r--docs/html/guide/publishing/app-signing.jd2
-rw-r--r--docs/html/guide/publishing/licensing.jd2
-rw-r--r--docs/html/guide/publishing/preparing.jd25
-rw-r--r--docs/html/guide/publishing/publishing.jd2
-rw-r--r--docs/html/guide/publishing/versioning.jd2
-rw-r--r--docs/html/guide/topics/appwidgets/index.jd21
-rw-r--r--docs/html/guide/topics/fundamentals.jd18
-rw-r--r--docs/html/guide/topics/fundamentals/activities.jd762
-rw-r--r--docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd568
-rw-r--r--docs/html/guide/topics/intents/intents-filters.jd18
-rw-r--r--docs/html/guide/topics/media/index.jd18
-rw-r--r--docs/html/guide/topics/providers/content-providers.jd13
-rw-r--r--docs/html/guide/topics/search/adding-custom-suggestions.jd2
-rw-r--r--docs/html/guide/topics/search/index.jd2
-rw-r--r--docs/html/guide/topics/search/search-dialog.jd2
-rwxr-xr-xdocs/html/guide/topics/testing/testing_android.jd6
-rw-r--r--docs/html/guide/topics/ui/binding.jd8
-rw-r--r--docs/html/guide/topics/ui/declaring-layout.jd12
-rw-r--r--docs/html/guide/topics/ui/dialogs.jd9
-rw-r--r--docs/html/guide/topics/ui/index.jd14
-rw-r--r--docs/html/guide/topics/ui/notifiers/index.jd8
-rw-r--r--docs/html/guide/topics/ui/notifiers/notifications.jd20
-rw-r--r--docs/html/guide/topics/ui/notifiers/toasts.jd14
-rw-r--r--docs/html/guide/topics/ui/ui-events.jd4
-rw-r--r--docs/html/guide/topics/wireless/bluetooth.jd26
-rw-r--r--docs/html/images/fundamentals/diagram_backstack.pngbin0 -> 45329 bytes
-rw-r--r--docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.pngbin0 -> 56854 bytes
-rw-r--r--docs/html/images/fundamentals/diagram_multiple_instances.pngbin0 -> 12140 bytes
-rw-r--r--docs/html/images/fundamentals/diagram_multitasking.pngbin0 -> 21931 bytes
-rw-r--r--docs/html/images/fundamentals/restore_instance.pngbin0 -> 83427 bytes
-rw-r--r--docs/html/images/home/market-intl.pngbin0 -> 4953 bytes
-rw-r--r--docs/html/images/screens_support/screens-ranges.pngbin0 -> 8514 bytes
-rw-r--r--docs/html/index.jd12
-rw-r--r--graphics/java/android/graphics/Bitmap.java9
-rw-r--r--graphics/java/android/graphics/drawable/ColorDrawable.java12
-rw-r--r--graphics/java/android/renderscript/Allocation.java18
-rw-r--r--graphics/java/android/renderscript/ProgramFragment.java2
-rw-r--r--graphics/java/android/renderscript/ProgramVertex.java6
-rw-r--r--graphics/java/android/renderscript/RenderScript.java9
-rw-r--r--graphics/jni/android_renderscript_RenderScript.cpp16
-rw-r--r--include/camera/CameraHardwareInterface.h53
-rw-r--r--include/media/stagefright/MPEG4Writer.h1
-rw-r--r--include/private/surfaceflinger/SharedBufferStack.h10
-rw-r--r--include/surfaceflinger/Surface.h2
-rw-r--r--include/ui/egl/android_natives.h12
-rw-r--r--include/utils/ZipFileRO.h24
-rw-r--r--libs/binder/CursorWindow.cpp6
-rw-r--r--libs/hwui/Caches.h2
-rw-r--r--libs/hwui/DisplayListRenderer.cpp2
-rw-r--r--libs/hwui/DisplayListRenderer.h2
-rw-r--r--libs/hwui/FboCache.cpp23
-rw-r--r--libs/hwui/FboCache.h2
-rw-r--r--libs/hwui/FontRenderer.cpp5
-rw-r--r--libs/hwui/FontRenderer.h12
-rw-r--r--libs/hwui/GenerationCache.h6
-rw-r--r--libs/hwui/Layer.h22
-rw-r--r--libs/hwui/LayerCache.cpp16
-rw-r--r--libs/hwui/LayerCache.h2
-rw-r--r--libs/hwui/Matrix.cpp9
-rw-r--r--libs/hwui/Matrix.h2
-rw-r--r--libs/hwui/OpenGLDebugRenderer.cpp4
-rw-r--r--libs/hwui/OpenGLDebugRenderer.h2
-rw-r--r--libs/hwui/OpenGLRenderer.cpp182
-rw-r--r--libs/hwui/OpenGLRenderer.h5
-rw-r--r--libs/hwui/ProgramCache.cpp6
-rw-r--r--libs/hwui/Properties.h2
-rw-r--r--libs/hwui/SkiaShader.cpp60
-rw-r--r--libs/hwui/SkiaShader.h10
-rw-r--r--libs/hwui/Snapshot.h49
-rw-r--r--libs/hwui/TextureCache.cpp22
-rw-r--r--libs/hwui/TextureCache.h1
-rw-r--r--libs/rs/Android.mk1
-rw-r--r--libs/rs/RenderScript.h2
-rw-r--r--libs/rs/RenderScriptEnv.h2
-rw-r--r--libs/rs/java/Samples/res/raw/multitexf.glsl2
-rw-r--r--libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java6
-rw-r--r--libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs46
-rw-r--r--libs/rs/rs.spec11
-rw-r--r--libs/rs/rsAllocation.cpp49
-rw-r--r--libs/rs/rsAllocation.h8
-rw-r--r--libs/rs/rsContext.cpp30
-rw-r--r--libs/rs/rsContext.h5
-rw-r--r--libs/rs/rsContextHostStub.h2
-rw-r--r--libs/rs/rsFileA3D.cpp3
-rw-r--r--libs/rs/rsFont.cpp158
-rw-r--r--libs/rs/rsFont.h51
-rw-r--r--libs/rs/rsLight.cpp144
-rw-r--r--libs/rs/rsLight.h67
-rw-r--r--libs/rs/rsProgramFragment.cpp2
-rw-r--r--libs/rs/rsProgramVertex.cpp5
-rw-r--r--libs/rs/rsScriptC_LibGL.cpp52
-rw-r--r--libs/rs/rsType.cpp53
-rw-r--r--libs/rs/rsType.h3
-rw-r--r--libs/rs/rsVertexArray.h3
-rw-r--r--libs/rs/scriptc/rs_graphics.rsh6
-rw-r--r--libs/surfaceflinger_client/SharedBufferStack.cpp30
-rw-r--r--libs/surfaceflinger_client/Surface.cpp34
-rw-r--r--libs/utils/ZipFileRO.cpp33
-rwxr-xr-xmedia/java/android/media/videoeditor/AudioTrack.java251
-rwxr-xr-xmedia/java/android/media/videoeditor/Effect.java17
-rwxr-xr-xmedia/java/android/media/videoeditor/MediaImageItem.java56
-rwxr-xr-xmedia/java/android/media/videoeditor/MediaItem.java73
-rwxr-xr-xmedia/java/android/media/videoeditor/MediaVideoItem.java94
-rwxr-xr-xmedia/java/android/media/videoeditor/Overlay.java17
-rwxr-xr-xmedia/java/android/media/videoeditor/VideoEditorFactory.java22
-rw-r--r--media/java/android/media/videoeditor/VideoEditorTestImpl.java88
-rw-r--r--media/libmediaplayerservice/StagefrightRecorder.cpp70
-rw-r--r--media/libmediaplayerservice/StagefrightRecorder.h4
-rw-r--r--media/libstagefright/AwesomePlayer.cpp20
-rw-r--r--media/libstagefright/MPEG4Writer.cpp217
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java200
-rw-r--r--opengl/libagl/egl.cpp2
-rw-r--r--packages/SystemUI/res/drawable-hdpi/battery_low_battery.pngbin4759 -> 4060 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/ic_notification_overlay.9.pngbin1014 -> 935 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.pngbin1058 -> 1188 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.pngbin313 -> 1188 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.pngbin1070 -> 1545 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.pngbin1070 -> 1558 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.pngbin1070 -> 1746 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.pngbin1070 -> 1755 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.pngbin1042 -> 1832 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.pngbin1042 -> 1833 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.pngbin986 -> 1890 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.pngbin986 -> 1890 bytes
-rw-r--r--packages/SystemUI/res/values/colors.xml21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java70
-rw-r--r--policy/src/com/android/internal/policy/impl/PhoneWindow.java6
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java82
-rw-r--r--services/java/com/android/server/BatteryService.java29
-rw-r--r--services/java/com/android/server/ClipboardService.java162
-rw-r--r--services/java/com/android/server/ConnectivityService.java2
-rw-r--r--services/java/com/android/server/InputManager.java1
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java350
-rw-r--r--services/java/com/android/server/PackageManagerService.java49
-rw-r--r--services/java/com/android/server/SystemServer.java14
-rw-r--r--services/java/com/android/server/TelephonyRegistry.java4
-rw-r--r--services/java/com/android/server/WifiService.java1
-rw-r--r--services/java/com/android/server/WindowManagerService.java91
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java79
-rw-r--r--services/java/com/android/server/am/ActivityStack.java4
-rw-r--r--services/java/com/android/server/am/UriPermissionOwner.java2
-rwxr-xr-xservices/java/com/android/server/location/GpsLocationProvider.java281
-rwxr-xr-xservices/jni/com_android_server_location_GpsLocationProvider.cpp228
-rw-r--r--telephony/java/com/android/internal/telephony/Connection.java1
-rw-r--r--telephony/java/com/android/internal/telephony/DataConnection.java34
-rwxr-xr-xtelephony/java/com/android/internal/telephony/sip/SipPhone.java3
-rw-r--r--tests/CoreTests/android/core/MiscRegressionTest.java31
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java57
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java2
-rw-r--r--tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java2
-rw-r--r--tools/aapt/ResourceTable.cpp18
-rw-r--r--tools/layoutlib/create/README.txt17
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java27
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java153
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java93
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java94
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java319
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java65
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java30
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java26
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java21
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java53
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java12
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java305
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java27
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java43
-rw-r--r--voip/java/android/net/sip/ISipService.aidl3
-rw-r--r--voip/java/android/net/sip/SipErrorCode.java5
-rw-r--r--voip/java/android/net/sip/SipManager.java62
-rw-r--r--voip/java/com/android/server/sip/SipService.java73
-rw-r--r--voip/java/com/android/server/sip/SipSessionGroup.java4
-rw-r--r--voip/jni/rtp/AmrCodec.cpp2
-rw-r--r--voip/jni/rtp/Android.mk1
-rw-r--r--voip/jni/rtp/AudioGroup.cpp7
-rw-r--r--voip/jni/rtp/EchoSuppressor.cpp172
-rw-r--r--voip/jni/rtp/EchoSuppressor.h51
-rw-r--r--wifi/java/android/net/wifi/WifiConfigStore.java369
-rw-r--r--wifi/java/android/net/wifi/WifiConfiguration.java101
-rw-r--r--wifi/java/android/net/wifi/WifiStateMachine.java27
308 files changed, 9843 insertions, 3226 deletions
diff --git a/api/current.xml b/api/current.xml
index c769794..07ac099 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2103,7 +2103,7 @@
type="int"
transient="false"
volatile="false"
- value="16843563"
+ value="16843567"
static="true"
final="true"
deprecated="not deprecated"
@@ -2125,7 +2125,7 @@
type="int"
transient="false"
volatile="false"
- value="16843572"
+ value="16843576"
static="true"
final="true"
deprecated="not deprecated"
@@ -2136,7 +2136,7 @@
type="int"
transient="false"
volatile="false"
- value="16843571"
+ value="16843575"
static="true"
final="true"
deprecated="not deprecated"
@@ -2147,7 +2147,7 @@
type="int"
transient="false"
volatile="false"
- value="16843573"
+ value="16843577"
static="true"
final="true"
deprecated="not deprecated"
@@ -2191,7 +2191,7 @@
type="int"
transient="false"
volatile="false"
- value="16843580"
+ value="16843584"
static="true"
final="true"
deprecated="not deprecated"
@@ -2213,7 +2213,7 @@
type="int"
transient="false"
volatile="false"
- value="16843576"
+ value="16843580"
static="true"
final="true"
deprecated="not deprecated"
@@ -2235,7 +2235,7 @@
type="int"
transient="false"
volatile="false"
- value="16843574"
+ value="16843578"
static="true"
final="true"
deprecated="not deprecated"
@@ -2246,7 +2246,7 @@
type="int"
transient="false"
volatile="false"
- value="16843581"
+ value="16843585"
static="true"
final="true"
deprecated="not deprecated"
@@ -2257,7 +2257,7 @@
type="int"
transient="false"
volatile="false"
- value="16843582"
+ value="16843586"
static="true"
final="true"
deprecated="not deprecated"
@@ -2466,7 +2466,7 @@
type="int"
transient="false"
volatile="false"
- value="16843570"
+ value="16843574"
static="true"
final="true"
deprecated="not deprecated"
@@ -2807,7 +2807,7 @@
type="int"
transient="false"
volatile="false"
- value="16843589"
+ value="16843593"
static="true"
final="true"
deprecated="not deprecated"
@@ -2818,7 +2818,7 @@
type="int"
transient="false"
volatile="false"
- value="16843588"
+ value="16843592"
static="true"
final="true"
deprecated="not deprecated"
@@ -4497,6 +4497,28 @@
visibility="public"
>
</field>
+<field name="fragmentNextEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843563"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentNextExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843564"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="fragmentOpenEnterAnimation"
type="int"
transient="false"
@@ -4519,6 +4541,28 @@
visibility="public"
>
</field>
+<field name="fragmentPrevEnterAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843565"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="fragmentPrevExitAnimation"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843566"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="freezesText"
type="int"
transient="false"
@@ -4996,7 +5040,7 @@
type="int"
transient="false"
volatile="false"
- value="16843579"
+ value="16843583"
static="true"
final="true"
deprecated="not deprecated"
@@ -5117,7 +5161,7 @@
type="int"
transient="false"
volatile="false"
- value="16843566"
+ value="16843570"
static="true"
final="true"
deprecated="not deprecated"
@@ -5128,7 +5172,7 @@
type="int"
transient="false"
volatile="false"
- value="16843564"
+ value="16843568"
static="true"
final="true"
deprecated="not deprecated"
@@ -5139,7 +5183,7 @@
type="int"
transient="false"
volatile="false"
- value="16843565"
+ value="16843569"
static="true"
final="true"
deprecated="not deprecated"
@@ -5502,7 +5546,7 @@
type="int"
transient="false"
volatile="false"
- value="16843575"
+ value="16843579"
static="true"
final="true"
deprecated="not deprecated"
@@ -6268,17 +6312,6 @@
visibility="public"
>
</field>
-<field name="kraken_resource_pad56"
- type="int"
- transient="false"
- volatile="false"
- value="16843465"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="kraken_resource_pad6"
type="int"
transient="false"
@@ -6789,7 +6822,7 @@
type="int"
transient="false"
volatile="false"
- value="16843568"
+ value="16843572"
static="true"
final="true"
deprecated="not deprecated"
@@ -6833,7 +6866,7 @@
type="int"
transient="false"
volatile="false"
- value="16843590"
+ value="16843594"
static="true"
final="true"
deprecated="not deprecated"
@@ -6844,7 +6877,7 @@
type="int"
transient="false"
volatile="false"
- value="16843584"
+ value="16843588"
static="true"
final="true"
deprecated="not deprecated"
@@ -6932,7 +6965,7 @@
type="int"
transient="false"
volatile="false"
- value="16843592"
+ value="16843596"
static="true"
final="true"
deprecated="not deprecated"
@@ -7687,6 +7720,17 @@
visibility="public"
>
</field>
+<field name="popupAnimationStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843465"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="popupBackground"
type="int"
transient="false"
@@ -7735,7 +7779,7 @@
type="int"
transient="false"
volatile="false"
- value="16843585"
+ value="16843589"
static="true"
final="true"
deprecated="not deprecated"
@@ -9044,7 +9088,7 @@
type="int"
transient="false"
volatile="false"
- value="16843569"
+ value="16843573"
static="true"
final="true"
deprecated="not deprecated"
@@ -9066,7 +9110,7 @@
type="int"
transient="false"
volatile="false"
- value="16843567"
+ value="16843571"
static="true"
final="true"
deprecated="not deprecated"
@@ -9165,7 +9209,7 @@
type="int"
transient="false"
volatile="false"
- value="16843583"
+ value="16843587"
static="true"
final="true"
deprecated="not deprecated"
@@ -9396,7 +9440,7 @@
type="int"
transient="false"
volatile="false"
- value="16843578"
+ value="16843582"
static="true"
final="true"
deprecated="not deprecated"
@@ -9781,7 +9825,7 @@
type="int"
transient="false"
volatile="false"
- value="16843586"
+ value="16843590"
static="true"
final="true"
deprecated="not deprecated"
@@ -9858,7 +9902,7 @@
type="int"
transient="false"
volatile="false"
- value="16843587"
+ value="16843591"
static="true"
final="true"
deprecated="not deprecated"
@@ -9902,7 +9946,7 @@
type="int"
transient="false"
volatile="false"
- value="16843591"
+ value="16843595"
static="true"
final="true"
deprecated="not deprecated"
@@ -10320,7 +10364,7 @@
type="int"
transient="false"
volatile="false"
- value="16843577"
+ value="16843581"
static="true"
final="true"
deprecated="not deprecated"
@@ -21025,20 +21069,7 @@
visibility="public"
>
</constructor>
-<method name="addTransitionListener"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="listener" type="android.animation.LayoutTransition.TransitionListener">
-</parameter>
-</method>
-<method name="childAdd"
+<method name="addChild"
return="void"
abstract="false"
native="false"
@@ -21053,7 +21084,7 @@
<parameter name="child" type="android.view.View">
</parameter>
</method>
-<method name="childRemove"
+<method name="addTransitionListener"
return="void"
abstract="false"
native="false"
@@ -21063,9 +21094,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="parent" type="android.view.ViewGroup">
-</parameter>
-<parameter name="child" type="android.view.View">
+<parameter name="listener" type="android.animation.LayoutTransition.TransitionListener">
</parameter>
</method>
<method name="getAnimator"
@@ -21144,6 +21173,36 @@
visibility="public"
>
</method>
+<method name="hideChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
+<method name="removeChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
<method name="removeTransitionListener"
return="void"
abstract="false"
@@ -21245,6 +21304,21 @@
<parameter name="delay" type="long">
</parameter>
</method>
+<method name="showChild"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="parent" type="android.view.ViewGroup">
+</parameter>
+<parameter name="child" type="android.view.View">
+</parameter>
+</method>
<field name="APPEARING"
type="int"
transient="false"
@@ -30842,6 +30916,17 @@
visibility="public"
>
</field>
+<field name="TRANSIT_FRAGMENT_NEXT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4099"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="TRANSIT_FRAGMENT_OPEN"
type="int"
transient="false"
@@ -30853,6 +30938,17 @@
visibility="public"
>
</field>
+<field name="TRANSIT_FRAGMENT_PREV"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8196"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="TRANSIT_NONE"
type="int"
transient="false"
@@ -55046,6 +55142,17 @@
visibility="public"
>
</field>
+<field name="SCREEN_ORIENTATION_FULL_SENSOR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="10"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SCREEN_ORIENTATION_LANDSCAPE"
type="int"
transient="false"
@@ -55079,6 +55186,28 @@
visibility="public"
>
</field>
+<field name="SCREEN_ORIENTATION_REVERSE_LANDSCAPE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SCREEN_ORIENTATION_REVERSE_PORTRAIT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="9"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SCREEN_ORIENTATION_SENSOR"
type="int"
transient="false"
@@ -55090,6 +55219,28 @@
visibility="public"
>
</field>
+<field name="SCREEN_ORIENTATION_SENSOR_LANDSCAPE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SCREEN_ORIENTATION_SENSOR_PORTRAIT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SCREEN_ORIENTATION_UNSPECIFIED"
type="int"
transient="false"
@@ -112132,6 +112283,348 @@
</method>
</class>
</package>
+<package name="android.nfc"
+>
+<class name="NdefMessage"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="NdefMessage"
+ type="android.nfc.NdefMessage"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="records" type="android.nfc.NdefRecord[]">
+</parameter>
+</constructor>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getRecords"
+ return="android.nfc.NdefRecord[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="NdefRecord"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="NdefRecord"
+ type="android.nfc.NdefRecord"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tnf" type="short">
+</parameter>
+<parameter name="type" type="byte[]">
+</parameter>
+<parameter name="id" type="byte[]">
+</parameter>
+<parameter name="payload" type="byte[]">
+</parameter>
+</constructor>
+<constructor name="NdefRecord"
+ type="android.nfc.NdefRecord"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="data" type="byte[]">
+</parameter>
+</constructor>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getId"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getPayload"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getTnf"
+ return="short"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getType"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="dest" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RTD_ALTERNATIVE_CARRIER"
+ type="byte[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RTD_HANDOVER_CARRIER"
+ type="byte[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RTD_HANDOVER_REQUEST"
+ type="byte[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RTD_HANDOVER_SELECT"
+ type="byte[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RTD_SMART_POSTER"
+ type="byte[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RTD_TEXT"
+ type="byte[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="RTD_URI"
+ type="byte[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TNF_ABSOLUTE_URI"
+ type="short"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TNF_EMPTY"
+ type="short"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TNF_EXTERNAL_TYPE"
+ type="short"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TNF_MIME_MEDIA"
+ type="short"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TNF_UNCHANGED"
+ type="short"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TNF_UNKNOWN"
+ type="short"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="TNF_WELL_KNOWN"
+ type="short"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+</package>
<package name="android.opengl"
>
<class name="ETC1"
@@ -134925,6 +135418,17 @@
visibility="public"
>
</method>
+<method name="isExternalStorageRemovable"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<field name="DIRECTORY_ALARMS"
type="java.lang.String"
transient="false"
@@ -143166,6 +143670,8 @@
</parameter>
<parameter name="args" type="android.os.Bundle">
</parameter>
+<parameter name="next" type="boolean">
+</parameter>
</method>
<field name="EXTRA_NO_HEADERS"
type="java.lang.String"
@@ -189868,6 +190374,16 @@
deprecated="not deprecated"
visibility="public"
>
+<parameter name="action" type="int">
+</parameter>
+<parameter name="x" type="float">
+</parameter>
+<parameter name="y" type="float">
+</parameter>
+<parameter name="description" type="android.content.ClipDescription">
+</parameter>
+<parameter name="data" type="android.content.ClipData">
+</parameter>
</method>
<method name="obtain"
return="android.view.DragEvent"
@@ -189879,15 +190395,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="action" type="int">
-</parameter>
-<parameter name="x" type="float">
-</parameter>
-<parameter name="y" type="float">
-</parameter>
-<parameter name="description" type="android.content.ClipDescription">
-</parameter>
-<parameter name="data" type="android.content.ClipData">
+<parameter name="source" type="android.view.DragEvent">
</parameter>
</method>
<method name="recycle"
@@ -200935,7 +201443,7 @@
static="false"
final="false"
deprecated="not deprecated"
- visibility="protected"
+ visibility="public"
>
<parameter name="event" type="android.view.DragEvent">
</parameter>
@@ -202677,6 +203185,23 @@
<parameter name="animation" type="android.view.animation.Animation">
</parameter>
</method>
+<method name="startDrag"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="data" type="android.content.ClipData">
+</parameter>
+<parameter name="thumbBuilder" type="android.view.View.DragThumbnailBuilder">
+</parameter>
+<parameter name="myWindowOnly" type="boolean">
+</parameter>
+</method>
<method name="unscheduleDrawable"
return="void"
abstract="false"
@@ -203374,6 +203899,53 @@
>
</field>
</class>
+<class name="View.DragThumbnailBuilder"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="View.DragThumbnailBuilder"
+ type="android.view.View.DragThumbnailBuilder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="android.view.View">
+</parameter>
+</constructor>
+<method name="onDrawThumbnail"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="canvas" type="android.graphics.Canvas">
+</parameter>
+</method>
+<method name="onProvideThumbnailMetrics"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="thumbnailSize" type="android.graphics.Point">
+</parameter>
+<parameter name="thumbnailTouchPoint" type="android.graphics.Point">
+</parameter>
+</method>
+</class>
<class name="View.MeasureSpec"
extends="java.lang.Object"
abstract="false"
@@ -215141,6 +215713,19 @@
<parameter name="id" type="java.lang.String">
</parameter>
</method>
+<method name="showInputMethodAndSubtypeEnabler"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="topId" type="java.lang.String">
+</parameter>
+</method>
<method name="showInputMethodPicker"
return="void"
abstract="false"
@@ -223693,7 +224278,7 @@
>
</method>
<method name="getInAnimation"
- return="android.view.animation.Animation"
+ return="android.animation.ObjectAnimator&lt;?&gt;"
abstract="false"
native="false"
synchronized="false"
@@ -223704,7 +224289,7 @@
>
</method>
<method name="getOutAnimation"
- return="android.view.animation.Animation"
+ return="android.animation.ObjectAnimator&lt;?&gt;"
abstract="false"
native="false"
synchronized="false"
@@ -223820,7 +224405,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="inAnimation" type="android.view.animation.Animation">
+<parameter name="inAnimation" type="android.animation.ObjectAnimator&lt;?&gt;">
</parameter>
</method>
<method name="setInAnimation"
@@ -223848,7 +224433,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="outAnimation" type="android.view.animation.Animation">
+<parameter name="outAnimation" type="android.animation.ObjectAnimator&lt;?&gt;">
</parameter>
</method>
<method name="setOutAnimation"
diff --git a/cmds/stagefright/recordvideo.cpp b/cmds/stagefright/recordvideo.cpp
index 330fbc2..f8eb514 100644
--- a/cmds/stagefright/recordvideo.cpp
+++ b/cmds/stagefright/recordvideo.cpp
@@ -18,12 +18,10 @@
#include <binder/ProcessState.h>
#include <media/stagefright/AudioPlayer.h>
-#include <media/stagefright/FileSource.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
-#include <media/stagefright/MediaExtractor.h>
#include <media/stagefright/MPEG4Writer.h>
#include <media/stagefright/OMXClient.h>
#include <media/stagefright/OMXCodec.h>
@@ -31,18 +29,21 @@
using namespace android;
-// print usage showing how to use this utility to record videos
+// Print usage showing how to use this utility to record videos
static void usage(const char *me) {
fprintf(stderr, "usage: %s\n", me);
fprintf(stderr, " -h(elp)\n");
- fprintf(stderr, " -b bit rate in bits per second (default 300000)\n");
- fprintf(stderr, " -c YUV420 color format: [0] semi planar or [1] planar (default 1)\n");
- fprintf(stderr, " -f frame rate in frames per second (default 30)\n");
- fprintf(stderr, " -i I frame interval in seconds (default 1)\n");
- fprintf(stderr, " -n number of frames to be recorded (default 300)\n");
- fprintf(stderr, " -w width in pixels (default 176)\n");
- fprintf(stderr, " -t height in pixels (default 144)\n");
- fprintf(stderr, " -v video codec: [0] AVC [1] M4V [2] H263 (default 0)\n");
+ fprintf(stderr, " -b bit rate in bits per second (default: 300000)\n");
+ fprintf(stderr, " -c YUV420 color format: [0] semi planar or [1] planar (default: 1)\n");
+ fprintf(stderr, " -f frame rate in frames per second (default: 30)\n");
+ fprintf(stderr, " -i I frame interval in seconds (default: 1)\n");
+ fprintf(stderr, " -n number of frames to be recorded (default: 300)\n");
+ fprintf(stderr, " -w width in pixels (default: 176)\n");
+ fprintf(stderr, " -t height in pixels (default: 144)\n");
+ fprintf(stderr, " -l encoder level. see omx il header (default: encoder specific)\n");
+ fprintf(stderr, " -p encoder profile. see omx il header (default: encoder specific)\n");
+ fprintf(stderr, " -v video codec: [0] AVC [1] M4V [2] H263 (default: 0)\n");
+ fprintf(stderr, "The output file is /sdcard/output.mp4\n");
exit(1);
}
@@ -56,6 +57,7 @@ public:
mFrameRate(fps),
mColorFormat(colorFormat),
mSize((width * height * 3) / 2) {
+
mGroup.add_buffer(new MediaBuffer(mSize));
// Check the color format to make sure
@@ -98,8 +100,11 @@ public:
return err;
}
- char x = (char)((double)rand() / RAND_MAX * 255);
- memset((*buffer)->data(), x, mSize);
+ // We don't care about the contents. we just test video encoder
+ // Also, by skipping the content generation, we can return from
+ // read() much faster.
+ //char x = (char)((double)rand() / RAND_MAX * 255);
+ //memset((*buffer)->data(), x, mSize);
(*buffer)->set_range(0, mSize);
(*buffer)->meta_data()->clear();
(*buffer)->meta_data()->setInt64(
@@ -125,38 +130,6 @@ private:
DummySource &operator=(const DummySource &);
};
-sp<MediaSource> createSource(const char *filename) {
- sp<MediaSource> source;
-
- sp<MediaExtractor> extractor =
- MediaExtractor::Create(new FileSource(filename));
- if (extractor == NULL) {
- return NULL;
- }
-
- size_t num_tracks = extractor->countTracks();
-
- sp<MetaData> meta;
- for (size_t i = 0; i < num_tracks; ++i) {
- meta = extractor->getTrackMetaData(i);
- CHECK(meta.get() != NULL);
-
- const char *mime;
- if (!meta->findCString(kKeyMIMEType, &mime)) {
- continue;
- }
-
- if (strncasecmp(mime, "video/", 6)) {
- continue;
- }
-
- source = extractor->getTrack(i);
- break;
- }
-
- return source;
-}
-
enum {
kYUV420SP = 0,
kYUV420P = 1,
@@ -186,12 +159,14 @@ int main(int argc, char **argv) {
int iFramesIntervalSeconds = 1;
int colorFormat = OMX_COLOR_FormatYUV420Planar;
int nFrames = 300;
+ int level = -1; // Encoder specific default
+ int profile = -1; // Encoder specific default
int codec = 0;
const char *fileName = "/sdcard/output.mp4";
android::ProcessState::self()->startThreadPool();
int res;
- while ((res = getopt(argc, argv, "b:c:f:i:n:w:t:v:o:h")) >= 0) {
+ while ((res = getopt(argc, argv, "b:c:f:i:n:w:t:l:p:v:h")) >= 0) {
switch (res) {
case 'b':
{
@@ -238,6 +213,18 @@ int main(int argc, char **argv) {
break;
}
+ case 'l':
+ {
+ level = atoi(optarg);
+ break;
+ }
+
+ case 'p':
+ {
+ profile = atoi(optarg);
+ break;
+ }
+
case 'v':
{
codec = atoi(optarg);
@@ -260,7 +247,8 @@ int main(int argc, char **argv) {
CHECK_EQ(client.connect(), OK);
status_t err = OK;
- sp<MediaSource> decoder = new DummySource(width, height, nFrames, frameRateFps, colorFormat);
+ sp<MediaSource> source =
+ new DummySource(width, height, nFrames, frameRateFps, colorFormat);
sp<MetaData> enc_meta = new MetaData;
switch (codec) {
@@ -282,10 +270,16 @@ int main(int argc, char **argv) {
enc_meta->setInt32(kKeySliceHeight, height);
enc_meta->setInt32(kKeyIFramesInterval, iFramesIntervalSeconds);
enc_meta->setInt32(kKeyColorFormat, colorFormat);
+ if (level != -1) {
+ enc_meta->setInt32(kKeyVideoLevel, level);
+ }
+ if (profile != -1) {
+ enc_meta->setInt32(kKeyVideoProfile, profile);
+ }
sp<MediaSource> encoder =
OMXCodec::Create(
- client.interface(), enc_meta, true /* createEncoder */, decoder);
+ client.interface(), enc_meta, true /* createEncoder */, source);
sp<MPEG4Writer> writer = new MPEG4Writer(fileName);
writer->addSource(encoder);
@@ -296,7 +290,7 @@ int main(int argc, char **argv) {
err = writer->stop();
int64_t end = systemTime();
- printf("$\n");
+ fprintf(stderr, "$\n");
client.disconnect();
if (err != OK && err != ERROR_END_OF_STREAM) {
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 69ad67e..52f0f16 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -459,21 +459,22 @@ public class LayoutTransition {
* @param transitionType one of {@link #CHANGE_APPEARING}, {@link #CHANGE_DISAPPEARING},
* {@link #APPEARING}, or {@link #DISAPPEARING}, which determines the animation whose
* duration is being set.
- * @param animator The animation being assigned.
+ * @param animator The animation being assigned. A value of <code>null</code> means that no
+ * animation will be run for the specified transitionType.
*/
public void setAnimator(int transitionType, Animator animator) {
switch (transitionType) {
case CHANGE_APPEARING:
- mChangingAppearingAnim = (animator != null) ? animator : defaultChangeIn;
+ mChangingAppearingAnim = animator;
break;
case CHANGE_DISAPPEARING:
- mChangingDisappearingAnim = (animator != null) ? animator : defaultChangeOut;
+ mChangingDisappearingAnim = animator;
break;
case APPEARING:
- mAppearingAnim = (animator != null) ? animator : defaultFadeIn;
+ mAppearingAnim = animator;
break;
case DISAPPEARING:
- mDisappearingAnim = (animator != null) ? animator : defaultFadeOut;
+ mDisappearingAnim = animator;
break;
}
}
@@ -516,6 +517,14 @@ public class LayoutTransition {
* transition is occuring because an item is being added to or removed from the parent.
*/
private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
+
+ Animator baseAnimator = (changeReason == APPEARING) ?
+ mChangingAppearingAnim : mChangingDisappearingAnim;
+ // If the animation is null, there's nothing to do
+ if (baseAnimator == null) {
+ return;
+ }
+
// reset the inter-animation delay, in case we use it later
staggerDelay = 0;
@@ -540,9 +549,10 @@ public class LayoutTransition {
}
// Make a copy of the appropriate animation
- final Animator anim = (changeReason == APPEARING) ?
- mChangingAppearingAnim.clone() :
- mChangingDisappearingAnim.clone();
+ final Animator anim = baseAnimator.clone();
+
+ // Cache the animation in case we need to cancel it later
+ currentAnimations.put(child, anim);
// Set the target object for the animation
anim.setTarget(child);
@@ -553,13 +563,10 @@ public class LayoutTransition {
// Add a listener to track layout changes on this view. If we don't get a callback,
// then there's nothing to animate.
- View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
+ final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
- // Cache the animation in case we need to cancel it later
- currentAnimations.put(child, anim);
-
// Tell the animation to extract end values from the changed object
anim.setupEndValues();
@@ -577,19 +584,6 @@ public class LayoutTransition {
anim.setStartDelay(startDelay);
anim.setDuration(duration);
- // Remove the animation from the cache when it ends
- anim.addListener(new AnimatorListenerAdapter() {
- private boolean canceled = false;
- public void onAnimationCancel(Animator animator) {
- // we remove canceled animations immediately, not here
- canceled = true;
- }
- public void onAnimationEnd(Animator animator) {
- if (!canceled) {
- currentAnimations.remove(child);
- }
- }
- });
if (anim instanceof ObjectAnimator) {
((ObjectAnimator) anim).setCurrentPlayTime(0);
}
@@ -601,6 +595,22 @@ public class LayoutTransition {
layoutChangeListenerMap.remove(child);
}
};
+ // Remove the animation from the cache when it ends
+ anim.addListener(new AnimatorListenerAdapter() {
+ private boolean canceled = false;
+ public void onAnimationCancel(Animator animator) {
+ // we remove canceled animations immediately, not here
+ canceled = true;
+ child.removeOnLayoutChangeListener(listener);
+ layoutChangeListenerMap.remove(child);
+ }
+ public void onAnimationEnd(Animator animator) {
+ if (!canceled) {
+ currentAnimations.remove(child);
+ }
+ }
+ });
+
child.addOnLayoutChangeListener(listener);
// cache the listener for later removal
layoutChangeListenerMap.put(child, listener);
@@ -630,6 +640,14 @@ public class LayoutTransition {
* @param child The View being added to the ViewGroup.
*/
private void runAppearingTransition(final ViewGroup parent, final View child) {
+ if (mAppearingAnim == null) {
+ if (mListeners != null) {
+ for (TransitionListener listener : mListeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
+ }
+ }
+ return;
+ }
Animator anim = mAppearingAnim.clone();
anim.setTarget(child);
anim.setStartDelay(mAppearingDelay);
@@ -656,13 +674,22 @@ public class LayoutTransition {
* @param child The View being removed from the ViewGroup.
*/
private void runDisappearingTransition(final ViewGroup parent, final View child) {
+ if (mDisappearingAnim == null) {
+ if (mListeners != null) {
+ for (TransitionListener listener : mListeners) {
+ listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
+ }
+ }
+ return;
+ }
Animator anim = mDisappearingAnim.clone();
anim.setStartDelay(mDisappearingDelay);
anim.setDuration(mDisappearingDuration);
anim.setTarget(child);
if (mListeners != null) {
anim.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd() {
+ @Override
+ public void onAnimationEnd(Animator anim) {
for (TransitionListener listener : mListeners) {
listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
}
@@ -684,7 +711,7 @@ public class LayoutTransition {
* @param parent The ViewGroup to which the View is being added.
* @param child The View being added to the ViewGroup.
*/
- public void childAdd(ViewGroup parent, View child) {
+ public void addChild(ViewGroup parent, View child) {
if (mListeners != null) {
for (TransitionListener listener : mListeners) {
listener.startTransition(this, parent, child, APPEARING);
@@ -695,6 +722,19 @@ public class LayoutTransition {
}
/**
+ * This method is called by ViewGroup when a child view is about to be added to the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup to which the View is being added.
+ * @param child The View being added to the ViewGroup.
+ */
+ public void showChild(ViewGroup parent, View child) {
+ addChild(parent, child);
+ }
+
+ /**
* This method is called by ViewGroup when a child view is about to be removed from the
* container. This callback starts the process of a transition; we grab the starting
* values, listen for changes to all of the children of the container, and start appropriate
@@ -703,7 +743,7 @@ public class LayoutTransition {
* @param parent The ViewGroup from which the View is being removed.
* @param child The View being removed from the ViewGroup.
*/
- public void childRemove(ViewGroup parent, View child) {
+ public void removeChild(ViewGroup parent, View child) {
if (mListeners != null) {
for (TransitionListener listener : mListeners) {
listener.startTransition(this, parent, child, DISAPPEARING);
@@ -714,6 +754,19 @@ public class LayoutTransition {
}
/**
+ * This method is called by ViewGroup when a child view is about to be removed from the
+ * container. This callback starts the process of a transition; we grab the starting
+ * values, listen for changes to all of the children of the container, and start appropriate
+ * animations.
+ *
+ * @param parent The ViewGroup from which the View is being removed.
+ * @param child The View being removed from the ViewGroup.
+ */
+ public void hideChild(ViewGroup parent, View child) {
+ removeChild(parent, child);
+ }
+
+ /**
* Add a listener that will be called when the bounds of the view change due to
* layout processing.
*
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index b558318..b34c243 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1341,6 +1341,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case CHECK_GRANT_URI_PERMISSION_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int callingUid = data.readInt();
+ String targetPkg = data.readString();
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ int modeFlags = data.readInt();
+ int res = checkGrantUriPermission(callingUid, targetPkg, uri, modeFlags);
+ reply.writeNoException();
+ reply.writeInt(res);
+ return true;
+ }
+
case DUMP_HEAP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String process = data.readString();
@@ -2998,6 +3010,23 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
+ public int checkGrantUriPermission(int callingUid, String targetPkg,
+ Uri uri, int modeFlags) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(callingUid);
+ data.writeString(targetPkg);
+ uri.writeToParcel(data, 0);
+ data.writeInt(modeFlags);
+ mRemote.transact(CHECK_GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
public boolean dumpHeap(String process, boolean managed,
String path, ParcelFileDescriptor fd) throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index df18ce7..f3f7ee7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -77,7 +77,6 @@ import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SamplingProfilerIntegration;
-import dalvik.system.VMDebug;
import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
import java.io.File;
@@ -710,14 +709,14 @@ public final class ActivityThread {
long dalvikAllocated = dalvikMax - dalvikFree;
long viewInstanceCount = ViewDebug.getViewInstanceCount();
long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
- long appContextInstanceCount = VMDebug.countInstancesOfClass(ContextImpl.class);
- long activityInstanceCount = VMDebug.countInstancesOfClass(Activity.class);
+ long appContextInstanceCount = Debug.countInstancesOfClass(ContextImpl.class);
+ long activityInstanceCount = Debug.countInstancesOfClass(Activity.class);
int globalAssetCount = AssetManager.getGlobalAssetCount();
int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
- int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount();
+ long openSslSocketCount = Debug.countInstancesOfClass(OpenSSLSocketImpl.class);
long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index da7ba6f..37e7253 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1380,6 +1380,12 @@ final class FragmentManagerImpl implements FragmentManager {
case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
break;
+ case FragmentTransaction.TRANSIT_FRAGMENT_NEXT:
+ rev = FragmentTransaction.TRANSIT_FRAGMENT_PREV;
+ break;
+ case FragmentTransaction.TRANSIT_FRAGMENT_PREV:
+ rev = FragmentTransaction.TRANSIT_FRAGMENT_NEXT;
+ break;
}
return rev;
@@ -1398,6 +1404,16 @@ final class FragmentManagerImpl implements FragmentManager {
? com.android.internal.R.styleable.FragmentAnimation_fragmentCloseEnterAnimation
: com.android.internal.R.styleable.FragmentAnimation_fragmentCloseExitAnimation;
break;
+ case FragmentTransaction.TRANSIT_FRAGMENT_NEXT:
+ animAttr = enter
+ ? com.android.internal.R.styleable.FragmentAnimation_fragmentNextEnterAnimation
+ : com.android.internal.R.styleable.FragmentAnimation_fragmentNextExitAnimation;
+ break;
+ case FragmentTransaction.TRANSIT_FRAGMENT_PREV:
+ animAttr = enter
+ ? com.android.internal.R.styleable.FragmentAnimation_fragmentPrevEnterAnimation
+ : com.android.internal.R.styleable.FragmentAnimation_fragmentPrevExitAnimation;
+ break;
}
return animAttr;
}
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 09d8d26..b00476b 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -106,10 +106,14 @@ public interface FragmentTransaction {
public final int TRANSIT_UNSET = -1;
/** No animation for transition. */
public final int TRANSIT_NONE = 0;
- /** Fragment is being added */
+ /** Fragment is being added onto the stack */
public final int TRANSIT_FRAGMENT_OPEN = 1 | TRANSIT_ENTER_MASK;
- /** Fragment is being removed */
+ /** Fragment is being removed from the stack */
public final int TRANSIT_FRAGMENT_CLOSE = 2 | TRANSIT_EXIT_MASK;
+ /** Fragment is being added in a 'next' operation*/
+ public final int TRANSIT_FRAGMENT_NEXT = 3 | TRANSIT_ENTER_MASK;
+ /** Fragment is being removed in a 'previous' operation */
+ public final int TRANSIT_FRAGMENT_PREV = 4 | TRANSIT_EXIT_MASK;
/**
* Set specific animation resources to run for the fragments that are
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 4d73817..cd229e3 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -326,6 +326,9 @@ public interface IActivityManager extends IInterface {
public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
int mode) throws RemoteException;
+ public int checkGrantUriPermission(int callingUid, String targetPkg,
+ Uri uri, int modeFlags) throws RemoteException;
+
// Cause the specified process to dump the specified heap.
public boolean dumpHeap(String process, boolean managed, String path,
ParcelFileDescriptor fd) throws RemoteException;
@@ -540,5 +543,6 @@ public interface IActivityManager extends IInterface {
int NEW_URI_PERMISSION_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+115;
int GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+116;
int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+117;
- int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118;
+ int CHECK_GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118;
+ int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+119;
}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 0ea0648..85a6765 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -108,7 +108,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public ClipData getPrimaryClip() {
try {
- return getService().getPrimaryClip();
+ return getService().getPrimaryClip(mContext.getPackageName());
} catch (RemoteException e) {
return null;
}
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index 3e1fe55..254901b 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -27,7 +27,7 @@ import android.content.IOnPrimaryClipChangedListener;
*/
interface IClipboard {
void setPrimaryClip(in ClipData clip);
- ClipData getPrimaryClip();
+ ClipData getPrimaryClip(String pkg);
ClipDescription getPrimaryClipDescription();
boolean hasPrimaryClip();
void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 950d339..c9115c5 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,6 +16,18 @@
package android.content;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.android.internal.R;
@@ -36,17 +48,6 @@ import android.content.pm.ProviderInfo;
import android.content.pm.RegisteredServicesCacheListener;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.format.DateUtils;
@@ -58,11 +59,13 @@ import android.util.Pair;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Random;
-import java.util.Collection;
import java.util.concurrent.CountDownLatch;
/**
@@ -81,30 +84,17 @@ public class SyncManager implements OnAccountsUpdateListener {
private static final long MAX_TIME_PER_SYNC;
static {
- String localSyncDelayString = SystemProperties.get("sync.local_sync_delay");
- long localSyncDelay = 30 * 1000; // 30 seconds
- if (localSyncDelayString != null) {
- try {
- localSyncDelay = Long.parseLong(localSyncDelayString);
- } catch (NumberFormatException nfe) {
- // ignore, use default
- }
- }
- LOCAL_SYNC_DELAY = localSyncDelay;
-
- String maxTimePerSyncString = SystemProperties.get("sync.max_time_per_sync");
- long maxTimePerSync = 5 * 60 * 1000; // 5 minutes
- if (maxTimePerSyncString != null) {
- try {
- maxTimePerSync = Long.parseLong(maxTimePerSyncString);
- } catch (NumberFormatException nfe) {
- // ignore, use default
- }
- }
- MAX_TIME_PER_SYNC = maxTimePerSync;
+ MAX_SIMULTANEOUS_INITIALIZATION_SYNCS = SystemProperties.getInt("sync.max_init_syncs", 5);
+ MAX_SIMULTANEOUS_REGULAR_SYNCS = SystemProperties.getInt("sync.max_regular_syncs", 2);
+ LOCAL_SYNC_DELAY =
+ SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */);
+ MAX_TIME_PER_SYNC =
+ SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */);
+ SYNC_NOTIFICATION_DELAY =
+ SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */);
}
- private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds
+ private static final long SYNC_NOTIFICATION_DELAY;
/**
* When retrying a sync for the first time use this delay. After that
@@ -123,21 +113,21 @@ public class SyncManager implements OnAccountsUpdateListener {
*/
private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
- /**
- * An error notification is sent if sync of any of the providers has been failing for this long.
- */
- private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
-
private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
+ private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
+
+ private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS;
+ private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS;
private Context mContext;
private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
+ volatile private PowerManager.WakeLock mSyncManagerWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
@@ -147,10 +137,8 @@ public class SyncManager implements OnAccountsUpdateListener {
private final SyncStorageEngine mSyncStorageEngine;
public final SyncQueue mSyncQueue;
- private ActiveSyncContext mActiveSyncContext = null;
+ private final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
- // set if the sync error indicator should be reported.
- private boolean mNeedSyncErrorNotification = false;
// set if the sync active indicator should be reported
private boolean mNeedSyncActiveNotification = false;
@@ -200,6 +188,9 @@ public class SyncManager implements OnAccountsUpdateListener {
private final PowerManager mPowerManager;
+ private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
+ private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
+
public void onAccountsUpdated(Account[] accounts) {
// remember if this was the first time this was called after an update
final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
@@ -207,11 +198,10 @@ public class SyncManager implements OnAccountsUpdateListener {
// if a sync is in progress yet it is no longer in the accounts list,
// cancel it
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (activeSyncContext != null) {
- if (!ArrayUtils.contains(accounts, activeSyncContext.mSyncOperation.account)) {
+ for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+ if (!ArrayUtils.contains(accounts, currentSyncContext.mSyncOperation.account)) {
Log.d(TAG, "canceling sync since the account has been removed");
- sendSyncFinishedOrCanceledMessage(activeSyncContext,
+ sendSyncFinishedOrCanceledMessage(currentSyncContext,
null /* no result since this is a cancel */);
}
}
@@ -238,6 +228,7 @@ public class SyncManager implements OnAccountsUpdateListener {
// If this was the bootup case then don't sync everything, instead only
// sync those that have an unknown syncable state, which will give them
// a chance to set their syncable state.
+
boolean onlyThoseWithUnkownSyncableState = justBootedUp;
scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState);
}
@@ -371,6 +362,15 @@ public class SyncManager implements OnAccountsUpdateListener {
HANDLE_SYNC_ALARM_WAKE_LOCK);
mHandleAlarmWakeLock.setReferenceCounted(false);
+ // This WakeLock is used to ensure that we stay awake while running the sync loop
+ // message handler. Normally we will hold a sync adapter wake lock while it is being
+ // synced but during the execution of the sync loop it might finish a sync for
+ // one sync adapter before starting the sync for the other sync adapter and we
+ // don't want the device to go to sleep during that window.
+ mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ SYNC_LOOP_WAKE_LOCK);
+ mSyncManagerWakeLock.setReferenceCounted(false);
+
mSyncStorageEngine.addStatusChangeListener(
ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
public void onStatusChanged(int which) {
@@ -427,8 +427,8 @@ public class SyncManager implements OnAccountsUpdateListener {
Intent intent = new Intent();
intent.setAction("android.content.SyncAdapter");
intent.setComponent(syncAdapterInfo.componentName);
- if (!mContext.bindService(intent, new InitializerServiceConnection(account, authority, mContext,
- mMainHandler),
+ if (!mContext.bindService(intent,
+ new InitializerServiceConnection(account, authority, mContext, mMainHandler),
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) {
Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent);
}
@@ -508,8 +508,8 @@ public class SyncManager implements OnAccountsUpdateListener {
* @param requestedAccount the account to sync, may be null to signify all accounts
* @param requestedAuthority the authority to sync, may be null to indicate all authorities
* @param extras a Map of SyncAdapter-specific information to control
-* syncs of a specific provider. Can be null. Is ignored
-* if the url is null.
+ * syncs of a specific provider. Can be null. Is ignored
+ * if the url is null.
* @param delay how many milliseconds in the future to wait before performing this
* @param onlyThoseWithUnkownSyncableState
*/
@@ -613,16 +613,29 @@ public class SyncManager implements OnAccountsUpdateListener {
continue;
}
- if (isLoggable) {
- Log.v(TAG, "scheduleSync:"
- + " delay " + delay
- + ", source " + source
- + ", account " + account
- + ", authority " + authority
- + ", extras " + extras);
+ Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(account, authority);
+ long delayUntil = mSyncStorageEngine.getDelayUntilTime(account, authority);
+ final long backoffTime = backoff != null ? backoff.first : 0;
+ if (isSyncable < 0) {
+ Bundle newExtras = new Bundle();
+ newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ scheduleSyncOperation(
+ new SyncOperation(account, source, authority, newExtras, 0,
+ backoffTime, delayUntil));
+ }
+ if (!onlyThoseWithUnkownSyncableState) {
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync:"
+ + " delay " + delay
+ + ", source " + source
+ + ", account " + account
+ + ", authority " + authority
+ + ", extras " + extras);
+ }
+ scheduleSyncOperation(
+ new SyncOperation(account, source, authority, extras, delay,
+ backoffTime, delayUntil));
}
- scheduleSyncOperation(
- new SyncOperation(account, source, authority, extras, delay));
}
}
}
@@ -636,7 +649,8 @@ public class SyncManager implements OnAccountsUpdateListener {
}
public SyncAdapterType[] getSyncAdapterTypes() {
- final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos =
+ final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>
+ serviceInfos =
mSyncAdapters.getAllServices();
SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
int i = 0;
@@ -666,6 +680,14 @@ public class SyncManager implements OnAccountsUpdateListener {
mSyncHandler.sendMessage(msg);
}
+ private void sendCancelSyncsMessage(final Account account, final String authority) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
+ Message msg = mSyncHandler.obtainMessage();
+ msg.what = SyncHandler.MESSAGE_CANCEL;
+ msg.obj = Pair.create(account, authority);
+ mSyncHandler.sendMessage(msg);
+ }
+
class SyncHandlerMessagePayload {
public final ActiveSyncContext activeSyncContext;
public final SyncResult syncResult;
@@ -683,11 +705,6 @@ public class SyncManager implements OnAccountsUpdateListener {
}
}
- private void clearBackoffSetting(SyncOperation op) {
- mSyncStorageEngine.setBackoff(op.account, op.authority,
- SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
- }
-
private void increaseBackoffSetting(SyncOperation op) {
final long now = SystemClock.elapsedRealtime();
@@ -713,6 +730,9 @@ public class SyncManager implements OnAccountsUpdateListener {
mSyncStorageEngine.setBackoff(op.account, op.authority,
now + newDelayInMs, newDelayInMs);
+ synchronized (mSyncQueue) {
+ mSyncQueue.onBackoffChanged(op.account, op.authority, now + newDelayInMs);
+ }
}
private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
@@ -725,6 +745,9 @@ public class SyncManager implements OnAccountsUpdateListener {
newDelayUntilTime = 0;
}
mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
+ synchronized (mSyncQueue) {
+ mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
+ }
}
/**
@@ -733,23 +756,7 @@ public class SyncManager implements OnAccountsUpdateListener {
* @param authority limit the cancelations to syncs with this authority, if non-null
*/
public void cancelActiveSync(Account account, String authority) {
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (activeSyncContext != null) {
- // if an authority was specified then only cancel the sync if it matches
- if (account != null) {
- if (!account.equals(activeSyncContext.mSyncOperation.account)) {
- return;
- }
- }
- // if an account was specified then only cancel the sync if it matches
- if (authority != null) {
- if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
- return;
- }
- }
- sendSyncFinishedOrCanceledMessage(activeSyncContext,
- null /* no result since this is a cancel */);
- }
+ sendCancelSyncsMessage(account, authority);
}
/**
@@ -758,22 +765,6 @@ public class SyncManager implements OnAccountsUpdateListener {
* @param syncOperation the SyncOperation to schedule
*/
public void scheduleSyncOperation(SyncOperation syncOperation) {
- // If this operation is expedited and there is a sync in progress then
- // reschedule the current operation and send a cancel for it.
- final ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (syncOperation.expedited && activeSyncContext != null) {
- final boolean hasSameKey =
- activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
- // This request is expedited and there is a sync in progress.
- // Interrupt the current sync only if it is not expedited and if it has a different
- // key than the one we are scheduling.
- if (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) {
- scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
- sendSyncFinishedOrCanceledMessage(activeSyncContext,
- null /* no result since this is a cancel */);
- }
- }
-
boolean queueChanged;
synchronized (mSyncQueue) {
queueChanged = mSyncQueue.add(syncOperation);
@@ -798,11 +789,11 @@ public class SyncManager implements OnAccountsUpdateListener {
* @param authority limit the removals to operations with this authority, if non-null
*/
public void clearScheduledSyncOperations(Account account, String authority) {
- mSyncStorageEngine.setBackoff(account, authority,
- SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
synchronized (mSyncQueue) {
mSyncQueue.remove(account, authority);
}
+ mSyncStorageEngine.setBackoff(account, authority,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
}
void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
@@ -829,7 +820,8 @@ public class SyncManager implements OnAccountsUpdateListener {
if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
+ operation);
- } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+ } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
+ && !syncResult.syncAlreadyInProgress) {
operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+ "encountered an error: " + operation);
@@ -850,7 +842,8 @@ public class SyncManager implements OnAccountsUpdateListener {
}
scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource,
operation.authority, operation.extras,
- DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000));
+ DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000,
+ operation.backoff, operation.delayUntil));
} else if (syncResult.hasSoftError()) {
if (isLoggable) {
Log.d(TAG, "retrying sync operation because it encountered a soft error: "
@@ -873,15 +866,33 @@ public class SyncManager implements OnAccountsUpdateListener {
final long mStartTime;
long mTimeoutStartTime;
boolean mBound;
+ final PowerManager.WakeLock mSyncWakeLock;
+ final int mSyncAdapterUid;
+ SyncInfo mSyncInfo;
- public ActiveSyncContext(SyncOperation syncOperation,
- long historyRowId) {
+ /**
+ * Create an ActiveSyncContext for an impending sync and grab the wakelock for that
+ * sync adapter. Since this grabs the wakelock you need to be sure to call
+ * close() when you are done with this ActiveSyncContext, whether the sync succeeded
+ * or not.
+ * @param syncOperation the SyncOperation we are about to sync
+ * @param historyRowId the row in which to record the history info for this sync
+ * @param syncAdapterUid the UID of the application that contains the sync adapter
+ * for this sync. This is used to attribute the wakelock hold to that application.
+ */
+ public ActiveSyncContext(SyncOperation syncOperation, long historyRowId,
+ int syncAdapterUid) {
super();
+ mSyncAdapterUid = syncAdapterUid;
mSyncOperation = syncOperation;
mHistoryRowId = historyRowId;
mSyncAdapter = null;
mStartTime = SystemClock.elapsedRealtime();
mTimeoutStartTime = mStartTime;
+ mSyncWakeLock = mSyncHandler.getSyncWakeLock(
+ mSyncOperation.account.type, mSyncOperation.authority);
+ mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
+ mSyncWakeLock.acquire();
}
public void sendHeartbeat() {
@@ -889,6 +900,7 @@ public class SyncManager implements OnAccountsUpdateListener {
}
public void onFinished(SyncResult result) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this);
// include "this" in the message so that the handler can ignore it if this
// ActiveSyncContext is no longer the mActiveSyncContext at message handling
// time
@@ -936,6 +948,10 @@ public class SyncManager implements OnAccountsUpdateListener {
return bindResult;
}
+ /**
+ * Performs the required cleanup, which is the releasing of the wakelock and
+ * unbinding from the sync adapter (if actually bound).
+ */
protected void close() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
@@ -944,6 +960,8 @@ public class SyncManager implements OnAccountsUpdateListener {
mBound = false;
mContext.unbindService(this);
}
+ mSyncWakeLock.setWorkSource(null);
+ mSyncWakeLock.release();
}
@Override
@@ -1003,62 +1021,28 @@ public class SyncManager implements OnAccountsUpdateListener {
pw.println("no alarm is scheduled (there had better not be any pending syncs)");
}
- final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext;
-
- pw.print("active sync: "); pw.println(activeSyncContext);
-
pw.print("notification info: ");
sb.setLength(0);
mSyncHandler.mSyncNotificationInfo.toString(sb);
pw.println(sb.toString());
+ pw.println();
+ pw.println("Active Syncs: " + mActiveSyncContexts.size());
+ for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+ final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000;
+ pw.print(" ");
+ pw.print(DateUtils.formatElapsedTime(durationInSeconds));
+ pw.print(" - ");
+ pw.print(activeSyncContext.mSyncOperation.dump(false));
+ pw.println();
+ }
+
synchronized (mSyncQueue) {
- pw.print("sync queue: ");
sb.setLength(0);
mSyncQueue.dump(sb);
- pw.println(sb.toString());
- }
-
- SyncInfo active = mSyncStorageEngine.getCurrentSync();
- if (active != null) {
- SyncStorageEngine.AuthorityInfo authority
- = mSyncStorageEngine.getAuthority(active.authorityId);
- final long durationInSeconds = (now - active.startTime) / 1000;
- pw.print("Active sync: ");
- pw.print(authority != null ? authority.account : "<no account>");
- pw.print(" ");
- pw.print(authority != null ? authority.authority : "<no account>");
- if (activeSyncContext != null) {
- pw.print(" ");
- pw.print(SyncStorageEngine.SOURCES[
- activeSyncContext.mSyncOperation.syncSource]);
- }
- pw.print(", duration is ");
- pw.println(DateUtils.formatElapsedTime(durationInSeconds));
- } else {
- pw.println("No sync is in progress.");
- }
-
- ArrayList<SyncStorageEngine.PendingOperation> ops
- = mSyncStorageEngine.getPendingOperations();
- if (ops != null && ops.size() > 0) {
- pw.println();
- pw.println("Pending Syncs");
- final int N = ops.size();
- for (int i=0; i<N; i++) {
- SyncStorageEngine.PendingOperation op = ops.get(i);
- pw.print(" #"); pw.print(i); pw.print(": account=");
- pw.print(op.account.name); pw.print(":");
- pw.print(op.account.type); pw.print(" authority=");
- pw.print(op.authority); pw.print(" expedited=");
- pw.println(op.expedited);
- if (op.extras != null && op.extras.size() > 0) {
- sb.setLength(0);
- SyncOperation.extrasToStringBuilder(op.extras, sb, false /* asKey */);
- pw.print(" extras: "); pw.println(sb.toString());
- }
- }
}
+ pw.println();
+ pw.print(sb.toString());
// join the installed sync adapter with the accounts list and emit for everything
pw.println();
@@ -1261,7 +1245,7 @@ public class SyncManager implements OnAccountsUpdateListener {
/** Call to let the tracker know that the sync state may have changed */
public synchronized void update() {
- final boolean isSyncInProgress = mActiveSyncContext != null;
+ final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty();
if (isSyncInProgress == mLastWasSyncing) return;
final long now = SystemClock.elapsedRealtime();
if (isSyncInProgress) {
@@ -1301,17 +1285,14 @@ public class SyncManager implements OnAccountsUpdateListener {
private static final int MESSAGE_CHECK_ALARMS = 3;
private static final int MESSAGE_SERVICE_CONNECTED = 4;
private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
+ private static final int MESSAGE_CANCEL = 6;
public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
private Long mAlarmScheduleTime = null;
public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
- private PowerManager.WakeLock mSyncWakeLock;
- private final HashMap<Pair<String, String>, PowerManager.WakeLock> mWakeLocks =
+ private final HashMap<Pair<String, String>, PowerManager.WakeLock> mWakeLocks =
Maps.newHashMap();
- // used to track if we have installed the error notification so that we don't reinstall
- // it if sync is still failing
- private boolean mErrorNotificationInstalled = false;
private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
public void onBootCompleted() {
mBootCompleted = true;
@@ -1351,12 +1332,6 @@ public class SyncManager implements OnAccountsUpdateListener {
* Used to keep track of whether a sync notification is active and who it is for.
*/
class SyncNotificationInfo {
- // only valid if isActive is true
- public Account account;
-
- // only valid if isActive is true
- public String authority;
-
// true iff the notification manager has been asked to send the notification
public boolean isActive = false;
@@ -1365,10 +1340,7 @@ public class SyncManager implements OnAccountsUpdateListener {
public Long startTime = null;
public void toString(StringBuilder sb) {
- sb.append("account ").append(account)
- .append(", authority ").append(authority)
- .append(", isActive ").append(isActive)
- .append(", startTime ").append(startTime);
+ sb.append("isActive ").append(isActive).append(", startTime ").append(startTime);
}
@Override
@@ -1384,60 +1356,72 @@ public class SyncManager implements OnAccountsUpdateListener {
}
public void handleMessage(Message msg) {
- Long earliestFuturePollTime = null;
+ long earliestFuturePollTime = Long.MAX_VALUE;
+ long nextPendingSyncTime = Long.MAX_VALUE;
try {
waitUntilReadyToRun();
+ mSyncManagerWakeLock.acquire();
// Always do this first so that we be sure that any periodic syncs that
// are ready to run have been converted into pending syncs. This allows the
// logic that considers the next steps to take based on the set of pending syncs
// to also take into account the periodic syncs.
earliestFuturePollTime = scheduleReadyPeriodicSyncs();
switch (msg.what) {
+ case SyncHandler.MESSAGE_CANCEL: {
+ Pair<Account, String> payload = (Pair<Account, String>)msg.obj;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
+ + payload.first + ", " + payload.second);
+ }
+ cancelActiveSyncLocked(payload.first, payload.second);
+ nextPendingSyncTime = maybeStartNextSyncLocked();
+ break;
+ }
+
case SyncHandler.MESSAGE_SYNC_FINISHED:
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
}
SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
- if (mActiveSyncContext != payload.activeSyncContext) {
- Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
- + "dropping: mActiveSyncContext " + mActiveSyncContext
- + " != " + payload.activeSyncContext);
- return;
+ if (!isSyncStillActive(payload.activeSyncContext)) {
+ Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
+ + "sync is no longer active: "
+ + payload.activeSyncContext);
+ break;
}
- runSyncFinishedOrCanceled(payload.syncResult);
+ runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
- // since we are no longer syncing, check if it is time to start a new sync
- runStateIdle();
+ // since a sync just finished check if it is time to start a new sync
+ nextPendingSyncTime = maybeStartNextSyncLocked();
break;
case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
- + msgData.activeSyncContext
- + " active is " + mActiveSyncContext);
+ + msgData.activeSyncContext);
}
// check that this isn't an old message
- if (mActiveSyncContext == msgData.activeSyncContext) {
- runBoundToSyncAdapter(msgData.syncAdapter);
+ if (isSyncStillActive(msgData.activeSyncContext)) {
+ runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
}
break;
}
case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
- ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
+ final ActiveSyncContext currentSyncContext =
+ ((ServiceConnectionData)msg.obj).activeSyncContext;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
- + msgData.activeSyncContext
- + " active is " + mActiveSyncContext);
+ + currentSyncContext);
}
// check that this isn't an old message
- if (mActiveSyncContext == msgData.activeSyncContext) {
+ if (isSyncStillActive(currentSyncContext)) {
// cancel the sync if we have a syncadapter, which means one is
// outstanding
- if (mActiveSyncContext.mSyncAdapter != null) {
+ if (currentSyncContext.mSyncAdapter != null) {
try {
- mActiveSyncContext.mSyncAdapter.cancelSync(mActiveSyncContext);
+ currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
} catch (RemoteException e) {
// we don't need to retry this in this case
}
@@ -1447,11 +1431,10 @@ public class SyncManager implements OnAccountsUpdateListener {
// which is a soft error
SyncResult syncResult = new SyncResult();
syncResult.stats.numIoExceptions++;
- runSyncFinishedOrCanceled(syncResult);
+ runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext);
- // since we are no longer syncing, check if it is time to start a new
- // sync
- runStateIdle();
+ // since a sync just finished check if it is time to start a new sync
+ nextPendingSyncTime = maybeStartNextSyncLocked();
}
break;
@@ -1464,22 +1447,7 @@ public class SyncManager implements OnAccountsUpdateListener {
}
mAlarmScheduleTime = null;
try {
- if (mActiveSyncContext != null) {
- if (isLoggable) {
- Log.v(TAG, "handleSyncHandlerMessage: sync context is active");
- }
- runStateSyncing();
- }
-
- // if the above call to runStateSyncing() resulted in the end of a sync,
- // check if it is time to start a new sync
- if (mActiveSyncContext == null) {
- if (isLoggable) {
- Log.v(TAG, "handleSyncHandlerMessage: "
- + "sync context is not active");
- }
- runStateIdle();
- }
+ nextPendingSyncTime = maybeStartNextSyncLocked();
} finally {
mHandleAlarmWakeLock.release();
}
@@ -1490,19 +1458,14 @@ public class SyncManager implements OnAccountsUpdateListener {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
}
- // we do all the work for this case in the finally block
+ nextPendingSyncTime = maybeStartNextSyncLocked();
break;
}
} finally {
- final boolean isSyncInProgress = mActiveSyncContext != null;
- if (!isSyncInProgress && mSyncWakeLock != null) {
- mSyncWakeLock.release();
- mSyncWakeLock = null;
- }
- manageSyncNotification();
- manageErrorNotification();
- manageSyncAlarm(earliestFuturePollTime);
+ manageSyncNotificationLocked();
+ manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
mSyncTimeTracker.update();
+ mSyncManagerWakeLock.release();
}
}
@@ -1511,10 +1474,10 @@ public class SyncManager implements OnAccountsUpdateListener {
* @return the desired start time of the earliest future periodic sync operation,
* in milliseconds since boot
*/
- private Long scheduleReadyPeriodicSyncs() {
+ private long scheduleReadyPeriodicSyncs() {
final boolean backgroundDataUsageAllowed =
getConnectivityManager().getBackgroundDataSetting();
- Long earliestFuturePollTime = null;
+ long earliestFuturePollTime = Long.MAX_VALUE;
if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) {
return earliestFuturePollTime;
}
@@ -1544,23 +1507,27 @@ public class SyncManager implements OnAccountsUpdateListener {
long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
// if it is ready to run then schedule it and mark it as having been scheduled
if (nextPollTimeAbsolute <= nowAbsolute) {
+ final Pair<Long, Long> backoff =
+ mSyncStorageEngine.getBackoff(info.account, info.authority);
scheduleSyncOperation(
new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC,
- info.authority, extras, 0 /* delay */));
+ info.authority, extras, 0 /* delay */,
+ backoff != null ? backoff.first : 0,
+ mSyncStorageEngine.getDelayUntilTime(
+ info.account, info.authority)));
status.setPeriodicSyncTime(i, nowAbsolute);
} else {
// it isn't ready to run, remember this time if it is earlier than
// earliestFuturePollTime
- if (earliestFuturePollTime == null
- || nextPollTimeAbsolute < earliestFuturePollTime) {
+ if (nextPollTimeAbsolute < earliestFuturePollTime) {
earliestFuturePollTime = nextPollTimeAbsolute;
}
}
}
}
- if (earliestFuturePollTime == null) {
- return null;
+ if (earliestFuturePollTime == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
}
// convert absolute time to elapsed time
@@ -1570,47 +1537,23 @@ public class SyncManager implements OnAccountsUpdateListener {
: (earliestFuturePollTime - nowAbsolute));
}
- private void runStateSyncing() {
- // if the sync timeout has been reached then cancel it
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
-
- final long now = SystemClock.elapsedRealtime();
- if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
- Pair<SyncOperation, Long> nextOpAndRunTime;
- synchronized (mSyncQueue) {
- nextOpAndRunTime = mSyncQueue.nextOperation();
- }
- if (nextOpAndRunTime != null && nextOpAndRunTime.second <= now) {
- Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
- + activeSyncContext.mSyncOperation);
- scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
- sendSyncFinishedOrCanceledMessage(activeSyncContext,
- null /* no result since this is a cancel */);
- } else {
- activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC;
- }
- }
-
- // no need to schedule an alarm, as that will be done by our caller.
- }
-
- private void runStateIdle() {
- boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (isLoggable) Log.v(TAG, "runStateIdle");
+ private long maybeStartNextSyncLocked() {
+ final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) Log.v(TAG, "maybeStartNextSync");
// If we aren't ready to run (e.g. the data connection is down), get out.
if (!mDataConnectionIsConnected) {
if (isLoggable) {
- Log.v(TAG, "runStateIdle: no data connection, skipping");
+ Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
}
- return;
+ return Long.MAX_VALUE;
}
if (mStorageIsLow) {
if (isLoggable) {
- Log.v(TAG, "runStateIdle: memory low, skipping");
+ Log.v(TAG, "maybeStartNextSync: memory low, skipping");
}
- return;
+ return Long.MAX_VALUE;
}
// If the accounts aren't known yet then we aren't ready to run. We will be kicked
@@ -1618,46 +1561,56 @@ public class SyncManager implements OnAccountsUpdateListener {
Account[] accounts = mAccounts;
if (accounts == INITIAL_ACCOUNTS_ARRAY) {
if (isLoggable) {
- Log.v(TAG, "runStateIdle: accounts not known, skipping");
+ Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
}
- return;
+ return Long.MAX_VALUE;
}
// Otherwise consume SyncOperations from the head of the SyncQueue until one is
// found that is runnable (not disabled, etc). If that one is ready to run then
// start it, otherwise just get out.
- SyncOperation op;
- int syncableState;
final boolean backgroundDataUsageAllowed =
getConnectivityManager().getBackgroundDataSetting();
final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
- synchronized (mSyncQueue) {
- final long now = SystemClock.elapsedRealtime();
- while (true) {
- Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
- if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: no more ready sync operations, returning");
- }
- return;
- }
- op = nextOpAndRunTime.first;
+ final long now = SystemClock.elapsedRealtime();
- // we are either going to run this sync or drop it so go ahead and
- // remove it from the queue now
- mSyncQueue.remove(op);
+ // will be set to the next time that a sync should be considered for running
+ long nextReadyToRunTime = Long.MAX_VALUE;
+
+ // order the sync queue, dropping syncs that are not allowed
+ ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>();
+ synchronized (mSyncQueue) {
+ if (isLoggable) {
+ Log.v(TAG, "build the operation array, syncQueue size is "
+ + mSyncQueue.mOperationsMap.size());
+ }
+ Iterator<SyncOperation> operationIterator =
+ mSyncQueue.mOperationsMap.values().iterator();
+ while (operationIterator.hasNext()) {
+ final SyncOperation op = operationIterator.next();
// drop the sync if the account of this operation no longer exists
if (!ArrayUtils.contains(mAccounts, op.account)) {
+ operationIterator.remove();
+ mSyncStorageEngine.deleteFromPending(op.pendingOperation);
continue;
}
-
- // drop this sync request if it isn't syncable, intializing the sync adapter
- // if the syncable state is set to "unknown"
- syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
+ // drop this sync request if it isn't syncable
+ int syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
if (syncableState == 0) {
+ operationIterator.remove();
+ mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+ continue;
+ }
+
+ // if the next run time is in the future, meaning there are no syncs ready
+ // to run, return the time
+ if (op.effectiveRunTime > now) {
+ if (nextReadyToRunTime > op.effectiveRunTime) {
+ nextReadyToRunTime = op.effectiveRunTime;
+ }
continue;
}
@@ -1669,30 +1622,139 @@ public class SyncManager implements OnAccountsUpdateListener {
|| !backgroundDataUsageAllowed
|| !mSyncStorageEngine.getSyncAutomatically(
op.account, op.authority))) {
+ operationIterator.remove();
+ mSyncStorageEngine.deleteFromPending(op.pendingOperation);
continue;
}
- // go ahead and try to sync this syncOperation
- break;
+ operations.add(op);
+ }
+ }
+
+ // find the next operation to dispatch, if one is ready
+ // iterate from the top, keep issuing (while potentially cancelling existing syncs)
+ // until the quotas are filled.
+ // once the quotas are filled iterate once more to find when the next one would be
+ // (also considering pre-emption reasons).
+ if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size());
+ Collections.sort(operations);
+ if (isLoggable) Log.v(TAG, "dispatch all ready sync operations");
+ for (int i = 0, N = operations.size(); i < N; i++) {
+ final SyncOperation candidate = operations.get(i);
+ final boolean candidateIsInitialization = candidate.isInitialization();
+
+ int numInit = 0;
+ int numRegular = 0;
+ ActiveSyncContext conflict = null;
+ ActiveSyncContext longRunning = null;
+ ActiveSyncContext toReschedule = null;
+
+ for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+ final SyncOperation activeOp = activeSyncContext.mSyncOperation;
+ if (activeOp.isInitialization()) {
+ numInit++;
+ } else {
+ numRegular++;
+ }
+ if (activeOp.account.type.equals(candidate.account.type)
+ && activeOp.authority.equals(candidate.authority)) {
+ conflict = activeSyncContext;
+ // don't break out since we want to do a full count of the varieties
+ } else {
+ if (candidateIsInitialization == activeOp.isInitialization()
+ && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
+ longRunning = activeSyncContext;
+ // don't break out since we want to do a full count of the varieties
+ }
+ }
}
- // We will do this sync. Run it outside of the synchronized block.
if (isLoggable) {
- Log.v(TAG, "runStateIdle: we are going to sync " + op);
+ Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate);
+ Log.v(TAG, " numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
+ Log.v(TAG, " longRunning: " + longRunning);
+ Log.v(TAG, " conflict: " + conflict);
}
+
+ if (conflict != null) {
+ if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
+ && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
+ toReschedule = conflict;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since an initialization "
+ + "takes higher priority, " + conflict);
+ }
+ } else if (candidate.expedited && !conflict.mSyncOperation.expedited
+ && (candidateIsInitialization
+ == conflict.mSyncOperation.isInitialization())) {
+ toReschedule = conflict;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since an expedited "
+ + "takes higher priority, " + conflict);
+ }
+ } else {
+ continue;
+ }
+ } else {
+ final boolean roomAvailable = candidateIsInitialization
+ ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS
+ : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
+ if (roomAvailable) {
+ // dispatch candidate
+ } else if (longRunning != null
+ && (candidateIsInitialization
+ == longRunning.mSyncOperation.isInitialization())) {
+ toReschedule = longRunning;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
+ + longRunning);
+ }
+ } else {
+ continue;
+ }
+ }
+
+ if (toReschedule != null) {
+ runSyncFinishedOrCanceledLocked(null, toReschedule);
+ scheduleSyncOperation(toReschedule.mSyncOperation);
+ }
+
+ synchronized (mSyncQueue){
+ mSyncQueue.remove(candidate);
+ }
+ dispatchSyncOperation(candidate);
}
- // convert the op into an initialization sync if the syncable state is "unknown" and
- // op isn't already an initialization sync. If it is marked syncable then convert
- // this into a regular sync
- final boolean initializeIsSet =
- op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
- if (syncableState < 0 && !initializeIsSet) {
- op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
- op = new SyncOperation(op);
- } else if (syncableState > 0 && initializeIsSet) {
- op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
- op = new SyncOperation(op);
+ return nextReadyToRunTime;
+ }
+
+ private boolean dispatchSyncOperation(SyncOperation op) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "maybeStartNextSync: we are going to sync " + op);
+ Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
+ for (ActiveSyncContext syncContext : mActiveSyncContexts) {
+ Log.v(TAG, syncContext.toString());
+ }
+ }
+
+ // if this is an initialization sync and there is already a sync running with
+ // the same account type and authority cancel that sync before starting this one
+ // since otherwise the syncadapter will likely reject this request
+ if (op.isInitialization()) {
+ Iterator<ActiveSyncContext> iterator = mActiveSyncContexts.iterator();
+ while (iterator.hasNext()) {
+ ActiveSyncContext syncContext = iterator.next();
+ if (!syncContext.mSyncOperation.isInitialization()
+ && syncContext.mSyncOperation.account.type.equals(op.account.type)
+ && syncContext.mSyncOperation.authority.equals(op.authority)) {
+ Log.d(TAG, "canceling and rescheduling " + syncContext.mSyncOperation
+ + " since we are about to start a sync that used the "
+ + "same sync adapter, " + op);
+ iterator.remove();
+ runSyncFinishedOrCanceledLocked(null, syncContext);
+ scheduleSyncOperation(syncContext.mSyncOperation);
+ }
+ }
}
// connect to the sync adapter
@@ -1703,79 +1765,70 @@ public class SyncManager implements OnAccountsUpdateListener {
Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
+ ", removing settings for it");
mSyncStorageEngine.removeAuthority(op.account, op.authority);
- runStateIdle();
- return;
+ return false;
}
ActiveSyncContext activeSyncContext =
- new ActiveSyncContext(op, insertStartSyncEvent(op));
- mActiveSyncContext = activeSyncContext;
+ new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
+ activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
+ mActiveSyncContexts.add(activeSyncContext);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext);
+ Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
}
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
- mActiveSyncContext.close();
- mActiveSyncContext = null;
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
- runStateIdle();
- return;
+ closeActiveSyncContext(activeSyncContext);
+ return false;
}
- // Find the wakelock for this account and authority and store it in mSyncWakeLock.
- // Be sure to release the previous wakelock so that we don't end up with it being
- // held until it is used again.
- // There are a couple tricky things about this code:
- // - make sure that we acquire the new wakelock before releasing the old one,
- // otherwise the device might go to sleep as soon as we release it.
- // - since we use non-reference counted wakelocks we have to be sure not to do
- // the release if the wakelock didn't change. Othewise we would do an
- // acquire followed by a release on the same lock, resulting in no lock
- // being held.
- PowerManager.WakeLock oldWakeLock = mSyncWakeLock;
- try {
- mSyncWakeLock = getSyncWakeLock(op.account.type, op.authority);
- mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid));
- mSyncWakeLock.acquire();
- } finally {
- if (oldWakeLock != null && oldWakeLock != mSyncWakeLock) {
- oldWakeLock.release();
- }
- }
-
- // no need to schedule an alarm, as that will be done by our caller.
-
- // the next step will occur when we get either a timeout or a
- // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
+ return true;
}
- private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
- mActiveSyncContext.mSyncAdapter = syncAdapter;
- final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
+ private void runBoundToSyncAdapter(ActiveSyncContext activeSyncContext,
+ ISyncAdapter syncAdapter) {
+ activeSyncContext.mSyncAdapter = syncAdapter;
+ final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
try {
- syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
+ syncAdapter.startSync(activeSyncContext, syncOperation.authority,
syncOperation.account, syncOperation.extras);
} catch (RemoteException remoteExc) {
- Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
- mActiveSyncContext.close();
- mActiveSyncContext = null;
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
+ closeActiveSyncContext(activeSyncContext);
increaseBackoffSetting(syncOperation);
scheduleSyncOperation(new SyncOperation(syncOperation));
} catch (RuntimeException exc) {
- mActiveSyncContext.close();
- mActiveSyncContext = null;
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ closeActiveSyncContext(activeSyncContext);
Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
}
}
- private void runSyncFinishedOrCanceled(SyncResult syncResult) {
+ private void cancelActiveSyncLocked(Account account, String authority) {
+ ArrayList<ActiveSyncContext> activeSyncs =
+ new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
+ for (ActiveSyncContext activeSyncContext : activeSyncs) {
+ if (activeSyncContext != null) {
+ // if an authority was specified then only cancel the sync if it matches
+ if (account != null) {
+ if (!account.equals(activeSyncContext.mSyncOperation.account)) {
+ return;
+ }
+ }
+ // if an account was specified then only cancel the sync if it matches
+ if (authority != null) {
+ if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
+ return;
+ }
+ }
+ runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
+ activeSyncContext);
+ }
+ }
+ }
+
+ private void runSyncFinishedOrCanceledLocked(SyncResult syncResult,
+ ActiveSyncContext activeSyncContext) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- final ActiveSyncContext activeSyncContext = mActiveSyncContext;
- mActiveSyncContext = null;
- mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ closeActiveSyncContext(activeSyncContext);
final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
@@ -1795,16 +1848,6 @@ public class SyncManager implements OnAccountsUpdateListener {
// TODO: set these correctly when the SyncResult is extended to include it
downstreamActivity = 0;
upstreamActivity = 0;
- clearBackoffSetting(syncOperation);
- // if this was an initialization sync and the sync adapter is now
- // marked syncable then reschedule the sync. The next time it runs it
- // will be made into a regular sync.
- if (syncOperation.extras.getBoolean(
- ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
- && mSyncStorageEngine.getIsSyncable(
- syncOperation.account, syncOperation.authority) > 0) {
- scheduleSyncOperation(new SyncOperation(syncOperation));
- }
} else {
Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
// the operation failed so increase the backoff time
@@ -1839,8 +1882,6 @@ public class SyncManager implements OnAccountsUpdateListener {
stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
upstreamActivity, downstreamActivity, elapsedTime);
- activeSyncContext.close();
-
if (syncResult != null && syncResult.tooManyDeletions) {
installHandleTooManyDeletesNotification(syncOperation.account,
syncOperation.authority, syncResult.stats.numDeletes);
@@ -1851,11 +1892,18 @@ public class SyncManager implements OnAccountsUpdateListener {
if (syncResult != null && syncResult.fullSyncRequested) {
scheduleSyncOperation(new SyncOperation(syncOperation.account,
- syncOperation.syncSource, syncOperation.authority, new Bundle(), 0));
+ syncOperation.syncSource, syncOperation.authority, new Bundle(), 0,
+ syncOperation.backoff, syncOperation.delayUntil));
}
// no need to schedule an alarm, as that will be done by our caller.
}
+ private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
+ activeSyncContext.close();
+ mActiveSyncContexts.remove(activeSyncContext);
+ mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo);
+ }
+
/**
* Convert the error-containing SyncResult into the Sync.History error number. Since
* the SyncResult may indicate multiple errors at once, this method just returns the
@@ -1885,11 +1933,11 @@ public class SyncManager implements OnAccountsUpdateListener {
throw new IllegalStateException("we are not in an error state, " + syncResult);
}
- private void manageSyncNotification() {
+ private void manageSyncNotificationLocked() {
boolean shouldCancel;
boolean shouldInstall;
- if (mActiveSyncContext == null) {
+ if (mActiveSyncContexts.isEmpty()) {
mSyncNotificationInfo.startTime = null;
// we aren't syncing. if the notification is active then remember that we need
@@ -1898,34 +1946,38 @@ public class SyncManager implements OnAccountsUpdateListener {
shouldInstall = false;
} else {
// we are syncing
- final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
-
final long now = SystemClock.elapsedRealtime();
if (mSyncNotificationInfo.startTime == null) {
mSyncNotificationInfo.startTime = now;
}
- // cancel the notification if it is up and the authority or account is wrong
- shouldCancel = mSyncNotificationInfo.isActive &&
- (!syncOperation.authority.equals(mSyncNotificationInfo.authority)
- || !syncOperation.account.equals(mSyncNotificationInfo.account));
-
- // there are four cases:
- // - the notification is up and there is no change: do nothing
- // - the notification is up but we should cancel since it is stale:
- // need to install
+ // there are three cases:
+ // - the notification is up: do nothing
// - the notification is not up but it isn't time yet: don't install
// - the notification is not up and it is time: need to install
if (mSyncNotificationInfo.isActive) {
- shouldInstall = shouldCancel;
+ shouldInstall = shouldCancel = false;
} else {
+ // it isn't currently up, so there is nothing to cancel
+ shouldCancel = false;
+
final boolean timeToShowNotification =
now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
- // show the notification immediately if this is a manual sync
- final boolean manualSync = syncOperation.extras
- .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- shouldInstall = timeToShowNotification || manualSync;
+ if (timeToShowNotification) {
+ shouldInstall = true;
+ } else {
+ // show the notification immediately if this is a manual sync
+ shouldInstall = false;
+ for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+ final boolean manualSync = activeSyncContext.mSyncOperation.extras
+ .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+ if (manualSync) {
+ shouldInstall = true;
+ break;
+ }
+ }
+ }
}
}
@@ -1936,94 +1988,82 @@ public class SyncManager implements OnAccountsUpdateListener {
}
if (shouldInstall) {
- SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
mNeedSyncActiveNotification = true;
sendSyncStateIntent();
mSyncNotificationInfo.isActive = true;
- mSyncNotificationInfo.account = syncOperation.account;
- mSyncNotificationInfo.authority = syncOperation.authority;
- }
- }
-
- /**
- * Check if there were any long-lasting errors, if so install the error notification,
- * otherwise cancel the error notification.
- */
- private void manageErrorNotification() {
- //
- long when = mSyncStorageEngine.getInitialSyncFailureTime();
- if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) {
- if (!mErrorNotificationInstalled) {
- mNeedSyncErrorNotification = true;
- sendSyncStateIntent();
- }
- mErrorNotificationInstalled = true;
- } else {
- if (mErrorNotificationInstalled) {
- mNeedSyncErrorNotification = false;
- sendSyncStateIntent();
- }
- mErrorNotificationInstalled = false;
}
}
- private void manageSyncAlarm(Long earliestFuturePollElapsedTime) {
+ private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime,
+ long nextPendingEventElapsedTime) {
// in each of these cases the sync loop will be kicked, which will cause this
// method to be called again
if (!mDataConnectionIsConnected) return;
if (mStorageIsLow) return;
- final long now = SystemClock.elapsedRealtime();
-
- // Compute the alarm fire time:
- // - not syncing: time of the next sync operation
- // - syncing, no notification: time from sync start to notification create time
- // - syncing, with notification: time till timeout of the active sync operation
- Long alarmTime;
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (activeSyncContext == null) {
- synchronized (mSyncQueue) {
- final Pair<SyncOperation, Long> candidate = mSyncQueue.nextOperation();
- if (earliestFuturePollElapsedTime == null && candidate == null) {
- alarmTime = null;
- } else if (earliestFuturePollElapsedTime == null) {
- alarmTime = candidate.second;
- } else if (candidate == null) {
- alarmTime = earliestFuturePollElapsedTime;
- } else {
- alarmTime = Math.min(earliestFuturePollElapsedTime, candidate.second);
- }
+ // When the status bar notification should be raised
+ final long notificationTime =
+ (!mSyncHandler.mSyncNotificationInfo.isActive
+ && mSyncHandler.mSyncNotificationInfo.startTime != null)
+ ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY
+ : Long.MAX_VALUE;
+
+ // When we should consider canceling an active sync
+ long earliestTimeoutTime = Long.MAX_VALUE;
+ for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+ final long currentSyncTimeoutTime =
+ currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is "
+ + currentSyncTimeoutTime);
}
- } else {
- final long notificationTime =
- mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
- final long timeoutTime =
- mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
- if (mSyncHandler.mSyncNotificationInfo.isActive) {
- alarmTime = timeoutTime;
- } else {
- alarmTime = Math.min(notificationTime, timeoutTime);
+ if (earliestTimeoutTime > currentSyncTimeoutTime) {
+ earliestTimeoutTime = currentSyncTimeoutTime;
}
}
- // adjust the alarmTime so that we will wake up when it is time to
- // install the error notification
- if (!mErrorNotificationInstalled) {
- long when = mSyncStorageEngine.getInitialSyncFailureTime();
- if (when > 0) {
- when += ERROR_NOTIFICATION_DELAY_MS;
- // convert when fron absolute time to elapsed run time
- long delay = when - System.currentTimeMillis();
- when = now + delay;
- alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime);
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime);
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is "
+ + nextPeriodicEventElapsedTime);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is "
+ + nextPendingEventElapsedTime);
+ }
+
+ long alarmTime = Math.min(notificationTime, earliestTimeoutTime);
+ alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime);
+ alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime);
+
+ // Bound the alarm time.
+ final long now = SystemClock.elapsedRealtime();
+ if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, "
+ + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
+ }
+ alarmTime = now + SYNC_ALARM_TIMEOUT_MIN;
+ } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, "
+ + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
}
+ alarmTime = now + SYNC_ALARM_TIMEOUT_MAX;
}
// determine if we need to set or cancel the alarm
boolean shouldSet = false;
boolean shouldCancel = false;
final boolean alarmIsActive = mAlarmScheduleTime != null;
- final boolean needAlarm = alarmTime != null;
+ final boolean needAlarm = alarmTime != Long.MAX_VALUE;
if (needAlarm) {
if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
shouldSet = true;
@@ -2035,6 +2075,11 @@ public class SyncManager implements OnAccountsUpdateListener {
// set or cancel the alarm as directed
ensureAlarmService();
if (shouldSet) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time "
+ + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000)
+ + " secs from now");
+ }
mAlarmScheduleTime = alarmTime;
mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
mSyncAlarmIntent);
@@ -2048,7 +2093,7 @@ public class SyncManager implements OnAccountsUpdateListener {
Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
- syncStateIntent.putExtra("failing", mNeedSyncErrorNotification);
+ syncStateIntent.putExtra("failing", false);
mContext.sendBroadcast(syncStateIntent);
}
@@ -2137,4 +2182,13 @@ public class SyncManager implements OnAccountsUpdateListener {
resultMessage, downstreamActivity, upstreamActivity);
}
}
+
+ private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
+ for (ActiveSyncContext sync : mActiveSyncContexts) {
+ if (sync == activeSyncContext) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index b016088..3d7f3fbf 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -33,9 +33,12 @@ public class SyncOperation implements Comparable {
public long earliestRunTime;
public boolean expedited;
public SyncStorageEngine.PendingOperation pendingOperation;
+ public Long backoff;
+ public long delayUntil;
+ public long effectiveRunTime;
public SyncOperation(Account account, int source, String authority, Bundle extras,
- long delayInMs) {
+ long delayInMs, long backoff, long delayUntil) {
this.account = account;
this.syncSource = source;
this.authority = authority;
@@ -48,6 +51,8 @@ public class SyncOperation implements Comparable {
removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+ this.delayUntil = delayUntil;
+ this.backoff = backoff;
final long now = SystemClock.elapsedRealtime();
if (delayInMs < 0) {
this.expedited = true;
@@ -56,6 +61,7 @@ public class SyncOperation implements Comparable {
this.expedited = false;
this.earliestRunTime = now + delayInMs;
}
+ updateEffectiveRunTime();
this.key = toKey();
}
@@ -72,49 +78,78 @@ public class SyncOperation implements Comparable {
this.extras = new Bundle(other.extras);
this.expedited = other.expedited;
this.earliestRunTime = SystemClock.elapsedRealtime();
+ this.backoff = other.backoff;
+ this.delayUntil = other.delayUntil;
+ this.updateEffectiveRunTime();
this.key = toKey();
}
public String toString() {
+ return dump(true);
+ }
+
+ public String dump(boolean useOneLine) {
StringBuilder sb = new StringBuilder();
- sb.append("authority: ").append(authority);
- sb.append(" account: ").append(account);
- sb.append(" extras: ");
- extrasToStringBuilder(extras, sb, false /* asKey */);
- sb.append(" syncSource: ").append(syncSource);
- sb.append(" when: ").append(earliestRunTime);
- sb.append(" expedited: ").append(expedited);
+ sb.append(account.name);
+ sb.append(" (" + account.type + ")");
+ sb.append(", " + authority);
+ sb.append(", ");
+ sb.append(SyncStorageEngine.SOURCES[syncSource]);
+ sb.append(", earliestRunTime " + earliestRunTime);
+ if (expedited) {
+ sb.append(", EXPEDITED");
+ }
+ if (!useOneLine && !extras.keySet().isEmpty()) {
+ sb.append("\n ");
+ extrasToStringBuilder(extras, sb);
+ }
return sb.toString();
}
+ public boolean isInitialization() {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+ }
+
+ public boolean ignoreBackoff() {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
+ }
+
private String toKey() {
StringBuilder sb = new StringBuilder();
sb.append("authority: ").append(authority);
- sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
+ sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
sb.append(" extras: ");
- extrasToStringBuilder(extras, sb, true /* asKey */);
+ extrasToStringBuilder(extras, sb);
return sb.toString();
}
- public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb, boolean asKey) {
+ public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
sb.append("[");
for (String key : bundle.keySet()) {
- // if we are writing this as a key don't consider whether this
- // is an initialization sync or not when computing the key since
- // we set this flag appropriately when dispatching the sync request.
- if (asKey && ContentResolver.SYNC_EXTRAS_INITIALIZE.equals(key)) {
- continue;
- }
sb.append(key).append("=").append(bundle.get(key)).append(" ");
}
sb.append("]");
}
+ public void updateEffectiveRunTime() {
+ effectiveRunTime = ignoreBackoff()
+ ? earliestRunTime
+ : Math.max(
+ Math.max(earliestRunTime, delayUntil),
+ backoff);
+ }
+
public int compareTo(Object o) {
SyncOperation other = (SyncOperation)o;
- if (earliestRunTime == other.earliestRunTime) {
+
+ if (expedited != other.expedited) {
+ return expedited ? -1 : 1;
+ }
+
+ if (effectiveRunTime == other.effectiveRunTime) {
return 0;
}
- return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+
+ return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
}
}
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index 28baa0d..f826147 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -16,17 +16,18 @@
package android.content;
-import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
import android.util.Pair;
import android.util.Log;
import android.accounts.Account;
-import java.util.HashMap;
import java.util.ArrayList;
-import java.util.Map;
+import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
/**
*
@@ -38,7 +39,7 @@ public class SyncQueue {
// A Map of SyncOperations operationKey -> SyncOperation that is designed for
// quick lookup of an enqueued SyncOperation.
- private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
+ public final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
public SyncQueue(SyncStorageEngine syncStorageEngine) {
mSyncStorageEngine = syncStorageEngine;
@@ -47,8 +48,11 @@ public class SyncQueue {
final int N = ops.size();
for (int i=0; i<N; i++) {
SyncStorageEngine.PendingOperation op = ops.get(i);
+ final Pair<Long, Long> backoff = syncStorageEngine.getBackoff(op.account, op.authority);
SyncOperation syncOperation = new SyncOperation(
- op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
+ op.account, op.syncSource, op.authority, op.extras, 0 /* delay */,
+ backoff != null ? backoff.first : 0,
+ syncStorageEngine.getDelayUntilTime(op.account, op.authority));
syncOperation.expedited = op.expedited;
syncOperation.pendingOperation = op;
add(syncOperation, op);
@@ -119,65 +123,26 @@ public class SyncQueue {
}
}
- /**
- * Find the operation that should run next. Operations are sorted by their earliestRunTime,
- * prioritizing first those with a syncable state of "unknown" that aren't retries then
- * expedited operations.
- * The earliestRunTime is adjusted by the sync adapter's backoff and delayUntil times, if any.
- * @return the operation that should run next and when it should run. The time may be in
- * the future. It is expressed in milliseconds since boot.
- */
- public Pair<SyncOperation, Long> nextOperation() {
- SyncOperation best = null;
- long bestRunTime = 0;
- boolean bestSyncableIsUnknownAndNotARetry = false;
+ public void onBackoffChanged(Account account, String providerName, long backoff) {
+ // for each op that matches the account and provider update its
+ // backoff and effectiveStartTime
for (SyncOperation op : mOperationsMap.values()) {
- long opRunTime = op.earliestRunTime;
- if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
- Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
- long delayUntil = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
- opRunTime = Math.max(
- Math.max(opRunTime, delayUntil),
- backoff != null ? backoff.first : 0);
- }
- // we know a sync is a retry if the intialization flag is set, since that will only
- // be set by the sync dispatching code, thus if it is set it must have already been
- // dispatched
- final boolean syncableIsUnknownAndNotARetry =
- !op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
- && mSyncStorageEngine.getIsSyncable(op.account, op.authority) < 0;
- // if the unsyncable state differs, make the current the best if it is unsyncable
- // else, if the expedited state differs, make the current the best if it is expedited
- // else, make the current the best if it is earlier than the best
- if (best == null
- || ((bestSyncableIsUnknownAndNotARetry == syncableIsUnknownAndNotARetry)
- ? (best.expedited == op.expedited
- ? opRunTime < bestRunTime
- : op.expedited)
- : syncableIsUnknownAndNotARetry)) {
- best = op;
- bestSyncableIsUnknownAndNotARetry = syncableIsUnknownAndNotARetry;
- bestRunTime = opRunTime;
+ if (op.account.equals(account) && op.authority.equals(providerName)) {
+ op.backoff = backoff;
+ op.updateEffectiveRunTime();
}
}
- if (best == null) {
- return null;
- }
- return Pair.create(best, bestRunTime);
}
- /**
- * Find and return the SyncOperation that should be run next and is ready to run.
- * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
- * decide if the sync operation is ready to run
- * @return the SyncOperation that should be run next and is ready to run.
- */
- public Pair<SyncOperation, Long> nextReadyToRun(long now) {
- Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
- if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
- return null;
+ public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
+ // for each op that matches the account and provider update its
+ // delayUntilTime and effectiveStartTime
+ for (SyncOperation op : mOperationsMap.values()) {
+ if (op.account.equals(account) && op.authority.equals(providerName)) {
+ op.delayUntil = delayUntil;
+ op.updateEffectiveRunTime();
+ }
}
- return nextOpAndRunTime;
}
public void remove(Account account, String authority) {
@@ -200,9 +165,17 @@ public class SyncQueue {
}
public void dump(StringBuilder sb) {
+ final long now = SystemClock.elapsedRealtime();
sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
for (SyncOperation operation : mOperationsMap.values()) {
- sb.append(operation).append("\n");
+ sb.append(" ");
+ if (operation.effectiveRunTime <= now) {
+ sb.append("READY");
+ } else {
+ sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
+ }
+ sb.append(" - ");
+ sb.append(operation.dump(false)).append("\n");
}
}
}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 6413cec..487f6ce 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -229,7 +229,7 @@ public class SyncStorageEngine extends Handler {
private final ArrayList<PendingOperation> mPendingOperations =
new ArrayList<PendingOperation>();
- private SyncInfo mCurrentSync;
+ private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>();
private final SparseArray<SyncStatusInfo> mSyncStatus =
new SparseArray<SyncStatusInfo>();
@@ -690,23 +690,12 @@ public class SyncStorageEngine extends Handler {
/**
* Returns true if there is currently a sync operation for the given
- * account or authority in the pending list, or actively being processed.
+ * account or authority actively being processed.
*/
public boolean isSyncActive(Account account, String authority) {
synchronized (mAuthorities) {
- int i = mPendingOperations.size();
- while (i > 0) {
- i--;
- // TODO(fredq): this probably shouldn't be considering
- // pending operations.
- PendingOperation op = mPendingOperations.get(i);
- if (op.account.equals(account) && op.authority.equals(authority)) {
- return true;
- }
- }
-
- if (mCurrentSync != null) {
- AuthorityInfo ainfo = getAuthority(mCurrentSync.authorityId);
+ for (SyncInfo syncInfo : mCurrentSyncs) {
+ AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
if (ainfo != null && ainfo.account.equals(account)
&& ainfo.authority.equals(authority)) {
return true;
@@ -887,40 +876,47 @@ public class SyncStorageEngine extends Handler {
}
/**
- * Called when the currently active sync is changing (there can only be
- * one at a time). Either supply a valid ActiveSyncContext with information
- * about the sync, or null to stop the currently active sync.
+ * Called when a sync is starting. Supply a valid ActiveSyncContext with information
+ * about the sync.
*/
- public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+ public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+ final SyncInfo syncInfo;
synchronized (mAuthorities) {
- if (activeSyncContext != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setActiveSync: account="
- + activeSyncContext.mSyncOperation.account
- + " auth=" + activeSyncContext.mSyncOperation.authority
- + " src=" + activeSyncContext.mSyncOperation.syncSource
- + " extras=" + activeSyncContext.mSyncOperation.extras);
- }
- if (mCurrentSync != null) {
- Log.w(TAG, "setActiveSync called with existing active sync!");
- }
- AuthorityInfo authority = getAuthorityLocked(
- activeSyncContext.mSyncOperation.account,
- activeSyncContext.mSyncOperation.authority,
- "setActiveSync");
- if (authority == null) {
- return;
- }
- mCurrentSync = new SyncInfo(authority.ident,
- authority.account, authority.authority,
- activeSyncContext.mStartTime);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null");
- mCurrentSync = null;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setActiveSync: account="
+ + activeSyncContext.mSyncOperation.account
+ + " auth=" + activeSyncContext.mSyncOperation.authority
+ + " src=" + activeSyncContext.mSyncOperation.syncSource
+ + " extras=" + activeSyncContext.mSyncOperation.extras);
+ }
+ AuthorityInfo authority = getOrCreateAuthorityLocked(
+ activeSyncContext.mSyncOperation.account,
+ activeSyncContext.mSyncOperation.authority,
+ -1 /* assign a new identifier if creating a new authority */,
+ true /* write to storage if this results in a change */);
+ syncInfo = new SyncInfo(authority.ident,
+ authority.account, authority.authority,
+ activeSyncContext.mStartTime);
+ mCurrentSyncs.add(syncInfo);
+ }
+
+ reportActiveChange();
+ return syncInfo;
+ }
+
+ /**
+ * Called to indicate that a previously active sync is no longer active.
+ */
+ public void removeActiveSync(SyncInfo syncInfo) {
+ synchronized (mAuthorities) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "removeActiveSync: account="
+ + syncInfo.account + " auth=" + syncInfo.authority);
}
+ mCurrentSyncs.remove(syncInfo);
}
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
+ reportActiveChange();
}
/**
@@ -1095,10 +1091,26 @@ public class SyncStorageEngine extends Handler {
* Return the currently active sync information, or null if there is no
* active sync. Note that the returned object is the real, live active
* sync object, so be careful what you do with it.
+ * <p>
+ * Since multiple concurrent syncs are now supported you should use
+ * {@link #getCurrentSyncs()} to get the accurate list of current syncs.
+ * This method returns the first item from the list of current syncs
+ * or null if there are none.
+ * @deprecated use {@link #getCurrentSyncs()}
*/
public SyncInfo getCurrentSync() {
synchronized (mAuthorities) {
- return mCurrentSync;
+ return !mCurrentSyncs.isEmpty() ? mCurrentSyncs.get(0) : null;
+ }
+ }
+
+ /**
+ * Return a list of the currently active syncs. Note that the returned items are the
+ * real, live active sync objects, so be careful what you do with it.
+ */
+ public List<SyncInfo> getCurrentSyncs() {
+ synchronized (mAuthorities) {
+ return new ArrayList<SyncInfo>(mCurrentSyncs);
}
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index e21cb97..e688c86 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -216,10 +216,41 @@ public class ActivityInfo extends ComponentInfo
public static final int SCREEN_ORIENTATION_SENSOR = 4;
/**
- * Constant corresponding to <code>sensor</code> in
+ * Constant corresponding to <code>nosensor</code> in
* the {@link android.R.attr#screenOrientation} attribute.
*/
public static final int SCREEN_ORIENTATION_NOSENSOR = 5;
+
+ /**
+ * Constant corresponding to <code>sensorLandscape</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6;
+
+ /**
+ * Constant corresponding to <code>sensorPortrait</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7;
+
+ /**
+ * Constant corresponding to <code>reverseLandscape</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8;
+
+ /**
+ * Constant corresponding to <code>reversePortrait</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9;
+
+ /**
+ * Constant corresponding to <code>fullSensor</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_FULL_SENSOR = 10;
+
/**
* The preferred screen orientation this activity would like to run in.
* From the {@link android.R.attr#screenOrientation} attribute, one of
@@ -229,7 +260,12 @@ public class ActivityInfo extends ComponentInfo
* {@link #SCREEN_ORIENTATION_USER},
* {@link #SCREEN_ORIENTATION_BEHIND},
* {@link #SCREEN_ORIENTATION_SENSOR},
- * {@link #SCREEN_ORIENTATION_NOSENSOR}.
+ * {@link #SCREEN_ORIENTATION_NOSENSOR},
+ * {@link #SCREEN_ORIENTATION_SENSOR_LANDSCAPE},
+ * {@link #SCREEN_ORIENTATION_SENSOR_PORTRAIT},
+ * {@link #SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
+ * {@link #SCREEN_ORIENTATION_REVERSE_PORTRAIT},
+ * {@link #SCREEN_ORIENTATION_FULL_SENSOR}.
*/
public int screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 38d897e..ba74d9b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -296,12 +296,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* the normal application lifecycle.
*
* <p>Comes from the
- * {@link android.R.styleable#AndroidManifestApplication_heavyWeight android:heavyWeight}
+ * {@link android.R.styleable#AndroidManifestApplication_cantSaveState android:cantSaveState}
* attribute of the &lt;application&gt; tag.
*
* {@hide}
*/
- public static final int CANT_SAVE_STATE = 1<<27;
+ public static final int FLAG_CANT_SAVE_STATE = 1<<27;
/**
* Flags associated with the application. Any combination of
@@ -380,6 +380,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public boolean enabled = true;
+ /**
+ * For convenient access to package's install location.
+ * @hide
+ */
+ public int installLocation = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+
public void dump(Printer pw, String prefix) {
super.dumpFront(pw, prefix);
if (className != null) {
@@ -456,6 +462,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
uid = orig.uid;
targetSdkVersion = orig.targetSdkVersion;
enabled = orig.enabled;
+ installLocation = orig.installLocation;
manageSpaceActivityName = orig.manageSpaceActivityName;
descriptionRes = orig.descriptionRes;
}
@@ -488,6 +495,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(uid);
dest.writeInt(targetSdkVersion);
dest.writeInt(enabled ? 1 : 0);
+ dest.writeInt(installLocation);
dest.writeString(manageSpaceActivityName);
dest.writeString(backupAgentName);
dest.writeInt(descriptionRes);
@@ -520,6 +528,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
uid = source.readInt();
targetSdkVersion = source.readInt();
enabled = source.readInt() != 0;
+ installLocation = source.readInt();
manageSpaceActivityName = source.readString();
backupAgentName = source.readString();
descriptionRes = source.readInt();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index eaf1e33..6cbc9b5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -783,7 +783,8 @@ public class PackageParser {
pkg.installLocation = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_installLocation,
PARSE_DEFAULT_INSTALL_LOCATION);
-
+ pkg.applicationInfo.installLocation = pkg.installLocation;
+
// Resource boolean are -1, so 1 means we don't know the value.
int supportsSmallScreens = 1;
int supportsNormalScreens = 1;
@@ -1612,7 +1613,7 @@ public class PackageParser {
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
false)) {
- ai.flags |= ApplicationInfo.CANT_SAVE_STATE;
+ ai.flags |= ApplicationInfo.FLAG_CANT_SAVE_STATE;
// A heavy-weight application can not be in a custom process.
// We can do direct compare because we intern all strings.
@@ -1929,7 +1930,7 @@ public class PackageParser {
sa.recycle();
- if (receiver && (owner.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) {
+ if (receiver && (owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
// A heavy-weight application can not have receives in its main process
// We can do direct compare because we intern all strings.
if (a.info.processName == owner.packageName) {
@@ -2219,7 +2220,7 @@ public class PackageParser {
sa.recycle();
- if ((owner.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) {
+ if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
// A heavy-weight application can not have providers in its main process
// We can do direct compare because we intern all strings.
if (p.info.processName == owner.packageName) {
@@ -2460,7 +2461,7 @@ public class PackageParser {
sa.recycle();
- if ((owner.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) {
+ if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
// A heavy-weight application can not have services in its main process
// We can do direct compare because we intern all strings.
if (s.info.processName == owner.packageName) {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index f0d0fb4..a98a305 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -410,8 +410,16 @@ public class SQLiteDatabase extends SQLiteClosable {
* @see #unlock()
*/
/* package */ void lock() {
+ lock(false);
+ }
+ private void lock(boolean forced) {
+ // make sure this method is NOT being called from a 'synchronized' method
+ if (Thread.holdsLock(this)) {
+ // STOPSHIP change the following line to Log.w()
+ throw new IllegalStateException("don't lock() while in a synchronized method");
+ }
verifyDbIsOpen();
- if (!mLockingEnabled) return;
+ if (!forced && !mLockingEnabled) return;
mLock.lock();
if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
if (mLock.getHoldCount() == 1) {
@@ -421,7 +429,6 @@ public class SQLiteDatabase extends SQLiteClosable {
}
}
}
-
/**
* Locks the database for exclusive access. The database lock must be held when
* touch the native sqlite3* object since it is single threaded and uses
@@ -431,15 +438,7 @@ public class SQLiteDatabase extends SQLiteClosable {
* @see #unlockForced()
*/
private void lockForced() {
- verifyDbIsOpen();
- mLock.lock();
- if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
- if (mLock.getHoldCount() == 1) {
- // Use elapsed real-time since the CPU may sleep when waiting for IO
- mLockAcquiredWallTime = SystemClock.elapsedRealtime();
- mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
- }
- }
+ lock(true);
}
/**
diff --git a/core/java/android/net/LinkAddress.aidl b/core/java/android/net/LinkAddress.aidl
new file mode 100644
index 0000000..e7d8646
--- /dev/null
+++ b/core/java/android/net/LinkAddress.aidl
@@ -0,0 +1,21 @@
+/**
+ *
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable LinkAddress;
+
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
new file mode 100644
index 0000000..cb302da
--- /dev/null
+++ b/core/java/android/net/LinkAddress.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Identifies an address of a network link
+ * @hide
+ */
+public class LinkAddress implements Parcelable {
+ /**
+ * IPv4 or IPv6 address.
+ */
+ private final InetAddress address;
+
+ /**
+ * Network prefix
+ */
+ private final int prefix;
+
+ public LinkAddress(InetAddress address, InetAddress mask) {
+ this.address = address;
+ this.prefix = computeprefix(mask);
+ }
+
+ public LinkAddress(InetAddress address, int prefix) {
+ this.address = address;
+ this.prefix = prefix;
+ }
+
+ public LinkAddress(InterfaceAddress interfaceAddress) {
+ this.address = interfaceAddress.getAddress();
+ this.prefix = interfaceAddress.getNetworkPrefixLength();
+ }
+
+ private static int computeprefix(InetAddress mask) {
+ int count = 0;
+ for (byte b : mask.getAddress()) {
+ for (int i = 0; i < 8; ++i) {
+ if ((b & (1 << i)) != 0) {
+ ++count;
+ }
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public String toString() {
+ return (address == null ? "" : (address.getHostAddress() + "/" + prefix));
+ }
+
+ /**
+ * Compares this {@code LinkAddress} instance against the specified address
+ * in {@code obj}. Two addresses are equal if their InetAddress and prefix
+ * are equal
+ *
+ * @param obj the object to be tested for equality.
+ * @return {@code true} if both objects are equal, {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof LinkAddress)) {
+ return false;
+ }
+ LinkAddress linkAddress = (LinkAddress) obj;
+ return this.address.equals(linkAddress.address) &&
+ this.prefix == linkAddress.prefix;
+ }
+
+ /**
+ * Returns the InetAddress for this address.
+ */
+ public InetAddress getAddress() {
+ return address;
+ }
+
+ /**
+ * Get network prefix length
+ */
+ public int getNetworkPrefix() {
+ return prefix;
+ }
+
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ if (address != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(address.getAddress());
+ dest.writeInt(prefix);
+ } else {
+ dest.writeByte((byte)0);
+ }
+ }
+
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
+ public static final Creator<LinkAddress> CREATOR =
+ new Creator<LinkAddress>() {
+ public LinkAddress createFromParcel(Parcel in) {
+ InetAddress address = null;
+ int prefix = 0;
+ if (in.readByte() == 1) {
+ try {
+ address = InetAddress.getByAddress(in.createByteArray());
+ prefix = in.readInt();
+ } catch (UnknownHostException e) { }
+ }
+ return new LinkAddress(address, prefix);
+ }
+
+ public LinkAddress[] newArray(int size) {
+ return new LinkAddress[size];
+ }
+ };
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index f411eac..f1545ea 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -36,8 +36,8 @@ import java.util.Collections;
*/
public class LinkProperties implements Parcelable {
- private NetworkInterface mIface;
- private Collection<InetAddress> mAddresses;
+ String mIfaceName;
+ private Collection<LinkAddress> mLinkAddresses;
private Collection<InetAddress> mDnses;
private InetAddress mGateway;
private ProxyProperties mHttpProxy;
@@ -49,34 +49,42 @@ public class LinkProperties implements Parcelable {
// copy constructor instead of clone
public LinkProperties(LinkProperties source) {
if (source != null) {
- mIface = source.getInterface();
- mAddresses = source.getAddresses();
+ mIfaceName = source.getInterfaceName();
+ mLinkAddresses = source.getLinkAddresses();
mDnses = source.getDnses();
mGateway = source.getGateway();
mHttpProxy = new ProxyProperties(source.getHttpProxy());
}
}
- public void setInterface(NetworkInterface iface) {
- mIface = iface;
- }
- public NetworkInterface getInterface() {
- return mIface;
+ public void setInterfaceName(String iface) {
+ mIfaceName = iface;
}
+
public String getInterfaceName() {
- return (mIface == null ? null : mIface.getName());
+ return mIfaceName;
}
- public void addAddress(InetAddress address) {
- mAddresses.add(address);
- }
public Collection<InetAddress> getAddresses() {
- return Collections.unmodifiableCollection(mAddresses);
+ Collection<InetAddress> addresses = new ArrayList<InetAddress>();
+ for (LinkAddress linkAddress : mLinkAddresses) {
+ addresses.add(linkAddress.getAddress());
+ }
+ return Collections.unmodifiableCollection(addresses);
+ }
+
+ public void addLinkAddress(LinkAddress address) {
+ mLinkAddresses.add(address);
+ }
+
+ public Collection<LinkAddress> getLinkAddresses() {
+ return Collections.unmodifiableCollection(mLinkAddresses);
}
public void addDns(InetAddress dns) {
mDnses.add(dns);
}
+
public Collection<InetAddress> getDnses() {
return Collections.unmodifiableCollection(mDnses);
}
@@ -96,8 +104,8 @@ public class LinkProperties implements Parcelable {
}
public void clear() {
- mIface = null;
- mAddresses = new ArrayList<InetAddress>();
+ mIfaceName = null;
+ mLinkAddresses = new ArrayList<LinkAddress>();
mDnses = new ArrayList<InetAddress>();
mGateway = null;
mHttpProxy = null;
@@ -113,11 +121,11 @@ public class LinkProperties implements Parcelable {
@Override
public String toString() {
- String ifaceName = (mIface == null ? "" : "InterfaceName: " + mIface.getName() + " ");
+ String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
- String ip = "IpAddresses: [";
- for (InetAddress addr : mAddresses) ip += addr.getHostAddress() + ",";
- ip += "] ";
+ String linkAddresses = "LinkAddresses: [";
+ for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString();
+ linkAddresses += "] ";
String dns = "DnsAddresses: [";
for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
@@ -126,7 +134,7 @@ public class LinkProperties implements Parcelable {
String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
String gateway = (mGateway == null ? "" : "Gateway: " + mGateway.getHostAddress() + " ");
- return ifaceName + ip + gateway + dns + proxy;
+ return ifaceName + linkAddresses + gateway + dns + proxy;
}
/**
@@ -135,12 +143,11 @@ public class LinkProperties implements Parcelable {
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getInterfaceName());
- dest.writeInt(mAddresses.size());
- //TODO: explore an easy alternative to preserve hostname
- // without doing a lookup
- for(InetAddress a : mAddresses) {
- dest.writeByteArray(a.getAddress());
+ dest.writeInt(mLinkAddresses.size());
+ for(LinkAddress linkAddress : mLinkAddresses) {
+ dest.writeParcelable(linkAddress, flags);
}
+
dest.writeInt(mDnses.size());
for(InetAddress d : mDnses) {
dest.writeByteArray(d.getAddress());
@@ -170,16 +177,14 @@ public class LinkProperties implements Parcelable {
String iface = in.readString();
if (iface != null) {
try {
- netProp.setInterface(NetworkInterface.getByName(iface));
+ netProp.setInterfaceName(iface);
} catch (Exception e) {
return null;
}
}
int addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
- try {
- netProp.addAddress(InetAddress.getByAddress(in.createByteArray()));
- } catch (UnknownHostException e) { }
+ netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
}
addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index ad7289d..3df8ec0 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -45,7 +45,7 @@ import android.text.TextUtils;
public class MobileDataStateTracker implements NetworkStateTracker {
private static final String TAG = "MobileDataStateTracker";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private Phone.DataState mMobileDataState;
private ITelephony mPhoneService;
diff --git a/core/java/android/nfc/NdefMessage.java b/core/java/android/nfc/NdefMessage.java
new file mode 100644
index 0000000..557f651
--- /dev/null
+++ b/core/java/android/nfc/NdefMessage.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.nfc.NdefRecord;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.UnsupportedOperationException;
+
+/**
+ * NDEF Message data.
+ * <p>
+ * Immutable data class. An NDEF message always contains zero or more NDEF
+ * records.
+ */
+public class NdefMessage implements Parcelable {
+ /**
+ * Create an NDEF message from raw bytes.
+ * <p>
+ * Validation is performed to make sure the Record format headers are valid,
+ * and the ID + TYPE + PAYLOAD fields are of the correct size.
+ *
+ * @hide
+ */
+ public NdefMessage(byte[] data) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Create an NDEF message from NDEF records.
+ */
+ public NdefMessage(NdefRecord[] records) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get the NDEF records inside this NDEF message.
+ *
+ * @return array of zero or more NDEF records.
+ */
+ public NdefRecord[] getRecords() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get a byte array representation of this NDEF message.
+ *
+ * @return byte array
+ * @hide
+ */
+ public byte[] toByteArray() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static final Parcelable.Creator<NdefMessage> CREATOR =
+ new Parcelable.Creator<NdefMessage>() {
+ public NdefMessage createFromParcel(Parcel in) {
+ throw new UnsupportedOperationException();
+ }
+ public NdefMessage[] newArray(int size) {
+ throw new UnsupportedOperationException();
+ }
+ };
+} \ No newline at end of file
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
new file mode 100644
index 0000000..54cbbeb
--- /dev/null
+++ b/core/java/android/nfc/NdefRecord.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.UnsupportedOperationException;
+
+/**
+ * NDEF Record data.
+ * <p>
+ * Immutable data class. An NDEF record always contains
+ * <ul>
+ * <li>3-bit TNF field
+ * <li>Variable length type
+ * <li>Variable length ID
+ * <li>Variable length payload
+ * </ul>
+ * The TNF (Type Name Format) field indicates how to interpret the type field.
+ * <p>
+ * This class represents a logical (unchunked) NDEF record. The underlying
+ * representation may be chunked across several NDEF records when the payload is
+ * large.
+ */
+public class NdefRecord implements Parcelable {
+ /**
+ * Indicates no type, id, or payload is associated with this NDEF Record.
+ * <p>
+ * Type, id and payload fields must all be empty to be a valid TNF_EMPTY
+ * record.
+ */
+ public static final short TNF_EMPTY = 0x00;
+
+ /**
+ * Indicates the type field uses the RTD type name format.
+ * <p>
+ * Use this TNF with RTD types such as RTD_TEXT, RTD_URI.
+ */
+ public static final short TNF_WELL_KNOWN = 0x01;
+
+ /**
+ * Indicates the type field contains a value that follows the media-type BNF
+ * construct defined by RFC 2046.
+ */
+ public static final short TNF_MIME_MEDIA = 0x02;
+
+ /**
+ * Indicates the type field contains a value that follows the absolute-URI
+ * BNF construct defined by RFC 3986.
+ */
+ public static final short TNF_ABSOLUTE_URI = 0x03;
+
+ /**
+ * Indicates the type field contains a value that follows the RTD external
+ * name specification.
+ * <p>
+ * Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
+ * Those are well known RTD constants, not external RTD constants.
+ */
+ public static final short TNF_EXTERNAL_TYPE = 0x04;
+
+ /**
+ * Indicates the payload type is unknown.
+ * <p>
+ * This is similar to the "application/octet-stream" MIME type. The payload
+ * type is not explicitly encoded within the NDEF Message.
+ * <p>
+ * The type field must be empty to be a valid TNF_UNKNOWN record.
+ */
+ public static final short TNF_UNKNOWN = 0x05;
+
+ /**
+ * Indicates the payload is an intermediate or final chunk of a chunked
+ * NDEF Record.
+ * <p>
+ * The payload type is specified in the first chunk, and subsequent chunks
+ * must use TNF_UNCHANGED with an empty type field. TNF_UNCHANGED must not
+ * be used in any other situation.
+ */
+ public static final short TNF_UNCHANGED = 0x06;
+
+ /**
+ * Reserved TNF type.
+ * <p>
+ * The NFC Forum NDEF Specification v1.0 suggests for NDEF parsers to treat this
+ * value like TNF_UNKNOWN.
+ * @hide
+ */
+ public static final short TNF_RESERVED = 0x07;
+
+ /**
+ * RTD Text type. For use with TNF_WELL_KNOWN.
+ */
+ public static final byte[] RTD_TEXT = {0x54}; // "T"
+
+ /**
+ * RTD URI type. For use with TNF_WELL_KNOWN.
+ */
+ public static final byte[] RTD_URI = {0x55}; // "U"
+
+ /**
+ * RTD Smart Poster type. For use with TNF_WELL_KNOWN.
+ */
+ public static final byte[] RTD_SMART_POSTER = {0x53, 0x70}; // "Sp"
+
+ /**
+ * RTD Alternative Carrier type. For use with TNF_WELL_KNOWN.
+ */
+ public static final byte[] RTD_ALTERNATIVE_CARRIER = {0x61, 0x63}; // "ac"
+
+ /**
+ * RTD Handover Carrier type. For use with TNF_WELL_KNOWN.
+ */
+ public static final byte[] RTD_HANDOVER_CARRIER = {0x48, 0x63}; // "Hc"
+
+ /**
+ * RTD Handover Request type. For use with TNF_WELL_KNOWN.
+ */
+ public static final byte[] RTD_HANDOVER_REQUEST = {0x48, 0x72}; // "Hr"
+
+ /**
+ * RTD Handover Select type. For use with TNF_WELL_KNOWN.
+ */
+ public static final byte[] RTD_HANDOVER_SELECT = {0x48, 0x73}; // "Hs"
+
+ /**
+ * Construct an NDEF Record.
+ * <p>
+ * Applications should not attempt to manually chunk NDEF Records - the
+ * implementation of android.nfc will automatically chunk an NDEF Record
+ * when necessary (and only present a single logical NDEF Record to the
+ * application). So applications should not use TNF_UNCHANGED.
+ *
+ * @param tnf a 3-bit TNF constant
+ * @param type byte array, containing zero to 255 bytes, must not be null
+ * @param id byte array, containing zero to 255 bytes, must not be null
+ * @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
+ * must not be null
+ */
+ public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Construct an NDEF Record from raw bytes.
+ * <p>
+ * Validation is performed to make sure the header is valid, and that
+ * the id, type and payload sizes appear to be valid.
+ *
+ * @throws FormatException if the data is not a valid NDEF record
+ */
+ public NdefRecord(byte[] data) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the 3-bit TNF.
+ * <p>
+ * TNF is the top-level type.
+ */
+ public short getTnf() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the variable length Type field.
+ * <p>
+ * This should be used in conjunction with the TNF field to determine the
+ * payload format.
+ */
+ public byte[] getType() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the variable length ID.
+ */
+ public byte[] getId() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the variable length payload.
+ */
+ public byte[] getPayload() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Return this NDEF Record as a byte array.
+ * @hide
+ */
+ public byte[] toByteArray() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static final Parcelable.Creator<NdefRecord> CREATOR =
+ new Parcelable.Creator<NdefRecord>() {
+ public NdefRecord createFromParcel(Parcel in) {
+ throw new UnsupportedOperationException();
+ }
+ public NdefRecord[] newArray(int size) {
+ throw new UnsupportedOperationException();
+ }
+ };
+} \ No newline at end of file
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 44b73c5..5fd2246 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -84,7 +84,15 @@ public class BatteryManager {
* String describing the technology of the current battery.
*/
public static final String EXTRA_TECHNOLOGY = "technology";
-
+
+ /**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Boolean value set to true if an unsupported charger is attached
+ * to the device.
+ * {@hide}
+ */
+ public static final String EXTRA_INVALID_CHARGER = "invalid_charger";
+
// values for "status" field in the ACTION_BATTERY_CHANGED Intent
public static final int BATTERY_STATUS_UNKNOWN = 1;
public static final int BATTERY_STATUS_CHARGING = 2;
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index a58e70b..2b4f39a 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -775,6 +775,15 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
public static native void dumpNativeHeap(FileDescriptor fd);
/**
+ * Returns a count of the extant instances of a class.
+ *
+ * @hide
+ */
+ public static long countInstancesOfClass(Class cls) {
+ return VMDebug.countInstancesOfClass(cls);
+ }
+
+ /**
* Returns the number of sent transactions from this process.
* @return The number of sent transactions or -1 if it could not read t.
*/
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index c7cbed6..e8ae7e6 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,6 +18,7 @@ package android.os;
import java.io.File;
+import android.content.res.Resources;
import android.os.storage.IMountService;
/**
@@ -116,6 +117,19 @@ public class Environment {
* happened. You can determine its current state with
* {@link #getExternalStorageState()}.
*
+ * <p><em>Note: don't be confused by the word "external" here. This
+ * directory can better be thought as media/shared storage. It is a
+ * filesystem that can hold a relatively large amount of data and that
+ * is shared across all applications (does not enforce permissions).
+ * Traditionally this is an SD card, but it may also be implemented as
+ * built-in storage in a device that is distinct from the protected
+ * internal storage and can be mounted as a filesystem on a computer.</em></p>
+ *
+ * <p>In devices with multiple "external" storage directories (such as
+ * both secure app storage and mountable shared storage), this directory
+ * represents the "primary" external storage that the user will interact
+ * with.</p>
+ *
* <p>Applications should not directly use this top-level directory, in
* order to avoid polluting the user's root namespace. Any files that are
* private to the application should be placed in a directory returned
@@ -130,6 +144,9 @@ public class Environment {
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
* monitor_storage}
+ *
+ * @see #getExternalStorageState()
+ * @see #isExternalStorageRemovable()
*/
public static File getExternalStorageDirectory() {
return EXTERNAL_STORAGE_DIRECTORY;
@@ -359,11 +376,9 @@ public class Environment {
public static final String MEDIA_UNMOUNTABLE = "unmountable";
/**
- * Gets the current state of the external storage device.
- * Note: This call should be deprecated as it doesn't support
- * multiple volumes.
+ * Gets the current state of the primary "external" storage device.
*
- * <p>See {@link #getExternalStorageDirectory()} for an example of its use.
+ * <p>See {@link #getExternalStorageDirectory()} for more information.
*/
public static String getExternalStorageState() {
try {
@@ -377,6 +392,19 @@ public class Environment {
}
}
+ /**
+ * Returns whether the primary "external" storage device is removable.
+ * If true is returned, this device is for example an SD card that the
+ * user can remove. If false is returned, the storage is built into
+ * the device and can not be physically removed.
+ *
+ * <p>See {@link #getExternalStorageDirectory()} for more information.
+ */
+ public static boolean isExternalStorageRemovable() {
+ return Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_externalStorageRemovable);
+ }
+
static File getDirectory(String variableName, String defaultPath) {
String path = System.getenv(variableName);
return path == null ? new File(defaultPath) : new File(path);
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index a6c7d9e..a59b2f8 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -893,11 +893,15 @@ public abstract class PreferenceActivity extends ListActivity implements
}
}
- public void switchToHeaderInner(String fragmentName, Bundle args) {
+ public void switchToHeaderInner(String fragmentName, Bundle args, boolean next) {
getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
Fragment f = Fragment.instantiate(this, fragmentName, args);
- getFragmentManager().openTransaction().replace(
- com.android.internal.R.id.prefs, f).commit();
+ FragmentTransaction transaction = getFragmentManager().openTransaction();
+ transaction.setTransition(next ?
+ FragmentTransaction.TRANSIT_FRAGMENT_NEXT :
+ FragmentTransaction.TRANSIT_FRAGMENT_PREV);
+ transaction.replace(com.android.internal.R.id.prefs, f);
+ transaction.commit();
}
/**
@@ -909,7 +913,7 @@ public abstract class PreferenceActivity extends ListActivity implements
*/
public void switchToHeader(String fragmentName, Bundle args) {
setSelectedHeader(null);
- switchToHeaderInner(fragmentName, args);
+ switchToHeaderInner(fragmentName, args, true);
}
/**
@@ -919,7 +923,8 @@ public abstract class PreferenceActivity extends ListActivity implements
* @param header The new header to display.
*/
public void switchToHeader(Header header) {
- switchToHeaderInner(header.fragment, header.fragmentArguments);
+ switchToHeaderInner(header.fragment, header.fragmentArguments,
+ mHeaders.indexOf(header) > mHeaders.indexOf(mCurHeader));
setSelectedHeader(header);
}
@@ -979,7 +984,10 @@ public abstract class PreferenceActivity extends ListActivity implements
FragmentTransaction transaction = getFragmentManager().openTransaction();
startPreferenceFragment(fragment, transaction);
if (push) {
+ transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(BACK_STACK_PREFS);
+ } else {
+ transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_NEXT);
}
transaction.commit();
}
@@ -1001,6 +1009,7 @@ public abstract class PreferenceActivity extends ListActivity implements
FragmentTransaction transaction = getFragmentManager().openTransaction();
startPreferenceFragment(f, transaction);
transaction.setBreadCrumbTitle(pref.getTitle());
+ transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(BACK_STACK_PREFS);
transaction.commit();
return true;
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index 47c6d3c..93598cd 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -50,10 +50,10 @@ public class DragEvent implements Parcelable {
public static final int ACTION_DRAG_EXITED = 6;
/* hide the constructor behind package scope */
- DragEvent() {
+ private DragEvent() {
}
- public static DragEvent obtain() {
+ static DragEvent obtain() {
return DragEvent.obtain(0, 0f, 0f, null, null);
}
@@ -81,6 +81,11 @@ public class DragEvent implements Parcelable {
return ev;
}
+ public static DragEvent obtain(DragEvent source) {
+ return obtain(source.mAction, source.mX, source.mY,
+ source.mClipDescription, source.mClipData);
+ }
+
public int getAction() {
return mAction;
}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index f0b00dd..2fadb82 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -135,10 +135,10 @@ class GLES20Canvas extends HardwareCanvas {
@Override
void onPreDraw() {
- nPrepare(mRenderer);
+ nPrepare(mRenderer, mOpaque);
}
- private native void nPrepare(int renderer);
+ private native void nPrepare(int renderer, boolean opaque);
@Override
void onPostDraw() {
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 2cc4052..b87dbc5 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -585,7 +585,7 @@ public abstract class HardwareRenderer {
@Override
GLES20Canvas createCanvas() {
- return mGlCanvas = new GLES20Canvas(true);
+ return mGlCanvas = new GLES20Canvas(mTranslucent);
}
@Override
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 6b9cda0..85a8c1a 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -17,8 +17,6 @@
package android.view;
-import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 54cb4ca..b45aa99 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -287,10 +287,12 @@ public class SurfaceView extends View {
setMeasuredDimension(width, height);
}
+ /** @hide */
@Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean result = super.setFrame(left, top, right, bottom);
updateWindow(false, false);
+ return result;
}
@Override
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4c01f4f..472f7b4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -66,6 +66,7 @@ import android.widget.ScrollBarDrawable;
import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
+import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -615,7 +616,6 @@ import java.util.WeakHashMap;
*/
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
private static final boolean DBG = false;
- static final boolean DEBUG_DRAG = true;
/**
* The logging tag used by this class with android.util.Log.
@@ -3957,6 +3957,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
return true;
}
+ /** Gets the ViewRoot, or null if not attached. */
+ /*package*/ ViewRoot getViewRoot() {
+ View root = getRootView();
+ return root != null ? (ViewRoot)root.getParent() : null;
+ }
+
/**
* Call this to try to give focus to a specific view or to one of its descendants. This is a
* special variant of {@link #requestFocus() } that will allow views that are not focuable in
@@ -3970,12 +3976,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
public final boolean requestFocusFromTouch() {
// Leave touch mode if we need to
if (isInTouchMode()) {
- View root = getRootView();
- if (root != null) {
- ViewRoot viewRoot = (ViewRoot)root.getParent();
- if (viewRoot != null) {
- viewRoot.ensureTouchMode(false);
- }
+ ViewRoot viewRoot = getViewRoot();
+ if (viewRoot != null) {
+ viewRoot.ensureTouchMode(false);
}
}
return requestFocus(View.FOCUS_DOWN);
@@ -4905,6 +4908,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
if ((changed & VISIBILITY_MASK) != 0) {
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup)mParent).onChildVisibilityChanged(this, (flags & VISIBILITY_MASK));
+ }
dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK));
}
@@ -5147,7 +5153,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Recomputes the transform matrix if necessary.
*/
- private final void updateMatrix() {
+ private void updateMatrix() {
if (mMatrixDirty) {
// transform-related properties have changed since the last time someone
// asked for the matrix; recalculate it with the current values
@@ -6202,7 +6208,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
}
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
+ (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
@@ -6233,7 +6240,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
}
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
+ (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
@@ -6272,7 +6280,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
}
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
+ (invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID)) {
mPrivateFlags &= ~DRAWN;
if (invalidateCache) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
@@ -7422,8 +7431,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final int restoreCount = canvas.save();
- mPrivateFlags |= DRAWN;
- mPrivateFlags |= DRAWING_CACHE_VALID;
+ mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
@@ -8535,7 +8543,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if ((privateFlags & PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED;
if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED;
- if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
+ if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
@@ -9830,40 +9838,108 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
- * Drag and drop. App calls startDrag(), then callbacks to onMeasureDragThumbnail()
- * and onDrawDragThumbnail() happen, then the drag operation is handed over to the
- * OS.
* !!! TODO: real docs
- * @hide
+ *
+ * The base class implementation makes the thumbnail the same size and appearance
+ * as the view itself, and positions it with its center at the touch point.
+ */
+ public static class DragThumbnailBuilder {
+ private final WeakReference<View> mView;
+
+ /**
+ * Construct a thumbnail builder object for use with the given view.
+ * @param view
+ */
+ public DragThumbnailBuilder(View view) {
+ mView = new WeakReference<View>(view);
+ }
+
+ /**
+ * Provide the draggable-thumbnail metrics for the operation: the dimensions of
+ * the thumbnail image itself, and the point within that thumbnail that should
+ * be centered under the touch location while dragging.
+ * <p>
+ * The default implementation sets the dimensions of the thumbnail to be the
+ * same as the dimensions of the View itself and centers the thumbnail under
+ * the touch point.
+ *
+ * @param thumbnailSize The application should set the {@code x} member of this
+ * parameter to the desired thumbnail width, and the {@code y} member to
+ * the desired height.
+ * @param thumbnailTouchPoint The application should set this point to be the
+ * location within the thumbnail that should track directly underneath
+ * the touch point on the screen during a drag.
+ */
+ public void onProvideThumbnailMetrics(Point thumbnailSize, Point thumbnailTouchPoint) {
+ final View view = mView.get();
+ if (view != null) {
+ thumbnailSize.set(view.getWidth(), view.getHeight());
+ thumbnailTouchPoint.set(thumbnailSize.x / 2, thumbnailSize.y / 2);
+ } else {
+ Log.e(View.VIEW_LOG_TAG, "Asked for drag thumb metrics but no view");
+ }
+ }
+
+ /**
+ * Draw the thumbnail image for the upcoming drag. The thumbnail canvas was
+ * created with the dimensions supplied by the onProvideThumbnailMetrics()
+ * callback.
+ *
+ * @param canvas
+ */
+ public void onDrawThumbnail(Canvas canvas) {
+ final View view = mView.get();
+ if (view != null) {
+ view.draw(canvas);
+ } else {
+ Log.e(View.VIEW_LOG_TAG, "Asked to draw drag thumb but no view");
+ }
+ }
+ }
+
+ /**
+ * Drag and drop. App calls startDrag(), then callbacks to the thumbnail builder's
+ * onProvideThumbnailMetrics() and onDrawThumbnail() methods happen, then the drag
+ * operation is handed over to the OS.
+ * !!! TODO: real docs
*/
- public final boolean startDrag(ClipData data, float touchX, float touchY,
- float thumbnailTouchX, float thumbnailTouchY, boolean myWindowOnly) {
- if (DEBUG_DRAG) {
- Log.d(VIEW_LOG_TAG, "startDrag: touch=(" + touchX + "," + touchY
- + ") thumb=(" + thumbnailTouchX + "," + thumbnailTouchY
- + ") data=" + data + " local=" + myWindowOnly);
+ public final boolean startDrag(ClipData data, DragThumbnailBuilder thumbBuilder,
+ boolean myWindowOnly) {
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "startDrag: data=" + data + " local=" + myWindowOnly);
}
boolean okay = false;
- measureThumbnail(); // throws if the view fails to specify dimensions
+ Point thumbSize = new Point();
+ Point thumbTouchPoint = new Point();
+ thumbBuilder.onProvideThumbnailMetrics(thumbSize, thumbTouchPoint);
+
+ if ((thumbSize.x < 0) || (thumbSize.y < 0) ||
+ (thumbTouchPoint.x < 0) || (thumbTouchPoint.y < 0)) {
+ throw new IllegalStateException("Drag thumb dimensions must not be negative");
+ }
Surface surface = new Surface();
try {
IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
myWindowOnly, mThumbnailWidth, mThumbnailHeight, surface);
- if (DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+ if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+ " surface=" + surface);
if (token != null) {
Canvas canvas = surface.lockCanvas(null);
try {
- onDrawDragThumbnail(canvas);
+ thumbBuilder.onDrawThumbnail(canvas);
} finally {
surface.unlockCanvasAndPost(canvas);
}
+ // repurpose 'thumbSize' for the last touch point
+ getViewRoot().getLastTouchPoint(thumbSize);
+
okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
- touchX, touchY, thumbnailTouchX, thumbnailTouchY, data);
- if (DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
+ (float) thumbSize.x, (float) thumbSize.y,
+ (float) thumbTouchPoint.x, (float) thumbTouchPoint.y, data);
+ if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
}
} catch (Exception e) {
Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
@@ -9885,7 +9961,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
+ " measured dimension by calling setDragThumbnailDimension()");
}
- if (DEBUG_DRAG) {
+ if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "Drag thumb measured: w=" + mThumbnailWidth
+ " h=" + mThumbnailHeight);
}
@@ -9950,7 +10026,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* For DRAG_ENDED_EVENT, the 'event' argument may be null. The view should return
* to its normal visual state.
*/
- protected boolean onDragEvent(DragEvent event) {
+ public boolean onDragEvent(DragEvent event) {
return false;
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index b1d5272..11e68c5 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -27,7 +27,6 @@ import android.graphics.Rect;
import android.os.Environment;
import android.os.Debug;
import android.os.RemoteException;
-import dalvik.system.VMDebug;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -136,6 +135,12 @@ public class ViewDebug {
public static final boolean DEBUG_SHOW_FPS = false;
/**
+ * Enables detailed logging of drag/drop operations.
+ * @hide
+ */
+ public static final boolean DEBUG_DRAG = true;
+
+ /**
* <p>Enables or disables views consistency check. Even when this property is enabled,
* view consistency checks happen only if {@link android.util.Config#DEBUG} is set
* to true. The value of this property can be configured externally in one of the
@@ -425,7 +430,7 @@ public class ViewDebug {
* @hide
*/
public static long getViewInstanceCount() {
- return VMDebug.countInstancesOfClass(View.class);
+ return Debug.countInstancesOfClass(View.class);
}
/**
@@ -436,7 +441,7 @@ public class ViewDebug {
* @hide
*/
public static long getViewRootInstanceCount() {
- return VMDebug.countInstancesOfClass(ViewRoot.class);
+ return Debug.countInstancesOfClass(ViewRoot.class);
}
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 8a3db38..5b3a091 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,8 +19,6 @@ package android.view;
import android.animation.LayoutTransition;
import com.android.internal.R;
-import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -36,7 +34,6 @@ import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Animation;
@@ -72,7 +69,6 @@ import java.util.ArrayList;
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
private static final boolean DBG = false;
- private static final String TAG = "ViewGroup";
/**
* Views which have been hidden or removed which need to be animated on
@@ -323,6 +319,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// being animated.
private ArrayList<View> mTransitioningViews;
+ // List of children changing visibility. This is used to potentially keep rendering
+ // views during a transition when they otherwise would have become gone/invisible
+ private ArrayList<View> mVisibilityChangingChildren;
+
public ViewGroup(Context context) {
super(context);
initViewGroup();
@@ -758,6 +758,32 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * @hide
+ * @param child
+ * @param visibility
+ */
+ void onChildVisibilityChanged(View child, int visibility) {
+ if (mTransition != null) {
+ if (visibility == VISIBLE) {
+ mTransition.showChild(this, child);
+ } else {
+ mTransition.hideChild(this, child);
+ }
+ if (visibility != VISIBLE) {
+ // Only track this on disappearing views - appearing views are already visible
+ // and don't need special handling during drawChild()
+ if (mVisibilityChangingChildren == null) {
+ mVisibilityChangingChildren = new ArrayList<View>();
+ }
+ mVisibilityChangingChildren.add(child);
+ if (mTransitioningViews != null && mTransitioningViews.contains(child)) {
+ addDisappearingView(child);
+ }
+ }
+ }
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -838,9 +864,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float tx = event.mX;
final float ty = event.mY;
- // !!! BUGCHECK: If we have a ViewGroup, we must necessarily have a ViewRoot,
- // so we don't need to check getRootView() for null here?
- ViewRoot root = (ViewRoot)(getRootView().getParent());
+ ViewRoot root = getViewRoot();
// Dispatch down the view hierarchy
switch (event.mAction) {
@@ -853,10 +877,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
- final boolean handled = children[i].dispatchDragEvent(event);
- children[i].mCanAcceptDrop = handled;
- if (handled) {
- mChildAcceptsDrag = true;
+ final View child = children[i];
+ if (child.getVisibility() == VISIBLE) {
+ final boolean handled = children[i].dispatchDragEvent(event);
+ children[i].mCanAcceptDrop = handled;
+ if (handled) {
+ mChildAcceptsDrag = true;
+ }
}
}
@@ -871,7 +898,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
- children[i].dispatchDragEvent(event);
+ final View child = children[i];
+ if (child.getVisibility() == VISIBLE) {
+ child.dispatchDragEvent(event);
+ }
}
// We consider drag-ended to have been handled if one of our children
// had offered to handle the drag.
@@ -891,7 +921,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
root.setDragFocus(event, target);
mCurrentDragView = target;
}
-
+
// Dispatch the actual drag location notice, localized into its coordinates
if (target != null) {
event.mX = mLocalPoint.x;
@@ -905,14 +935,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
} break;
case DragEvent.ACTION_DROP: {
- if (View.DEBUG_DRAG) Slog.d(TAG, "Drop event: " + event);
+ if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, "Drop event: " + event);
View target = findFrontmostDroppableChildAt(event.mX, event.mY, mLocalPoint);
if (target != null) {
+ if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, " dispatch drop to " + target);
event.mX = mLocalPoint.x;
event.mY = mLocalPoint.y;
retval = target.dispatchDragEvent(event);
event.mX = tx;
event.mY = ty;
+ } else {
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(View.VIEW_LOG_TAG, " not dropped on an accepting view");
+ }
}
} break;
}
@@ -927,30 +962,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Find the frontmost child view that lies under the given point, and calculate
// the position within its own local coordinate system.
View findFrontmostDroppableChildAt(float x, float y, PointF outLocalPoint) {
- final float scrolledX = x + mScrollX;
- final float scrolledY = y + mScrollY;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
- if (child.mCanAcceptDrop == false) {
+ if (!child.mCanAcceptDrop) {
continue;
}
- float localX = scrolledX - child.mLeft;
- float localY = scrolledY - child.mTop;
- if (!child.hasIdentityMatrix() && mAttachInfo != null) {
- // non-identity matrix: transform the point into the view's coordinates
- final float[] localXY = mAttachInfo.mTmpTransformLocation;
- localXY[0] = localX;
- localXY[1] = localY;
- child.getInverseMatrix().mapPoints(localXY);
- localX = localXY[0];
- localY = localXY[1];
- }
- if (localX >= 0 && localY >= 0 && localX < (child.mRight - child.mLeft) &&
- localY < (child.mBottom - child.mTop)) {
- outLocalPoint.set(localX, localY);
+ if (isTransformedTouchPointInView(x, y, child, outLocalPoint)) {
return child;
}
}
@@ -1078,7 +1098,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
continue;
}
- if (!isTransformedTouchPointInView(x, y, child)) {
+ if (!isTransformedTouchPointInView(x, y, child, null)) {
// New pointer is out of child's bounds.
continue;
}
@@ -1162,16 +1182,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return handled;
}
- /* Resets all touch state in preparation for a new cycle. */
- private final void resetTouchState() {
+ /**
+ * Resets all touch state in preparation for a new cycle.
+ */
+ private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
- /* Resets the cancel next up flag.
- * Returns true if the flag was previously set. */
- private final boolean resetCancelNextUpFlag(View view) {
+ /**
+ * Resets the cancel next up flag.
+ * Returns true if the flag was previously set.
+ */
+ private boolean resetCancelNextUpFlag(View view) {
if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
return true;
@@ -1179,8 +1203,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return false;
}
- /* Clears all touch targets. */
- private final void clearTouchTargets() {
+ /**
+ * Clears all touch targets.
+ */
+ private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
@@ -1192,8 +1218,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
- /* Cancels and clears all touch targets. */
- private final void cancelAndClearTouchTargets(MotionEvent event) {
+ /**
+ * Cancels and clears all touch targets.
+ */
+ private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
@@ -1215,9 +1243,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
- /* Gets the touch target for specified child view.
- * Returns null if not found. */
- private final TouchTarget getTouchTarget(View child) {
+ /**
+ * Gets the touch target for specified child view.
+ * Returns null if not found.
+ */
+ private TouchTarget getTouchTarget(View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
@@ -1226,17 +1256,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return null;
}
- /* Adds a touch target for specified child to the beginning of the list.
- * Assumes the target child is not already present. */
- private final TouchTarget addTouchTarget(View child, int pointerIdBits) {
+ /**
+ * Adds a touch target for specified child to the beginning of the list.
+ * Assumes the target child is not already present.
+ */
+ private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
- /* Removes the pointer ids from consideration. */
- private final void removePointersFromTouchTargets(int pointerIdBits) {
+ /**
+ * Removes the pointer ids from consideration.
+ */
+ private void removePointersFromTouchTargets(int pointerIdBits) {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
@@ -1259,10 +1293,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
- /* Returns true if a child view contains the specified point when transformed
+ /**
+ * Returns true if a child view contains the specified point when transformed
* into its coordinate space.
- * Child must not be null. */
- private final boolean isTransformedTouchPointInView(float x, float y, View child) {
+ * Child must not be null.
+ */
+ private boolean isTransformedTouchPointInView(float x, float y, View child,
+ PointF outLocalPoint) {
float localX = x + mScrollX - child.mLeft;
float localY = y + mScrollY - child.mTop;
if (! child.hasIdentityMatrix() && mAttachInfo != null) {
@@ -1273,13 +1310,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
localX = localXY[0];
localY = localXY[1];
}
- return child.pointInView(localX, localY);
+ final boolean isInView = child.pointInView(localX, localY);
+ if (isInView && outLocalPoint != null) {
+ outLocalPoint.set(localX, localY);
+ }
+ return isInView;
}
- /* Transforms a motion event into the coordinate space of a particular child view,
+ /**
+ * Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
- * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */
- private final boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
+ * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
+ */
+ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
@@ -1454,9 +1497,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return handled;
}
- /* Enlarge the temporary pointer arrays for splitting pointers.
- * May discard contents (but keeps PointerCoords objects to avoid reallocating them). */
- private final void growTmpPointerArrays(int desiredCapacity) {
+ /**
+ * Enlarge the temporary pointer arrays for splitting pointers.
+ * May discard contents (but keeps PointerCoords objects to avoid reallocating them).
+ */
+ private void growTmpPointerArrays(int desiredCapacity) {
final MotionEvent.PointerCoords[] oldTmpPointerCoords = mTmpPointerCoords;
int capacity;
if (oldTmpPointerCoords != null) {
@@ -2152,7 +2197,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (!canvas.isHardwareAccelerated()) {
cache = child.getDrawingCache(true);
} else {
- displayList = child.getDisplayList();
+ // TODO: bring back
+ // displayList = child.getDisplayList();
}
}
@@ -2598,7 +2644,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
if (mTransition != null) {
- mTransition.childAdd(this, child);
+ mTransition.addChild(this, child);
}
if (!checkLayoutParams(params)) {
@@ -2817,7 +2863,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private void removeViewInternal(int index, View view) {
if (mTransition != null) {
- mTransition.childRemove(this, view);
+ mTransition.removeChild(this, view);
}
boolean clearChildFocus = false;
@@ -2893,7 +2939,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View view = children[i];
if (mTransition != null) {
- mTransition.childRemove(this, view);
+ mTransition.removeChild(this, view);
}
if (view == focused) {
@@ -2962,7 +3008,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View view = children[i];
if (mTransition != null) {
- mTransition.childRemove(this, view);
+ mTransition.removeChild(this, view);
}
if (view == focused) {
@@ -3005,7 +3051,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
protected void removeDetachedView(View child, boolean animate) {
if (mTransition != null) {
- mTransition.childRemove(this, child);
+ mTransition.removeChild(this, child);
}
if (child == mFocused) {
@@ -4025,11 +4071,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final ArrayList<View> disappearingChildren = mDisappearingChildren;
if (disappearingChildren != null && disappearingChildren.contains(view)) {
disappearingChildren.remove(view);
- if (view.mAttachInfo != null) {
- view.dispatchDetachedFromWindow();
- }
- if (view.mParent != null) {
- view.mParent = null;
+ if (mVisibilityChangingChildren != null &&
+ mVisibilityChangingChildren.contains(view)) {
+ mVisibilityChangingChildren.remove(view);
+ } else {
+ if (view.mAttachInfo != null) {
+ view.dispatchDetachedFromWindow();
+ }
+ if (view.mParent != null) {
+ view.mParent = null;
+ }
}
mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index c63f7d6..79f1f5b 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -16,24 +16,41 @@
package android.view;
-import com.android.internal.view.BaseSurfaceHolder;
-import com.android.internal.view.IInputMethodCallback;
-import com.android.internal.view.IInputMethodSession;
-import com.android.internal.view.RootViewSurfaceTaker;
-
+import android.Manifest;
+import android.app.ActivityManagerNative;
+import android.content.ClipDescription;
+import android.content.ComponentCallbacks;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
+import android.graphics.Point;
import android.graphics.PointF;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
-import android.os.*;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.LatencyTimer;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
import android.util.Config;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.EventLog;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.View.MeasureSpec;
@@ -42,21 +59,14 @@ import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
-import android.content.pm.PackageManager;
-import android.content.res.CompatibilityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ComponentCallbacks;
-import android.content.Context;
-import android.app.ActivityManagerNative;
-import android.Manifest;
-import android.media.AudioManager;
+import com.android.internal.view.BaseSurfaceHolder;
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.RootViewSurfaceTaker;
-import java.lang.ref.WeakReference;
import java.io.IOException;
import java.io.OutputStream;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
@@ -205,6 +215,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
ClipDescription mDragDescription;
View mCurrentDragView;
final PointF mDragPoint = new PointF();
+ final PointF mLastTouchPoint = new PointF();
/**
* see {@link #playSoundEffect(int)}
@@ -2024,6 +2035,9 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
if (MEASURE_LATENCY) {
lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
}
+ // cache for possible drag-initiation
+ mLastTouchPoint.x = event.getRawX();
+ mLastTouchPoint.y = event.getRawY();
handled = mView.dispatchTouchEvent(event);
if (MEASURE_LATENCY) {
lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano());
@@ -2509,6 +2523,11 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn
event.recycle();
}
+ public void getLastTouchPoint(Point outLocation) {
+ outLocation.x = (int) mLastTouchPoint.x;
+ outLocation.y = (int) mLastTouchPoint.y;
+ }
+
public void setDragFocus(DragEvent event, View newDragTarget) {
final int action = event.mAction;
// If we've dragged off of a view, send it the EXITED message
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 8ffa158..3e2e92b 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -28,6 +28,11 @@ import android.util.Log;
* A special helper class used by the WindowManager
* for receiving notifications from the SensorManager when
* the orientation of the device has changed.
+ *
+ * NOTE: If changing anything here, please run the API demo
+ * "App/Activity/Screen Orientation" to ensure that all orientation
+ * modes still work correctly.
+ *
* @hide
*/
public abstract class WindowOrientationListener {
@@ -103,6 +108,10 @@ public abstract class WindowOrientationListener {
}
}
+ public void setAllow180Rotation(boolean allowed) {
+ mSensorEventListener.setAllow180Rotation(allowed);
+ }
+
public int getCurrentRotation(int lastRotation) {
if (mEnabled) {
return mSensorEventListener.getCurrentRotation(lastRotation);
@@ -151,19 +160,20 @@ public abstract class WindowOrientationListener {
private static final int ROTATION_0 = 0;
private static final int ROTATION_90 = 1;
private static final int ROTATION_270 = 2;
+ private static final int ROTATION_180 = 3;
// Mapping our internal aliases into actual Surface rotation values
private static final int[] INTERNAL_TO_SURFACE_ROTATION = new int[] {
- Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270};
+ Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270,
+ Surface.ROTATION_180};
// Mapping Surface rotation values to internal aliases.
- // We have no constant for Surface.ROTATION_180. That should never happen, but if it
- // does, we'll arbitrarily choose a mapping.
private static final int[] SURFACE_TO_INTERNAL_ROTATION = new int[] {
- ROTATION_0, ROTATION_90, ROTATION_90, ROTATION_270};
+ ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270};
// Threshold ranges of orientation angle to transition into other orientation states.
- // The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc.
+ // The first list is for transitions from ROTATION_0, ROTATION_90, ROTATION_270,
+ // and then ROTATION_180.
// ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
// in sync with this.
// We generally transition about the halfway point between two states with a swing of 30
@@ -172,13 +182,32 @@ public abstract class WindowOrientationListener {
{{60, 180}, {180, 300}},
{{0, 30}, {195, 315}, {315, 360}},
{{0, 45}, {45, 165}, {330, 360}},
- };
+ // Handle situation where we are currently doing 180 rotation
+ // but that is no longer allowed.
+ {{0, 45}, {45, 135}, {135, 225}, {225, 315}, {315, 360}},
+ };
// See THRESHOLDS
private static final int[][] ROTATE_TO = new int[][] {
{ROTATION_90, ROTATION_270},
{ROTATION_0, ROTATION_270, ROTATION_0},
{ROTATION_0, ROTATION_90, ROTATION_0},
+ {ROTATION_0, ROTATION_90, ROTATION_0, ROTATION_270, ROTATION_0},
+ };
+
+ // Thresholds that allow all 4 orientations.
+ private static final int[][][] THRESHOLDS_WITH_180 = new int[][][] {
+ {{60, 165}, {165, 195}, {195, 300}},
+ {{0, 30}, {165, 195}, {195, 315}, {315, 360}},
+ {{0, 45}, {45, 165}, {165, 195}, {330, 360}},
+ {{0, 45}, {45, 135}, {225, 315}, {315, 360}},
+ };
+ // See THRESHOLDS_WITH_180
+ private static final int[][] ROTATE_TO_WITH_180 = new int[][] {
+ {ROTATION_90, ROTATION_180, ROTATION_270},
+ {ROTATION_0, ROTATION_180, ROTATION_90, ROTATION_0},
+ {ROTATION_0, ROTATION_270, ROTATION_180, ROTATION_0},
+ {ROTATION_0, ROTATION_90, ROTATION_270, ROTATION_0},
};
// Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
@@ -188,7 +217,7 @@ public abstract class WindowOrientationListener {
// Additional limits on tilt angle to transition to each new orientation. We ignore all
// data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a
// particular orientation here.
- private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65};
+ private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65, 40};
// Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
// with a higher time constant, making us less sensitive to change. This primarily helps
@@ -228,6 +257,8 @@ public abstract class WindowOrientationListener {
private static final float ACCELERATING_LOWPASS_ALPHA =
computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS);
+ private boolean mAllow180Rotation = false;
+
private WindowOrientationListener mOrientationListener;
private int mRotation = ROTATION_0; // Current orientation state
private float mTiltAngle = 0; // low-pass filtered
@@ -249,6 +280,10 @@ public abstract class WindowOrientationListener {
return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS);
}
+ void setAllow180Rotation(boolean allowed) {
+ mAllow180Rotation = allowed;
+ }
+
int getCurrentRotation(int lastRotation) {
if (mTiltDistrust > 0) {
// we really don't know the current orientation, so trust what's currently displayed
@@ -259,7 +294,9 @@ public abstract class WindowOrientationListener {
private void calculateNewRotation(float orientation, float tiltAngle) {
if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
- int thresholdRanges[][] = THRESHOLDS[mRotation];
+ final boolean allow180Rotation = mAllow180Rotation;
+ int thresholdRanges[][] = allow180Rotation
+ ? THRESHOLDS_WITH_180[mRotation] : THRESHOLDS[mRotation];
int row = -1;
for (int i = 0; i < thresholdRanges.length; i++) {
if (orientation >= thresholdRanges[i][0] && orientation < thresholdRanges[i][1]) {
@@ -269,13 +306,15 @@ public abstract class WindowOrientationListener {
}
if (row == -1) return; // no matching transition
- int rotation = ROTATE_TO[mRotation][row];
+ int rotation = allow180Rotation
+ ? ROTATE_TO_WITH_180[mRotation][row] : ROTATE_TO[mRotation][row];
if (tiltAngle > MAX_TRANSITION_TILT[rotation]) {
// tilted too far flat to go to this rotation
return;
}
- if (localLOGV) Log.i(TAG, " new rotation = " + rotation);
+ if (localLOGV) Log.i(TAG, "orientation " + orientation + " gives new rotation = "
+ + rotation);
mRotation = rotation;
mOrientationListener.onOrientationChanged(INTERNAL_TO_SURFACE_ROTATION[mRotation]);
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8bd3298..8e355d6 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1401,6 +1401,16 @@ public final class InputMethodManager {
}
}
+ public void showInputMethodAndSubtypeEnabler(String topId) {
+ synchronized (mH) {
+ try {
+ mService.showInputMethodAndSubtypeEnablerFromClient(mClient, topId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+
void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
final Printer p = new PrintWriterPrinter(fout);
p.println("Input method client state for " + this + ":");
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
index 4ff849e..9b866d3 100755
--- a/core/java/android/webkit/DeviceOrientationService.java
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -41,6 +41,7 @@ final class DeviceOrientationService implements SensorEventListener {
private Double mAlpha;
private Double mBeta;
private Double mGamma;
+ private boolean mHaveSentErrorEvent;
private static final double DELTA_DEGRESS = 1.0;
@@ -75,11 +76,16 @@ final class DeviceOrientationService implements SensorEventListener {
private void sendErrorEvent() {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+ // The spec requires that each listener receives the error event only once.
+ if (mHaveSentErrorEvent)
+ return;
+ mHaveSentErrorEvent = true;
mHandler.post(new Runnable() {
@Override
public void run() {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
if (mIsRunning) {
+ // The special case of all nulls is used to signify a failure to get data.
mManager.onOrientationChange(null, null, null);
}
}
@@ -169,6 +175,8 @@ final class DeviceOrientationService implements SensorEventListener {
mBeta = beta;
mGamma = gamma;
mManager.onOrientationChange(mAlpha, mBeta, mGamma);
+ // Now that we have successfully sent some data, reset whether we've sent an error.
+ mHaveSentErrorEvent = false;
}
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 20aafbd..7b9af5b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1081,6 +1081,7 @@ public class WebSettings {
if (mBlockNetworkLoads != flag) {
mBlockNetworkLoads = flag;
verifyNetworkAccess();
+ postSync();
}
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 7462668..7c089d8 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -22,6 +22,7 @@ import android.database.Cursor;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.media.MediaFile;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -252,6 +253,13 @@ final class WebViewCore {
return mSettings;
}
+ /*
+ * Given mimeType, check whether it's supported in Android media framework.
+ * mimeType could be such as "audio/ogg" and "video/mp4".
+ */
+ /* package */ static boolean supportsMimeType(String mimeType) {
+ return MediaFile.getFileTypeForMimeType(mimeType) > 0;
+ }
/**
* Add an error message to the client's console.
* @param message The message to add
diff --git a/core/java/android/widget/Adapter.java b/core/java/android/widget/Adapter.java
index f2b3e2a..9b6c5a4 100644
--- a/core/java/android/widget/Adapter.java
+++ b/core/java/android/widget/Adapter.java
@@ -72,7 +72,7 @@ public interface Adapter {
long getItemId(int position);
/**
- * Indicated whether the item ids are stable across changes to the
+ * Indicates whether the item ids are stable across changes to the
* underlying data.
*
* @return True if the same id always refers to the same object.
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index ab75420..f5afb94 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -172,7 +172,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
int mItemCount;
/**
- * The number of items in the adapter before a data changed event occured.
+ * The number of items in the adapter before a data changed event occurred.
*/
int mOldItemCount;
@@ -557,7 +557,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
/**
* @return The number of items owned by the Adapter associated with this
* AdapterView. (This is the number of data items, which may be
- * larger than the number of visible view.)
+ * larger than the number of visible views.)
*/
@ViewDebug.CapturedViewProperty
public int getCount() {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 9983d54..b7b1a23 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -19,6 +19,7 @@ package android.widget;
import java.util.ArrayList;
import java.util.HashMap;
+import android.animation.AnimatorInflater;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
@@ -135,12 +136,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
int mReferenceChildHeight = -1;
/**
- * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
+ * In and out animations.
*/
- Animation mInAnimation;
- Animation mOutAnimation;
+ ObjectAnimator<?> mInAnimation;
+ ObjectAnimator<?> mOutAnimation;
+
private ArrayList<View> mViewsToBringToFront;
+ private static final int DEFAULT_ANIMATION_DURATION = 200;
+
public AdapterViewAnimator(Context context) {
super(context);
initViewAnimator();
@@ -155,11 +159,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
if (resource > 0) {
setInAnimation(context, resource);
+ } else {
+ setInAnimation(getDefaultInAnimation());
}
resource = a.getResourceId(com.android.internal.R.styleable.AdapterViewAnimator_outAnimation, 0);
if (resource > 0) {
setOutAnimation(context, resource);
+ } else {
+ setOutAnimation(getDefaultOutAnimation());
}
boolean flag = a.getBoolean(
@@ -229,17 +237,23 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* @param view The view that is being animated
*/
void animateViewForTransition(int fromIndex, int toIndex, View view) {
- ObjectAnimator pa;
if (fromIndex == -1) {
- view.setAlpha(0.0f);
- pa = new ObjectAnimator(400, view, "alpha", 0.0f, 1.0f);
- pa.start();
+ mInAnimation.setTarget(view);
+ mInAnimation.start();
} else if (toIndex == -1) {
- pa = new ObjectAnimator(400, view, "alpha", 1.0f, 0.0f);
- pa.start();
+ mOutAnimation.setTarget(view);
+ mOutAnimation.start();
}
}
+ ObjectAnimator<?> getDefaultInAnimation() {
+ return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 0.0f, 1.0f);
+ }
+
+ ObjectAnimator<?> getDefaultOutAnimation() {
+ return new ObjectAnimator<Float>(DEFAULT_ANIMATION_DURATION, null, "alpha", 1.0f, 0.0f);
+ }
+
/**
* Sets which child view will be displayed.
*
@@ -265,20 +279,6 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
/**
- * Return default inAnimation. To be overriden by subclasses.
- */
- Animation getDefaultInAnimation() {
- return null;
- }
-
- /**
- * Return default outAnimation. To be overridden by subclasses.
- */
- Animation getDefaultOutAnimation() {
- return null;
- }
-
- /**
* To be overridden by subclasses. This method applies a view / index specific
* transform to the child view.
*
@@ -324,7 +324,11 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
private int modulo(int pos, int size) {
- return (size + (pos % size)) % size;
+ if (size > 0) {
+ return (size + (pos % size)) % size;
+ } else {
+ return 0;
+ }
}
/**
@@ -383,6 +387,8 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
void showOnly(int childIndex, boolean animate, boolean onLayout) {
if (mAdapter == null) return;
+ final int adapterCount = mAdapter.getCount();
+ if (adapterCount == 0) return;
for (int i = 0; i < mPreviousViews.size(); i++) {
View viewToRemove = mViewsMap.get(mPreviousViews.get(i)).view;
@@ -399,7 +405,6 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
removeViewInLayout(viewToRemove);
}
mPreviousViews.clear();
- int adapterCount = mAdapter.getCount();
int newWindowStartUnbounded = childIndex - mActiveOffset;
int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
int newWindowStart = Math.max(0, newWindowStartUnbounded);
@@ -690,7 +695,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* @see #setInAnimation(android.view.animation.Animation)
* @see #setInAnimation(android.content.Context, int)
*/
- public Animation getInAnimation() {
+ public ObjectAnimator<?> getInAnimation() {
return mInAnimation;
}
@@ -702,7 +707,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* @see #getInAnimation()
* @see #setInAnimation(android.content.Context, int)
*/
- public void setInAnimation(Animation inAnimation) {
+ public void setInAnimation(ObjectAnimator<?> inAnimation) {
mInAnimation = inAnimation;
}
@@ -714,7 +719,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* @see #setOutAnimation(android.view.animation.Animation)
* @see #setOutAnimation(android.content.Context, int)
*/
- public Animation getOutAnimation() {
+ public ObjectAnimator<?> getOutAnimation() {
return mOutAnimation;
}
@@ -726,7 +731,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* @see #getOutAnimation()
* @see #setOutAnimation(android.content.Context, int)
*/
- public void setOutAnimation(Animation outAnimation) {
+ public void setOutAnimation(ObjectAnimator<?> outAnimation) {
mOutAnimation = outAnimation;
}
@@ -740,7 +745,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* @see #setInAnimation(android.view.animation.Animation)
*/
public void setInAnimation(Context context, int resourceID) {
- setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
+ setInAnimation((ObjectAnimator<?>) AnimatorInflater.loadAnimator(context, resourceID));
}
/**
@@ -753,7 +758,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
* @see #setOutAnimation(android.view.animation.Animation)
*/
public void setOutAnimation(Context context, int resourceID) {
- setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
+ setOutAnimation((ObjectAnimator<?>) AnimatorInflater.loadAnimator(context, resourceID));
}
/**
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 95ebdd3..b09ade7 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.animation.ObjectAnimator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -43,10 +44,8 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
private static final boolean LOGD = false;
private static final int DEFAULT_INTERVAL = 10000;
- private static final int DEFAULT_ANIMATION_DURATION = 200;
private int mFlipInterval = DEFAULT_INTERVAL;
- private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
private boolean mAutoStart = false;
private boolean mRunning = false;
@@ -56,7 +55,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
public AdapterViewFlipper(Context context) {
super(context);
- initDefaultAnimations();
}
public AdapterViewFlipper(Context context, AttributeSet attrs) {
@@ -74,19 +72,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
com.android.internal.R.styleable.AdapterViewAnimator_loopViews, true);
a.recycle();
- initDefaultAnimations();
- }
-
- private void initDefaultAnimations() {
- // Set the default animations to be fade in/out
- if (mInAnimation == null) {
- mInAnimation = new AlphaAnimation(0.0f, 1.0f);
- mInAnimation.setDuration(mAnimationDuration);
- }
- if (mOutAnimation == null) {
- mOutAnimation = new AlphaAnimation(1.0f, 0.0f);
- mOutAnimation.setDuration(mAnimationDuration);
- }
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 8bd797b..3d21048 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -185,7 +185,6 @@ public class ExpandableListView extends ListView {
/** Drawable to be used as a divider when it is adjacent to any children */
private Drawable mChildDivider;
- private boolean mClipChildDivider;
// Bounds of the indicator to be drawn
private final Rect mIndicatorRect = new Rect();
@@ -379,7 +378,6 @@ public class ExpandableListView extends ListView {
*/
public void setChildDivider(Drawable childDivider) {
mChildDivider = childDivider;
- mClipChildDivider = childDivider != null && childDivider instanceof ColorDrawable;
}
@Override
@@ -396,17 +394,8 @@ public class ExpandableListView extends ListView {
pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
// These are the cases where we draw the child divider
final Drawable divider = mChildDivider;
- final boolean clip = mClipChildDivider;
- if (!clip) {
- divider.setBounds(bounds);
- } else {
- canvas.save();
- canvas.clipRect(bounds);
- }
+ divider.setBounds(bounds);
divider.draw(canvas);
- if (clip) {
- canvas.restore();
- }
pos.recycle();
return;
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index e5a34e8..7c4897a 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -107,7 +107,6 @@ public class ListView extends AbsListView {
private boolean mIsCacheColorOpaque;
private boolean mDividerIsOpaque;
- private boolean mClipDivider;
private boolean mHeaderDividersEnabled;
private boolean mFooterDividersEnabled;
@@ -3057,20 +3056,9 @@ public class ListView extends AbsListView {
void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
// This widget draws the same divider for all children
final Drawable divider = mDivider;
- final boolean clipDivider = mClipDivider;
-
- if (!clipDivider) {
- divider.setBounds(bounds);
- } else {
- canvas.save();
- canvas.clipRect(bounds);
- }
+ divider.setBounds(bounds);
divider.draw(canvas);
-
- if (clipDivider) {
- canvas.restore();
- }
}
/**
@@ -3091,10 +3079,8 @@ public class ListView extends AbsListView {
public void setDivider(Drawable divider) {
if (divider != null) {
mDividerHeight = divider.getIntrinsicHeight();
- mClipDivider = divider instanceof ColorDrawable;
} else {
mDividerHeight = 0;
- mClipDivider = false;
}
mDivider = divider;
mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE;
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 6799ffd..d6c2db1 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -178,7 +178,10 @@ public class PopupWindow {
attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
- mAnimationStyle = a.getResourceId(R.styleable.PopupWindow_windowAnimationStyle, -1);
+
+ final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
+ mAnimationStyle = animStyle == com.android.internal.R.style.Animation_PopupWindow ? -1 :
+ animStyle;
// If this is a StateListDrawable, try to find and store the drawable to be
// used when the drop-down is placed above its anchor view, and the one to be
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 839de7d..f0954e2 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -749,7 +749,9 @@ public class StackView extends AdapterViewAnimator {
if (mAdapter != null && mWhichChild == -1) {
mWhichChild = mAdapter.getCount() - 1;
}
- setDisplayedChild(mWhichChild);
+ if (mWhichChild >= 0) {
+ setDisplayedChild(mWhichChild);
+ }
}
LayoutParams createOrReuseLayoutParams(View v) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0c50742..d63af4e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -64,7 +64,6 @@ import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.text.method.ArrowKeyMovementMethod;
import android.text.method.DateKeyListener;
import android.text.method.DateTimeKeyListener;
import android.text.method.DialerKeyListener;
@@ -98,6 +97,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewParent;
import android.view.ViewRoot;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
@@ -199,7 +199,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mCurrentAlpha = 255;
- private final int[] mTempCoords = new int[2];
+ final int[] mTempCoords = new int[2];
+ Rect mTempRect;
private ColorStateList mTextColor;
private int mCurTextColor;
@@ -1160,7 +1161,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
fixFocusableAndClickableSettings();
- // SelectionModifierCursorController depends on canSelectText, which depends on mMovement
+ // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement
prepareCursorControllers();
}
@@ -2730,7 +2731,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sendAfterTextChanged((Editable) text);
}
- // SelectionModifierCursorController depends on canSelectText, which depends on text
+ // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
prepareCursorControllers();
}
@@ -6667,6 +6668,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else {
terminateSelectionActionMode();
}
+
+ mLastTouchOffset = -1;
}
startStopMarquee(focused);
@@ -6899,7 +6902,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mInsertionPointCursorController = null;
}
- if (canSelectText() && mLayout != null) {
+ if (textCanBeSelected() && mLayout != null) {
if (mSelectionModifierCursorController == null) {
mSelectionModifierCursorController = new SelectionModifierCursorController();
}
@@ -7119,7 +7122,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_A:
- if (canSelectAll()) {
+ if (canSelectText()) {
return onTextContextMenuItem(ID_SELECT_ALL);
}
@@ -7150,11 +7153,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.onKeyShortcut(keyCode, event);
}
- private boolean canSelectAll() {
- return canSelectText() && mText.length() != 0;
+ private boolean canSelectText() {
+ return textCanBeSelected() && mText.length() != 0;
}
- private boolean canSelectText() {
+ private boolean textCanBeSelected() {
// prepareCursorController() relies on this method.
// If you change this condition, make sure prepareCursorController is called anywhere
// the value of this condition might be changed.
@@ -7624,7 +7627,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean atLeastOne = false;
- if (canSelectAll()) {
+ if (canSelectText()) {
menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
setIcon(com.android.internal.R.drawable.ic_menu_select_all).
setAlphabeticShortcut('a');
@@ -7791,6 +7794,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private float mOffsetY;
private float mHotspotX;
private float mHotspotY;
+ private int mLastParentX;
+ private int mLastParentY;
public HandleView(CursorController controller, Drawable handle) {
super(TextView.this.mContext);
@@ -7800,8 +7805,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
com.android.internal.R.attr.textSelectHandleWindowStyle);
mContainer.setSplitTouchEnabled(true);
mContainer.setClippingEnabled(false);
- mHotspotX = mDrawable.getIntrinsicWidth() * 0.5f;
- mHotspotY = -mDrawable.getIntrinsicHeight() * 0.2f;
+
+ final int handleWidth = mDrawable.getIntrinsicWidth();
+ final int handleHeight = mDrawable.getIntrinsicHeight();
+ mHotspotX = handleWidth * 0.5f;
+ mHotspotY = -handleHeight * 0.2f;
}
@Override
@@ -7811,7 +7819,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
public void show() {
- if (!isPositionInBounds()) {
+ if (!isPositionVisible()) {
hide();
return;
}
@@ -7832,7 +7840,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mContainer.isShowing();
}
- private boolean isPositionInBounds() {
+ private boolean isPositionVisible() {
+ // Always show a dragging handle.
+ if (mIsDragging) {
+ return true;
+ }
+
final int extendedPaddingTop = getExtendedPaddingTop();
final int extendedPaddingBottom = getExtendedPaddingBottom();
final int compoundPaddingLeft = getCompoundPaddingLeft();
@@ -7844,28 +7857,55 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int top = 0;
final int bottom = hostView.getHeight();
- final int clipLeft = left + compoundPaddingLeft;
- final int clipTop = top + extendedPaddingTop;
- final int clipRight = right - compoundPaddingRight;
- final int clipBottom = bottom - extendedPaddingBottom;
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ final Rect clip = mTempRect;
+ clip.left = left + compoundPaddingLeft;
+ clip.top = top + extendedPaddingTop;
+ clip.right = right - compoundPaddingRight;
+ clip.bottom = bottom - extendedPaddingBottom;
- return mPositionX + mHotspotX >= clipLeft && mPositionX + mHotspotX <= clipRight &&
- mPositionY + mHotspotY >= clipTop && mPositionY + mHotspotY <= clipBottom;
+ final ViewParent parent = hostView.getParent();
+ if (parent == null || !parent.getChildVisibleRect(hostView, clip, null)) {
+ return false;
+ }
+
+ final int[] coords = mTempCoords;
+ hostView.getLocationInWindow(coords);
+ final int posX = coords[0] + mPositionX + (int) mHotspotX;
+ final int posY = coords[1] + mPositionY;
+
+ return posX >= clip.left && posX <= clip.right &&
+ posY >= clip.top && posY + mHotspotY <= clip.bottom;
}
private void moveTo(int x, int y) {
mPositionX = x - TextView.this.mScrollX;
mPositionY = y - TextView.this.mScrollY;
- if (isPositionInBounds()) {
+ if (isPositionVisible()) {
+ int[] coords = null;
if (mContainer.isShowing()){
- final int[] coords = mTempCoords;
+ coords = mTempCoords;
TextView.this.getLocationInWindow(coords);
- coords[0] += mPositionX;
- coords[1] += mPositionY;
- mContainer.update(coords[0], coords[1], mRight - mLeft, mBottom - mTop);
+ mContainer.update(coords[0] + mPositionX, coords[1] + mPositionY,
+ mRight - mLeft, mBottom - mTop);
} else {
show();
}
+
+ if (mIsDragging) {
+ if (coords == null) {
+ coords = mTempCoords;
+ TextView.this.getLocationInWindow(coords);
+ }
+ if (coords[0] != mLastParentX || coords[1] != mLastParentY) {
+ mOffsetX += coords[0] - mLastParentX;
+ mOffsetY += coords[1] - mLastParentY;
+ mLastParentX = coords[0];
+ mLastParentY = coords[1];
+ }
+ }
} else {
hide();
}
@@ -7892,6 +7932,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final float rawY = ev.getRawY();
mOffsetX = rawX - mPositionX;
mOffsetY = rawY - mPositionY;
+ final int[] coords = mTempCoords;
+ TextView.this.getLocationInWindow(coords);
+ mLastParentX = coords[0];
+ mLastParentY = coords[1];
mIsDragging = true;
break;
}
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 4a0617c..fefdcea 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -17,6 +17,9 @@
package com.android.internal.app;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import com.android.internal.R;
+
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -32,10 +35,11 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
-import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckedTextView;
@@ -48,9 +52,6 @@ import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
-
-import com.android.internal.R;
import java.lang.ref.WeakReference;
@@ -403,12 +404,10 @@ public class AlertController {
mIconView = (ImageView) mWindow.findViewById(R.id.icon);
if (hasTextTitle) {
-
/* Display the title if a title is supplied, else hide it */
mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
mTitleView.setText(mTitle);
- mIconView.setImageResource(R.drawable.ic_dialog_menu_generic);
/* Do this last so that if the user has supplied any
* icons we use them instead of the default ones. If the
@@ -689,7 +688,7 @@ public class AlertController {
public final Context mContext;
public final LayoutInflater mInflater;
- public int mIconId = -1;
+ public int mIconId = 0;
public Drawable mIcon;
public CharSequence mTitle;
public View mCustomTitleView;
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index d012b0f..bffec1d 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -49,6 +49,7 @@ interface IInputMethodManager {
void showInputMethodPickerFromClient(in IInputMethodClient client);
void showInputMethodSubtypePickerFromClient(in IInputMethodClient client);
+ void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
void setInputMethod(in IBinder token, String id);
void hideMySoftInput(in IBinder token, int flags);
void showMySoftInput(in IBinder token, int flags);
diff --git a/core/java/com/android/internal/view/InputMethodAndSubtypeEnabler.java b/core/java/com/android/internal/view/InputMethodAndSubtypeEnabler.java
new file mode 100644
index 0000000..200d49f
--- /dev/null
+++ b/core/java/com/android/internal/view/InputMethodAndSubtypeEnabler.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class InputMethodAndSubtypeEnabler extends PreferenceActivity {
+
+ private boolean mHaveHardKeyboard;
+
+ private List<InputMethodInfo> mInputMethodProperties;
+
+ private final TextUtils.SimpleStringSplitter mStringColonSplitter
+ = new TextUtils.SimpleStringSplitter(':');
+
+ private String mLastInputMethodId;
+ private String mLastTickedInputMethodId;
+
+ private AlertDialog mDialog = null;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ Configuration config = getResources().getConfiguration();
+ mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
+ onCreateIMM();
+ setPreferenceScreen(createPreferenceHierarchy());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ loadInputMethodSubtypeList();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ saveInputMethodSubtypeList();
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(
+ PreferenceScreen preferenceScreen, Preference preference) {
+
+ if (preference instanceof CheckBoxPreference) {
+ final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
+ final String id = chkPref.getKey();
+ // TODO: Check subtype or not here
+ if (chkPref.isChecked()) {
+ InputMethodInfo selImi = null;
+ final int N = mInputMethodProperties.size();
+ for (int i = 0; i < N; i++) {
+ InputMethodInfo imi = mInputMethodProperties.get(i);
+ if (id.equals(imi.getId())) {
+ selImi = imi;
+ if (isSystemIme(imi)) {
+ setSubtypesPreferenceEnabled(id, true);
+ // This is a built-in IME, so no need to warn.
+ mLastTickedInputMethodId = id;
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+ break;
+ }
+ }
+ if (selImi == null) {
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+ chkPref.setChecked(false);
+ if (mDialog == null) {
+ mDialog = (new AlertDialog.Builder(this))
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ chkPref.setChecked(true);
+ setSubtypesPreferenceEnabled(id, true);
+ mLastTickedInputMethodId = id;
+ }
+
+ })
+ .setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+
+ })
+ .create();
+ } else {
+ if (mDialog.isShowing()) {
+ mDialog.dismiss();
+ }
+ }
+ mDialog.setMessage(getResources().getString(
+ com.android.internal.R.string.ime_enabler_security_warning,
+ selImi.getServiceInfo().applicationInfo.loadLabel(getPackageManager())));
+ mDialog.show();
+ } else {
+ if (id.equals(mLastTickedInputMethodId)) {
+ mLastTickedInputMethodId = null;
+ }
+ setSubtypesPreferenceEnabled(id, false);
+ }
+ }
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ }
+
+ private void onCreateIMM() {
+ InputMethodManager imm = (InputMethodManager) getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+
+ // TODO: Change mInputMethodProperties to Map
+ mInputMethodProperties = imm.getInputMethodList();
+
+ mLastInputMethodId = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ }
+
+ private PreferenceScreen createPreferenceHierarchy() {
+ // Root
+ PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this);
+
+ int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size());
+ // TODO: Use iterator.
+ for (int i = 0; i < N; ++i) {
+ PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(this);
+ root.addPreference(keyboardSettingsCategory);
+ InputMethodInfo property = mInputMethodProperties.get(i);
+ String prefKey = property.getId();
+
+ PackageManager pm = getPackageManager();
+ CharSequence label = property.loadLabel(pm);
+ boolean systemIME = isSystemIme(property);
+
+ keyboardSettingsCategory.setTitle(label);
+
+ // Add a check box.
+ // Don't show the toggle if it's the only keyboard in the system, or it's a system IME.
+ if (mHaveHardKeyboard || (N > 1 && !systemIME)) {
+ CheckBoxPreference chkbxPref = new CheckBoxPreference(this);
+ chkbxPref.setKey(prefKey);
+ chkbxPref.setTitle(label);
+ keyboardSettingsCategory.addPreference(chkbxPref);
+ }
+
+ ArrayList<InputMethodSubtype> subtypes = property.getSubtypes();
+ if (subtypes.size() > 0) {
+ PreferenceCategory subtypesCategory = new PreferenceCategory(this);
+ subtypesCategory.setTitle(getResources().getString(
+ com.android.internal.R.string.ime_enabler_subtype_title, label));
+ root.addPreference(subtypesCategory);
+ for (InputMethodSubtype subtype: subtypes) {
+ CharSequence subtypeLabel;
+ int nameResId = subtype.getNameResId();
+ if (nameResId != 0) {
+ subtypeLabel = pm.getText(property.getPackageName(), nameResId,
+ property.getServiceInfo().applicationInfo);
+ } else {
+ int modeResId = subtype.getModeResId();
+ CharSequence language = subtype.getLocale();
+ CharSequence mode = modeResId == 0 ? null
+ : pm.getText(property.getPackageName(), modeResId,
+ property.getServiceInfo().applicationInfo);
+ // TODO: Use more friendly Title and UI
+ subtypeLabel = (mode == null ? "" : mode) + ","
+ + (language == null ? "" : language);
+ }
+ CheckBoxPreference chkbxPref = new CheckBoxPreference(this);
+ chkbxPref.setKey(prefKey + subtype.hashCode());
+ chkbxPref.setTitle(subtypeLabel);
+ chkbxPref.setSummary(label);
+ subtypesCategory.addPreference(chkbxPref);
+ }
+ }
+ }
+ return root;
+ }
+
+ private void loadInputMethodSubtypeList() {
+ final HashSet<String> enabled = new HashSet<String>();
+ String enabledStr = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS);
+ if (enabledStr != null) {
+ final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+ splitter.setString(enabledStr);
+ while (splitter.hasNext()) {
+ enabled.add(splitter.next());
+ }
+ }
+
+ // Update the statuses of the Check Boxes.
+ int N = mInputMethodProperties.size();
+ // TODO: Use iterator.
+ for (int i = 0; i < N; ++i) {
+ final String id = mInputMethodProperties.get(i).getId();
+ CheckBoxPreference pref = (CheckBoxPreference) findPreference(
+ mInputMethodProperties.get(i).getId());
+ if (pref != null) {
+ boolean isEnabled = enabled.contains(id);
+ pref.setChecked(isEnabled);
+ setSubtypesPreferenceEnabled(id, isEnabled);
+ }
+ }
+ mLastTickedInputMethodId = null;
+ }
+
+ private void saveInputMethodSubtypeList() {
+ StringBuilder builder = new StringBuilder();
+ StringBuilder disabledSysImes = new StringBuilder();
+
+ int firstEnabled = -1;
+ int N = mInputMethodProperties.size();
+ for (int i = 0; i < N; ++i) {
+ final InputMethodInfo property = mInputMethodProperties.get(i);
+ final String id = property.getId();
+ CheckBoxPreference pref = (CheckBoxPreference) findPreference(id);
+ boolean currentInputMethod = id.equals(mLastInputMethodId);
+ boolean systemIme = isSystemIme(property);
+ // TODO: Append subtypes by using the separator ";"
+ if (((N == 1 || systemIme) && !mHaveHardKeyboard)
+ || (pref != null && pref.isChecked())) {
+ if (builder.length() > 0) builder.append(':');
+ builder.append(id);
+ if (firstEnabled < 0) {
+ firstEnabled = i;
+ }
+ } else if (currentInputMethod) {
+ mLastInputMethodId = mLastTickedInputMethodId;
+ }
+ // If it's a disabled system ime, add it to the disabled list so that it
+ // doesn't get enabled automatically on any changes to the package list
+ if (pref != null && !pref.isChecked() && systemIme && mHaveHardKeyboard) {
+ if (disabledSysImes.length() > 0) disabledSysImes.append(":");
+ disabledSysImes.append(id);
+ }
+ }
+
+ // If the last input method is unset, set it as the first enabled one.
+ if (TextUtils.isEmpty(mLastInputMethodId)) {
+ if (firstEnabled >= 0) {
+ mLastInputMethodId = mInputMethodProperties.get(firstEnabled).getId();
+ } else {
+ mLastInputMethodId = null;
+ }
+ }
+
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, disabledSysImes.toString());
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD,
+ mLastInputMethodId != null ? mLastInputMethodId : "");
+ }
+
+ private void setSubtypesPreferenceEnabled(String id, boolean enabled) {
+ PreferenceScreen preferenceScreen = getPreferenceScreen();
+ final int N = mInputMethodProperties.size();
+ // TODO: Use iterator.
+ for (int i = 0; i < N; i++) {
+ InputMethodInfo imi = mInputMethodProperties.get(i);
+ if (id.equals(imi.getId())) {
+ for (InputMethodSubtype subtype: imi.getSubtypes()) {
+ preferenceScreen.findPreference(id + subtype.hashCode()).setEnabled(enabled);
+ }
+ }
+ }
+ }
+
+ private boolean isSystemIme(InputMethodInfo property) {
+ return (property.getServiceInfo().applicationInfo.flags
+ & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+}
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
index 05e1ff3..1b68ba9 100644
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -77,7 +77,7 @@ static char *createStr(const char *path, short extra) {
static void sqlLogger(void *databaseName, int iErrCode, const char *zMsg) {
// skip printing this message if it is due to certain types of errors
- if (iErrCode == SQLITE_CONSTRAINT) return;
+ if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT) return;
LOGI("sqlite returned: error code = %d, msg = %s\n", iErrCode, zMsg);
}
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index cb1556b..2001919 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -50,7 +50,20 @@ using namespace uirenderer;
*/
#ifdef USE_OPENGL_RENDERER
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
#define DEBUG_RENDERER 0
+#define PROFILE_RENDERER 0
+
+// Debug
+#if DEBUG_RENDERER
+ #define RENDERER_LOGD(...) LOGD(__VA_ARGS__)
+#else
+ #define RENDERER_LOGD(...)
+#endif
// ----------------------------------------------------------------------------
// Java APIs
@@ -66,7 +79,8 @@ static struct {
// ----------------------------------------------------------------------------
static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject canvas) {
-#if DEBUG_RENDERER
+ RENDERER_LOGD("Create OpenGLRenderer");
+#if PROFILE_RENDERER
return new OpenGLDebugRenderer;
#else
return new OpenGLRenderer;
@@ -75,6 +89,7 @@ static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, job
static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject canvas,
OpenGLRenderer* renderer) {
+ RENDERER_LOGD("Destroy OpenGLRenderer");
delete renderer;
}
@@ -88,8 +103,8 @@ static void android_view_GLES20Canvas_setViewport(JNIEnv* env, jobject canvas,
}
static void android_view_GLES20Canvas_prepare(JNIEnv* env, jobject canvas,
- OpenGLRenderer* renderer) {
- renderer->prepare();
+ OpenGLRenderer* renderer, jboolean opaque) {
+ renderer->prepare(opaque);
}
static void android_view_GLES20Canvas_finish(JNIEnv* env, jobject canvas,
@@ -430,7 +445,7 @@ static JNINativeMethod gMethods[] = {
{ "nCreateRenderer", "()I", (void*) android_view_GLES20Canvas_createRenderer },
{ "nDestroyRenderer", "(I)V", (void*) android_view_GLES20Canvas_destroyRenderer },
{ "nSetViewport", "(III)V", (void*) android_view_GLES20Canvas_setViewport },
- { "nPrepare", "(I)V", (void*) android_view_GLES20Canvas_prepare },
+ { "nPrepare", "(IZ)V", (void*) android_view_GLES20Canvas_prepare },
{ "nFinish", "(I)V", (void*) android_view_GLES20Canvas_finish },
{ "nAcquireContext", "(I)V", (void*) android_view_GLES20Canvas_acquireContext },
{ "nReleaseContext", "(I)V", (void*) android_view_GLES20Canvas_releaseContext },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3a1d76f..f15d0a4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1373,6 +1373,11 @@
android:permission="android.permission.BIND_WALLPAPER">
</service>
+ <activity android:name="com.android.internal.view.InputMethodAndSubtypeEnabler"
+ android:excludeFromRecents="true"
+ android:exported="true">
+ </activity>
+
<receiver android:name="com.android.server.BootReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
diff --git a/core/res/res/anim/fragment_close_enter.xml b/core/res/res/anim/fragment_close_enter.xml
index 53afa2a..7a9a3b9 100644
--- a/core/res/res/anim/fragment_close_enter.xml
+++ b/core/res/res/anim/fragment_close_enter.xml
@@ -19,20 +19,6 @@
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:interpolator="@anim/decelerate_interpolator"
- android:valueFrom="2"
- android:valueTo="1"
- android:valueType="floatType"
- android:propertyName="scaleX"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/decelerate_interpolator"
- android:valueFrom="2"
- android:valueTo="1"
- android:valueType="floatType"
- android:propertyName="scaleY"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/decelerate_interpolator"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
diff --git a/core/res/res/anim/fragment_close_exit.xml b/core/res/res/anim/fragment_close_exit.xml
index 1554a4e..0743577 100644
--- a/core/res/res/anim/fragment_close_exit.xml
+++ b/core/res/res/anim/fragment_close_exit.xml
@@ -20,20 +20,6 @@
<objectAnimator
android:interpolator="@anim/accelerate_interpolator"
android:valueFrom="1"
- android:valueTo=".5"
- android:valueType="floatType"
- android:propertyName="scaleX"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/accelerate_interpolator"
- android:valueFrom="1"
- android:valueTo=".5"
- android:valueType="floatType"
- android:propertyName="scaleY"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/accelerate_interpolator"
- android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:propertyName="alpha"
diff --git a/core/res/res/anim/fragment_next_enter.xml b/core/res/res/anim/fragment_next_enter.xml
new file mode 100644
index 0000000..d2d6ec9
--- /dev/null
+++ b/core/res/res/anim/fragment_next_enter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"
+ android:propertyName="alpha"
+ android:duration="@android:integer/config_longAnimTime"/>
+ <objectAnimator
+ android:valueFrom="50"
+ android:valueTo="0"
+ android:valueType="floatType"
+ android:propertyName="translationY"
+ android:duration="@android:integer/config_longAnimTime"/>
+</set> \ No newline at end of file
diff --git a/core/res/res/anim/fragment_next_exit.xml b/core/res/res/anim/fragment_next_exit.xml
new file mode 100644
index 0000000..fbb82d9
--- /dev/null
+++ b/core/res/res/anim/fragment_next_exit.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType"
+ android:propertyName="alpha"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <objectAnimator
+ android:valueFrom="0"
+ android:valueTo="-50"
+ android:valueType="floatType"
+ android:propertyName="translationY"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+</set> \ No newline at end of file
diff --git a/core/res/res/anim/fragment_open_enter.xml b/core/res/res/anim/fragment_open_enter.xml
index 142f60c..ac60494 100644
--- a/core/res/res/anim/fragment_open_enter.xml
+++ b/core/res/res/anim/fragment_open_enter.xml
@@ -18,20 +18,6 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
- android:interpolator="@anim/decelerate_interpolator"
- android:valueFrom="2"
- android:valueTo="1"
- android:valueType="floatType"
- android:propertyName="scaleX"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/decelerate_interpolator"
- android:valueFrom="2"
- android:valueTo="1"
- android:valueType="floatType"
- android:propertyName="scaleY"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
diff --git a/core/res/res/anim/fragment_open_exit.xml b/core/res/res/anim/fragment_open_exit.xml
index 21260b9..3bf1ad4 100644
--- a/core/res/res/anim/fragment_open_exit.xml
+++ b/core/res/res/anim/fragment_open_exit.xml
@@ -18,20 +18,6 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
- android:interpolator="@anim/accelerate_interpolator"
- android:valueFrom="1"
- android:valueTo="2"
- android:valueType="floatType"
- android:propertyName="scaleX"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
- android:interpolator="@anim/accelerate_interpolator"
- android:valueFrom="1"
- android:valueTo="2"
- android:valueType="floatType"
- android:propertyName="scaleY"
- android:duration="@android:integer/config_mediumAnimTime"/>
- <objectAnimator
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
diff --git a/core/res/res/anim/fragment_prev_enter.xml b/core/res/res/anim/fragment_prev_enter.xml
new file mode 100644
index 0000000..d37afd0
--- /dev/null
+++ b/core/res/res/anim/fragment_prev_enter.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"
+ android:propertyName="alpha"
+ android:duration="@android:integer/config_longAnimTime"/>
+ <objectAnimator
+ android:valueFrom="-50"
+ android:valueTo="0"
+ android:valueType="floatType"
+ android:propertyName="translationY"
+ android:duration="@android:integer/config_longAnimTime"/>
+</set> \ No newline at end of file
diff --git a/core/res/res/anim/fragment_prev_exit.xml b/core/res/res/anim/fragment_prev_exit.xml
new file mode 100644
index 0000000..a445a4d
--- /dev/null
+++ b/core/res/res/anim/fragment_prev_exit.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType"
+ android:propertyName="alpha"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <objectAnimator
+ android:valueFrom="0"
+ android:valueTo="50"
+ android:valueType="floatType"
+ android:propertyName="translationY"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+</set> \ No newline at end of file
diff --git a/core/res/res/drawable-hdpi/ic_dialog_menu_generic.png b/core/res/res/drawable-hdpi/ic_dialog_menu_generic.png
deleted file mode 100644
index ef8a877..0000000
--- a/core/res/res/drawable-hdpi/ic_dialog_menu_generic.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_gray.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_gray.9.png
index 53ed136..abb91cc 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_gray.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_gray.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png
index 6455790..7c4f40e 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_red.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_red.9.png
index 49bb9c1..6dbf925 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_red.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_red.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_yellow.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_yellow.9.png
index b3c4c4c..b05a49f 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_yellow.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_confirm_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
index 00dea6ec..109be42 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png
index 45b1850..2800cab 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_gray.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_gray.9.png
index 35b3529..51cbfa6 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_gray.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_gray.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_green.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_green.9.png
index 720de7f..ca51ccc 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_green.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png
index b3387be..fd98571 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_yellow.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_yellow.9.png
index 7ddfbcc..723815b 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_yellow.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_confirm_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
index 1855e5f..030c9e9 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png
index 844f304..ffc5433 100644
--- a/core/res/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_badge_pressed.9.png b/core/res/res/drawable-hdpi/quickcontact_badge_pressed.9.png
index 0cfd09d..75c8162 100644
--- a/core/res/res/drawable-hdpi/quickcontact_badge_pressed.9.png
+++ b/core/res/res/drawable-hdpi/quickcontact_badge_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png b/core/res/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
index ee030fb..aebfa29 100644
--- a/core/res/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
+++ b/core/res/res/drawable-hdpi/quickcontact_badge_small_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png b/core/res/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
index 7140957..ed416f1 100644
--- a/core/res/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
+++ b/core/res/res/drawable-hdpi/quickcontact_badge_small_unpressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/quickcontact_badge_unpressed.9.png b/core/res/res/drawable-hdpi/quickcontact_badge_unpressed.9.png
index 1eeabf4..d063229 100644
--- a/core/res/res/drawable-hdpi/quickcontact_badge_unpressed.9.png
+++ b/core/res/res/drawable-hdpi/quickcontact_badge_unpressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_100.png b/core/res/res/drawable-hdpi/stat_sys_battery_100.png
index e85f524..e49448d 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_100.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_100.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_40.png b/core/res/res/drawable-hdpi/stat_sys_battery_40.png
index ca70784..441bbfb 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_40.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_40.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_60.png b/core/res/res/drawable-hdpi/stat_sys_battery_60.png
index fee28db..d9467ed 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_60.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_60.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_80.png b/core/res/res/drawable-hdpi/stat_sys_battery_80.png
index 7f4a38f..e3f4805 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_80.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_80.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png
index 2277096..426a66b 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png
index 7a3b19e..21582ca 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png
index 5c23c13..8a94763 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim4.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png
index 321545f..fad0d65 100644
--- a/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png
+++ b/core/res/res/drawable-hdpi/stat_sys_battery_charge_anim5.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim1.png b/core/res/res/drawable-hdpi/stat_sys_download_anim1.png
index 4f47db0..851eff9 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim1.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim2.png b/core/res/res/drawable-hdpi/stat_sys_download_anim2.png
index e50d3c3..2d06bb1 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim2.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim3.png b/core/res/res/drawable-hdpi/stat_sys_download_anim3.png
index 6c1029c..5c0ae7a 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim3.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim4.png b/core/res/res/drawable-hdpi/stat_sys_download_anim4.png
index 693f085..f8d5c8a 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim4.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim4.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_download_anim5.png b/core/res/res/drawable-hdpi/stat_sys_download_anim5.png
index eabe0ef..32b1425 100755
--- a/core/res/res/drawable-hdpi/stat_sys_download_anim5.png
+++ b/core/res/res/drawable-hdpi/stat_sys_download_anim5.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim0.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim0.png
index d72afbe..32b5077 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim0.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim0.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png
index 5de2873..8387b45 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png
index dd0b49f..c9a56a0 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim3.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim3.png
index 8ac29b3..972808a 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim3.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim4.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim4.png
index 26c3e714..d47c761 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim4.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim4.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_upload_anim5.png b/core/res/res/drawable-hdpi/stat_sys_upload_anim5.png
index 431ac6b..7aeafbe 100755
--- a/core/res/res/drawable-hdpi/stat_sys_upload_anim5.png
+++ b/core/res/res/drawable-hdpi/stat_sys_upload_anim5.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/status_bar_close_on.9.png b/core/res/res/drawable-hdpi/status_bar_close_on.9.png
deleted file mode 100644
index f313ffb..0000000
--- a/core/res/res/drawable-hdpi/status_bar_close_on.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_dialog_menu_generic.png b/core/res/res/drawable-mdpi/ic_dialog_menu_generic.png
deleted file mode 100755
index de07bda..0000000
--- a/core/res/res/drawable-mdpi/ic_dialog_menu_generic.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/status_bar_close_on.9.png b/core/res/res/drawable-mdpi/status_bar_close_on.9.png
deleted file mode 100644
index 9cbd9fe..0000000
--- a/core/res/res/drawable-mdpi/status_bar_close_on.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5df95de..33d3eeb 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1088,6 +1088,10 @@
<attr name="fragmentOpenExitAnimation" format="reference" />
<attr name="fragmentCloseEnterAnimation" format="reference" />
<attr name="fragmentCloseExitAnimation" format="reference" />
+ <attr name="fragmentNextEnterAnimation" format="reference" />
+ <attr name="fragmentNextExitAnimation" format="reference" />
+ <attr name="fragmentPrevEnterAnimation" format="reference" />
+ <attr name="fragmentPrevExitAnimation" format="reference" />
</declare-styleable>
<!-- Window animation class attributes. -->
@@ -2363,7 +2367,7 @@
</declare-styleable>
<declare-styleable name="PopupWindow">
<attr name="popupBackground" format="reference|color" />
- <attr name="windowAnimationStyle" />
+ <attr name="popupAnimationStyle" format="reference" />
</declare-styleable>
<declare-styleable name="ViewAnimator">
<!-- Identifier for the animation to use when a view is shown. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8cf65ae..949d960 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -497,10 +497,10 @@
orientation will changed based on how the user rotates the device -->
<enum name="unspecified" value="-1" />
<!-- Would like to have the screen in a landscape orientation: that
- is, with the display wider than it is tall. -->
+ is, with the display wider than it is tall, ignoring sensor data. -->
<enum name="landscape" value="0" />
<!-- Would like to have the screen in a portrait orientation: that
- is, with the display taller than it is wide. -->
+ is, with the display taller than it is wide, ignoring sensor data. -->
<enum name="portrait" value="1" />
<!-- Use the user's current preferred orientation of the handset. -->
<enum name="user" value="2" />
@@ -513,6 +513,24 @@
<!-- Always ignore orientation determined by orientation sensor:
the display will not rotate when the user moves the device. -->
<enum name="nosensor" value="5" />
+ <!-- Would like to have the screen in landscape orientation, but can
+ use the sensor to change which direction the screen is facing. -->
+ <enum name="sensorLandscape" value="6" />
+ <!-- Would like to have the screen in portrait orientation, but can
+ use the sensor to change which direction the screen is facing. -->
+ <enum name="sensorPortait" value="7" />
+ <!-- Would like to have the screen in landscape orientation, turned in
+ the opposite direction from normal landscape. -->
+ <enum name="reverseLandscape" value="8" />
+ <!-- Would like to have the screen in portrait orientation, turned in
+ the opposite direction from normal portrait. -->
+ <enum name="reversePortait" value="9" />
+ <!-- Orientation is determined by a physical orientation sensor:
+ the display will rotate based on how the user moves the device.
+ This allows any of the 4 possible rotations, regardless of what
+ the device will normally do (for example some devices won't
+ normally use 180 degree rotation). -->
+ <enum name="fullSensor" value="10" />
</attr>
<!-- Specify one or more configuration changes that the activity will
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f704874..71967d4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -82,6 +82,15 @@
<!-- Set to true if external storage is case sensitive.
Typically external storage is FAT, which is case insensitive. -->
<bool name="config_caseSensitiveExternalStorage">false</bool>
+
+ <!-- A product with no SD card == not removable. -->
+ <bool name="config_externalStorageRemovable" product="nosdcard">false</bool>
+ <!-- Configures whether the primary external storage device is
+ removable. For example, if external storage is on an SD card,
+ it is removable; if it is built in to the device, it is not removable.
+ The default product has external storage on an SD card, which is
+ removable. -->
+ <bool name="config_externalStorageRemovable" product="default">true</bool>
<!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
Please don't copy them, copy anything else. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 8592820..2c3c4fc 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1256,6 +1256,7 @@
<public type="attr" name="textSelectHandleRight" id="0x010102c6" />
<public type="attr" name="textSelectHandle" id="0x010102c7" />
<public type="attr" name="textSelectHandleWindowStyle" id="0x010102c8" />
+ <public type="attr" name="popupAnimationStyle" id="0x010102c9" />
<public-padding type="attr" name="kraken_resource_pad" end="0x01010300" />
@@ -1331,6 +1332,10 @@
<public type="attr" name="fragmentOpenExitAnimation" />
<public type="attr" name="fragmentCloseEnterAnimation" />
<public type="attr" name="fragmentCloseExitAnimation" />
+ <public type="attr" name="fragmentNextEnterAnimation" />
+ <public type="attr" name="fragmentNextExitAnimation" />
+ <public type="attr" name="fragmentPrevEnterAnimation" />
+ <public type="attr" name="fragmentPrevExitAnimation" />
<public type="attr" name="actionBarSize" />
<public type="attr" name="imeSubtypeLocale" />
<public type="attr" name="imeSubtypeMode" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8b4f91f..521a739 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1639,7 +1639,7 @@
<!-- Do not translate. WebView User Agent string -->
<string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>)
- AppleWebKit/534.9 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.9</string>
+ AppleWebKit/534.10 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.10</string>
<!-- Do not translate. WebView User Agent targeted content -->
<string name="web_user_agent_target_content" translatable="false">"Mobile "</string>
@@ -2385,5 +2385,9 @@
<item quantity="other"><xliff:g id="index" example="2">%d</xliff:g> of <xliff:g id="total" example="137">%d</xliff:g></item>
</plurals>
+ <!-- Warning message about security implications of enabling an input method, displayed as a dialog message when the user selects to enable an IME. -->
+ <string name="ime_enabler_security_warning">This input method may be able to collect all the text you type, including personal data like passwords and credit card numbers. It comes from the application <xliff:g id="ime_application_name">%1$s</xliff:g>. Use this input method?</string>
+ <!-- Label for selecting the input method to use -->
+ <string name="ime_enabler_subtype_title">Select inputmethods in <xliff:g id="ime_application_name">%1$s</xliff:g></string>
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 6e9530c..0f653f1 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -78,6 +78,10 @@
<item name="fragmentOpenExitAnimation">@anim/fragment_open_exit</item>
<item name="fragmentCloseEnterAnimation">@anim/fragment_close_enter</item>
<item name="fragmentCloseExitAnimation">@anim/fragment_close_exit</item>
+ <item name="fragmentNextEnterAnimation">@anim/fragment_next_enter</item>
+ <item name="fragmentNextExitAnimation">@anim/fragment_next_exit</item>
+ <item name="fragmentPrevEnterAnimation">@anim/fragment_prev_enter</item>
+ <item name="fragmentPrevExitAnimation">@anim/fragment_prev_exit</item>
</style>
<!-- Standard animations for a non-full-screen window or activity. -->
@@ -196,6 +200,9 @@
<item name="windowExitAnimation">@anim/fade_out</item>
</style>
+ <!-- A special animation value used internally for popup windows. -->
+ <style name="Animation.PopupWindow" />
+
<!-- Status Bar Styles -->
<style name="TextAppearance.StatusBar">
@@ -564,6 +571,7 @@
<style name="Widget.PopupWindow">
<item name="android:popupBackground">@android:drawable/editbox_dropdown_background_dark</item>
+ <item name="android:popupAnimationStyle">@android:style/Animation.PopupWindow</item>
</style>
<!-- Default style for {@link android.app.FragmentBreadCrumbs} view. -->
@@ -587,7 +595,7 @@
</style>
<style name="Widget.QuickContactBadge">
- <item name="android:layout_width">50dip</item>
+ <item name="android:layout_width">47.33333dip</item>
<item name="android:layout_height">56dip</item>
<item name="android:background">@android:drawable/quickcontact_badge</item>
<item name="android:clickable">true</item>
@@ -883,7 +891,7 @@
<!-- Style for the small popup windows that contain text selection anchors. -->
<style name="Widget.TextSelectHandle">
- <item name="android:windowAnimationStyle">@android:style/Animation.TextSelectHandle</item>
+ <item name="android:popupAnimationStyle">@android:style/Animation.TextSelectHandle</item>
</style>
<!-- Style for animating text selection handles. -->
@@ -1131,13 +1139,13 @@
<item name="android:textColorLink">?textColorLinkInverse</item>
</style>
- <style name="TextAppearance.Holo.Light.Large">
+ <style name="TextAppearance.Holo.Light.Large" parent="TextAppearance.Large">
</style>
- <style name="TextAppearance.Holo.Light.Medium">
+ <style name="TextAppearance.Holo.Light.Medium" parent="TextAppearance.Medium">
</style>
- <style name="TextAppearance.Holo.Light.Small">
+ <style name="TextAppearance.Holo.Light.Small" parent="TextAppearance.Small">
</style>
<style name="TextAppearance.Holo.Light.Large.Inverse">
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index ed42e64..37fc6c7 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -66,6 +66,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase {
protected MockWebServer mServer = null;
protected String mFileType = "text/plain";
protected Context mContext = null;
+ protected MultipleDownloadsCompletedReceiver mReceiver = null;
protected static final int DEFAULT_FILE_SIZE = 130 * 1024; // 130kb
protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024;
@@ -131,12 +132,15 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase {
*/
@Override
public void onReceive(Context context, Intent intent) {
+ Log.i(LOG_TAG, "Received Notification:");
if (intent.getAction().equalsIgnoreCase(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
- ++mNumDownloadsCompleted;
- Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " +
- intent.getAction() + " --> total count: " + mNumDownloadsCompleted);
- Bundle extras = intent.getExtras();
- downloadIds.add(new Long(extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID)));
+ synchronized(this) {
+ ++mNumDownloadsCompleted;
+ Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " +
+ intent.getAction() + " --> total count: " + mNumDownloadsCompleted);
+ Bundle extras = intent.getExtras();
+ downloadIds.add(new Long(extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID)));
+ }
}
}
@@ -212,6 +216,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase {
mContext = getInstrumentation().getContext();
mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
mServer = new MockWebServer();
+ mReceiver = registerNewMultipleDownloadsReceiver();
// Note: callers overriding this should call mServer.play() with the desired port #
}
@@ -712,8 +717,9 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase {
Cursor cursor = mDownloadManager.query(query);
try {
- // If we've finished the downloads then we're done
- if (cursor.getCount() == 0) {
+ // @TODO: there may be a little cleaner way to check for success, perhaps
+ // via STATUS_SUCCESSFUL and/or STATUS_FAILED
+ if (cursor.getCount() == 0 && mReceiver.numDownloadsCompleted() > 0) {
break;
}
currentWaitTime = timeoutWait(currentWaitTime, poll, timeoutMillis,
diff --git a/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
index 27eea4d..cb7c2d2 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerIntegrationTest.java
@@ -61,7 +61,6 @@ public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
Environment.getRootDirectory().getAbsolutePath();
private final static String CACHE_DIR =
Environment.getDownloadCacheDirectory().getAbsolutePath();
- protected MultipleDownloadsCompletedReceiver mReceiver = null;
/**
* {@inheritDoc}
@@ -72,7 +71,6 @@ public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
setWiFiStateOn(true);
mServer.play();
removeAllCurrentDownloads();
- mReceiver = registerNewMultipleDownloadsReceiver();
}
/**
@@ -270,7 +268,7 @@ public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
try {
verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
verifyInt(cursor, DownloadManager.COLUMN_REASON,
- DownloadManager.ERROR_FILE_ERROR);
+ DownloadManager.ERROR_FILE_ALREADY_EXISTS);
} finally {
cursor.close();
}
@@ -429,6 +427,7 @@ public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
}
}
+ Log.i(LOG_TAG, "Done creating filler file.");
assertTrue(DOWNLOAD_FILE_SIZE > (fs.getAvailableBlocks() * blockSize));
byte[] blobData = generateData(DOWNLOAD_FILE_SIZE, DataType.TEXT);
long dlRequest = doBasicDownload(blobData);
diff --git a/core/tests/coretests/src/android/content/SyncQueueTest.java b/core/tests/coretests/src/android/content/SyncQueueTest.java
deleted file mode 100644
index 1da59d1..0000000
--- a/core/tests/coretests/src/android/content/SyncQueueTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.test.AndroidTestCase;
-import android.test.RenamingDelegatingContext;
-import android.test.mock.MockContext;
-import android.test.mock.MockContentResolver;
-import android.accounts.Account;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-public class SyncQueueTest extends AndroidTestCase {
- private static final Account ACCOUNT1 = new Account("test.account1", "test.type1");
- private static final Account ACCOUNT2 = new Account("test.account2", "test.type2");
- private static final String AUTHORITY1 = "test.authority1";
- private static final String AUTHORITY2 = "test.authority2";
- private static final String AUTHORITY3 = "test.authority3";
-
- private SyncStorageEngine mSettings;
- private SyncQueue mSyncQueue;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- MockContentResolver mockResolver = new MockContentResolver();
- mSettings = SyncStorageEngine.newTestInstance(new TestContext(mockResolver, getContext()));
- mSyncQueue = new SyncQueue(mSettings);
- }
-
- public void testSyncQueueOrder() throws Exception {
- final SyncOperation op1 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
- final SyncOperation op2 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
- final SyncOperation op3 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
- final SyncOperation op4 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("4"), 60);
- final SyncOperation op5 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
- final SyncOperation op6 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
- op6.expedited = true;
-
- mSyncQueue.add(op1);
- mSyncQueue.add(op2);
- mSyncQueue.add(op3);
- mSyncQueue.add(op4);
- mSyncQueue.add(op5);
- mSyncQueue.add(op6);
-
- long now = SystemClock.elapsedRealtime() + 200;
-
- assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op6);
-
- assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op1);
-
- assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op4);
-
- assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op5);
-
- assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op2);
-
- assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op3);
- }
-
- public void testOrderWithBackoff() throws Exception {
- final SyncOperation op1 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
- final SyncOperation op2 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
- final SyncOperation op3 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
- final SyncOperation op4 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY3, newTestBundle("4"), 60);
- final SyncOperation op5 = new SyncOperation(
- ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
- final SyncOperation op6 = new SyncOperation(
- ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
- op6.expedited = true;
-
- mSyncQueue.add(op1);
- mSyncQueue.add(op2);
- mSyncQueue.add(op3);
- mSyncQueue.add(op4);
- mSyncQueue.add(op5);
- mSyncQueue.add(op6);
-
- long now = SystemClock.elapsedRealtime() + 200;
-
- assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op6);
-
- assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op1);
-
- mSettings.setBackoff(ACCOUNT2, AUTHORITY3, now + 200, 5);
- assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-
- mSettings.setBackoff(ACCOUNT2, AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
- assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
-
- mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, now + 200);
- assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
-
- mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, 0);
- assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op4);
-
- assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op5);
-
- assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op2);
-
- assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
- mSyncQueue.remove(op3);
- }
-
- Bundle newTestBundle(String val) {
- Bundle bundle = new Bundle();
- bundle.putString("test", val);
- return bundle;
- }
-
- static class TestContext extends ContextWrapper {
- ContentResolver mResolver;
-
- public TestContext(ContentResolver resolver, Context realContext) {
- super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
- mResolver = resolver;
- }
-
- @Override
- public void enforceCallingOrSelfPermission(String permission, String message) {
- }
-
- @Override
- public ContentResolver getContentResolver() {
- return mResolver;
- }
- }
-}
diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
index 0b494a7..ae41409 100644
--- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -39,7 +39,8 @@ public class SyncStorageEngineTest extends AndroidTestCase {
* correcponding sync is finished. This can happen if the clock changes while we are syncing.
*
*/
- @SmallTest
+ // TODO: this test causes AidlTest to fail. Omit for now
+ // @SmallTest
public void testPurgeActiveSync() throws Exception {
final Account account = new Account("a@example.com", "example.type");
final String authority = "testprovider";
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
index bbffe70..f6b1d04 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
@@ -20,6 +20,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
@@ -97,7 +98,10 @@ public class SQLiteCursorTest extends AndroidTestCase {
assertTrue(db.isOpen());
}
- @SmallTest
+ /**
+ * this test could take a while to execute. so, designate it as LargetTest
+ */
+ @LargeTest
public void testFillWindow() {
// create schema
final String testTable = "testV";
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
index 217545f..955336a 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
@@ -128,7 +128,7 @@ public class SQLiteStatementTest extends AndroidTestCase {
* pre-compiled SQL statement id except in during the period of binding the arguments
* and executing the SQL statement.
*/
- @SmallTest
+ @LargeTest
public void testReferenceToPrecompiledStatementId() {
mDatabase.execSQL("create table t (i int, j text);");
verifyReferenceToPrecompiledStatementId(false);
diff --git a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
index 4d016d1..fe62764 100644
--- a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
@@ -16,7 +16,7 @@
package com.android.internal.os;
-import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.Suppress;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -25,6 +25,10 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import junit.framework.TestCase;
+
+// this test causes a IllegalAccessError: superclass not accessible
+@Suppress
public class LoggingPrintStreamTest extends TestCase {
TestPrintStream out = new TestPrintStream();
diff --git a/docs/html/guide/appendix/market-filters.jd b/docs/html/guide/appendix/market-filters.jd
index 0797892..e74cefb 100644
--- a/docs/html/guide/appendix/market-filters.jd
+++ b/docs/html/guide/appendix/market-filters.jd
@@ -4,7 +4,7 @@ page.title=Market Filters
<div id="qv-wrapper">
<div id="qv">
-<h2 align="left">Market filters quickview</h2>
+<h2>Quickview</h2>
<ul> <li>Android Market applies filters to that let you control whether your app is shown to a
user who is browing or searching for apps.</li>
<li>Filtering is determined by elements in an app's manifest file,
diff --git a/docs/html/guide/practices/design/performance.jd b/docs/html/guide/practices/design/performance.jd
index 56872a7..f5588ac 100644
--- a/docs/html/guide/practices/design/performance.jd
+++ b/docs/html/guide/practices/design/performance.jd
@@ -1,6 +1,30 @@
page.title=Designing for Performance
@jd:body
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+<ol>
+ <li><a href="#intro">Introduction</a></li>
+ <li><a href="#optimize_judiciously">Optimize Judiciously</a></li>
+ <li><a href="#object_creation">Avoid Creating Objects</a></li>
+ <li><a href="#myths">Performance Myths</a></li>
+ <li><a href="#prefer_static">Prefer Static Over Virtual</a></li>
+ <li><a href="#internal_get_set">Avoid Internal Getters/Setters</a></li>
+ <li><a href="#use_final">Use Static Final For Constants</a></li>
+ <li><a href="#foreach">Use Enhanced For Loop Syntax</a></li>
+ <li><a href="#avoid_enums">Avoid Enums Where You Only Need Ints</a></li>
+ <li><a href="#package_inner">Use Package Scope with Inner Classes</a></li>
+ <li><a href="#avoidfloat">Use Floating-Point Judiciously</a> </li>
+ <li><a href="#library">Know And Use The Libraries</a></li>
+ <li><a href="#native_methods">Use Native Methods Judiciously</a></li>
+ <li><a href="#closing_notes">Closing Notes</a></li>
+</ol>
+
+</div>
+</div>
+
<p>An Android application will run on a mobile device with limited computing
power and storage, and constrained battery life. Because of
this, it should be <em>efficient</em>. Battery life is one reason you might
@@ -8,24 +32,6 @@ want to optimize your app even if it already seems to run "fast enough".
Battery life is important to users, and Android's battery usage breakdown
means users will know if your app is responsible draining their battery.</p>
-<p>This document covers these topics: </p>
-<ul>
- <li><a href="#intro">Introduction</a></li>
- <li><a href="#optimize_judiciously">Optimize Judiciously</a></li>
- <li><a href="#object_creation">Avoid Creating Objects</a></li>
- <li><a href="#myths">Performance Myths</a></li>
- <li><a href="#prefer_static">Prefer Static Over Virtual</a></li>
- <li><a href="#internal_get_set">Avoid Internal Getters/Setters</a></li>
- <li><a href="#use_final">Use Static Final For Constants</a></li>
- <li><a href="#foreach">Use Enhanced For Loop Syntax</a></li>
- <li><a href="#avoid_enums">Avoid Enums Where You Only Need Ints</a></li>
- <li><a href="#package_inner">Use Package Scope with Inner Classes</a></li>
- <li><a href="#avoidfloat">Use Floating-Point Judiciously</a> </li>
- <li><a href="#library">Know And Use The Libraries</a></li>
- <li><a href="#native_methods">Use Native Methods Judiciously</a></li>
- <li><a href="#closing_notes">Closing Notes</a></li>
-</ul>
-
<p>Note that although this document primarily covers micro-optimizations,
these will almost never make or break your software. Choosing the right
algorithms and data structures should always be your priority, but is
diff --git a/docs/html/guide/practices/design/responsiveness.jd b/docs/html/guide/practices/design/responsiveness.jd
index 8a4e7cf..b811d1b 100644
--- a/docs/html/guide/practices/design/responsiveness.jd
+++ b/docs/html/guide/practices/design/responsiveness.jd
@@ -1,13 +1,44 @@
page.title=Designing for Responsiveness
@jd:body
-<p>It's possible to write code that wins every performance test in the world, but still sends users in a fiery rage when they try to use the application. These are the applications that aren't <em>responsive</em> enough &mdash; the ones that feel
-sluggish, hang or freeze for significant periods, or take too long to process
-input. </p>
+<div id="qv-wrapper">
+<div id="qv">
-<p>In Android, the system guards against applications that are insufficiently responsive for a period of time by displaying a dialog to the user, called the Application Not Responding (ANR) dialog. The user can choose to let the application continue, but the user won't appreciate having to act on this dialog every time he or she uses your application. So it's important to design responsiveness into your application, so that the system never has cause to display an ANR to the user. </p>
+<h2>In this document</h2>
+<ol>
+ <li><a href="#anr">What Triggers ANR?</a></li>
+ <li><a href="#avoiding">How to Avoid ANR</a></li>
+ <li><a href="#reinforcing">Reinforcing Responsiveness</a></li>
+</ol>
-<p>Generally, the system displays an ANR if an application cannot respond to user input. For example, if an application blocks on some I/O operation (frequently a network access), then the main application thread won't be able to process incoming user input events. After a time, the system concludes that the application has hung, and displays the ANR to give the user the option to kill it.
+</div>
+</div>
+
+<div class="figure">
+<img src="{@docRoot}images/anr.png" alt="Screenshot of ANR dialog box" width="240" height="320"/>
+<p><strong>Figure 1.</strong> An ANR dialog displayed to the user.</p>
+</div>
+
+<p>It's possible to write code that wins every performance test in the world,
+but still sends users in a fiery rage when they try to use the application.
+These are the applications that aren't <em>responsive</em> enough &mdash; the
+ones that feel sluggish, hang or freeze for significant periods, or take too
+long to process input. </p>
+
+<p>In Android, the system guards against applications that are insufficiently
+responsive for a period of time by displaying a dialog to the user, called the
+Application Not Responding (ANR) dialog, shown at right in Figure 1. The user
+can choose to let the application continue, but the user won't appreciate having
+to act on this dialog every time he or she uses your application. It's critical
+to design responsiveness into your application, so that the system never has
+cause to display an ANR dialog to the user. </p>
+
+<p>Generally, the system displays an ANR if an application cannot respond to
+user input. For example, if an application blocks on some I/O operation
+(frequently a network access), then the main application thread won't be able to
+process incoming user input events. After a time, the system concludes that the
+application is frozen, and displays the ANR to give the user the option to kill
+it. </p>
<p>Similarly, if your application spends too much time building an elaborate in-memory
structure, or perhaps computing the next move in a game, the system will
@@ -15,31 +46,17 @@ conclude that your application has hung. It's always important to make
sure these computations are efficient using the techniques above, but even the
most efficient code still takes time to run.</p>
-<p>In both of these cases, the fix is usually to create a child thread, and do
+<p>In both of these cases, the recommended approach is to create a child thread and do
most of your work there. This keeps the main thread (which drives the user
-interface event loop) running, and prevents the system from concluding your code
+interface event loop) running and prevents the system from concluding that your code
has frozen. Since such threading usually is accomplished at the class
level, you can think of responsiveness as a <em>class</em> problem. (Compare
this with basic performance, which was described above as a <em>method</em>-level
concern.)</p>
-<div class="sidebox-wrapper">
-<div class="sidebox">
-<img src="{@docRoot}images/anr.png" width="240" height="320" alt="Screenshot of ANR dialog box">
-<p style="margin-top:.5em;padding:.5em;">An ANR dialog displayed to the user.</p>
-</div>
-</div>
-
-<p>This document discusses how the Android system determines whether an application is
-not responding and provides guidelines for
-ensuring that your application is responsive. </p>
-
-<p>This document covers these topics: </p>
-<ul>
- <li><a href="#anr">What Triggers ANR?</a></li>
- <li><a href="#avoiding">How to Avoid ANR</a></li>
- <li><a href="#reinforcing">Reinforcing Responsiveness</a></li>
-</ul>
+<p>This document describes how the Android system determines whether an
+application is not responding and provides guidelines for ensuring that your
+application stays responsive. </p>
<h2 id="anr">What Triggers ANR?</h2>
@@ -48,8 +65,10 @@ and Window Manager system services. Android will display the ANR dialog
for a particular application when it detects one of the following
conditions:</p>
<ul>
- <li>No response to an input event (e.g. key press, screen touch) within 5 seconds</li>
- <li>A {@link android.content.BroadcastReceiver BroadcastReceiver} hasn't finished executing within 10 seconds</li>
+ <li>No response to an input event (e.g. key press, screen touch)
+ within 5 seconds</li>
+ <li>A {@link android.content.BroadcastReceiver BroadcastReceiver}
+ hasn't finished executing within 10 seconds</li>
</ul>
<h2 id="avoiding">How to Avoid ANR</h2>
diff --git a/docs/html/guide/practices/design/seamlessness.jd b/docs/html/guide/practices/design/seamlessness.jd
index a6c1641..dedc16f 100644
--- a/docs/html/guide/practices/design/seamlessness.jd
+++ b/docs/html/guide/practices/design/seamlessness.jd
@@ -1,6 +1,26 @@
page.title=Designing for Seamlessness
@jd:body
+<div id="qv-wrapper">
+<div id="qv">
+
+<h2>In this document</h2>
+<ol>
+ <li><a href="#drop">Don't Drop Data</a></li>
+ <li><a href="#expose">Don't Expose Raw Data</a></li>
+ <li><a href="#interrupt">Don't Interrupt the User</a></li>
+ <li><a href="#threads">Got a Lot to Do? Do it in a Thread</a></li>
+ <li><a href="#multiple-activities">Don't Overload a Single Activity Screen</a></li>
+ <li><a href="#themes">Extend System Themes</a></li>
+ <li><a href="#flexui">Design Your UI to Work with Multiple Screen Resolutions</a></li>
+ <li><a href="#network">Assume the Network is Slow</a></li>
+ <li><a href="#keyboard">Don't Assume Touchscreen or Keyboard</a></li>
+ <li><a href="#battery">Do Conserve the Device Battery</a></li>
+</ol>
+
+</div>
+</div>
+
<p>Even if your application is fast and responsive, certain design decisions can
still cause problems for users &mdash; because of unplanned interactions with
other applications or dialogs, inadvertent loss of data, unintended blocking,
@@ -42,20 +62,7 @@ system as just an even-larger federation of these components. This benefits you
by allowing you to integrate cleanly and seamlessly with other applications, and
so you should design your own code to return the favor.</p>
-<p>This document discusses common seamlessness problems and how to avoid them.
-It covers these topics: </p>
-<ul>
- <li><a href="#drop">Don't Drop Data</a></li>
- <li><a href="#expose">Don't Expose Raw Data</a></li>
- <li><a href="#interrupt">Don't Interrupt the User</a></li>
- <li><a href="#threads">Got a Lot to Do? Do it in a Thread</a></li>
- <li><a href="#multiple-activities">Don't Overload a Single Activity Screen</a></li>
- <li><a href="#themes">Extend System Themes</a></li>
- <li><a href="#flexui">Design Your UI to Work with Multiple Screen Resolutions</a></li>
- <li><a href="#network">Assume the Network is Slow</a></li>
- <li><a href="#keyboard">Don't Assume Touchscreen or Keyboard</a></li>
- <li><a href="#battery">Do Conserve the Device Battery</a></li>
-</ul>
+<p>This document discusses common seamlessness problems and how to avoid them.</p>
<h2 id="drop">Don't Drop Data</h2>
diff --git a/docs/html/guide/practices/screens_support.jd b/docs/html/guide/practices/screens_support.jd
index 2863fb2..13b5e3a 100644
--- a/docs/html/guide/practices/screens_support.jd
+++ b/docs/html/guide/practices/screens_support.jd
@@ -5,7 +5,7 @@ page.title=Supporting Multiple Screens
<div id="qv-wrapper">
<div id="qv">
- <h2>Multiple screens quickview: </h2>
+ <h2>Quickview</h2>
<ul>
<li>Android runs on devices that have different screen sizes and resolutions.</li>
<li>The screen on which your application is displayed can affect its user interface.</li>
@@ -131,57 +131,79 @@ screens. </p></dd>
</dl>
-<h3 id="range">Range of Screens Supported</h3>
+<h3 id="range">Range of screens supported</h3>
-<p>Android 1.5 and earlier versions of the platform were designed to support a
-single screen configuration &mdash; HVGA (320x480) resolution on a 3.2" screen.
-Because the platform targeted just one screen, application developers could
-write their applications specifically for that screen, without needing to worry
-about how their applications would be displayed on other screens. </p>
-
-<p>Starting from Android 1.6, the platform adds support for multiple screen
+<p>Starting from Android 1.6, the platform provides support for multiple screen
sizes and resolutions, reflecting the many new types and sizes of devices on
-which the platform will run. This means that developers must design their
-applications for proper display on a range of devices and screens.</p>
+which the platform runs. If you are developing an application that will run
+on Android 1.6 or later, you can use the compatibility features of the Android
+platform to ensure that your application UI renders properly across the range of
+supported screen sizes and resolutions.</p>
-<p>To simplify the way application developers design their user interfaces for
-multiple devices, and to allow more devices to participate without impacting
+<p>To simplify the way that developers design their user interfaces for
+multiple devices and to allow more devices to participate without affecting
applications, the platform divides the range of actual supported screen sizes
and resolutions into:</p>
<ul>
<li>A set of three generalized sizes: <em>large</em>, <em>normal</em>, and <em>small</em>, and </li>
-<li>A set of three generalized densities: high (<em>hdpi</em>), medium (<em>mdpi</em>), and low (<em>ldpi</em>)
+<li>A set of three generalized densities: <em>hdpi</em> (high), <em>mdpi</em> (medium), and <em>ldpi</em> (low)
</ul>
-<!--<p>Applications use to these generalized sizesThe to let you apply custom UI
-and enable/disable functionality according to the generalized class of screen,
-rather than by the specific screen. When you are developing your application,
-you use these generalized sizes and densities and Applications can use these
-generalized sizes and densities to tell the platform I will do it or you do it.
-Or a combination of both. -->
-
<p>Applications can provide custom resources (primarily layouts) for any of the
-three generalized sizes, if needed, and they can also provide resources
-(primarily drawables such as images) for any of the three generalized densities.
-Applications do not need to work with the actual physical size or density of the
-device screen. At run time, the platform handles the loading of the correct size
-or density resources, based on the generalized size or density of the current
-device screen, and adapts them to the actual pixel map of the screen.</p>
-
-<p>The table below lists some of the more common screens supported
-by Android and illustrates how the platform maps them to generalized screen
-configurations. Some devices use screens that are not specifically listed
-in the table &mdash; the platform maps those to the same set generalized
-screen configurations. </p>
-
-<p class="table-caption" id="screens-table"><strong>Table 1.</strong> Examples of
-device screens supported by Android.</p>
-
- <table id="screens-table" width="80%" style="margin-top:2em;">
+three generalized sizes and can provide resources (primarily drawables such as
+images) for any of the three generalized densities. Applications do not need to
+work with the actual physical size or density of the device screen. At run time,
+the platform handles the loading of the correct size or density resources, based
+on the generalized size or density of the current device screen, and adapts them
+to the actual pixel map of the screen.</p>
+
+<p>The generalized size/density configurations are arranged around a
+baseline configuration that is assigned a size of <em>normal</em> and a density of
+<em>mdpi</em> (medium). All applications written for Android 1.5 or earlier are (by
+definition) designed for the baseline HVGA screen used on the T-Mobile G1 and
+similar devices, which is size <em>normal</em> and density
+<em>mdpi</em>.</p>
+
+<p>Each generalized screen configuration spans a range of actual screen
+densities and physical sizes. For example, that means that multiple devices that
+report a screen size of <em>normal</em> might offer screens that differ slightly
+in actual size or aspect ratio. Similarly, devices that report a screen density
+of <em>hdpi</em> might offer screens with slightly different pixel densities.
+The platform makes these differences abstract, however &mdash; applications can
+offer UI designed for the generalized sizes and densities and let the system
+handle the actual rendering of the UI on the current device screen according to
+its characteristics. </p>
+
+
+<img src="{@docRoot}images/screens_support/screens-ranges.png" />
+<p class="img-caption"><strong>Figure 1.</strong>
+Illustration of how the Android platform maps actual screen densities and sizes
+to generalized density and size configurations. </p>
+
+<p>Although the platform lets your application provide layouts and resources for
+generalized size-density configurations, you do not necessarily need to do write
+custom code or provide custom resources for each of the nine supported
+configurations. The platform provides robust compatibility features, described
+in the sections below, that can handle most of the work of rendering your
+application on any device screen, provided that you've implemented your
+application UI properly. For more information about how to implement a UI that
+renders properly across device screens and platform versions, see
+<a href="#screen-independence">Best Practices for Screen Independence</a>.</p>
+
+<p>To help you test your applications, the Android SDK includes emulator skins
+that replicate the sizes and densities of actual device screens on which your
+application is likely to run. You can also modify the default size and density
+of the emulator skins to replicate the characteristics of any specific
+screen.</p>
+
+<p class="table-caption" id="screens-table"><strong>Table 1.</strong> Screen
+sizes and densities of emulator skins included in the Android SDK.</p>
+
+ <table id="screens-table">
<tbody>
<tr>
- <td></td>
+ <td style="border:none"></td>
<td style="background-color:#f3f3f3">
<nobr>Low density (120), <em>ldpi</em></nobr>
</td>
@@ -196,10 +218,7 @@ device screens supported by Android.</p>
<td style="background-color:#f3f3f3">
<em>Small</em> screen
</td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">QVGA (240x320), <nobr>2.6"-3.0" diagonal</nobr></li>
- </ul>
+ <td style="font-size:.9em;">QVGA (240x320)</td>
</td>
<td></td>
<td></td>
@@ -208,74 +227,29 @@ device screens supported by Android.</p>
<td style="background-color:#f3f3f3">
<em>Normal</em> screen
</td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">WQVGA (240x400), <nobr>3.2"-3.5" diagonal</nobr></li>
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">FWQVGA (240x432), <nobr>3.5"-3.8" diagonal</nobr></li>
- </ul>
- </td>
- <td style="font-size:.9em;background-color:#FFE;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">HVGA (320x480), <nobr>3.0"-3.5" diagonal</nobr></li>
- </ul>
- </td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">WVGA (480x800), <nobr>3.3"-4.0" diagonal</nobr></li>
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">FWVGA (480x854), <nobr>3.5"-4.0" diagonal</nobr></li>
- </ul>
- </td>
+ <td style="font-size:.9em;">WQVGA400 (240x400)<br>WQVGA432 (240x432)</td>
+ <td style="font-size:.9em;">HVGA (320x480)</td>
+ <td style="font-size:.9em;">WVGA800 (480x800)<br>WVGA854 (480x854)</td>
</tr>
<tr>
<td style="background-color:#f3f3f3">
<em>Large</em> screen
</td>
<td></td>
- <td style="font-size:.9em;">
- <ul style="padding:0">
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">WVGA (480x800), <nobr>4.8"-5.5" diagonal</nobr></li>
- <li style="margin: 0 0 0 1em;padding:.25em 0 0 0; font-size:.9em;">FWVGA (480x854), <nobr>5.0"-5.8" diagonal</nobr></li>
- </ul>
- </td>
+ <td style="font-size:.9em;">WVGA800* (480x800)<br>WVGA854* (480x854)</td>
<td></td>
</tr>
- </tbody>
- </table>
-
-<p class="caption" style="margin-top:1em;margin-bottom:1.5em;"> </p>
-
-<p>As shown above, the various screen configurations are arranged around a
-<em>baseline screen</em> that is assigned a size of "normal" and a density of
-"medium". The HVGA screen is used as the baseline because all applications
-written against Android 1.5 or earlier are (by definition) written for the HVGA
-screen used on the T-Mobile G1 and similar devices.</p>
-
-<!-- <p>Note that each screen configuration spans a range of actual resolutions
-and physical screen sizes. For example, the The baseline configuration spans a
-range of actual screen sizes &mdash; from 3.0" to 3.5" diagonal &mdash; all with
-the same HVGA resolution. That means that the actual pixel density of devices in
-a single screen configuration can vary. </p>
-
-Because differences in density can affect the displayed size of UI elements
-declared in pixels, the framework provides a density-independent pixel (dip)
-unit that applications can use to declare UI dimensions, letting the platform
-automatically handle the scaling to the actual pixel density of the screen. When
-UI dimensions are declared in dip, the result is that they are displayed at the
-same physical size on all screens in a given configuration. </p> -->
-
-<p>Although the platform currently supports the nine possible size-density
-configurations listed in the table, you do not necessarily need to create custom
-resources for each one of them. The platform provides robust compatibility
-features, described in the sections below, that can handle most of the work of
-rendering your application on the current device screen, provided that the UI is
-properly implemented. For more information, see <a
-href="#screen-independence">Best Practices for Screen Independence</a>.</p>
-
-<!--
+ <tr>
+ <td colspan="4" style="border:none;font-size:90%;">* To emulate this
+ configuration, specify a custom density of 160 when
+ creating an AVD that uses a WVGA800 or WVGA854 skin.
+ </td>
+</table>
+
<p>For an overview of the relative numbers of high (hdpi), medium (mdpi), and
-low (ldpi) density screens, see the <a
-href="{@docRoot}guide/resources/dashboard/screen-densities.html">Screen Densities dashboard</a>.</p>
--->
+low (ldpi) density screens in Android-powered devices available now, see the <a
+href="{@docRoot}resources/dashboard/screens.html">Screen Sizes and Densities</a> dashboard.</p>
+
<h3 id="support">How Android supports multiple screens</h3>
@@ -307,8 +281,8 @@ size- and density-specific resources, if needed. The qualifiers for
size-specific resources are <code>large</code>, <code>normal</code>, and
<code>small</code>, and those for density-specific resources are
<code>hdpi</code> (high), <code>mdpi</code> (medium), and <code>ldpi</code>
-(low). The qualifiers correspond to the generalized densities given in
-<a href="#range">Table 1</a>, above.</li>
+(low). The qualifiers correspond to the generalized densities described in
+<a href="#range">Range of screens supported</a>, above.</li>
<li>The platform also provides a
<a href="{@docRoot}guide/topics/manifest/supports-screens-element.html">
<code>&lt;supports-screens&gt;</code></a>
@@ -457,7 +431,7 @@ different.</p>
<div id=vi09 style=TEXT-ALIGN:left>
<img src="{@docRoot}images/screens_support/dip.png" style="padding-bottom:0;margin-bottom:0;" />
<p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0
-1em;"><strong>Figure 1.</strong> Examples of density independence on WVGA high
+1em;"><strong>Figure 2.</strong> Examples of density independence on WVGA high
density (left), HVGA medium density (center), and QVGA low density (right). </p>
</div>
@@ -479,7 +453,8 @@ whose attributes you can use to control the
display of your application on different classes of device screens, as listed
below. The <code>smallScreens</code>, <code>normalScreens</code>, and
<code>largeScreens</code> attributes correspond to the generalized screen sizes
-shown in <a href="#range">Table 1</a>, earlier in this document.</p>
+described in <a href="#range">Range of screens supported</a>, earlier in this
+document.</p>
<table id="vrr8">
<tr>
@@ -489,6 +464,12 @@ shown in <a href="#range">Table 1</a>, earlier in this document.</p>
<th >
Description
</th>
+ <th>
+ Default value,<br><nobr>Android 1.5 and Lower</nobr>
+ </th>
+ <th>
+ Default value,<br><nobr>Android 1.6 and Higher</nobr>
+ </th>
</tr>
<tr>
<td>
@@ -497,10 +478,10 @@ shown in <a href="#range">Table 1</a>, earlier in this document.</p>
<td>
Whether or not the application UI is designed for use on
<em>small</em> screens &mdash; "<code>true</code>" if it is, and
-"<code>false</code>" if not. See <a href="#defaults">Default values for
-attributes</a> for information about the assumed value of this attribute, if not
-declared.
+"<code>false</code>" if not. </p>
</td>
+<td>"<code>false</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
<tr>
<td>
@@ -509,8 +490,10 @@ declared.
<td>
Whether or not the application UI is designed for use on
<em>normal</em> screens &mdash; "<code>true</code>" if it is, and
-"<code>false</code>" if not. The default value is "<code>true</code>".
+"<code>false</code>" if not. The default value is always "<code>true</code>".
</td>
+<td>"<code>true</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
<tr>
<td>
@@ -519,10 +502,10 @@ declared.
<td>
Whether or not the application UI is designed for use on
<em>large</em> screens &mdash; "<code>true</code>" if it is, and
-"<code>false</code>" if not. See <a href="#defaults">Default values for
-attributes</a> for information about the assumed value of this attribute, if not
-declared.
+"<code>false</code>" if not.
</td>
+<td>"<code>false</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
<tr>
<td>
@@ -535,9 +518,13 @@ in different density environments &mdash; "<code>true</code>" if so, and
<ul>
<li>If set to "<code>true</code>", the platform disables its
density-compatibility features for all screen densities &mdash; specifically,
-the auto-scaling of absolute pixel units and math &mdash; and relies on the
-application to use density-independent pixel units and/or to manage the
-adaptation of pixel values according to density of the current screen. </li>
+the auto-scaling of absolute pixel units (<code>px</code>) and math &mdash; and
+relies on the application to use density-independent pixel units
+(<code>dp</code>) and/or math to manage the adaptation of pixel values according
+to density of the current screen. That is, as long as your application uses
+density-independent units (dp) for screen layout sizes, then it will perform
+properly on different densities when this attribute is set to
+"<code>true</code>".</li>
<li>If set to "<code>false</code>", the platform enables its
density-compatibility features for all screen densities. In this case, the
@@ -546,45 +533,73 @@ which it can layout and draw its UI as though against a medium-density screen
(160). The platform then transparently auto-scales the application's pixel units
and math as needed to match the actual device screen density. </li>
</ul>
- <p>See <a href="#defaults">Default values for attributes</a> for
-information about the assumed value of this attribute, if not declared.</p>
+<p>Note that the setting of this attribute affects density-compatibility only.
+It does not affect size-compatibility features such as display on a virtual
+baseline screen.</p>
</td>
+<td>"<code>false</code>"</td>
+<td>"<code>true</code>"</td>
</tr>
</table>
<p>In general, when you declare a screen-size attribute
(<code>smallScreens</code>, <code>normalScreens</code>, or
-<code>largeScreens</code>) as "true", you are signaling to the platform that
-your application wants to manage its UI by itself, for all screen sizes, without
-the platform applying any size-compatibility behaviors (such as a virtual HVGA
-display area). If you declare a screen-size attribute as "false", you are
-signaling that your application is not designed for that screen size. The
-effects are conditioned by the screen size that your application does not
-support:</p>
-
+<code>largeScreens</code>) as "<code>true</code>", you are signaling to the
+platform that your application is designed to render properly on that screen
+size. As a result, the platform does not apply any size-compatibility features
+(such as a virtual HVGA display area). If you declare a screen-size attribute as
+"<code>false</code>", you are signaling that your application is <em>not</em>
+designed for that screen size. In this case, the platform <em>does</em> apply
+size-compatibility features, rendering the application in an HVGA baseline
+display area. If the current screen is larger than <em>normal</em> size, the
+platform renders the application in a virtual HVGA screen on the larger screen.
+See <a href="#compatibility-examples">Screen-Compatibility Examples</a> for an
+illustration of what an application looks like when displayed in a virtual HVGA
+screen.</p>
+
+<p>In other words, setting a <code>&lt;supports-screens&gt;</code> attribute to
+"<code>false</code>" tells the platform to enable it's compatibility features
+when displaying the application on a screen of that size <em>or any larger
+size</em>, if also disallowed. Otherwise, the platform gives the application a
+normal display area that can use the full device screen area, if
+appropriate.</p>
+
+<p>Android Market also makes use of the <code>&lt;supports-screens&gt;</code>
+attributes. It uses them to filter the application from devices whose screens
+are not supported by the application. Specifically, Android Market considers an
+application compatible with a device if the application supports a screen that
+is the same or smaller than the current device screen. Android Market filters
+the application if it disallows the device's screen size and does not support a
+smaller size. In general, Android does not provide downward size-compatibility
+features for applications.</p>
+
+<p>Here are some examples:</p>
+
<ul>
- <li>If you declare <code>largeScreens="false"</code>, your application can
-still be installed by users of devices with large screens. When run on a device
-with a large screen, this attribute value causes the platform to run the
-application in compatibility mode, rendering it in a baseline screen area
-(normal size, medium density) reserved on the larger screen. See
-<a href="#compatibility-examples">Screen-Compatibility Examples</a> for an
-illustration of what an application looks like when displayed in compatibility
-mode.</li>
- <li>If you declare <code>smallScreens="false"</code>, your application can
-still be installed by users of devices with small screens. However, this
-attribute value causes Android Market to filter your application from the list
-of applications available to such users. In effect, this prevents users from
-installing the application on small-screen devices. </li>
+ <li>Assume that you declare <code>smallScreens="false" normalScreens="true"
+largeScreens="false" </code> in your application's manifest. <p>Although the
+application is not designed for display on large screens, the platform can still
+run it successfully in <a href="#compatibility-examples">size-compatibility
+mode</a>. Android Market does not filter the application from devices
+<em>normal</em> and <em>large</em> size screens, but does filter it from
+<em>small</em> size screens, since the application provides no screen support at
+<em>small</em> size (and there is no smaller size).</p></li>
+
+ <li>Assume that you declare <code>smallScreens="false" normalScreens="false"
+largeScreens="true"</code> in your application's manifest. <p>Android Market
+filters the application from users of devices with <em>small</em> and
+<em>normal</em> size screens. In effect, this prevents such users from
+installing the application.</p></li>
</ul>
-<p>If you declare the <code>android:anyDensity</code> attribute as "true", you
-are signaling to the platform that your application wants to manage its UI by
-itself, for all screen densities, using the actual screen dimensions and pixels.
-In this case, the application must ensure that it declares its UI dimensions
-using density-independent pixels and scales any actual pixel values or math by
-the scaling factor available from
-{@link android.util.DisplayMetrics#density android.util.DisplayMetrics.density}.</p>
+<p>If you declare the <code>android:anyDensity</code> attribute as
+"<code>true</code>", you are signaling to the platform that your application is
+designed to display properly on any screen density. In this case, the
+application must ensure that it declares its UI dimensions using
+density-independent pixels (<code>dp</code>) and scales any absolute pixel
+values (<code>px</code>) or math by the scaling factor available from {@link
+android.util.DisplayMetrics#density android.util.DisplayMetrics.density}. See <a
+href="#dips-pels">Converting from dips to pixels</a> for an example.</p>
<p>Note that the setting of the <code>android:anyDensity</code> attribute does
not affect the platform's pre-scaling of drawable resources, such as bitmaps and
@@ -594,22 +609,22 @@ nine-patch images, which always takes place by default. </p>
normal, and small screens in any densities.</p>
<pre>&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"&gt;
-
+ ...
&lt;supports-screens
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:largeScreens="true"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:normalScreens="true"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:smallScreens="true"
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:resizable="true"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:anyDensity="true" /&gt;
- &lt;/manifest&gt;
+ ...
+&lt;/manifest&gt;
</pre>
-
+<!-- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:resizeable="true" -->
<h4 id="defaults">
Default values for attributes
</h4>
<p>The default values for the <code>&lt;supports-screens&gt;</code> attributes
-differs, depending on the the value of the
+differ, depending on the the value of the
<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code>android:minSdkVersion</code></a>
attribute in the application's manifest, as well as on
the value of <code>android:targetSdkVersion</code>, if declared:</p>
@@ -618,19 +633,20 @@ the value of <code>android:targetSdkVersion</code>, if declared:</p>
<ul>
<li>
If <code>android:minSdkVersion</code> or
-<code>android:targetSdkVersion</code> is "3" (Android 1.5) or lower, the default
-value for everything except android:normalScreens is <code>false</code>. If you
-are primarily targeting pre-Android 1.6 platforms but also want to support other
-densities/screen sizes, you need to set the appropriate attributes to
-<code>true</code>.
+<code>android:targetSdkVersion</code> is "4" (Android 1.6) or higher, the
+default value for everything is "<code>true</code>". If your application uses
+APIs introduced in Android 1.6 or higher, but does not support specific screen
+densities and/or screen sizes, you need to explicitly set the appropriate
+attributes to "<code>false</code>".
</li>
<li>
- If <code>android:minSdkVersion</code> or
-<code>android:targetSdkVersion</code> is "4" (Android 1.6) or higher, the
-default value for everything is <code>true</code>. If your application
-requires&nbsp;<span style=BACKGROUND-COLOR:#ffffff>Android 1.6 </span>features,
-but does not support these densities and/or screen sizes, you need to set the
-appropriate attributes to <code>false</code>.
+ If <code>android:minSdkVersion</code> is declared with a value of "3"
+(Android 1.5) or lower <em>and</em> a <code>android:targetSdkVersion</code>
+attribute is <em>not</em> declared with a value of "4" or higher, the default
+value for all attributes except <code>android:normalScreens</code> is
+"<code>false</code>". If you are primarily targeting pre-Android 1.6 platforms
+but also want to support other densities/screen sizes, you need to explicitly
+set the appropriate attributes to "<code>true</code>".
</li>
<li>
Note that <code>android:normalScreens</code> always defaults to
@@ -646,8 +662,8 @@ appropriate attributes to <code>false</code>.
of resources based on the characteristics of the screen on which your application
is running. You can use these qualifiers to provide size- and density-specific
resources in your application. For more information about the generalized sizes
-and densities that correspond to the qualifiers, see <a href="#range">Table
-1</a>, earlier in this document.</p>
+and densities that correspond to the qualifiers, see <a href="#range">Range
+of Screens Supported</a>, earlier in this document.</p>
<table>
<tr>
@@ -659,30 +675,29 @@ and densities that correspond to the qualifiers, see <a href="#range">Table
<tr>
<td rowspan="3">Size</td>
<td><code>small</code></td>
- <td>Resources for small screens, such as QVGA low density.</td>
+ <td>Resources designed for <em>small</em> size screens.</td>
</tr>
<tr>
<td><code>normal</code></td>
- <td>Resources for normal (baseline configuration) screens, such as T-Mobile
-G1/HTC Magic screen size, or equivalent.</td>
+ <td>Resources designed for <em>normal</em> size screens.</td>
</tr>
<tr>
<td><code>large</code></td>
-<td>Resources for large screens. Typical example is a tablet like device.</td>
+<td>Resources for <em>large</em> size screens.</td>
</tr>
<tr>
<td rowspan="4">Density</td>
<td><code>ldpi</code></td>
-<td>Low-density resources, for 100 to 140 dpi screens.</td>
+<td>Resources designed for low-density (<em>ldpi</em>) screens.</td>
</tr>
<tr>
<td><code>mdpi</code></td>
-<td>Medium-density resources for 140 to 180 dpi screens.</td>
+<td>Resources designed for medium-density (<em>mdpi</em>) screens.</td>
</tr>
<tr>
<td><code>hdpi</code></td>
-<td>High-density resources for 190 to 250 dpi screens.</td>
+<td>Resources designed for high-density (<em>hdpi</em>) screens.</td>
</tr>
<tr>
<td><code>nodpi</code></td>
@@ -747,8 +762,8 @@ Alternative Resources</a>.</p>
<h2 id="screen-independence">Best practices for Screen Independence</h2>
<p>The objective of supporting multiple screens is to create an application that
-can run properly on any display and function properly on any of the screen
-configurations listed in <a href="#range">Table 1</a> earlier in this document.
+can run properly on any display and function properly on any of the generalized
+screen configurations supported by the platform.
</p>
<p>You can easily ensure that your application will display properly on
@@ -855,7 +870,7 @@ scroll threshold can be obtained as follows:</p>
<div style="float: right;background-color:#fff;margin: 0;padding: 20px 0 20px 20px;">
<img src="{@docRoot}images/screens_support/scale-test.png" style="padding:0;margin:0;">
-<p class="caption" style="margin:0;padding:0;"><strong>Figure 2.</strong> Comparison of pre-scaled and auto-scaled bitmaps.</p>
+<p class="caption" style="margin:0;padding:0;"><strong>Figure 3.</strong> Comparison of pre-scaled and auto-scaled bitmaps.</p>
</div>
<p>Even with the size- and density-compatibility features that the platform
@@ -947,7 +962,7 @@ pre-scaling but uses less memory. You can refer to the documentation of
{@link android.graphics.Canvas Canvas} for more
information on auto-scaling.</p>
-<p>Figure 2, at right, demonstrates the results of the pre-scale and auto-scale
+<p>Figure 3, at right, demonstrates the results of the pre-scale and auto-scale
mechanisms when loading low (120), medium (160) and high (240) density bitmaps
on a baseline screen. The differences are subtle, because all of the bitmaps are
being scaled to match the current screen density, however the scaled bitmaps
@@ -1078,7 +1093,7 @@ attribute(s) to your application's manifest. -->
<div id="f9.5" style="float:right;margin:0;padding:0;">
<img src="{@docRoot}images/screens_support/avds-config.png" style="padding:0;margin:0;">
- <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em;"><strong>Figure 3.</strong>
+ <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em;"><strong>Figure 4.</strong>
A typical set of AVDs for testing screens support.</p>
</div>
@@ -1143,52 +1158,29 @@ launching the emulator, for example:</p>
<p>Note that starting the emulator with the <code>-scale</code> option will
scale the entire emulator display, based on both the dpi of the skin and of your
-monitor. Using the default densities, the emulator skins included in the Android
-1.6 SDK will emulate the following screen sizes:</p>
-
-<ul>
- <li>
- QVGA, low density: 3.3"
- </li>
- <li>
- WQVGA, low density: 3.9"
- </li>
- <li>
- WQVGA432, low density: 4.1"
- </li>
- <li>
- HVGA, medium density: 3.6"
- </li>
- <li>
- WVGA800, high density: 3.9"
- </li>
- <li>
- WVGA854, high density: 4.1"
- </li>
-</ul>
+monitor. The default emulator skins included in the Android SDK are listed
+in <a href="#screens-table">Table 1</a>, earlier in this document.</p>
<div style="float: right;background-color:#fff;margin: 0;padding: 20px 0 20px 20px;width:520px;">
<img src="{@docRoot}images/screens_support/avd-density.png" style="padding:0;margin:0;">
- <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em; width:280px;"><strong>Figure 4.</strong>
+ <p class="caption" style="margin:0 0 1.5em 1em;padding:0 0 0 1em; width:280px;"><strong>Figure 5.</strong>
Resolution and density options that you can use, when creating an AVD using the AVD Manager.</p>
</div>
<p>You should also make sure to test your application on different physical
-screen sizes within a single size-density configuration. For example, according
-to <a href="#range">Table 1</a>, the minimum supported diagonal of QVGA is 2.8".
-To display this is on a 30" monitor you will need to adjust the value passed to
-<code>-scale</code> to 96*2.8/3.3 = 81dpi. You can also pass a float value to
-<code>-scale</code> to specify your own scaling factor:</p>
+screen sizes within a single size-density configuration. For example, to
+display this screen configuration on a 30" monitor you will need to adjust
+the value passed to <code>-scale</code> to 96*2.8/3.3 = 81dpi. You can also
+pass a float value to <code>-scale</code> to specify your own scaling factor:</p>
<pre>emulator -avd &lt;name&gt; -scale 0.6</pre>
<p>If you would like to test your application on a screen that uses a resolution
or density not supported by the built-in skins, you can either adjust an
-existing skin, or create an AVD
-that uses a custom resolution or density.</p>
+existing skin, or create an AVD that uses a custom resolution or density.</p>
<p>In the AVD Manager, you can specify a custom skin resolution or density in
-the Create New AVD dialog, as shown in Figure 4, at right.</p>
+the Create New AVD dialog, as shown in Figure 5, at right.</p>
<p>In the <code>android</code> tool, follow these steps to create an AVD with a
custom resolution or density:</p>
@@ -1203,9 +1195,9 @@ Here's an example:
<li>To specify a custom density for the skin, answer "yes" when asked whether
you want to create a custom hardware profile for the new AVD.</li>
<li>Continue through the various profile settings until the tool asks you to
-specify "Abstracted LCD density" (<em>hw.lcd.density</em>). Consult <a
-href="#range">Table 1</a>, earlier in this document, and enter the appropriate
-value. For example, enter "160" to use medium density for the WVGA800 screen.</li>
+specify "Abstracted LCD density" (<em>hw.lcd.density</em>). Enter an appropriate
+value, such as "120" for a low-density screen, "160" for a medium density screen,
+or "240" for a high-density screen.</li>
<li>Set any other hardware options and complete the AVD creation.</li>
</ol>
diff --git a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
index c8d241c..6cb98e6 100644
--- a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
@@ -4,7 +4,7 @@ page.title=Activity and Task Design Guidelines
<div id="qv-wrapper">
<div id="qv">
-<h2>Activity and task design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>Activities are the main building blocks of Android applications. </li>
diff --git a/docs/html/guide/practices/ui_guidelines/icon_design.jd b/docs/html/guide/practices/ui_guidelines/icon_design.jd
index 51ccfaf..389d5fa 100644
--- a/docs/html/guide/practices/ui_guidelines/icon_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/icon_design.jd
@@ -4,7 +4,7 @@ page.title=Icon Design Guidelines, Android 2.0
<div id="qv-wrapper">
<div id="qv">
-<h2>Icon design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>You can use several types of icons in an Android application.</li>
@@ -35,25 +35,30 @@ page.title=Icon Design Guidelines, Android 2.0
</ol>
-<h2>See also</h2>
-
-<ol>
-<li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple
-Screens</a></li>
-<li><a href="{@docRoot}shareables/icon_templates-v2.0.zip">Android Icon
-Templates Pack, v2.0 &raquo;</a></li>
-</ol>
-
<h2>Older versions</h2>
<ol>
<li style="margin-top:4px;"><a
href="{@docRoot}guide/practices/ui_guidelines/icon_design_1.html">Icon Design
Guidelines, Android 1.0</a></li>
+</ol>
+
+<h2>Downloads</h2>
+
+<ol>
+<li><a href="{@docRoot}shareables/icon_templates-v2.0.zip">Android Icon
+Templates Pack, v2.0 &raquo;</a></li>
<li><a href="{@docRoot}shareables/icon_templates-v1.0.zip">Android Icon
Templates Pack, v1.0 &raquo;</a></li>
</ol>
+<h2>See also</h2>
+
+<ol>
+<li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple
+Screens</a></li>
+</ol>
+
</div>
</div>
diff --git a/docs/html/guide/practices/ui_guidelines/icon_design_1.jd b/docs/html/guide/practices/ui_guidelines/icon_design_1.jd
index 1c75843..995cfea 100644
--- a/docs/html/guide/practices/ui_guidelines/icon_design_1.jd
+++ b/docs/html/guide/practices/ui_guidelines/icon_design_1.jd
@@ -4,7 +4,7 @@ page.title=Icon Design Guidelines, Android 1.0
<div id="qv-wrapper">
<div id="qv">
-<h2>Icon design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>You can use several types of icons in an Android application.</li>
@@ -35,14 +35,19 @@ application can use the standard icons by referencing them as resources.</li>
</ol>
-<h2>See also</h2>
+<h2>Downloads</h2>
<ol>
-<li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></li>
<li><a href="{@docRoot}shareables/icon_templates-v1.0.zip">Android Icon
Templates Pack, v1.0 &raquo;</a></li>
</ol>
+<h2>See also</h2>
+
+<ol>
+<li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></li>
+</ol>
+
<h2>Newer versions</h2>
diff --git a/docs/html/guide/practices/ui_guidelines/menu_design.jd b/docs/html/guide/practices/ui_guidelines/menu_design.jd
index ebf8a4b..840ee66 100644
--- a/docs/html/guide/practices/ui_guidelines/menu_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/menu_design.jd
@@ -4,7 +4,7 @@ page.title=Menu Design Guidelines
<div id="qv-wrapper">
<div id="qv">
-<h2>Menu design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>An Options menu is for any commands that are global to the current activity. </li>
diff --git a/docs/html/guide/practices/ui_guidelines/widget_design.jd b/docs/html/guide/practices/ui_guidelines/widget_design.jd
index fc62fe6..e978069 100644
--- a/docs/html/guide/practices/ui_guidelines/widget_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/widget_design.jd
@@ -4,7 +4,7 @@ page.title=Widget Design Guidelines
<div id="qv-wrapper">
<div id="qv">
-<h2>Widget design quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>Widgets have six standard sizes on the Home screen</li>
@@ -27,7 +27,7 @@ page.title=Widget Design Guidelines
<h2>See also</h2>
<ol>
-<li><a href="{@docRoot}guide/topics/appwidgets/index.html">AppWidgets</a> topic in the <em>Dev Guide</em></li>
+<li><a href="{@docRoot}guide/topics/appwidgets/index.html">App Widgets</a></li>
<li><a href="http://android-developers.blogspot.com/2009/04/introducing-home-screen-widgets-and.html">AppWidgets blog post</a></li>
</ol>
diff --git a/docs/html/guide/publishing/app-signing.jd b/docs/html/guide/publishing/app-signing.jd
index 34d9419..6758054 100644
--- a/docs/html/guide/publishing/app-signing.jd
+++ b/docs/html/guide/publishing/app-signing.jd
@@ -4,7 +4,7 @@ page.title=Signing Your Applications
<div id="qv-wrapper">
<div id="qv">
-<h2>Signing quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>All Android apps <em>must</em> be signed</a></li>
diff --git a/docs/html/guide/publishing/licensing.jd b/docs/html/guide/publishing/licensing.jd
index 07af68d..fc83ec0 100644
--- a/docs/html/guide/publishing/licensing.jd
+++ b/docs/html/guide/publishing/licensing.jd
@@ -4,7 +4,7 @@ page.title=Licensing Your Applications
<div id="qv-wrapper">
<div id="qv">
- <h2>Market Licensing quickview: </h2>
+ <h2>Quickview</h2>
<ul>
<li>Licensing lets you protect your application on any device that includes Android Market.</li>
<li>Your app maintains control of how it enforces its licensing status. </li>
diff --git a/docs/html/guide/publishing/preparing.jd b/docs/html/guide/publishing/preparing.jd
index 442c12a..45a5b77 100644
--- a/docs/html/guide/publishing/preparing.jd
+++ b/docs/html/guide/publishing/preparing.jd
@@ -1,20 +1,6 @@
page.title=Preparing to Publish: A Checklist
@jd:body
-<!--
-<div id="qv-wrapper">
-<div id="qv">
-
-<h2>In this document</h2>
-
-<ol>
-<li><a href=""></a></li>
-</ol>
-
-</div>
-</div>
--->
-
<p>Publishing an application means testing it, packaging it appropriately, and
making it available to users of Android-powered mobile devices.</p>
@@ -34,7 +20,7 @@ Applications</a> document. </p>
<div class="special">
-<p>Before you consider your application ready for release:</p>
+<p><a href="#releaseready">Before you consider your application ready for release</a>:</p>
<ol>
<li>Test your application extensively on an actual device </li>
@@ -44,7 +30,7 @@ Applications</a> document. </p>
<li>Turn off logging and debugging and clean up data/files</li>
</ol>
-<p>Before you do the final compile of your application:</p>
+<p><a href="#finalcompile">Before you do the final compile of your application</a>:</p>
<ol start="6">
<li>Version your application</li>
@@ -52,8 +38,9 @@ Applications</a> document. </p>
<li>Register for a Maps API Key, if your application is using MapView elements</li>
</ol>
-<p><em>Compile your application...</em></p>
-<p>After compiling your application:</p>
+<p><a href="#compile">Compile your application</a></p>
+
+<p><a href="#post-compile">After you compile your application</a>:</p>
<ol start="9">
<li>Sign your application</li>
<li>Test your compiled application</li>
@@ -242,7 +229,7 @@ to download Maps data. </li>
you can compile your application for release.</p>
-<h2 id="post-compile">After compiling your application</h2>
+<h2 id="post-compile">After you compile your application</h2>
<h3 id="signapp">9. Sign your application</h3>
diff --git a/docs/html/guide/publishing/publishing.jd b/docs/html/guide/publishing/publishing.jd
index 9b470c8..af1ea74 100644
--- a/docs/html/guide/publishing/publishing.jd
+++ b/docs/html/guide/publishing/publishing.jd
@@ -4,7 +4,7 @@ page.title=Publishing Your Applications
<div id="qv-wrapper">
<div id="qv">
-<h2>Publishing quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>You can publish your application using a hosted service such as Android Market or through a web server.</li>
diff --git a/docs/html/guide/publishing/versioning.jd b/docs/html/guide/publishing/versioning.jd
index 1d55f8a..b646247 100644
--- a/docs/html/guide/publishing/versioning.jd
+++ b/docs/html/guide/publishing/versioning.jd
@@ -4,7 +4,7 @@ page.title=Versioning Your Applications
<div id="qv-wrapper">
<div id="qv">
-<h2>Versioning quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>Your application <em>must</em> be versioned</a></li>
diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd
index 7a8dd59..3de5627 100644
--- a/docs/html/guide/topics/appwidgets/index.jd
+++ b/docs/html/guide/topics/appwidgets/index.jd
@@ -3,12 +3,14 @@ page.title=App Widgets
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.appwidget.AppWidgetProvider}</li>
- <li>{@link android.appwidget.AppWidgetProviderInfo}</li>
- <li>{@link android.appwidget.AppWidgetManager}</li>
- </ol>
+ <h2>Quickview</h2>
+ <ul>
+ <li>App Widgets provide users access to some of your application features
+directly from the Home screen (without the need to launch an activity)</li>
+ <li>App Widgets are backed by a special kind of broadcast receiver that handles the App
+Widget lifecycle</li>
+ </ul>
+
<h2>In this document</h2>
<ol>
<li><a href="#Basics">The Basics</a></li>
@@ -28,6 +30,13 @@ page.title=App Widgets
</li>
</ol>
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.appwidget.AppWidgetProvider}</li>
+ <li>{@link android.appwidget.AppWidgetProviderInfo}</li>
+ <li>{@link android.appwidget.AppWidgetManager}</li>
+ </ol>
+
<h2>See also</h2>
<ol>
<li><a href="{@docRoot}guide/practices/ui_guidelines/widget_design.html">App Widget Design
diff --git a/docs/html/guide/topics/fundamentals.jd b/docs/html/guide/topics/fundamentals.jd
index db06efc..84c2ed2 100644
--- a/docs/html/guide/topics/fundamentals.jd
+++ b/docs/html/guide/topics/fundamentals.jd
@@ -3,14 +3,6 @@ page.title=Application Fundamentals
<div id="qv-wrapper">
<div id="qv">
-<h2>Key classes</h2>
-<ol>
-<li>{@link android.app.Activity}</li>
-<li>{@link android.app.Service}</li>
-<li>{@link android.content.BroadcastReceiver}</li>
-<li>{@link android.content.ContentProvider}</li>
-<li>{@link android.content.Intent}</li>
-</ol>
<h2>In this document</h2>
<ol>
@@ -43,6 +35,16 @@ page.title=Application Fundamentals
<li><a href="#proclife">Processes and lifecycles</a></li>
</ol></li>
</ol>
+
+<h2>Key classes</h2>
+<ol>
+<li>{@link android.app.Activity}</li>
+<li>{@link android.app.Service}</li>
+<li>{@link android.content.BroadcastReceiver}</li>
+<li>{@link android.content.ContentProvider}</li>
+<li>{@link android.content.Intent}</li>
+</ol>
+
</div>
</div>
diff --git a/docs/html/guide/topics/fundamentals/activities.jd b/docs/html/guide/topics/fundamentals/activities.jd
new file mode 100644
index 0000000..b616ab8
--- /dev/null
+++ b/docs/html/guide/topics/fundamentals/activities.jd
@@ -0,0 +1,762 @@
+page.title=Activities
+parent.title=Application Fundamentals
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+ <li>An activity provides a user interface for a single screen in your application</li>
+ <li>Activities can move into the background and then be resumed with their state restored</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+ <li><a href="#Creating">Creating an Activity</a>
+ <ol>
+ <li><a href="#UI">Implementing a user interface</a></li>
+ <li><a href="#Declaring">Declaring the activity in the manifest</a></li>
+ </ol>
+ </li>
+ <li><a href="#StartingAnActivity">Starting an Activity</a>
+ <ol>
+ <li><a href="#StartingAnActivityForResult">Starting an Activity for a Result</a></li>
+ </ol>
+ </li>
+ <li><a href="#Lifecycle">Managing the Activity Lifecycle</a>
+ <ol>
+ <li><a href="#ImplementingLifecycleCallbacks">Implementing the lifecycle callbacks</a></li>
+ <li><a href="#SavingActivityState">Saving activity state</a></li>
+ <li><a href="#ConfigurationChanges">Handling configuration changes</a></li>
+ <li><a href="#CoordinatingActivities">Coordinating activities</a></li>
+ </ol>
+ </li>
+</ol>
+
+<h2>Key classes</h2>
+<ol>
+ <li>{@link android.app.Activity}</li>
+</ol>
+
+<h2>See also</h2>
+<ol>
+ <li><a href="{@docRoot}resources/tutorials/hello-world.html">Hello World Tutorial</a></li>
+ <li><a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack">Tasks and Back
+Stack</a></li>
+</ol>
+
+</div>
+</div>
+
+
+
+<p>An {@link android.app.Activity} is an application component that provides a screen with which
+users can interact in order to do something, such as dial the phone, take a photo, send an email, or
+view a map. Each activity is given a window in which to draw its user interface. The window
+typically fills the screen, but may be smaller than the screen and float on top of other
+windows.</p>
+
+<p> An application usually consists of multiple activities that are loosely bound
+to each other. Typically, one activity in an application is specified as the "main" activity, which
+is presented to the user when launching the application for the first time. Each
+activity can then start another activity in order to perform different actions. Each time a new
+activity starts, the previous activity is stopped, but the system preserves the activity
+in a stack (the "back stack"). When a new activity starts, it is pushed onto the back stack and
+takes user focus. The back stack abides to the basic "last in, first out" queue mechanism,
+so, when the user is done with the current activity and presses the BACK key, it
+is popped from the stack (and destroyed) and the previous activity resumes. (The back stack is
+discussed more in the <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks
+and Back Stack</a> document.)</p>
+
+<p>When an activity is stopped because a new activity starts, it is notified of this change in state
+through the activity's lifecycle callback methods.
+There are several callback methods that an activity might receive, due to a change in its
+state&mdash;whether the system is creating it, stopping it, resuming it, or destroying it&mdash;and
+each callback provides you the opportunity to perform specific work that's
+appropriate to that state change. For instance, when stopped, your activity should release any
+large objects, such as network or database connections. When the activity resumes, you can
+reacquire the necessary resources and resume actions that were interrupted. These state transitions
+are all part of the activity lifecycle.</p>
+
+<p>The rest of this document discusses the basics of how to build and use an activity,
+including a complete discussion of how the activity lifecycle works, so you can properly manage
+the transition between various activity states.</p>
+
+
+
+<h2 id="Creating">Creating an Activity</h2>
+
+<p>To create an activity, you must create a subclass of {@link android.app.Activity} (or
+an existing subclass of it). In your subclass, you need to implement callback methods that the
+system calls when the activity transitions between various states of its lifecycle, such as when
+the activity is being created, stopped, resumed, or destroyed. The two most important callback
+methods are:</p>
+
+<dl>
+ <dt>{@link android.app.Activity#onCreate onCreate()}</dt>
+ <dd>You must implement this method. The system calls this when creating your
+ activity. Within your implementation, you should initialize the essential components of your
+activity.
+ Most importantly, this is where you must call {@link android.app.Activity#setContentView
+ setContentView()} to define the layout for the activity's user interface.</dd>
+ <dt>{@link android.app.Activity#onPause onPause()}</dt>
+ <dd>The system calls this method as the first indication that the user is leaving your
+activity (though it does not always mean the activity is being destroyed). This is usually where you
+should commit any changes that should be persisted beyond the current user session (because
+the user might not come back).</dd>
+</dl>
+
+<p>There are several other lifecycle callback methods that you should use in order to provide a
+fluid user experience between activities and handle unexpected interuptions that cause your activity
+to be stopped and even destroyed. All of the lifecycle callback methods are discussed later, in
+the section about <a href="#Lifecycle">Managing the Activity Lifecycle</a>.</p>
+
+
+
+<h3 id="UI">Implementing a user interface</h3>
+
+<p> The user interface for an activity is provided by a hierarchy of views&mdash;objects derived
+from the {@link android.view.View} class. Each view controls a particular rectangular space
+within the activity's window and can respond to user interaction. For example, a view might be a
+button that initiates an action when the user touches it.</p>
+
+<p>Android provides a number of ready-made views that you can use to design and organize your
+layout. "Widgets" are views that provide a visual (and interactive) elements for the screen, such
+as a button, text field, checkbox, or just an image. "Layouts" are views derived from {@link
+android.view.ViewGroup} that provide a unique layout model for its child views, such as a linear
+layout, a grid layout, or relative layout. You can also subclass the {@link android.view.View} and
+{@link android.view.ViewGroup} classes (or existing subclasses) to create your own widgets and
+layouts and apply them to your activity layout.</p>
+
+<p>The most common way to define a layout using views is with an XML layout file saved in your
+application resources. This way, you can maintain the design of your user interface separately from
+the source code that defines the activity's behavior. You can set the layout as the UI for your
+activity with {@link android.app.Activity#setContentView(int) setContentView()}, passing the
+resource ID for the layout. However, you can also create new {@link android.view.View}s in your
+activity code and build a view hierarchy by inserting new {@link
+android.view.View}s into a {@link android.view.ViewGroup}, then use that layout by passing the root
+{@link android.view.ViewGroup} to {@link android.app.Activity#setContentView(View)
+setContentView()}.</p>
+
+<p>For information about creating a user interface, see the <a
+href="{@docRoot}guide/topics/ui/index.html">User Interface</a> documentation.</p>
+
+
+
+<h3 id="Declaring">Declaring the activity in the manifest</h3>
+
+<p>You must declare your activity in the manifest file in order for it to
+be accessible to the system. To decalare your activity, open your manifest file and add an <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a> element
+as a child of the <a
+href="{@docRoot}guide/topics/manifest/application-element.html">{@code &lt;application&gt;}</a>
+element. For example:</p>
+
+<pre>
+&lt;manifest ... &gt;
+ &lt;application ... &gt;
+ &lt;activity android:name=".ExampleActivity" /&gt;
+ ...
+ &lt;/application ... &gt;
+ ...
+&lt;/manifest &gt;
+</pre>
+
+<p>There are several other attributes that you can include in this element, to define properties
+such as the label for the activity, an icon for the activity, or a theme to style the activity's
+UI. See the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a> element
+reference for more information about available attributes.</p>
+
+
+<h4>Using intent filters</h4>
+
+<p>An <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code
+&lt;activity&gt;}</a> element can also specify various intent filters&mdash;using the <a
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code
+&lt;intent-filter&gt;}</a> element&mdash;in order to declare how other application components may
+activate it.</p>
+
+<p>When you create a new application using the Android SDK tools, the stub activity
+that's created for you automatically includes an intent filter that declares the activity
+responds to the "main" action and should be placed in the "launcher" category. The intent filter
+looks like this:</p>
+
+<pre>
+&lt;activity android:name=".ExampleActivity" android:icon="@drawable/app_icon"&gt;
+ &lt;intent-filter&gt;
+ &lt;action android:name="android.intent.action.MAIN" /&gt;
+ &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
+ &lt;/intent-filter&gt;
+&lt;/activity&gt;
+</pre>
+
+<p>The <a href="{@docRoot}guide/topics/manifest/action-element.html">{@code
+&lt;action&gt;}</a> element specifies that this is the "main" entry point to the application. The <a
+href="{@docRoot}guide/topics/manifest/category-element.html">{@code
+&lt;category&gt;}</a> element specifies that this activity should be listed in the
+system's application launcher (to allow users to launch this activity).</p>
+
+<p>If you intend for your application to be self-contained and not allow other applications to
+activate its activities, then you don't need any other intent filters. Only one activity should
+have the "main" action and "launcher" category, as in the previous example. Activities that
+you don't want to make available to other applications should have no intent filters and you can
+start them yourself using explicit intents (as discussed in the following section).</p>
+
+<p>However, if you want your activity to respond to implicit intents that are delivered from
+other applications (and your own), then you must define additional intent filters for your
+activity. For each type of intent to which you want to respond, you must include an <a
+href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code
+&lt;intent-filter&gt;}</a> that includes an
+<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code
+&lt;action&gt;}</a> element and, optionally, a <a
+href="{@docRoot}guide/topics/manifest/category-element.html">{@code
+&lt;category&gt;}</a> element and/or a <a
+href="{@docRoot}guide/topics/manifest/data-element.html">{@code
+&lt;data&gt;}</a> element. These elements specify the type of intent to which your activity can
+respond.</p>
+
+<p>For more information about how your activities can respond to intents, see the <a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>
+document.</p>
+
+
+
+<h2 id="StartingAnActivity">Starting an Activity</h2>
+
+<p>You can start another activity by calling {@link android.app.Activity#startActivity
+ startActivity()}, passing it an {@link android.content.Intent} that describes the activity you
+ want to start. The intent specifies either the exact activity you want to start or describes the
+ type of action you want to perform (and the system selects the appropriate activity for you,
+which
+ can even be from a different application). An intent can also carry small amounts of data to be
+ used by the activity that is started.</p>
+
+<p>When working within your own application, you'll often need to simply launch a known activity.
+ You can do so by creating an intent that explicitly defines the activity you want to start,
+using the class name. For example, here's how one activity starts another activity named {@code
+SignInActivity}:</p>
+
+<pre>
+Intent intent = new Intent(this, SignInActivity.class);
+startActivity(intent);
+</pre>
+
+<p>However, your application might also want to perform some action, such as send an email, text
+ message, or status update, using data from your activity. In this case, your application might
+ not have its own activities to perform such actions, so you can instead leverage the activities
+ provided by other applications on the device, which can perform the actions for you. This is where
+intents are really valuable&mdash;you can create an intent that describes an action you want to
+perform and the system
+ launches the appropriate activity from another application. If there are
+ multiple activities that can handle the intent, then the user can select which one to use. For
+ example, if you want to allow the user to send an email message, you can create the
+ following intent:</p>
+
+<pre>
+Intent intent = new Intent(Intent.ACTION_SEND);
+intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
+startActivity(intent);
+</pre>
+
+<p>The {@link android.content.Intent#EXTRA_EMAIL} extra added to the intent is a string array of
+ email addresses to which the email should be sent. When an email application responds to this
+ intent, it reads the string array provided in the extra and places them in the "to" field of the
+ email composition form. In this situation, the email application's activity starts and when the
+ user is done, your activity resumes.</p>
+
+
+
+
+<h3 id="StartingAnActivityForResult">Starting an activity for a result</h3>
+
+<p>Sometimes, you might want to receive a result from the activity that you start. In that case,
+ start the activity by calling {@link android.app.Activity#startActivityForResult
+ startActivityForResult()} (instead of {@link android.app.Activity#startActivity
+ startActivity()}). To then receive the result from the subsequent
+activity, implement the {@link android.app.Activity#onActivityResult onActivityResult()} callback
+ method. When the subsequent activity is done, it returns a result in an {@link
+android.content.Intent} to your {@link android.app.Activity#onActivityResult onActivityResult()}
+method.</p>
+
+<p>For example, perhaps you want the user to pick one of their contacts, so your activity can
+do something with the information in that contact. Here's how you can create such an intent and
+handle the result:</p>
+
+<pre>
+private void pickContact() {
+ // Create an intent to "pick" a contact, as defined by the content provider URI
+ Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
+ startActivityForResult(intent, PICK_CONTACT_REQUEST);
+}
+
+&#64;Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
+ if (resultCode == Activity.RESULT_OK &amp;&amp; requestCode == PICK_CONTACT_REQUEST) {
+ // Perform a query to the contact's content provider for the contact's name
+ Cursor cursor = getContentResolver().query(data.getData(),
+ new String[] {Contacts.DISPLAY_NAME}, null, null, null);
+ if (cursor.moveToFirst()) { // True if the cursor is not empty
+ int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
+ String name = cursor.getString(columnIndex);
+ // Do something with the selected contact's name...
+ }
+ }
+}
+</pre>
+
+<p>This example shows the basic logic you should use in your {@link
+android.app.Activity#onActivityResult onActivityResult()} method in order to handle an
+activity result. The first condition checks whether the request was successful&mdash;if it was, then
+the {@code resultCode} will be {@link android.app.Activity#RESULT_OK}&mdash;and whether the request
+to which this result is responding is known&mdash;in this case, the {@code requestCode} matches the
+second parameter sent with {@link android.app.Activity#startActivityForResult
+startActivityForResult()}. From there, the code handles the activity result by querying the
+data returned in an {@link android.content.Intent} (the {@code data} parameter).</p>
+
+<p>What happens is, a {@link
+android.content.ContentResolver} performs a query against a content provider, which returns a
+{@link android.database.Cursor} that allows the queried data to be read. For more information, see
+the <a
+href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> document.</p>
+
+<p>For more information about using intents, see the <a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent
+Filters</a> document.</p>
+
+
+<h2 id="ShuttingDown">Shutting Down an Activity</h2>
+
+<p>You can shut down an activity by calling its {@link android.app.Activity#finish
+finish()} method. You can also shut down a separate activity that you previously started by calling
+{@link android.app.Activity#finishActivity finishActivity()}.</p>
+
+<p class="note"><strong>Note:</strong> In most cases, you should not explicitly finish an activity
+using these methods. As discussed in the following section about the activity lifecycle, the
+Android system manages the life of an activity for you, so you do not need to finish your own
+activities. Calling these methods could adversely affect the expected user
+experience and should only be used when you absolutely do not want the user to return to this
+instance of the activity.</p>
+
+
+<h2 id="Lifecycle">Managing the Activity Lifecycle</h2>
+
+<p>Managing the lifecycle of your activities by implementing callback methods is
+crucial to developing a strong
+and flexible application. The lifecycle of an activity is directly affected by its association with
+other activities, its task and back stack.</p>
+
+<p>An activity can exist in essentially three states:</p>
+
+<dl>
+ <dt><i>Resumed</i></dt>
+ <dd>The activity is in the foreground of the screen and has user focus. (This state is
+also sometimes referred to as "running".)</dd>
+
+ <dt><i>Paused</i></dt>
+ <dd>Another activity is in the foreground and has focus, but this one is still visible. That is,
+another activity is visible on top of this one and that activity is partially transparent or doesn't
+cover the entire screen. A paused activity is completely alive (the {@link android.app.Activity}
+object is retained in memory, it maintains all state and member information, and remains attached to
+the window manager), but can be killed by the system in extremely low memory situations.</dd>
+
+ <dt><i>Stopped</i></dt>
+ <dd>The activity is completely obscured by another activity (the activity is now in the
+"background"). A stopped activity is also still alive (the {@link android.app.Activity}
+object is retained in memory, it maintains all state and member information, but is <em>not</em>
+attached to the window manager). However, it is no longer visible to the user and it
+can be killed by the system when memory is needed elsewhere.</dd>
+</dl>
+
+<p>If an activity is paused or stopped, the system can drop it from memory either by asking it to
+finish (calling its {@link android.app.Activity#finish finish()} method), or simply killing its
+process. When the activity is opened again (after being finished or killed), it must be created all
+over.</p>
+
+
+
+<h3 id="ImplementingLifecycleCallbacks">Implementing the lifecycle callbacks</h3>
+
+<p>When an activity transitions into and out of the different states described above, it is notified
+through various callback methods. All of the callback methods are hooks that you
+can override to do appropriate work when the state of your activity changes. The following skeleton
+activity includes each of the fundamental lifecycle methods:</p>
+
+
+<pre>
+public class ExampleActivity extends Activity {
+ &#64;Override
+ public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // The activity is being created.
+ }
+ &#64;Override
+ protected void {@link android.app.Activity#onStart onStart()} {
+ super.onStart();
+ // The activity is about to become visible.
+ }
+ &#64;Override
+ protected void {@link android.app.Activity#onResume onResume()} {
+ super.onResume();
+ // The activity has become visible (it is now "resumed").
+ }
+ &#64;Override
+ protected void {@link android.app.Activity#onPause onPause()} {
+ super.onPause();
+ // Another activity is taking focus (this activity is about to be "paused").
+ }
+ &#64;Override
+ protected void {@link android.app.Activity#onStop onStop()} {
+ super.onStop();
+ // The activity is no longer visible (it is now "stopped")
+ }
+ &#64;Override
+ protected void {@link android.app.Activity#onDestroy onDestroy()} {
+ super.onDestroy();
+ // The activity is about to be destroyed.
+ }
+}
+</pre>
+
+<p class="note"><strong>Note:</strong> Your implementation of these lifecycle methods must
+always call the superclass implementation before doing any work, as shown in the examples above.</p>
+
+<p>Taken together, these methods define the entire lifecycle of an activity. By implementing these
+methods, you can monitor three nested loops in the activity lifecycle: </p>
+
+<ul>
+<li>The <b>entire lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onCreate onCreate()} and the call to {@link
+android.app.Activity#onDestroy}. Your activity should perform setup of
+"global" state (such as defining layout) in {@link android.app.Activity#onCreate onCreate()}, and
+release all remaining resources in {@link android.app.Activity#onDestroy}. For example, if your
+activity has a thread running in the background to download data from the network, it might create
+that thread in {@link android.app.Activity#onCreate onCreate()} and then stop the thread in {@link
+android.app.Activity#onDestroy}.</li>
+
+<li><p>The <b>visible lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onStart onStart()} and the call to {@link
+android.app.Activity#onStop onStop()}. During this time, the user can see the activity
+on-screen and interact with it. For example, {@link android.app.Activity#onStop onStop()} is called
+when a new activity starts and this one is no longer visible. Between these two methods, you can
+maintain resources that are needed to show the activity to the user. For example, you can register a
+{@link android.content.BroadcastReceiver} in {@link
+android.app.Activity#onStart onStart()} to monitor changes that impact your UI, and unregister
+it in {@link android.app.Activity#onStop onStop()} when the user can no longer see what you are
+displaying. The system might call {@link android.app.Activity#onStart onStart()} and {@link
+android.app.Activity#onStop onStop()} multiple times during the entire lifetime of the activity, as
+the activity alternates between being visible and hidden to the user.</p></li>
+
+<li><p>The <b>foreground lifetime</b> of an activity happens between the call to {@link
+android.app.Activity#onResume onResume()} and the call to {@link android.app.Activity#onPause
+onPause()}. During this time, the activity is in front of all other activities on screen and has
+user input focus. An activity can frequently transition in and out of the foreground&mdash;for
+example, {@link android.app.Activity#onPause onPause()} is called when the device goes to sleep or
+when a dialog appears. Because this state can transition often, the code in these two methods should
+be fairly lightweight in order to avoid slow transitions that make the user wait.</p></li>
+</ul>
+
+<p>Figure 1 illustrates these loops and the paths an activity might take between states.
+The rectangles represent the callback methods you can implement to perform operations when
+the activity transitions between states. <p>
+
+<img src="{@docRoot}images/activity_lifecycle.png" alt="" />
+<p class="img-caption"><strong>Figure 1.</strong> The activity lifecycle.</p>
+
+<p>The same lifecycle callback methods are listed in table 1, which describes each of the callback
+methods in more detail and locates each one within the
+activity's overall lifecycle, including whether the system can kill the activity after the
+callback method completes.</p>
+
+<p class="table-caption"><strong>Table 1.</strong> A summary of the activity lifecycle's
+callback methods.</p>
+
+<table border="2" width="85%" frame="hsides" rules="rows">
+<colgroup align="left" span="3"></colgroup>
+<colgroup align="left"></colgroup>
+<colgroup align="center"></colgroup>
+<colgroup align="center"></colgroup>
+
+<thead>
+<tr><th colspan="3">Method</th> <th>Description</th> <th>Killable after?</th> <th>Next</th></tr>
+</thead>
+
+<tbody>
+<tr>
+ <td colspan="3" align="left"><code>{@link android.app.Activity#onCreate onCreate()}</code></td>
+ <td>Called when the activity is first created.
+ This is where you should do all of your normal static set up &mdash;
+ create views, bind data to lists, and so on. This method is passed
+ a Bundle object containing the activity's previous state, if that
+ state was captured (see <a href="#actstate">Saving Activity State</a>,
+ later).
+ <p>Always followed by {@code onStart()}.</p></td>
+ <td align="center">No</td>
+ <td align="center">{@code onStart()}</td>
+</tr>
+
+<tr>
+ <td rowspan="5" style="border-left: none; border-right: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td colspan="2" align="left"><code>{@link android.app.Activity#onRestart
+onRestart()}</code></td>
+ <td>Called after the activity has been stopped, just prior to it being
+ started again.
+ <p>Always followed by {@code onStart()}</p></td>
+ <td align="center">No</td>
+ <td align="center">{@code onStart()}</td>
+</tr>
+
+<tr>
+ <td colspan="2" align="left"><code>{@link android.app.Activity#onStart onStart()}</code></td>
+ <td>Called just before the activity becomes visible to the user.
+ <p>Followed by {@code onResume()} if the activity comes
+ to the foreground, or {@code onStop()} if it becomes hidden.</p></td>
+ <td align="center">No</td>
+ <td align="center">{@code onResume()} <br/>or<br/> {@code onStop()}</td>
+</tr>
+
+<tr>
+ <td rowspan="2" style="border-left: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ <td align="left"><code>{@link android.app.Activity#onResume onResume()}</code></td>
+ <td>Called just before the activity starts
+ interacting with the user. At this point the activity is at
+ the top of the activity stack, with user input going to it.
+ <p>Always followed by {@code onPause()}.</p></td>
+ <td align="center">No</td>
+ <td align="center">{@code onPause()}</td>
+</tr>
+
+<tr>
+ <td align="left"><code>{@link android.app.Activity#onPause onPause()}</code></td>
+ <td>Called when the system is about to start resuming another
+ activity. This method is typically used to commit unsaved changes to
+ persistent data, stop animations and other things that may be consuming
+ CPU, and so on. It should do whatever it does very quickly, because
+ the next activity will not be resumed until it returns.
+ <p>Followed either by {@code onResume()} if the activity
+ returns back to the front, or by {@code onStop()} if it becomes
+ invisible to the user.</td>
+ <td align="center"><strong style="color:#800000">Yes</strong></td>
+ <td align="center">{@code onResume()} <br/>or<br/> {@code onStop()}</td>
+</tr>
+
+<tr>
+ <td colspan="2" align="left"><code>{@link android.app.Activity#onStop onStop()}</code></td>
+ <td>Called when the activity is no longer visible to the user. This
+ may happen because it is being destroyed, or because another activity
+ (either an existing one or a new one) has been resumed and is covering it.
+ <p>Followed either by {@code onRestart()} if
+ the activity is coming back to interact with the user, or by
+ {@code onDestroy()} if this activity is going away.</p></td>
+ <td align="center"><strong style="color:#800000">Yes</strong></td>
+ <td align="center">{@code onRestart()} <br/>or<br/> {@code onDestroy()}</td>
+</tr>
+
+<tr>
+ <td colspan="3" align="left"><code>{@link android.app.Activity#onDestroy
+onDestroy()}</code></td>
+ <td>Called before the activity is destroyed. This is the final call
+ that the activity will receive. It could be called either because the
+ activity is finishing (someone called <code>{@link android.app.Activity#finish
+ finish()}</code> on it), or because the system is temporarily destroying this
+ instance of the activity to save space. You can distinguish
+ between these two scenarios with the <code>{@link
+ android.app.Activity#isFinishing isFinishing()}</code> method.</td>
+ <td align="center"><strong style="color:#800000">Yes</strong></td>
+ <td align="center"><em>nothing</em></td>
+</tr>
+</tbody>
+</table>
+
+<p>The column labeled "Killable after?" indicates whether or not the system can
+kill the process hosting the activity at any time <em>after the method returns</em>, without
+executing another line of the activity's code. Three methods are marked "yes": ({@link
+android.app.Activity#onPause
+onPause()}, {@link android.app.Activity#onStop onStop()}, and {@link android.app.Activity#onDestroy
+onDestroy()}). Because {@link android.app.Activity#onPause onPause()} is the first
+of the three, once the activity is created, {@link android.app.Activity#onPause onPause()} is the
+last method that's guaranteed to be called before the process <em>can</em> be killed&mdash;if
+the system must recover memory in an emergency, then {@link
+android.app.Activity#onStop onStop()} and {@link android.app.Activity#onDestroy onDestroy()} might
+not be called. Therefore, you should use {@link android.app.Activity#onPause onPause()} to write
+crucial persistent data (such as user edits) to storage. However, you should be selective about
+what information must be retained during {@link android.app.Activity#onPause onPause()}, because any
+blocking procedures in this method block the transition to the next activity and slow the user
+experience.</p>
+
+<p> Methods that are marked "No" in the <b>Killable</b> column protect the process hosting the
+activity from being killed from the moment they are called. Thus, an activity is killable
+from the time {@link android.app.Activity#onPause onPause()} returns to the time
+{@link android.app.Activity#onResume onResume()} is called. It will not again be killable until
+{@link android.app.Activity#onPause onPause()} is again called and returns. </p>
+
+<p class="note"><strong>Note:</strong> An activity that's not technically "killable" by this
+definition in table 1 might still be killed by the system&mdash;but that would happen only in
+extreme circumstances when there is no other recourse. When an activity might be killed is
+discussed more in the <a
+href="{@docRoot}guide/topics/fundamentals/processes-and-threading.html">Processes and
+Threading</a> document.</p>
+
+
+<h3 id="SavingActivityState">Saving activity state</h3>
+
+<p>The introduction to <a href="Lifecycle">Managing the Activity Lifecycle</a> briefly mentions that
+when an activity is paused or stopped, the state of the activity is retained. This is true because
+the {@link android.app.Activity} object is still held in memory when it is paused or
+stopped&mdash;all information about its members and current state is still alive. Thus, any changes
+the user made within the activity are retained in memory, so that when the activity returns to the
+foreground (when it "resumes"), those changes are still there.</p>
+
+<div class="figure" style="width:615px">
+<img src="{@docRoot}images/fundamentals/restore_instance.png" alt="" />
+<p class="img-caption"><strong>Figure 2.</strong> The two ways in which an activity returns to user
+focus with its state intact: either the activity is stopped, then resumed and the activity state
+remains intact (left), or the activity is destroyed, then recreated and the activity must restore
+the previous activity state (right).</p>
+</div>
+
+<p>However, when the system destroys an activity in order to recover memory, the {@link
+android.app.Activity} object is destroyed, so the system cannot simply resume it with its state
+intact. Instead, the system must recreate the {@link android.app.Activity} object if the user
+navigates back to it. Yet, the user is unaware
+that the system destroyed the activity and recreated it and, thus, probably
+expects the activity to be exactly as it was. In this situation, you can ensure that
+important information about the activity state is preserved by implementing an additional
+callback method that allows you to save information about the state of your activity and then
+restore it when the the system recreates the activity.</p>
+
+<p>The callback method in which you can save information about the current state of your activity is
+{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}. The system calls this method
+before making the activity vulnerable to being destroyed and passes it
+a {@link android.os.Bundle} object. The {@link android.os.Bundle} is where you can store
+state information about the activity as name-value pairs, using methods such as {@link
+android.os.Bundle#putString putString()}. Then, if the system kills your activity's
+process and the user navigates back to your activity, the system passes the {@link
+android.os.Bundle} to {@link android.app.Activity#onCreate onCreate()} so you can restore the
+activity state you saved during {@link android.app.Activity#onSaveInstanceState
+onSaveInstanceState()}. If there is no state information to restore, then the {@link
+android.os.Bundle} passed to {@link android.app.Activity#onCreate onCreate()} is null.</p>
+
+<p class="note"><strong>Note:</strong> There's no guarantee that {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} will be called before your
+activity is destroyed, because there are cases in which it won't be necessary to save the state
+(such as when the user leaves your activity using the BACK key, because the user is explicitly
+closing the activity). If the method is called, it is always called before {@link
+android.app.Activity#onStop onStop()} and possibly before {@link android.app.Activity#onPause
+onPause()}.</p>
+
+<p>However, even if you do nothing and do not implement {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()}, some of the activity state is
+restored by the {@link android.app.Activity} class's default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Specifically, the default
+implementation calls {@link
+android.view.View#onSaveInstanceState onSaveInstanceState()} for every {@link android.view.View}
+in the layout, which allows each view to provide information about itself
+that should be saved. Almost every widget in the Android framework implements this method as
+appropriate, such that any visible changes to the UI are automatically saved and restored when your
+activity is recreated. For example, the {@link android.widget.EditText} widget saves any text
+entered by the user and the {@link android.widget.CheckBox} widget saves whether it's checked or
+not. The only work required by you is to provide a unique ID (with the <a
+href="{@docRoot}guide/topics/resources/layout-resource.html#idvalue">{@code android:id}</a>
+attribute) for each widget you want to save its state. If a widget does not have an ID, then it
+cannot save its state.</p>
+
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<p>You can also explicitly stop a view in your layout from saving its state by setting the
+{@link android.R.attr#saveEnabled android:saveEnabled} attribute to {@code "false"} or by calling
+the {@link android.view.View#setSaveEnabled setSaveEnabled()} method. Usually, you should not
+disable this, but you might if you want to restore the state of the activity UI differently.</p>
+</div>
+</div>
+
+<p>Although the default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} saves useful information about
+your activity's UI, you still might need to override it to save additional information.
+For example, you might need to save member values that changed during the activity's life (which
+might correlate to values restored in the UI, but the members that hold those UI values are not
+restored, by default).</p>
+
+<p>Because the default implementation of {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} helps save the state of the UI, if
+you override the method in order to save additional state information, you should always call the
+superclass implementation of {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}
+before doing any work.</p>
+
+<p class="note"><strong>Note:</strong> Because {@link android.app.Activity#onSaveInstanceState
+onSaveInstanceState()} is not guaranteed
+to be called, you should use it only to record the transient state of the activity (the state of
+the UI)&mdash;you should never use it to store persistent data. Instead, you should use {@link
+android.app.Activity#onPause onPause()} to store persistent data (such as data that should be saved
+to a database) when the user leaves the activity.</p>
+
+<p>A good way to test your application's ability to restore its state is to simply rotate the
+device so that the screen orientation changes. When the screen orientation changes, the system
+destroys and recreates the activity in order to apply alternative resources that might be available
+for the new orientation. For this reason alone, it's very important that your activity
+completely restores its state when it is recreated, because users regularly rotate the screen while
+using applications.</p>
+
+
+<h3 id="ConfigurationChanges">Handling configuration changes</h3>
+
+<p>Some device configurations can change during runtime (such as screen orientation, keyboard
+availability, and language). When such a change occurs, Android restarts the running Activity
+({@link android.app.Activity#onDestroy} is called, followed immediately by {@link
+android.app.Activity#onCreate onCreate()}). The restart behavior is
+designed to help your application adapt to new configurations by automatically reloading your
+application with alternative resources that you've provided. If you design your activity to
+properly handle this event, it will be more resilient to unexpected events in the activity
+lifecycle.</p>
+
+<p>The best way to handle a configuration change, such as a change in the screen orientation, is
+ to simply preserve the state of your application using {@link
+ android.app.Activity#onSaveInstanceState onSaveInstanceState()} and {@link
+android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (or {@link
+android.app.Activity#onCreate onCreate()}), as discussed in the previous section.</p>
+
+<p>For a detailed discussion about configuration changes that happen at runtime and how you should
+handle them, read <a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling
+Runtime Changes</a>.</p>
+
+
+
+<h3 id="CoordinatingActivities">Coordinating activities</h3>
+
+ <p>When one activity starts another, they both experience lifecycle transitions. The first activity
+pauses and stops (though, it won't stop if it's still visible in the background), while the other
+activity is created. In case these activities share data saved to disc or elsewhere, it's important
+to understand that the first activity is not completely stopped before the second one is created.
+Rather, the process of starting the second one overlaps with the process of stopping the first
+one.</p>
+
+<p>The order of lifecycle callbacks is well defined, particularly when the two activities are in the
+same process and one is starting the other. Here's the order of operations that occur when Activity
+A starts Acivity B: </p>
+
+<ol>
+<li>Activity A's {@link android.app.Activity#onPause onPause()} method executes.</li>
+
+<li>Activity B's {@link android.app.Activity#onCreate onCreate()}, {@link
+android.app.Activity#onStart onStart()}, and {@link android.app.Activity#onResume onResume()}
+methods execute in sequence. (Activity B now has user focus.)</li>
+
+<li>Then, if Activity A is no longer visible on screen, its {@link
+android.app.Activity#onStop onStop()} method executes.</li>
+</ol>
+
+ <p>This predictable sequence of lifecycle callbacks allows you to manage the transition of
+information from one activity to another. For example, if you must write to a database when the
+first activity stops so that the following activity can read it, then you should write to the
+database during {@link android.app.Activity#onPause onPause()} instead of during {@link
+android.app.Activity#onStop onStop()}.</p>
+
+
+<h2>Beginner's Path</h2>
+
+<p>For more information about how Android maintains a history of activities and
+enables user multitasking, continue with the <b><a
+href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
+Stack</a></b> document.</p>
diff --git a/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd
new file mode 100644
index 0000000..47dc547
--- /dev/null
+++ b/docs/html/guide/topics/fundamentals/tasks-and-back-stack.jd
@@ -0,0 +1,568 @@
+page.title=Tasks and Back Stack
+parent.title=Application Fundamentals
+parent.link=index.html
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+<h2>Quickview</h2>
+<ul>
+ <li>All activities belong to a task</li>
+ <li>A task contains a collection of activities in the order in which the user interacts with
+them</li>
+ <li>Tasks can move to the background and retain the state of each activity in order for the user
+to perform other tasks without loosing their work</li>
+</ul>
+
+<h2>In this document</h2>
+<ol>
+<li><a href="#ActivityState">Saving Activity State</a></li></li>
+<li><a href="#ManagingTasks">Managing Tasks</a>
+ <ol>
+ <li><a href="#TaskLaunchModes">Defining launch modes</a></li>
+ <li><a href="#Affinities">Handling affinities</a></li>
+ <li><a href="#Clearing">Clearing the back stack</a></li>
+ <li><a href="#Starting">Starting a task</a></li>
+ </ol>
+</li>
+</ol>
+
+<h2>See also</h2>
+<ol>
+ <li><a><a href="{@docRoot}videos/index.html#v=fL6gSd4ugSI">Application Lifecycle video</a></li>
+ <li><a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;} manifest
+element</a></li>
+</ol>
+</div>
+</div>
+
+
+<p>An application usually contains multiple <a
+href="{@docRoot}guide/topics/fundamentals/activities.html">activities</a>. Each activity
+should be designed around a specific kind of action the user can perform and can start other
+activities. For example, an email application might have one activity to show a list of new email.
+When the user selects an email, a new activity opens to view that email.</p>
+
+<p>An activity can even start activities that exist in other applications on the device. For
+example, if your application wants to send an email, you can define an intent to perform a "send"
+action and include some data, such as an email address and a message. An activity from another
+application that declares itself to handle this kind of intent then opens. In this case, the intent
+is to send an email, so an email application's "compose" activity starts (if multiple activities
+support the same intent, then the system lets the user select which one to use). When the email is
+sent, your activity resumes and it seems as if the email activity was part of your application. Even
+though the activities may be from different applications, Android maintains this seamless user
+experience by keeping both activities in the same <em>task</em>.</p>
+
+<p>A task is a collection of activities that users interact with
+when performing a certain job. The activities are arranged in a stack (the "back stack"), in the
+order in which each activity is opened.</p>
+
+<!-- SAVE FOR WHEN THE FRAGMENT DOC IS ADDED
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<h3>Adding fragments to a task's back stack</h3>
+
+<p>Your activity can also include {@link android.app.Fragment}s to the back stack. For example,
+suppose you have a two-pane layout using fragments, one of which is a list view (fragment A) and the
+other being a layout to display an item from the list (fragment B). When the user selects an item
+from the list, fragment B is replaced by a new fragment (fragment C). In this case, it might be
+desireable for the user to navigate back to reveal fragment B, using the BACK key.</p>
+<p>In order to add fragment B to the back stack so that this is possible, you must call {@link
+android.app.FragmentTransaction#addToBackStack addToBackStack()} before you {@link
+android.app.FragmentTransaction#commit()} the transaction that replaces fragment B with fragment
+C.</p>
+<p>For more information about using fragments and adding them to the back stack, see the {@link
+android.app.Fragment} class documentation.</p>
+
+</div>
+</div>
+-->
+
+<p>The device Home screen is the starting place for most tasks. When the user touches an icon in the
+application
+launcher (or a shortcut on the Home screen), that application's task comes to the foreground. If no
+task exists for the application (the application has not been used recently), then a new task
+is created and the "main" activity for that application opens as the root activity in the stack.</p>
+
+<p>When the current activity starts another, the new activity is pushed on the top of the stack and
+takes focus. The previous activity remains in the stack, but is stopped. When an activity
+stops, the system retains the current state of its user interface. When the user presses the BACK
+key, the current activity is popped from the top of the stack (the activity is destroyed) and the
+previous activity resumes (the previous state of its UI is restored). Activities in the stack are
+never rearranged, only pushed and popped from the stack&mdash;pushed onto the stack when started by
+the current activity and popped off when the user leaves it using the BACK key. As such, the back
+stack operates as a "last in, first out" object structure. Figure 1 visualizes
+this behavior with a timeline showing the progress between activities along with the current back
+stack at each point in time.</p>
+
+<img src="{@docRoot}images/fundamentals/diagram_backstack.png" alt="" />
+<p class="img-caption"><strong>Figure 1.</strong> A representation of how each new activity in a
+task adds an item to the back stack. When the user presses the BACK key, the current activity is
+destroyed and the previous activity resumes.</p>
+
+
+<p>If the user continues to press BACK, then each activity in the stack is popped off to reveal the
+previous one, until the user returns to the Home screen (or to whichever activity was running when
+the task began). When all activities are removed from the stack, the task no longer exists.</p>
+
+<div class="figure" style="width:369px">
+<img src="{@docRoot}images/fundamentals/diagram_multitasking.png" alt="" /> <p
+class="img-caption"><strong>Figure 2.</strong> Two tasks: Task A is in the background, waiting
+to be resumed, while Task B receives user interaction in the foreground.</p>
+</div>
+<div class="figure" style="width:178px">
+ <img src="{@docRoot}images/fundamentals/diagram_multiple_instances.png" alt="" /> <p
+class="img-caption"><strong>Figure 3.</strong> A single activity is instantiated multiple times.</p>
+</div>
+
+<p>A task is a cohesive unit that can move to the "background" when users begin a new task or go
+to the Home screen, via the HOME key. While in the background, all the activities in the task are
+stopped, but the back stack for the task remains intact&mdash;the task has simply lost focus while
+another task takes place, as shown in figure 2. A task can then return to the "foreground" so users
+can pick up where they left off. Suppose, for example, that the current task (Task A) has three
+activities in its stack&mdash;two under the current activity. The user presses the HOME key, then
+starts a new application from the application launcher. When the Home screen appears, Task A goes
+into the background. When the new application starts, the system starts a task for that application
+(Task B) with its own stack of activities. After interacting with
+that application, the user returns Home again and selects the application that originally
+started Task A. Now, Task A comes to the
+foreground&mdash;all three activities in its stack are intact and the activity at the top of the
+stack resumes. At
+this point, the user can also switch back to Task B by going Home and selecting the application icon
+that started that task (or by touching and holding the HOME key to reveal recent tasks and selecting
+one). This is an example of multitasking on Android.</p>
+
+<p class="note"><strong>Note:</strong> Multiple tasks can be held in the background at once.
+However, if the user is running many background tasks at the same time, the system might begin
+destroying background activities in order to recover memory, causing the activity states to be lost.
+See the following section about <a href="#ActivityState">Activity state</a>.</p>
+
+<p>Because the activities in the back stack are never rearranged, if your application allows
+users to start a particular activity from more than one activity, a new instance of
+that activity is created and popped onto the stack (rather than bringing any previous instance of
+the activity to the top). As such, one activity in your application might be instantiated multiple
+times (even from different tasks), as shown in figure 3. As such, if the user navigates backward
+using the BACK key, each instance of the activity is revealed in the order they were opened (each
+with their own UI state). However, you can modify this behavior if you do not want an activity to be
+instantiated more than once. How to do so is discussed in the later section about <a
+href="#ManagingTasks">Managing Tasks</a>.</p>
+
+
+<p>To summarize the default behavior for activities and tasks:</p>
+
+<ul>
+ <li>When Activity A starts Activity B, Activity A is stopped, but the system retains its state
+(such as scroll position and text entered into forms).
+If the user presses the BACK key while in Activity B, Activity A resumes with its state
+restored.</li>
+ <li>When the user leaves a task by pressing the HOME key, the current activity is stopped and
+its task goes into the background. The system retains the state of every activity in the task. If
+the user later resumes the task by selecting the launcher icon that began the task, the task comes
+to the foreground and resumes the activity at the top of the stack.</li>
+ <li>If the user presses the BACK key, the current activity is popped from the stack and
+destroyed. The previous activity in the stack is resumed. When an activity is destroyed, the system
+<em>does not</em> retain the activity's state.</li>
+ <li>Activities can be instantiated multiple times, even from other tasks.</li>
+</ul>
+
+
+<h2 id="ActivityState">Saving Activity State</h2>
+
+<p>As discussed above, the system's default behavior preserves the state of an activity when it is
+stopped. This way, when users navigate back to a previous activity, its user interface appears
+the way they left it. However, you can&mdash;and <strong>should</strong>&mdash;proactively retain
+the state of your activities using callback methods, in case the activity is destroyed and must
+be recreated.</p>
+
+<p>When the system stops one of your activities (such as when a new activity starts or the task
+moves to the background), the system might destroy that activity completely if it needs to recover
+system memory. When this happens, information about the activity state is lost. If this happens, the
+system still
+knows that the activity has a place in the back stack, but when the activity is brought to the
+top of the stack the system must recreate it (rather than resume it). In order to
+avoid loosing the user's work, you should proactively retain it by implementing the {@link
+android.app.Activity#onSaveInstanceState onSaveInstanceState()} callback
+methods in your activity.</p>
+
+<p>For more information about how to save your activity state, see the <a
+href="{@docRoot}guide/topics/fundamentals/activities.html#SavingActivityState">Activities</a>
+document.</p>
+
+
+
+<h2 id="ManagingTasks">Managing Tasks</h2>
+
+<p>The way Android manages tasks and the back stack, as described above&mdash;by placing all
+activities started in succession in the same task and in a "last in, first out" stack&mdash;works
+great for most applications and you shouldn't have to worry about how your activities are associated
+with tasks or how they exist in the back stack. However, you might decide that you want to interrupt
+the normal behavior. Perhaps you want an activity in your application to begin a new task when it is
+started (instead of being placed within the current task); or, when you start an activity, you want
+to bring forward an existing instance of it (instead of creating a new
+instance on top of the back stack); or, you want your back stack to be cleared of all
+activitiesstart an activity except for the root activity when the user leaves the task.</p>
+
+<p>You can do these things and more, with attributes in the
+<a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code
+&lt;activity&gt;}</a> manifest element and with flags in the intent that you pass to {@link
+android.app.Activity#startActivity startActivity()}.</p>
+
+<p>In this regard, the the principal <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a>
+attributes you can use are:</p>
+
+<ul class="nolist">
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code
+taskAffinity}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#reparent">{@code
+allowTaskReparenting}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code
+clearTaskOnLaunch}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#always">{@code
+alwaysRetainTaskState}</a></li>
+ <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code
+finishOnTaskLaunch}</a></li>
+</ul>
+
+<p>And the principal intent flags you can use are:</p>
+
+<ul class="nolist">
+ <li>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</li>
+ <li>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</li>
+ <li>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</li>
+</ul>
+
+<p>In the following sections, you'll see how you can use these manifest attributes and intent
+flags to define how activities are associated with tasks and how the behave in the back stack.</p>
+
+
+<p class="caution"><strong>Caution:</strong> Most applications should not interrupt the default
+behavior for activities and tasks. If you determine that it's necessary for your activity to modify
+the default behaviors, use caution and be sure to test the usability of the activity during
+launch and when navigating back to it from other activities and tasks with the BACK key. Be sure
+to test for navigation behaviors that might conflict with the user's expected behavior.</p>
+
+
+<h3 id="TaskLaunchModes">Defining launch modes</h3>
+
+<p>Launch modes allow you to define how a new instance of an activity is associated with the
+current task. You can define different launch modes in two ways:</p>
+<ul class="nolist">
+ <li><a href="#ManifestForTasks">Using the manifest file</a>
+ <p>When you declare an activity in your manifest file, you can specify how the activity
+should associate with tasks when it starts.</li>
+ <li><a href="#IntentFlagsForTasks">Using Intent flags</a>
+ <p>When you call {@link android.app.Activity#startActivity startActivity()},
+you can include a flag in the {@link android.content.Intent} that declares how (or
+whether) the new activity should associate with the current task.</p></li>
+</ul>
+
+<p>As such, if Activity A starts Activity B, Activity B can define in its manifest how it
+should associate with the current task (if at all) and Activity A can also request how Activity
+B should associate with current task. If both activities define how Activity B
+should associate with a task, then Activity A's request (as defined in the intent) is honored
+over Activity B's request (as defined in its manifest).</p>
+
+<p class="note"><strong>Note:</strong> Some the launch modes available in the manifest
+are not available as flags for an intent and, likewise, some launch modes available as flags
+for an intent cannot be defined in the manifest.</p>
+
+
+<h4 id="ManifestForTasks">Using the manifest file</h4>
+
+<p>When declaring an activity in your manifest file, you can specify how the activity should
+associate with a task using the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a>
+element's <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a> attribute.</p>
+
+<p>The <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code
+launchMode}</a> attribute specifies an instruction on how the activity should be launched into a
+task. There are four different launch modes you can assign to the
+<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">launchMode</a></code>
+attribute:</p>
+
+<dl>
+<dt>{@code "standard"} (the default mode)</dt>
+ <dd>Default. The system creates a new instance of the activity in the task from
+which it was started and routes the intent to it. The activity can be instantiated multiple times,
+each instance can belong to different tasks, and one task can have multiple instances.</dd>
+<dt>{@code "singleTop"}</dt>
+ <dd>If an instance of the activity already exists at the top of the current task, the system
+routes the intent to that instance through a call to its {@link
+android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance of the
+activity. The activity can be instantiated multiple times, each instance can
+belong to different tasks, and one task can have multiple instances (but only if the the
+activity at the top of the back stack is <em>not</em> an existing instance of the activity).
+ <p>For example, suppose a task's back stack consists of root activity A with activities B, C,
+and D on top (the stack is A-B-C-D; D is on top). An intent arrives for an activity of type D.
+If D has the default {@code "standard"} launch mode, a new instance of the class is launched and the
+stack becomes A-B-C-D-D. However, if D's launch mode is {@code "singleTop"}, the existing instance
+of D is deliverd the intent through {@link
+android.app.Activity#onNewIntent onNewIntent()}, because it's at the top of the stack&mdash;the
+stack remains A-B-C-D. However, if an intent arrives for an activity of type B, then a new
+instance of B is added to the stack, even if its launch mode is {@code "singleTop"}.</p>
+ <p class="note"><strong>Note:</strong> When a new instance of an activity is created,
+the user can press the BACK key to return to the previous activity. But when an existing instance of
+an activity handles a new intent, the user cannot press the BACK key to return to the state of
+the activity before the new intent arrived in {@link android.app.Activity#onNewIntent
+onNewIntent()}.</p>
+</dd>
+
+<dt>{@code "singleTask"}</dt>
+ <dd>The system creates a new task and instantiates the activity at the root of the new task.
+However, if an instance of the activity already exists in a separate task, the system routes the
+intent to the existing instance through a call to its {@link
+android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a new instance. Only
+one instance of the activity can exist at a time.
+ <p class="note"><strong>Note:</strong> Although the activity starts in a new task, the
+BACK key still returns the user to the previous activity.</p></dd>
+<dt>{@code "singleInstance"}.</dt>
+ <dd>Same as {@code "singleTask"}, except that the system doesn't launch any other activities into
+the task holding the instance. The activity is always the single and only member of its task;
+any activities started by this one open in a separate task.</dd>
+</dl>
+
+
+<p>As another example, the Android Browser application declares that the web browser activity should
+always open in its own task&mdash;by specifying the {@code singleTask} launch mode in the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a> element.
+This means that if your application issues an
+intent to open the Android Browser, its activity is <em>not</em> placed in the same
+task as your application. Instead, either a new task starts for the Browser or, if the Browser
+already has a task running in the background, that task is brought forward to handle the new
+intent.</p>
+
+<p>Regardless of whether an activity starts in a new task or in the same task as the activity that
+started it, the BACK key always takes the user to the previous activity. However, if you
+start an activity from your task (Task A) that specifies the {@code singleTask} launch mode, then
+that activity might have an instance in the background that belongs to a task with its own back
+stack (Task B). In this
+case, when Task B is brought forward to handle a new intent, the BACK key first navigates
+backward through the activities in Task B before returning to
+the top-most activity in Task A. Figure 4 visualizes this type of scenario.</p>
+
+<img src="{@docRoot}images/fundamentals/diagram_backstack_singletask_multiactivity.png" alt="" />
+<p class="img-caption"><strong>Figure 4.</strong> A representation of how an activity with
+launch mode "singleTask" is added to the back stack. If the activity is already a part of a
+background task with its own back stack (Task B), then the entire back stack also comes
+forward, on top of the current task (Task A).</p>
+
+<p>For more information about using launch modes in the manifest file, see the
+<code><a href="{@docRoot}guide/topics/manifest/activity-element.html">&lt;activity&gt;</a></code>
+element documentation, where the {@code launchMode} attribute and the accepted values are
+discussed more.</p>
+
+<p class="note"><strong>Note:</strong> The behaviors that you specify for your activity with the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> attribute
+can be overriden by flags included with the intent that start your activity, as discussed in the
+next section.</p>
+
+
+
+<h4 id="#IntentFlagsForTasks">Using Intent flags</h4>
+
+<p>When starting an activity, you can modify the default association of an activity to its task
+by including flags in the intent that you deliver to {@link
+android.app.Activity#startActivity startActivity()}. The flags you can use to modify the
+default behavior are:</p>
+
+<p>
+ <dt>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</dt>
+ <dd>Start the activity in a new task. If a task is already running for the activity you are now
+starting, that task is brought to the foreground with its last state restored and the activity
+receives the new intent in {@link android.app.Activity#onNewIntent onNewIntent()}.
+ <p>This produces the same behavior as the {@code "singleTask"} <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> value,
+discussed in the previous section.</p></dd>
+ <dt>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</dt>
+ <dd>If the activity being started is the current activity (at the top of the back stack), then
+the existing instance receives a call to {@link android.app.Activity#onNewIntent onNewIntent()},
+instead of creating a new instance of the activity.
+ <p>This produces the same behavior as the {@code "singleTop"} <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> value,
+discussed in the previous section.</p></dd>
+ <dt>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</dt>
+ <dd>If the activity being started is already running in the current task, then instead
+of launching a new instance of that activity, all of the other activities on top of it are
+destroyed and this intent is delivered to the resumed instance of the activity (now on top),
+through {@link android.app.Activity#onNewIntent onNewIntent()}).
+ <p>There is no value for the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a>
+attribute that produces this behavior.</p>
+ <p>{@code FLAG_ACTIVITY_CLEAR_TOP} is most often used in conjunction with {@code
+FLAG_ACTIVITY_NEW_TASK}. When used together, these flags are a way of locating an existing activity
+in another task and putting it in a position where it can respond to the intent. </p>
+ <p class="note"><strong>Note:</strong> If the launch mode of the designated activity is {@code
+"standard"}, it too is removed from the stack and a new instance is launched in its place to handle
+the incoming intent. That's because a new instance is always created for a new intent when the
+launch mode is {@code "standard"}. </p>
+</dd>
+</dl>
+
+
+
+
+
+<h3 id="Affinities">Handling affinities</h3>
+
+<p>The <em>affinity</em> indicates which task an activity prefers to belong to. By default, all the
+activities from the same application have an affinity for each other. So, by default, all
+activities in the same application prefer to be in the same task. However, you can modify
+the default affinity for an activity. Activities defined in
+different applications can share an affinity, or activities defined in the same application can be
+assigned different task affinities.</p>
+
+<p>You can modify the affinity for any given activity with the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> attribute
+of the <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code &lt;activity&gt;}</a>
+element.</p>
+
+<p>The <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a>
+attribute takes a string value, which must be unique from the default package name
+declared in the <a href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code
+&lt;manifest&gt;}</a> element, because the system uses that name to identify the default task
+affinity for the application.</p>
+
+<p>The affinity comes into play in two circumstances:</p>
+<ul>
+ <li>When the intent that launches an activity contains the {@link
+android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag.
+
+<p>A new activity is, by default, launched into the task of the activity
+that called {@link android.app.Activity#startActivity startActivity()}. It's pushed onto the same
+back stack as the caller. However, if the intent passed to {@link
+android.app.Activity#startActivity startActivity()} contains the {@link
+android.content.Intent#FLAG_ACTIVITY_NEW_TASK}
+flag, the system looks for a different task to house the new activity. Often, it's a new task.
+However, it doesn't have to be. If there's already an existing task with the same affinity as the
+new activity, the activity is launched into that task. If not, it begins a new task.</p>
+
+<p>If this flag causes an activity to begin a new task and the user presses the HOME key to leave
+it, there must be some way for the user to navigate back to the task. Some entities (such as the
+notification manager) always start activities in an external task, never as part of their own, so
+they always put {@code FLAG_ACTIVITY_NEW_TASK} in the intents they pass to {@link
+android.app.Activity#startActivity startActivity()}. If you have an activity that can be invoked by
+an external entity that might use this flag, take care that the user has a independent way to get
+back to the task that's started, such as with a launcher icon (the root activity of the task
+has a {@link android.content.Intent#CATEGORY_LAUNCHER} intent filter; see the <a
+href="#Starting">Starting a task</a> section below).</p>
+</li>
+
+ <li>When an activity has its <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#reparent">{@code
+allowTaskReparenting}</a> attribute set to {@code "true"}.
+ <p>In this case, the activity can move from the task it starts to the task it has an affinity
+for, when that task comes to the foreground.</p>
+ <p>For example, suppose that an activity that reports weather conditions in selected cities is
+defined as part of a travel application. It has the same affinity as other activities in the same
+application (the default application affinity) and it allows re-parenting with this attribute.
+When one of your activities starts the weather reporter activity, it initially belongs to the same
+task as your activity. However, when the travel application's task comes to the foreground, the
+weather reporter activity is reassigned to that task and displayed within it.</p>
+</li>
+</ul>
+
+<p class="note"><strong>Tip:</strong> If an {@code .apk} file contains more than one "application"
+from the user's point of view, you probably want to use the <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a>
+attribute to assign different affinities to the activities associated with each "application".</p>
+
+
+
+<h3 id="Clearing">Clearing the back stack</h3>
+
+<p>If the user leaves a task for a long time, the system clears the task of all activities except
+the root activity. When the user returns to the task again, only the root activity is restored.
+The system behaves this way, because, after an extended amount of time, users likely have abandoned
+what they were doing before and are returning to the task to begin something new. </p>
+
+<p>There are some activity attributes that you can use to modify this behavior: </p>
+
+<dl>
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#always">alwaysRetainTaskState</a></code>
+</dt>
+<dd>If this attribute is set to {@code "true"} in the root activity of a task,
+the default behavior just described does not happen.
+The task retains all activities in its stack even after a long period.</dd>
+
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#clear">clearTaskOnLaunch</a></code></dt>
+<dd>If this attribute is set to {@code "true"} in the root activity of a task,
+the stack is cleared down to the root activity whenever the user leaves the task
+and returns to it. In other words, it's the opposite of <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#always">{@code
+alwaysRetainTaskState}</a>. The user always returns to the task in its
+initial state, even after a leaving the task for only a moment.</dd>
+
+<dt><code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html#finish">finishOnTaskLaunch</a></code>
+</dt>
+<dd>This attribute is like <a
+href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code clearTaskOnLaunch}</a>,
+but it operates on a
+single activity, not an entire task. It can also cause any activity to go
+away, including the root activity. When it's set to {@code "true"}, the
+activity remains part of the task only for the current session. If the user
+leaves and then returns to the task, it is no longer present.</dd>
+</dl>
+
+
+
+
+<h3 id="Starting">Starting a task</h3>
+
+<p>You can set up an activity as the entry point for a task by giving it an intent filter with
+{@code "android.intent.action.MAIN"} as the specified action and {@code
+"android.intent.category.LAUNCHER"} as the specified category. For example:</p>
+
+<pre>
+&lt;activity ... &gt;
+ &lt;intent-filter ... &gt;
+ &lt;action android:name="android.intent.action.MAIN" /&gt;
+ &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
+ &lt;/intent-filter&gt;
+ ...
+&lt;/activity&gt;
+</pre>
+
+<p>An intent filter of this kind causes an icon and label for the
+activity to be displayed in the application launcher, giving users a way to launch the activity and
+to return to the task that it creates any time after it has been launched.
+</p>
+
+<p>This second ability is important: Users must be able to leave a task and then come back to it
+later using this activity launcher. For this reason, the two <a href="#LaunchModes">launch
+modes</a> that mark activities as always initiating a task, {@code "singleTask"} and "{@code
+"singleInstance"}, should be used only when the activity has an {@link
+android.content.Intent#ACTION_MAIN}
+and a {@link android.content.Intent#CATEGORY_LAUNCHER}
+filter. Imagine, for example, what could happen if the filter is missing: An intent launches a
+{@code "singleTask"} activity, initiating a new task, and the user spends some time working in
+that task. The user then presses the HOME key. The task is now sent to the background and not
+visible. Because it is not represented in the application launcher, the user has no way to return to
+the task.
+</p>
+
+<p>For those cases where you don't want the user to be able to return to an activity, set the
+ <code><a
+href="{@docRoot}guide/topics/manifest/activity-element.html">&lt;activity&gt;</a></code> element's
+<a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code
+finishOnTaskLaunch}</a> to {@code "true"} (see <a
+href="#Clearing">Clearing the stack</a>).</p>
+
+
+
+
+<h2>Beginner's Path</h2>
+
+<p>For more information about how to use intents to
+activate other application components and publish the intents to which your components
+respond, continue with the <b><a
+href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent
+Filters</a></b> document.</p>
diff --git a/docs/html/guide/topics/intents/intents-filters.jd b/docs/html/guide/topics/intents/intents-filters.jd
index bd1d694..5905214 100644
--- a/docs/html/guide/topics/intents/intents-filters.jd
+++ b/docs/html/guide/topics/intents/intents-filters.jd
@@ -3,15 +3,6 @@ page.title=Intents and Intent Filters
<div id="qv-wrapper">
<div id="qv">
-<h2>Key classes</h2>
-<ol>
-<li>{@link android.content.Intent}</li>
-<li>{@link android.content.IntentFilter}</li>
-<li>{@link android.app.Activity}</li>
-<li>{@link android.app.Service}</li>
-<li>{@link android.content.BroadcastReceiver}</li>
-<li>{@link android.content.pm.PackageManager}</li>
-</ol>
<h2>In this document</h2>
<ol>
@@ -22,6 +13,15 @@ page.title=Intents and Intent Filters
<li style="margin-left: 2em"><a href="#imatch">Using intent matching</a></li>
<li><a href="#npex">Note Pad Example</a></li>
</ol>
+
+<h2>Key classes</h2>
+<ol>
+<li>{@link android.content.Intent}</li>
+<li>{@link android.content.IntentFilter}</li>
+<li>{@link android.content.BroadcastReceiver}</li>
+<li>{@link android.content.pm.PackageManager}</li>
+</ol>
+
</div>
</div>
diff --git a/docs/html/guide/topics/media/index.jd b/docs/html/guide/topics/media/index.jd
index 96c500c..558d453 100644
--- a/docs/html/guide/topics/media/index.jd
+++ b/docs/html/guide/topics/media/index.jd
@@ -4,7 +4,7 @@ page.title=Audio and Video
<div id="qv-wrapper">
<div id="qv">
-<h2>Audio/Video quickview</h2>
+<h2>Quickview</h2>
<ul>
<li>Audio playback and record</li>
<li>Video playback</li>
@@ -12,14 +12,6 @@ page.title=Audio and Video
<li>Built-in codecs for a variety of media. See <a href="{@docRoot}guide/appendix/media-formats.html">Android Supported Media Formats</a></li>
</ul>
-<h2>Key classes</h2>
-<ol>
-<li>{@link android.media.MediaPlayer MediaPlayer} (all available formats)</li>
-<li>{@link android.media.MediaRecorder MediaRecorder} (all available formats)</li>
-<li>{@link android.media.JetPlayer JetPlayer} (playback, JET content)</li>
-<li>{@link android.media.SoundPool SoundPool} (sound management)</li>
-</ol>
-
<h2>In this document</h2>
<ol>
<li><a href="#playback.html">Audio and Video Playback</a>
@@ -32,6 +24,14 @@ page.title=Audio and Video
<li><a href="#capture">Audio Capture</a></li>
</ol>
+<h2>Key classes</h2>
+<ol>
+<li>{@link android.media.MediaPlayer MediaPlayer}</li>
+<li>{@link android.media.MediaRecorder MediaRecorder}</li>
+<li>{@link android.media.JetPlayer JetPlayer}</li>
+<li>{@link android.media.SoundPool SoundPool}</li>
+</ol>
+
<h2>See also</h2>
<ol>
<li><a href="{@docRoot}guide/topics/data/data-storage.html">Data Storage</a></li>
diff --git a/docs/html/guide/topics/providers/content-providers.jd b/docs/html/guide/topics/providers/content-providers.jd
index da4e7a1..2aed5e1 100644
--- a/docs/html/guide/topics/providers/content-providers.jd
+++ b/docs/html/guide/topics/providers/content-providers.jd
@@ -3,12 +3,6 @@ page.title=Content Providers
<div id="qv-wrapper">
<div id="qv">
-<h2>Key classes</h2>
-<ol>
-<li>{@link android.content.ContentProvider}</li>
-<li>{@link android.content.ContentResolver}</li>
-<li>{@link android.database.Cursor}</li>
-</ol>
<h2>In this document</h2>
<ol>
@@ -18,6 +12,13 @@ page.title=Content Providers
<li><a href="#creating">Creating a content provider</a></li>
<li><a href="#urisum">Content URI summary</a></li>
</ol>
+
+<h2>Key classes</h2>
+<ol>
+<li>{@link android.content.ContentProvider}</li>
+<li>{@link android.content.ContentResolver}</li>
+<li>{@link android.database.Cursor}</li>
+</ol>
</div>
</div>
diff --git a/docs/html/guide/topics/search/adding-custom-suggestions.jd b/docs/html/guide/topics/search/adding-custom-suggestions.jd
index ce0c619..c8f06b9 100644
--- a/docs/html/guide/topics/search/adding-custom-suggestions.jd
+++ b/docs/html/guide/topics/search/adding-custom-suggestions.jd
@@ -33,7 +33,7 @@ parent.link=index.html
<li>{@link android.content.ContentProvider}</li>
</ol>
-<h2>Related Samples</h2>
+<h2>Related samples</h2>
<ol>
<li><a href="{@docRoot}resources/samples/SearchableDictionary/index.html">Searchable
Dictionary</a></li>
diff --git a/docs/html/guide/topics/search/index.jd b/docs/html/guide/topics/search/index.jd
index 78e0be2..f563715 100644
--- a/docs/html/guide/topics/search/index.jd
+++ b/docs/html/guide/topics/search/index.jd
@@ -13,7 +13,7 @@ page.title=Search
<ol>
<li><a href="searchable-config.html">Searchable Configuration</a></li>
</ol>
-<h2>Related Samples</h2>
+<h2>Related samples</h2>
<ol>
<li><a href="{@docRoot}resources/samples/SearchableDictionary/index.html">Searchable
Dictionary</a></li>
diff --git a/docs/html/guide/topics/search/search-dialog.jd b/docs/html/guide/topics/search/search-dialog.jd
index 49c6627..49938b4 100644
--- a/docs/html/guide/topics/search/search-dialog.jd
+++ b/docs/html/guide/topics/search/search-dialog.jd
@@ -29,7 +29,7 @@ parent.link=index.html
<li>{@link android.app.SearchManager}</li>
</ol>
-<h2>Related Samples</h2>
+<h2>Related samples</h2>
<ol>
<li><a href="{@docRoot}resources/samples/SearchableDictionary/index.html">Searchable
Dictionary</a></li>
diff --git a/docs/html/guide/topics/testing/testing_android.jd b/docs/html/guide/topics/testing/testing_android.jd
index 513e472..1d5f911 100755
--- a/docs/html/guide/topics/testing/testing_android.jd
+++ b/docs/html/guide/topics/testing/testing_android.jd
@@ -50,14 +50,14 @@ page.title=Testing Fundamentals
<a href="#NextSteps">Next Steps</a>
</li>
</ol>
- <h2>Key Classes and Packages</h2>
+ <h2>Key classes</h2>
<ol>
<li>{@link android.test.InstrumentationTestRunner}</li>
<li>{@link android.test}</li>
<li>{@link android.test.mock}</li>
<li>{@link junit.framework}</li>
</ol>
- <h2>Related Tutorials</h2>
+ <h2>Related tutorials</h2>
<ol>
<li>
<a href="{@docRoot}resources/tutorials/testing/helloandroid_test.html">
@@ -67,7 +67,7 @@ page.title=Testing Fundamentals
<a href="{@docRoot}resources/tutorials/testing/activity_test.html">Activity Testing</a>
</li>
</ol>
- <h2>See Also</h2>
+ <h2>See also</h2>
<ol>
<li>
<a href="{@docRoot}guide/developing/testing/testing_eclipse.html">
diff --git a/docs/html/guide/topics/ui/binding.jd b/docs/html/guide/topics/ui/binding.jd
index 6ac0bb0..26364ee 100644
--- a/docs/html/guide/topics/ui/binding.jd
+++ b/docs/html/guide/topics/ui/binding.jd
@@ -11,11 +11,11 @@ parent.link=index.html
<li><a href="#HandlingUserSelections">Handling User Selections</a></li>
</ol>
- <h2>See also</h2>
+ <h2>Related tutorials</h2>
<ol>
- <li><a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Hello Spinner tutorial</a></li>
- <li><a href="{@docRoot}resources/tutorials/views/hello-listview.html">Hello ListView tutorial</a></li>
- <li><a href="{@docRoot}resources/tutorials/views/hello-gridview.html">Hello GridView tutorial</a></li>
+ <li><a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner</a></li>
+ <li><a href="{@docRoot}resources/tutorials/views/hello-listview.html">List View</a></li>
+ <li><a href="{@docRoot}resources/tutorials/views/hello-gridview.html">Grid View</a></li>
</ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/declaring-layout.jd b/docs/html/guide/topics/ui/declaring-layout.jd
index 5c12db9..fe641a2 100644
--- a/docs/html/guide/topics/ui/declaring-layout.jd
+++ b/docs/html/guide/topics/ui/declaring-layout.jd
@@ -5,12 +5,6 @@ parent.link=index.html
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.view.View}</li>
- <li>{@link android.view.ViewGroup}</li>
- <li>{@link android.view.ViewGroup.LayoutParams}</li>
- </ol>
<h2>In this document</h2>
<ol>
<li><a href="#write">Write the XML</a></li>
@@ -26,6 +20,12 @@ parent.link=index.html
<li><a href="#example">Example Layout</a></li>
</ol>
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.view.View}</li>
+ <li>{@link android.view.ViewGroup}</li>
+ <li>{@link android.view.ViewGroup.LayoutParams}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd
index f47a709..879eb8b 100644
--- a/docs/html/guide/topics/ui/dialogs.jd
+++ b/docs/html/guide/topics/ui/dialogs.jd
@@ -5,10 +5,6 @@ parent.link=index.html
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.app.Dialog}</li>
- </ol>
<h2>In this document</h2>
<ol>
<li><a href="#ShowingADialog">Showing a Dialog</a></li>
@@ -26,6 +22,11 @@ parent.link=index.html
</li>
<li><a href="#CustomDialog">Creating a Custom Dialog</a></li>
</ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.app.Dialog}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/index.jd b/docs/html/guide/topics/ui/index.jd
index abcf6be..375c9fe 100644
--- a/docs/html/guide/topics/ui/index.jd
+++ b/docs/html/guide/topics/ui/index.jd
@@ -4,13 +4,6 @@ page.title=User Interface
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.view.View}</li>
- <li>{@link android.view.ViewGroup}</li>
- <li>{@link android.widget Widget classes}</li>
- </ol>
-
<h2>In this document</h2>
<ol>
<li><a href="#ViewHierarchy">View Hierarchy</a></li>
@@ -25,6 +18,13 @@ page.title=User Interface
</ol>
</li>
</ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.view.View}</li>
+ <li>{@link android.view.ViewGroup}</li>
+ <li>{@link android.widget Widget classes}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/notifiers/index.jd b/docs/html/guide/topics/ui/notifiers/index.jd
index f7ccce7..d29324c 100644
--- a/docs/html/guide/topics/ui/notifiers/index.jd
+++ b/docs/html/guide/topics/ui/notifiers/index.jd
@@ -3,13 +3,7 @@ page.title=Notifying the User
<div id="qv-wrapper">
<div id="qv">
- <h2>In this document</h2>
- <ol>
- <li><a href="#Toast">Toast Notification</a></li>
- <li><a href="#StatusBarNotification">Status Bar Notification</a></li>
- <li><a href="#Dialog">Dialog Notification</a></li>
- </ol>
- <h2>More about</h2>
+ <h2>Topics</h2>
<ol>
<li><a href="toasts.html">Creating Toast Notifications</a></li>
<li><a href="notifications.html">Creating Status Bar Notifications</a></li>
diff --git a/docs/html/guide/topics/ui/notifiers/notifications.jd b/docs/html/guide/topics/ui/notifiers/notifications.jd
index a0dd9f1..abc945a 100644
--- a/docs/html/guide/topics/ui/notifiers/notifications.jd
+++ b/docs/html/guide/topics/ui/notifiers/notifications.jd
@@ -5,18 +5,21 @@ parent.link=index.html
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.app.Notification}</li>
- <li>{@link android.app.NotificationManager}</li>
- </ol>
+ <h2>Quickview</h2>
+ <ul>
+ <li>A status bar notification allows your application to notify the user of an event
+without interupting their current activity</li>
+ <li>You can attach an intent to your notification that the system will initiate when the
+user clicks it</li>
+ </ul>
+
<h2>In this document</h2>
<ol>
<li><a href="#Basics">The Basics</a></li>
<li><a href="#ManageYourNotifications">Managing your Notifications</a></li>
<li><a href="#CreateANotification">Creating a Notification</a>
<ol>
- <li><a href="#Update">Updating the notification</a></li>
+ <li><a href="#Updating">Updating the notification</a></li>
<li><a href="#Sound">Adding a sound</a></li>
<li><a href="#Vibration">Adding vibration</a></li>
<li><a href="#Lights">Adding flashing lights</a></li>
@@ -25,6 +28,11 @@ parent.link=index.html
</li>
<li><a href="#CustomExpandedView">Creating a Custom Expanded View</a></li>
</ol>
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.app.Notification}</li>
+ <li>{@link android.app.NotificationManager}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/notifiers/toasts.jd b/docs/html/guide/topics/ui/notifiers/toasts.jd
index 5b324d2..0d3e10c 100644
--- a/docs/html/guide/topics/ui/notifiers/toasts.jd
+++ b/docs/html/guide/topics/ui/notifiers/toasts.jd
@@ -5,16 +5,24 @@ parent.link=index.html
<div id="qv-wrapper">
<div id="qv">
- <h2>Key classes</h2>
+ <h2>Quickview</h2>
<ol>
- <li>{@link android.widget.Toast}</li>
+ <li>A toast is a message that appears on the surface of the screen for a moment, but it
+does not take focus (or pause the current activity), so it cannot accept user input</li>
+ <li>You can customize the toast layout to include images</li>
</ol>
+
<h2>In this document</h2>
<ol>
<li><a href="#Basics">The Basics</a></li>
- <li><a href="#Position">Positioning your Toast</a></li>
+ <li><a href="#Positioning">Positioning your Toast</a></li>
<li><a href="#CustomToastView">Creating a Custom Toast View</a></li>
</ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.widget.Toast}</li>
+ </ol>
</div>
</div>
diff --git a/docs/html/guide/topics/ui/ui-events.jd b/docs/html/guide/topics/ui/ui-events.jd
index ccef64f..7d7bfaf 100644
--- a/docs/html/guide/topics/ui/ui-events.jd
+++ b/docs/html/guide/topics/ui/ui-events.jd
@@ -13,9 +13,9 @@ parent.link=index.html
<li><a href="#HandlingFocus">Handling Focus</a></li>
</ol>
- <h2>See also</h2>
+ <h2>Related tutorials</h2>
<ol>
- <li><a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Hello Form Stuff tutorial</a></li>
+ <li><a href="{@docRoot}resources/tutorials/views/hello-formstuff.html">Form Stuff</a></li>
</ol>
</div>
</div>
diff --git a/docs/html/guide/topics/wireless/bluetooth.jd b/docs/html/guide/topics/wireless/bluetooth.jd
index a8ff007..fa2875b 100644
--- a/docs/html/guide/topics/wireless/bluetooth.jd
+++ b/docs/html/guide/topics/wireless/bluetooth.jd
@@ -3,14 +3,13 @@ page.title=Bluetooth
<div id="qv-wrapper">
<div id="qv">
- <h2>Key Classes</h2>
- <ol>
- <li>{@link android.bluetooth.BluetoothAdapter}</li>
- <li>{@link android.bluetooth.BluetoothDevice}</li>
- <li>{@link android.bluetooth.BluetoothSocket}</li>
- <li>{@link android.bluetooth.BluetoothServerSocket}</li>
- </ol>
+ <h2>Quickview</h2>
+ <ul>
+ <li>Android's bluetooth APIs allow your application to perform wireless data transactions with
+other devices</li>
+ </ul>
+
<h2>In this document</h2>
<ol>
<li><a href="#TheBasics">The Basics</a></li>
@@ -33,11 +32,18 @@ page.title=Bluetooth
</li>
<li><a href="#ManagingAConnection">Managing a Connection</a></li>
</ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.bluetooth.BluetoothAdapter}</li>
+ <li>{@link android.bluetooth.BluetoothDevice}</li>
+ <li>{@link android.bluetooth.BluetoothSocket}</li>
+ <li>{@link android.bluetooth.BluetoothServerSocket}</li>
+ </ol>
- <h2>See also</h2>
+ <h2>Related samples</h2>
<ol>
- <li><a href="{@docRoot}resources/samples/BluetoothChat/index.html">Bluetooth Chat sample
- app</a></li>
+ <li><a href="{@docRoot}resources/samples/BluetoothChat/index.html">Bluetooth Chat</a></li>
</ol>
</div>
diff --git a/docs/html/images/fundamentals/diagram_backstack.png b/docs/html/images/fundamentals/diagram_backstack.png
new file mode 100644
index 0000000..2c6e33f
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_backstack.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png b/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png
new file mode 100644
index 0000000..d6a21d7
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_backstack_singletask_multiactivity.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_multiple_instances.png b/docs/html/images/fundamentals/diagram_multiple_instances.png
new file mode 100644
index 0000000..380e7788
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_multiple_instances.png
Binary files differ
diff --git a/docs/html/images/fundamentals/diagram_multitasking.png b/docs/html/images/fundamentals/diagram_multitasking.png
new file mode 100644
index 0000000..b8c7b45
--- /dev/null
+++ b/docs/html/images/fundamentals/diagram_multitasking.png
Binary files differ
diff --git a/docs/html/images/fundamentals/restore_instance.png b/docs/html/images/fundamentals/restore_instance.png
new file mode 100644
index 0000000..fa428a7
--- /dev/null
+++ b/docs/html/images/fundamentals/restore_instance.png
Binary files differ
diff --git a/docs/html/images/home/market-intl.png b/docs/html/images/home/market-intl.png
new file mode 100644
index 0000000..2bb22f1
--- /dev/null
+++ b/docs/html/images/home/market-intl.png
Binary files differ
diff --git a/docs/html/images/screens_support/screens-ranges.png b/docs/html/images/screens_support/screens-ranges.png
new file mode 100644
index 0000000..034ac34
--- /dev/null
+++ b/docs/html/images/screens_support/screens-ranges.png
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index f37a122..049df62 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -11,12 +11,14 @@ home=true
</div><!-- end homeTitle -->
<div id="announcement-block">
<!-- total max width is 520px -->
- <img src="{@docRoot}images/home/io-logo.png" alt="Google IO
-2010" width="200" height="41" style="padding:22px 12px;"/>
+ <img src="{@docRoot}images/home/market-intl.png" alt="Android
+Market" width="104" height="120" style="padding:10px 60px 5px" />
<div id="announcement" style="width:295px">
-<p>Thanks to everyone who visited us at Google I/O in San Francisco! Stay tuned for
-videos and slides from the Android sessions, which will be posted at the Google I/O web site.</p><p><a
-href="http://code.google.com/events/io/2010/sessions.html#Android">Learn more &raquo;</a></p>
+<p>We're pleased to announce that paid apps are available in more locations of the world! Developers
+from 20 more locations can now sell paid apps on Android Market. Users in more locations will also
+soon be able to purchase apps.</p><p><a
+href="http://android-developers.blogspot.com/2010/09/more-countries-more-sellers-more-buyers.html">
+Learn more &raquo;</a></p>
</div> <!-- end annoucement -->
</div> <!-- end annoucement-block -->
</div><!-- end topAnnouncement -->
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 9a19056..d283dea 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -346,6 +346,15 @@ public final class Bitmap implements Parcelable {
return b;
}
+ /**
+ * Creates a new bitmap, scaled from an existing bitmap.
+ *
+ * @param src The source bitmap.
+ * @param dstWidth The new bitmap's desired width.
+ * @param dstHeight The new bitmap's desired height.
+ * @param filter true if the source should be filtered.
+ * @return the new scaled bitmap.
+ */
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth,
int dstHeight, boolean filter) {
Matrix m;
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 604c602..a25fad4 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -26,10 +26,8 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
- * A specialized Drawable that fills the Canvas with a specified color,
- * with respect to the clip region. Note that a ColorDrawable ignores the ColorFilter.
- * It also ignores the Bounds, meaning it will draw everywhere in the current clip,
- * even if setBounds(...) was called with a smaller area.
+ * A specialized Drawable that fills the Canvas with a specified color.
+ * Note that a ColorDrawable ignores the ColorFilter.
*
* <p>It can be defined in an XML file with the <code>&lt;color></code> element.</p>
*
@@ -37,6 +35,7 @@ import java.io.IOException;
*/
public class ColorDrawable extends Drawable {
private ColorState mState;
+ private final Paint mPaint = new Paint();
/**
* Creates a new black ColorDrawable.
@@ -66,7 +65,10 @@ public class ColorDrawable extends Drawable {
@Override
public void draw(Canvas canvas) {
- canvas.drawColor(mState.mUseColor);
+ if ((mState.mUseColor >>> 24) != 0) {
+ mPaint.setColor(mState.mUseColor);
+ canvas.drawRect(getBounds(), mPaint);
+ }
}
/**
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 985d700..2c076b3 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -182,6 +182,24 @@ public class Allocation extends BaseObj {
mRS.nAllocationRead(mID, d);
}
+ public void resize(int dimX) {
+ if ((mType.getY() > 0)|| (mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) {
+ throw new IllegalStateException("Resize only support for 1D allocations at this time.");
+ }
+ mRS.nAllocationResize1D(mID, dimX);
+ }
+
+ /*
+ public void resize(int dimX, int dimY) {
+ if ((mType.getZ() > 0) || mType.getFaces() || mType.getLOD()) {
+ throw new IllegalStateException("Resize only support for 2D allocations at this time.");
+ }
+ if (mType.getY() == 0) {
+ throw new IllegalStateException("Resize only support for 2D allocations at this time.");
+ }
+ mRS.nAllocationResize2D(mID, dimX, dimY);
+ }
+ */
public class Adapter1D extends BaseObj {
Adapter1D(int id, RenderScript rs) {
diff --git a/graphics/java/android/renderscript/ProgramFragment.java b/graphics/java/android/renderscript/ProgramFragment.java
index c1d6428..8858b74 100644
--- a/graphics/java/android/renderscript/ProgramFragment.java
+++ b/graphics/java/android/renderscript/ProgramFragment.java
@@ -104,7 +104,7 @@ public class ProgramFragment extends Program {
private void buildShaderString() {
mShader = "//rs_shader_internal\n";
mShader += "varying lowp vec4 varColor;\n";
- mShader += "varying vec4 varTex0;\n";
+ mShader += "varying vec2 varTex0;\n";
mShader += "void main() {\n";
if (mVaryingColorEnable) {
diff --git a/graphics/java/android/renderscript/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java
index 63e2598..65a0af2 100644
--- a/graphics/java/android/renderscript/ProgramVertex.java
+++ b/graphics/java/android/renderscript/ProgramVertex.java
@@ -101,7 +101,7 @@ public class ProgramVertex extends Program {
mShader = "//rs_shader_internal\n";
mShader += "varying vec4 varColor;\n";
- mShader += "varying vec4 varTex0;\n";
+ mShader += "varying vec2 varTex0;\n";
mShader += "void main() {\n";
mShader += " gl_Position = UNI_MVP * ATTRIB_position;\n";
@@ -109,7 +109,7 @@ public class ProgramVertex extends Program {
mShader += " varColor = ATTRIB_color;\n";
if (mTextureMatrixEnable) {
- mShader += " varTex0 = UNI_TexMatrix * ATTRIB_texture0;\n";
+ mShader += " varTex0 = (UNI_TexMatrix * vec4(ATTRIB_texture0, 0.0, 1.0)).xy;\n";
} else {
mShader += " varTex0 = ATTRIB_texture0;\n";
}
@@ -126,7 +126,7 @@ public class ProgramVertex extends Program {
b.add(Element.F32_4(mRS), "position");
b.add(Element.F32_4(mRS), "color");
b.add(Element.F32_3(mRS), "normal");
- b.add(Element.F32_4(mRS), "texture0");
+ b.add(Element.F32_2(mRS), "texture0");
addInput(b.create());
return super.create();
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 3c0b4e5..0088373 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -254,6 +254,15 @@ public class RenderScript {
return rsnAllocationGetType(mContext, id);
}
+ native void rsnAllocationResize1D(int con, int id, int dimX);
+ synchronized void nAllocationResize1D(int id, int dimX) {
+ rsnAllocationResize1D(mContext, id, dimX);
+ }
+ native void rsnAllocationResize2D(int con, int id, int dimX, int dimY);
+ synchronized void nAllocationResize2D(int id, int dimX, int dimY) {
+ rsnAllocationResize2D(mContext, id, dimX, dimY);
+ }
+
native int rsnFileA3DCreateFromAssetStream(int con, int assetStream);
synchronized int nFileA3DCreateFromAssetStream(int assetStream) {
return rsnFileA3DCreateFromAssetStream(mContext, assetStream);
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 67a2b63..8f1e93c 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -587,6 +587,20 @@ nAllocationGetType(JNIEnv *_env, jobject _this, RsContext con, jint a)
return (jint) rsAllocationGetType(con, (RsAllocation)a);
}
+static void
+nAllocationResize1D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint dimX)
+{
+ LOG_API("nAllocationResize1D, con(%p), alloc(%p), sizeX(%i)", con, (RsAllocation)alloc, dimX);
+ rsAllocationResize1D(con, (RsAllocation)alloc, dimX);
+}
+
+static void
+nAllocationResize2D(JNIEnv *_env, jobject _this, RsContext con, jint alloc, jint dimX, jint dimY)
+{
+ LOG_API("nAllocationResize1D, con(%p), alloc(%p), sizeX(%i), sizeY(%i)", con, (RsAllocation)alloc, dimX, dimY);
+ rsAllocationResize2D(con, (RsAllocation)alloc, dimX, dimY);
+}
+
// -----------------------------------
static int
@@ -1252,6 +1266,8 @@ static JNINativeMethod methods[] = {
{"rsnAllocationRead", "(II[I)V", (void*)nAllocationRead_i },
{"rsnAllocationRead", "(II[F)V", (void*)nAllocationRead_f },
{"rsnAllocationGetType", "(II)I", (void*)nAllocationGetType},
+{"rsnAllocationResize1D", "(III)V", (void*)nAllocationResize1D },
+{"rsnAllocationResize2D", "(IIII)V", (void*)nAllocationResize2D },
{"rsnAdapter1DBindAllocation", "(III)V", (void*)nAdapter1DBindAllocation },
{"rsnAdapter1DSetConstraint", "(IIII)V", (void*)nAdapter1DSetConstraint },
diff --git a/include/camera/CameraHardwareInterface.h b/include/camera/CameraHardwareInterface.h
index 3a77dd1..561a46d 100644
--- a/include/camera/CameraHardwareInterface.h
+++ b/include/camera/CameraHardwareInterface.h
@@ -21,6 +21,8 @@
#include <ui/egl/android_natives.h>
#include <utils/RefBase.h>
#include <surfaceflinger/ISurface.h>
+#include <ui/android_native_buffer.h>
+#include <ui/GraphicBuffer.h>
#include <camera/Camera.h>
#include <camera/CameraParameters.h>
@@ -47,6 +49,17 @@ typedef void (*data_callback)(int32_t msgType,
const sp<IMemory>& dataPtr,
void* user);
+#ifdef USE_GRAPHIC_VIDEO_BUFFERS
+/**
+ * Replace data_callback_timestamp. Once we are done, this
+ * should be renamed as data_callback_timestamp, and the existing
+ * data_callback_timestamp should be deleted.
+ */
+typedef void (*videobuffer_callback_timestamp)(nsecs_t timestamp,
+ int32_t msgType,
+ const sp<android_native_buffer_t>& buf,
+ void* user);
+#endif
typedef void (*data_callback_timestamp)(nsecs_t timestamp,
int32_t msgType,
const sp<IMemory>& dataPtr,
@@ -87,6 +100,46 @@ class CameraHardwareInterface : public virtual RefBase {
public:
virtual ~CameraHardwareInterface() { }
+#ifdef USE_GRAPHIC_VIDEO_BUFFERS
+ /**
+ * Replace existing setCallbacks() method. Once we are done, the
+ * videobuffer_callback_timestamp parameter will be renamed to
+ * data_callback_timestamp, but its signature will be the same
+ * as videobuffer_callback_timestamp, which will be renamed
+ * to data_callback_timestamp and the exiting data_callback_timestamp
+ * will be deleted.
+ */
+ virtual void setCallbacks(notify_callback notify_cb,
+ data_callback data_cb,
+ videobuffer_callback_timestamp data_cb_timestamp,
+ void* user) = 0;
+
+ /**
+ * Replace releaseRecordingFrame(). releaseRecordingFrame() should be
+ * changed so that it has the same signature of releaseVideoBuffer(),
+ * once we are done, and releaseVideoBuffer() will be deleted.
+ */
+ virtual void releaseVideoBuffer(const sp<android_native_buffer_t>& buf) = 0;
+
+ /**
+ * This method should be called after startRecording().
+ *
+ * @param nBuffers the total number of video buffers allocated by the camera
+ * hal
+ * @param buffers an array allocated by the camera hal to hold the pointers
+ * to the individual video buffers. The video buffers and the buffers array
+ * should NOT be modified/released by camera hal until stopRecording() is
+ * called and all outstanding video buffers previously sent out via
+ * CAMERA_MSG_VIDEO_FRAME have been released via releaseVideoBuffer().
+ * Camera hal client must not release the individual buffers and the buffers
+ * array.
+ * @return no error if OK.
+ */
+ virtual status_t getVideoBufferInfo(
+ sp<android_native_buffer_t>** buffers,
+ size_t *nBuffers) = 0;
+#endif
+
/** Set the ANativeWindow to which preview frames are sent */
virtual status_t setPreviewWindow(const sp<ANativeWindow>& buf) = 0;
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index 70bd8e8..cff38b2 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -64,6 +64,7 @@ private:
FILE *mFile;
bool mUse4ByteNalLength;
bool mUse32BitOffset;
+ bool mIsFileSizeLimitExplicitlyRequested;
bool mPaused;
bool mStarted;
off_t mOffset;
diff --git a/include/private/surfaceflinger/SharedBufferStack.h b/include/private/surfaceflinger/SharedBufferStack.h
index d689667..d6ae5e9 100644
--- a/include/private/surfaceflinger/SharedBufferStack.h
+++ b/include/private/surfaceflinger/SharedBufferStack.h
@@ -114,8 +114,9 @@ public:
int32_t identity; // surface's identity (const)
int32_t token; // surface's token (for debugging)
- int32_t reserved32[1];
Statistics stats;
+ int8_t headBuf; // last retired buffer
+ uint8_t reservedBytes[3];
int32_t reserved;
BufferData buffers[NUM_BUFFER_MAX]; // 1024 bytes
};
@@ -201,6 +202,7 @@ public:
status_t undoDequeue(int buf);
status_t lock(int buf);
+ status_t cancel(int buf);
status_t queue(int buf);
bool needNewBuffer(int buffer) const;
status_t setDirtyRegion(int buffer, const Region& reg);
@@ -230,8 +232,9 @@ private:
inline ssize_t operator()();
};
- struct UndoDequeueUpdate : public UpdateBase {
- inline UndoDequeueUpdate(SharedBufferBase* sbb);
+ struct CancelUpdate : public UpdateBase {
+ int tail, buf;
+ inline CancelUpdate(SharedBufferBase* sbb, int tail, int buf);
inline ssize_t operator()();
};
@@ -256,7 +259,6 @@ private:
int mNumBuffers;
int32_t tail;
- int32_t undoDequeueTail;
int32_t queued_head;
// statistics...
nsecs_t mDequeueTime[SharedBufferStack::NUM_BUFFER_MAX];
diff --git a/include/surfaceflinger/Surface.h b/include/surfaceflinger/Surface.h
index a210880..cef439c 100644
--- a/include/surfaceflinger/Surface.h
+++ b/include/surfaceflinger/Surface.h
@@ -200,6 +200,7 @@ private:
*/
static int setSwapInterval(ANativeWindow* window, int interval);
static int dequeueBuffer(ANativeWindow* window, android_native_buffer_t** buffer);
+ static int cancelBuffer(ANativeWindow* window, android_native_buffer_t* buffer);
static int lockBuffer(ANativeWindow* window, android_native_buffer_t* buffer);
static int queueBuffer(ANativeWindow* window, android_native_buffer_t* buffer);
static int query(ANativeWindow* window, int what, int* value);
@@ -208,6 +209,7 @@ private:
int dequeueBuffer(android_native_buffer_t** buffer);
int lockBuffer(android_native_buffer_t* buffer);
int queueBuffer(android_native_buffer_t* buffer);
+ int cancelBuffer(android_native_buffer_t* buffer);
int query(int what, int* value);
int perform(int operation, va_list args);
diff --git a/include/ui/egl/android_natives.h b/include/ui/egl/android_natives.h
index d59d72b..654d0f3 100644
--- a/include/ui/egl/android_natives.h
+++ b/include/ui/egl/android_natives.h
@@ -218,7 +218,17 @@ struct ANativeWindow
int (*perform)(struct ANativeWindow* window,
int operation, ... );
- void* reserved_proc[3];
+ /*
+ * hook used to cancel a buffer that has been dequeued.
+ * No synchronization is performed between dequeue() and cancel(), so
+ * either external synchronization is needed, or these functions must be
+ * called from the same thread.
+ */
+ int (*cancelBuffer)(struct ANativeWindow* window,
+ struct android_native_buffer_t* buffer);
+
+
+ void* reserved_proc[2];
};
// Backwards compatibility... please switch to ANativeWindow.
diff --git a/include/utils/ZipFileRO.h b/include/utils/ZipFileRO.h
index e1ff780..3c1f3ca 100644
--- a/include/utils/ZipFileRO.h
+++ b/include/utils/ZipFileRO.h
@@ -14,13 +14,19 @@
* limitations under the License.
*/
-//
-// Read-only access to Zip archives, with minimal heap allocation.
-//
-// This is similar to the more-complete ZipFile class, but no attempt
-// has been made to make them interchangeable. This class operates under
-// a very different set of assumptions and constraints.
-//
+/*
+ * Read-only access to Zip archives, with minimal heap allocation.
+ *
+ * This is similar to the more-complete ZipFile class, but no attempt
+ * has been made to make them interchangeable. This class operates under
+ * a very different set of assumptions and constraints.
+ *
+ * One such assumption is that if you're getting file descriptors for
+ * use with this class as a child of a fork() operation, you must be on
+ * a pread() to guarantee correct operation. This is because pread() can
+ * atomically read at a file offset without worrying about a lock around an
+ * lseek() + read() pair.
+ */
#ifndef __LIBS_ZIPFILERO_H
#define __LIBS_ZIPFILERO_H
@@ -55,6 +61,10 @@ typedef void* ZipEntryRO;
* the record structure. However, this requires a private mapping of
* every page that the Central Directory touches. Easier to tuck a copy
* of the string length into the hash table entry.
+ *
+ * NOTE: If this is used on file descriptors inherited from a fork() operation,
+ * you must be on a platform that implements pread() to guarantee correctness
+ * on the shared file descriptors.
*/
class ZipFileRO {
public:
diff --git a/libs/binder/CursorWindow.cpp b/libs/binder/CursorWindow.cpp
index 20b27c9..bdd4dd6 100644
--- a/libs/binder/CursorWindow.cpp
+++ b/libs/binder/CursorWindow.cpp
@@ -141,10 +141,12 @@ uint32_t CursorWindow::alloc(size_t requestedSize, bool aligned)
size = requestedSize + padding;
if (size > freeSpace()) {
- LOGE("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size, freeSpace(), mHeader->numRows);
+ LOGV("need to grow: mSize = %d, size = %d, freeSpace() = %d, numRows = %d", mSize, size,
+ freeSpace(), mHeader->numRows);
// Only grow the window if the first row doesn't fit
if (mHeader->numRows > 1) {
-LOGE("not growing since there are already %d row(s), max size %d", mHeader->numRows, mMaxSize);
+ LOGV("not growing since there are already %d row(s), max size %d", mHeader->numRows,
+ mMaxSize);
return 0;
}
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index a4933c0..2952a66 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -65,8 +65,8 @@ public:
PathCache pathCache;
PatchCache patchCache;
TextDropShadowCache dropShadowCache;
- GammaFontRenderer fontRenderer;
FboCache fboCache;
+ GammaFontRenderer fontRenderer;
Line line;
}; // class Caches
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index ce85d46..61e5408 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -342,7 +342,7 @@ void DisplayListRenderer::setViewport(int width, int height) {
mHeight = height;
}
-void DisplayListRenderer::prepare() {
+void DisplayListRenderer::prepare(bool opaque) {
mSnapshot = new Snapshot(mFirstSnapshot,
SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
mSaveCount = 1;
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 5d02bd7..0fbfce1 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -209,7 +209,7 @@ public:
~DisplayListRenderer();
void setViewport(int width, int height);
- void prepare();
+ void prepare(bool opaque);
void acquireContext();
void releaseContext();
diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp
index 77fbda2..2ef71c2 100644
--- a/libs/hwui/FboCache.cpp
+++ b/libs/hwui/FboCache.cpp
@@ -16,6 +16,8 @@
#define LOG_TAG "OpenGLRenderer"
+#include <stdlib.h>
+
#include "FboCache.h"
#include "Properties.h"
@@ -57,14 +59,31 @@ uint32_t FboCache::getMaxSize() {
///////////////////////////////////////////////////////////////////////////////
void FboCache::clear() {
-
+ for (size_t i = 0; i < mCache.size(); i++) {
+ const GLuint fbo = mCache.itemAt(i);
+ glDeleteFramebuffers(1, &fbo);
+ }
+ mCache.clear();
}
GLuint FboCache::get() {
- return 0;
+ GLuint fbo;
+ if (mCache.size() > 0) {
+ fbo = mCache.itemAt(mCache.size() - 1);
+ mCache.removeAt(mCache.size() - 1);
+ } else {
+ glGenFramebuffers(1, &fbo);
+ }
+ return fbo;
}
bool FboCache::put(GLuint fbo) {
+ if (mCache.size() < mMaxSize) {
+ mCache.add(fbo);
+ return true;
+ }
+
+ glDeleteFramebuffers(1, &fbo);
return false;
}
diff --git a/libs/hwui/FboCache.h b/libs/hwui/FboCache.h
index 66f66ea..ec4afe9 100644
--- a/libs/hwui/FboCache.h
+++ b/libs/hwui/FboCache.h
@@ -21,8 +21,6 @@
#include <utils/SortedVector.h>
-#include "GenerationCache.h"
-
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 5485113..8389e56 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -437,8 +437,9 @@ void FontRenderer::initTextTexture() {
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0,
GL_ALPHA, GL_UNSIGNED_BYTE, 0);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ mLinearFiltering = false;
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 4fb8f8d..f10efad 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -156,8 +156,16 @@ public:
DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
uint32_t len, int numGlyphs, uint32_t radius);
- GLuint getTexture() {
+ GLuint getTexture(bool linearFiltering = false) {
checkInit();
+ if (linearFiltering != mLinearFiltering) {
+ mLinearFiltering = linearFiltering;
+ const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST;
+
+ glBindTexture(GL_TEXTURE_2D, mTextureId);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+ }
return mTextureId;
}
@@ -252,6 +260,8 @@ protected:
bool mInitialized;
+ bool mLinearFiltering;
+
void computeGaussianWeights(float* weights, int32_t radius);
void horizontalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest,
int32_t width, int32_t height);
diff --git a/libs/hwui/GenerationCache.h b/libs/hwui/GenerationCache.h
index 070e33f..35c6bea 100644
--- a/libs/hwui/GenerationCache.h
+++ b/libs/hwui/GenerationCache.h
@@ -65,6 +65,7 @@ public:
void put(K key, V value);
V remove(K key);
V removeOldest();
+ V getValueAt(uint32_t index) const;
uint32_t size() const;
@@ -128,6 +129,11 @@ K GenerationCache<K, V>::getKeyAt(uint32_t index) const {
}
template<typename K, typename V>
+V GenerationCache<K, V>::getValueAt(uint32_t index) const {
+ return mCache.valueAt(index);
+}
+
+template<typename K, typename V>
V GenerationCache<K, V>::get(K key) {
ssize_t index = mCache.indexOfKey(key);
if (index >= 0) {
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index a0cc5d6..6024765 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -32,21 +32,14 @@ namespace uirenderer {
* Dimensions of a layer.
*/
struct LayerSize {
- LayerSize(): width(0), height(0), id(0) { }
- LayerSize(const uint32_t width, const uint32_t height): width(width), height(height), id(0) { }
- LayerSize(const LayerSize& size): width(size.width), height(size.height), id(size.id) { }
+ LayerSize(): width(0), height(0) { }
+ LayerSize(const uint32_t width, const uint32_t height): width(width), height(height) { }
+ LayerSize(const LayerSize& size): width(size.width), height(size.height) { }
uint32_t width;
uint32_t height;
- // Incremental id used by the layer cache to store multiple
- // LayerSize with the same dimensions
- uint32_t id;
-
bool operator<(const LayerSize& rhs) const {
- if (id != 0 && rhs.id != 0 && id != rhs.id) {
- return id < rhs.id;
- }
if (width == rhs.width) {
return height < rhs.height;
}
@@ -54,12 +47,12 @@ struct LayerSize {
}
bool operator==(const LayerSize& rhs) const {
- return id == rhs.id && width == rhs.width && height == rhs.height;
+ return width == rhs.width && height == rhs.height;
}
}; // struct LayerSize
/**
- * A layer has dimensions and is backed by an OpenGL texture.
+ * A layer has dimensions and is backed by an OpenGL texture or FBO.
*/
struct Layer {
/**
@@ -71,6 +64,11 @@ struct Layer {
*/
GLuint texture;
/**
+ * Name of the FBO used to render the layer. If the name is 0
+ * this layer is not backed by an FBO, but a simple texture.
+ */
+ GLuint fbo;
+ /**
* Opacity of the layer.
*/
int alpha;
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 8c70cf9..2183718 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -32,7 +32,7 @@ namespace uirenderer {
LayerCache::LayerCache():
mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
- mIdGenerator(1), mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
+ mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) {
char property[PROPERTY_VALUE_MAX];
if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, NULL) > 0) {
LOGD(" Setting layer cache size to %sMB", property);
@@ -44,7 +44,7 @@ LayerCache::LayerCache():
LayerCache::LayerCache(uint32_t maxByteSize):
mCache(GenerationCache<LayerSize, Layer*>::kUnlimitedCapacity),
- mIdGenerator(1), mSize(0), mMaxSize(maxByteSize) {
+ mSize(0), mMaxSize(maxByteSize) {
}
LayerCache::~LayerCache() {
@@ -110,6 +110,7 @@ Layer* LayerCache::get(LayerSize& size) {
layer = new Layer;
layer->blend = true;
layer->empty = true;
+ layer->fbo = 0;
glGenTextures(1, &layer->texture);
glBindTexture(GL_TEXTURE_2D, layer->texture);
@@ -121,6 +122,14 @@ Layer* LayerCache::get(LayerSize& size) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+#if DEBUG_LAYERS
+ uint32_t size = mCache.size();
+ for (uint32_t i = 0; i < size; i++) {
+ LayerSize ls = mCache.getKeyAt(i);
+ LAYER_LOGD(" Layer size %dx%d", ls.width, ls.height);
+ }
+#endif
}
return layer;
@@ -133,9 +142,10 @@ bool LayerCache::put(LayerSize& layerSize, Layer* layer) {
while (mSize + size > mMaxSize) {
Layer* oldest = mCache.removeOldest();
deleteLayer(oldest);
+ LAYER_LOGD(" Deleting layer %.2fx%.2f", oldest->layer.getWidth(),
+ oldest->layer.getHeight());
}
- layerSize.id = mIdGenerator++;
mCache.put(layerSize, layer);
mSize += size;
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index c0c7542..cbb7ae2 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -64,6 +64,7 @@ public:
* @param size The dimensions of the desired layer
*/
Layer* get(LayerSize& size);
+
/**
* Adds the layer to the cache. The layer will not be added if there is
* not enough space available.
@@ -96,7 +97,6 @@ private:
void deleteLayer(Layer* layer);
GenerationCache<LayerSize, Layer*> mCache;
- uint32_t mIdGenerator;
uint32_t mSize;
uint32_t mMaxSize;
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index c698b5a..219fd5e 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -53,6 +53,15 @@ void Matrix4::loadIdentity() {
mSimpleMatrix = true;
}
+#define EPSILON 0.00001f
+#define almost(u, v) (fabs((u) - (v)) < EPSILON)
+
+bool Matrix4::changesBounds() {
+ return !(almost(data[0], 1.0f) && almost(data[1], 0.0f) && almost(data[2], 0.0f) &&
+ almost(data[4], 0.0f) && almost(data[5], 1.0f) && almost(data[6], 0.0f) &&
+ almost(data[8], 0.0f) && almost(data[9], 0.0f) && almost(data[10], 1.0f));
+}
+
void Matrix4::load(const float* v) {
memcpy(data, v, sizeof(data));
mSimpleMatrix = false;
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index 0608efe..fe81159 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -103,6 +103,8 @@ public:
multiply(u);
}
+ bool changesBounds();
+
void copyTo(float* v) const;
void copyTo(SkMatrix& v) const;
diff --git a/libs/hwui/OpenGLDebugRenderer.cpp b/libs/hwui/OpenGLDebugRenderer.cpp
index 4e5123e..b9583e5 100644
--- a/libs/hwui/OpenGLDebugRenderer.cpp
+++ b/libs/hwui/OpenGLDebugRenderer.cpp
@@ -23,10 +23,10 @@
namespace android {
namespace uirenderer {
-void OpenGLDebugRenderer::prepare() {
+void OpenGLDebugRenderer::prepare(bool opaque) {
mPrimitivesCount = 0;
LOGD("========= Frame start =========");
- OpenGLRenderer::prepare();
+ OpenGLRenderer::prepare(opaque);
}
void OpenGLDebugRenderer::finish() {
diff --git a/libs/hwui/OpenGLDebugRenderer.h b/libs/hwui/OpenGLDebugRenderer.h
index ce15512..2ac19ae 100644
--- a/libs/hwui/OpenGLDebugRenderer.h
+++ b/libs/hwui/OpenGLDebugRenderer.h
@@ -34,7 +34,7 @@ public:
~OpenGLDebugRenderer() {
}
- void prepare();
+ void prepare(bool opaque);
void finish();
int saveLayer(float left, float top, float right, float bottom,
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 0810fb8..5399668 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -113,8 +113,6 @@ static const GLenum gTextureUnits[] = {
///////////////////////////////////////////////////////////////////////////////
OpenGLRenderer::OpenGLRenderer(): mCaches(Caches::getInstance()) {
- LOGD("Create OpenGLRenderer");
-
mShader = NULL;
mColorFilter = NULL;
mHasShadow = false;
@@ -133,7 +131,6 @@ OpenGLRenderer::OpenGLRenderer(): mCaches(Caches::getInstance()) {
}
OpenGLRenderer::~OpenGLRenderer() {
- LOGD("Destroy OpenGLRenderer");
// The context has already been destroyed at this point, do not call
// GL APIs. All GL state should be kept in Caches.h
}
@@ -148,9 +145,12 @@ void OpenGLRenderer::setViewport(int width, int height) {
mWidth = width;
mHeight = height;
+
+ mFirstSnapshot->height = height;
+ mFirstSnapshot->viewport.set(0, 0, width, height);
}
-void OpenGLRenderer::prepare() {
+void OpenGLRenderer::prepare(bool opaque) {
mSnapshot = new Snapshot(mFirstSnapshot,
SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
mSaveCount = 1;
@@ -160,8 +160,10 @@ void OpenGLRenderer::prepare() {
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
+ if (!opaque) {
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ }
glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, mWidth, mHeight);
@@ -188,7 +190,7 @@ void OpenGLRenderer::acquireContext() {
}
void OpenGLRenderer::releaseContext() {
- glViewport(0, 0, mWidth, mHeight);
+ glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
glEnable(GL_SCISSOR_TEST);
setScissorFromClip();
@@ -240,10 +242,17 @@ int OpenGLRenderer::saveSnapshot(int flags) {
bool OpenGLRenderer::restoreSnapshot() {
bool restoreClip = mSnapshot->flags & Snapshot::kFlagClipSet;
bool restoreLayer = mSnapshot->flags & Snapshot::kFlagIsLayer;
+ bool restoreOrtho = mSnapshot->flags & Snapshot::kFlagDirtyOrtho;
sp<Snapshot> current = mSnapshot;
sp<Snapshot> previous = mSnapshot->previous;
+ if (restoreOrtho) {
+ Rect& r = previous->viewport;
+ glViewport(r.left, r.top, r.right, r.bottom);
+ mOrthoMatrix.load(current->orthoMatrix);
+ }
+
mSaveCount--;
mSnapshot = previous;
@@ -264,7 +273,8 @@ bool OpenGLRenderer::restoreSnapshot() {
int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
const SkPaint* p, int flags) {
- int count = saveSnapshot(flags);
+ const GLuint previousFbo = mSnapshot->fbo;
+ const int count = saveSnapshot(flags);
int alpha = 255;
SkXfermode::Mode mode;
@@ -284,7 +294,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);
+ createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags, previousFbo);
return count;
}
@@ -317,7 +327,10 @@ int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bot
* - Issue the drawing
*
* Switching rendering target n + 1 times per drawn primitive is extremely costly.
- * To avoid this, layers are implemented in a different way here.
+ * To avoid this, layers are implemented in a different way here, at least in the
+ * general case. FBOs are used, as an optimization, when the "clip to layer" flag
+ * is set. When this flag is set we can redirect all drawing operations into a
+ * single FBO.
*
* This implementation relies on the frame buffer being at least RGBA 8888. When
* a layer is created, only a texture is created, not an FBO. The content of the
@@ -349,17 +362,21 @@ int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bot
* 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) {
- LAYER_LOGD("Requesting layer %fx%f", right - left, bottom - 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 bounds(left, top, right, bottom);
- mSnapshot->transform->mapRect(bounds);
-
- // Layers only make sense if they are in the framebuffer's bounds
- bounds.intersect(*mSnapshot->clipRect);
- bounds.snapToPixelBoundaries();
+ if (!fboLayer) {
+ mSnapshot->transform->mapRect(bounds);
+ // Layers only make sense if they are in the framebuffer's bounds
+ bounds.intersect(*mSnapshot->clipRect);
+ bounds.snapToPixelBoundaries();
+ }
if (bounds.isEmpty() || bounds.getWidth() > mMaxTextureSize ||
bounds.getHeight() > mMaxTextureSize) {
@@ -382,29 +399,77 @@ bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
snapshot->flags |= Snapshot::kFlagIsLayer;
snapshot->layer = layer;
- // Copy the framebuffer into the layer
- glBindTexture(GL_TEXTURE_2D, layer->texture);
-
- // TODO: Workaround for b/3054204
- glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
- bounds.getWidth(), bounds.getHeight(), 0);
+ if (fboLayer) {
+ layer->fbo = mCaches.fboCache.get();
- // TODO: Waiting for b/3054204 to be fixed
-// if (layer->empty) {
-// glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
-// bounds.getWidth(), bounds.getHeight(), 0);
-// layer->empty = false;
-// } else {
-// glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
-// bounds.getWidth(), bounds.getHeight());
-// }
+ snapshot->flags |= Snapshot::kFlagIsFboLayer;
+ snapshot->fbo = layer->fbo;
+ snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
+ snapshot->resetClip(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+ snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+ snapshot->height = bounds.getHeight();
+ snapshot->flags |= Snapshot::kFlagDirtyOrtho;
+ snapshot->orthoMatrix.load(mOrthoMatrix);
- if (flags & SkCanvas::kClipToLayer_SaveFlag && mSnapshot->clipTransformed(bounds)) {
setScissorFromClip();
- }
- // Enqueue the buffer coordinates to clear the corresponding region later
- mLayers.push(new Rect(bounds));
+ // Bind texture to FBO
+ glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
+ glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+ // Initialize the texture if needed
+ if (layer->empty) {
+ layer->empty = false;
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ }
+
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ layer->texture, 0);
+
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ LOGE("Framebuffer incomplete (GL error code 0x%x)", status);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ glDeleteTextures(1, &layer->texture);
+ mCaches.fboCache.put(layer->fbo);
+
+ delete layer;
+
+ return false;
+ }
+
+ // Clear the FBO
+ glDisable(GL_SCISSOR_TEST);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glEnable(GL_SCISSOR_TEST);
+
+ // Change the ortho projection
+ glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
+ mOrthoMatrix.loadOrtho(0.0f, bounds.getWidth(), bounds.getHeight(), 0.0f, -1.0f, 1.0f);
+ } else {
+ // Copy the framebuffer into the layer
+ glBindTexture(GL_TEXTURE_2D, layer->texture);
+
+ // TODO: Workaround for b/3054204
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+ bounds.getWidth(), bounds.getHeight(), 0);
+
+ // TODO: Waiting for b/3054204 to be fixed
+ // if (layer->empty) {
+ // glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+ // bounds.getWidth(), bounds.getHeight(), 0);
+ // layer->empty = false;
+ // } else {
+ // glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left, mHeight - bounds.bottom,
+ // bounds.getWidth(), bounds.getHeight());
+ // }
+
+ // Enqueue the buffer coordinates to clear the corresponding region later
+ mLayers.push(new Rect(bounds));
+ }
return true;
}
@@ -418,14 +483,21 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
return;
}
+ const bool fboLayer = current->flags & SkCanvas::kClipToLayer_SaveFlag;
+
+ if (fboLayer) {
+ // Unbind current FBO and restore previous one
+ glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+ }
+
// Restore the clip from the previous snapshot
const Rect& clip = *previous->clipRect;
- glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
Layer* layer = current->layer;
const Rect& rect = layer->layer;
- if (layer->alpha < 255) {
+ if (!fboLayer && layer->alpha < 255) {
drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
}
@@ -433,20 +505,32 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
// Layers are already drawn with a top-left origin, don't flip the texture
resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
- drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
- 1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
- &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+ if (fboLayer) {
+ drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
+ layer->texture, layer->alpha / 255.0f, layer->mode, layer->blend);
+ } else {
+ drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
+ 1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
+ &mMeshVertices[0].texture[0], GL_TRIANGLE_STRIP, gMeshCount, true, true);
+ }
resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+ if (fboLayer) {
+ // Detach the texture from the FBO
+ glBindFramebuffer(GL_FRAMEBUFFER, current->fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+
+ // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed
+ mCaches.fboCache.put(current->fbo);
+ }
+
LayerSize size(rect.getWidth(), rect.getHeight());
- // Failing to add the layer to the cache should happen only if the
- // layer is too large
+ // Failing to add the layer to the cache should happen only if the layer is too large
if (!mCaches.layerCache.put(size, layer)) {
LAYER_LOGD("Deleting layer");
-
glDeleteTextures(1, &layer->texture);
-
delete layer;
}
}
@@ -506,7 +590,7 @@ void OpenGLRenderer::concatMatrix(SkMatrix* matrix) {
void OpenGLRenderer::setScissorFromClip() {
const Rect& clip = *mSnapshot->clipRect;
- glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight());
}
const Rect& OpenGLRenderer::getClipBounds() {
@@ -767,8 +851,10 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
GLuint textureUnit = 0;
glActiveTexture(gTextureUnits[textureUnit]);
- setupTextureAlpha8(fontRenderer.getTexture(), 0, 0, textureUnit, x, y, r, g, b, a,
- mode, false, true);
+ // Assume that the modelView matrix does not force scales, rotates, etc.
+ const bool linearFilter = mSnapshot->transform->changesBounds();
+ setupTextureAlpha8(fontRenderer.getTexture(linearFilter), 0, 0, textureUnit,
+ x, y, r, g, b, a, mode, false, true);
const Rect& clip = mSnapshot->getLocalClip();
clearLayerRegions();
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 1974cf0..4caa8fb 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -67,7 +67,7 @@ public:
virtual void setViewport(int width, int height);
- virtual void prepare();
+ virtual void prepare(bool opaque);
virtual void finish();
virtual void acquireContext();
@@ -167,11 +167,12 @@ private:
* @param alpha The translucency of the layer
* @param mode The blending mode of the layer
* @param flags The layer save flags
+ * @param previousFbo The name of the current framebuffer
*
* @return True if the layer was successfully created, false otherwise
*/
bool createLayer(sp<Snapshot> snapshot, float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags);
+ int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo);
/**
* Clears all the regions corresponding to the current list of layers.
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 3e9412c..d1a1b45 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -35,9 +35,6 @@ const char* gVS_Header_Uniforms =
"uniform mat4 transform;\n";
const char* gVS_Header_Uniforms_HasGradient[3] = {
// Linear
- "uniform float gradientLength;\n"
- "uniform vec2 gradient;\n"
- "uniform vec2 gradientStart;\n"
"uniform mat4 screenSpace;\n",
// Circular
"uniform vec2 gradientStart;\n"
@@ -69,8 +66,7 @@ const char* gVS_Main_OutTexCoords =
" outTexCoords = texCoords;\n";
const char* gVS_Main_OutGradient[3] = {
// Linear
- " vec4 location = screenSpace * position;\n"
- " index = dot(location.xy - gradientStart, gradient) * gradientLength;\n",
+ " index = (screenSpace * position).x;\n",
// Circular
" vec4 location = screenSpace * position;\n"
" circular = (gradientMatrix * vec4(location.xy - gradientStart, 0.0, 0.0)).xy;\n",
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index d573805..3012824 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -45,7 +45,7 @@
#define MB(s) s * 1024 * 1024
#define DEFAULT_TEXTURE_CACHE_SIZE 18.0f
-#define DEFAULT_LAYER_CACHE_SIZE 4.0f
+#define DEFAULT_LAYER_CACHE_SIZE 8.0f
#define DEFAULT_PATH_CACHE_SIZE 5.0f
#define DEFAULT_PATCH_CACHE_SIZE 100
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 165c0da..83de2b2 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -153,11 +153,31 @@ void SkiaBitmapShader::updateTransforms(Program* program, const mat4& modelView,
// Linear gradient shader
///////////////////////////////////////////////////////////////////////////////
+static void toUnitMatrix(const SkPoint pts[2], SkMatrix* matrix) {
+ SkVector vec = pts[1] - pts[0];
+ const float mag = vec.length();
+ const float inv = mag ? 1.0f / mag : 0;
+
+ vec.scale(inv);
+ matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY);
+ matrix->postTranslate(-pts[0].fX, -pts[0].fY);
+ matrix->postScale(inv, inv);
+}
+
SkiaLinearGradientShader::SkiaLinearGradientShader(float* bounds, uint32_t* colors,
float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
SkMatrix* matrix, bool blend):
SkiaShader(kLinearGradient, key, tileMode, tileMode, matrix, blend),
mBounds(bounds), mColors(colors), mPositions(positions), mCount(count) {
+ SkPoint points[2];
+ points[0].set(bounds[0], bounds[1]);
+ points[1].set(bounds[2], bounds[3]);
+
+ SkMatrix unitMatrix;
+ toUnitMatrix(points, &unitMatrix);
+ mUnitMatrix.load(unitMatrix);
+
+ updateLocalMatrix(matrix);
}
SkiaLinearGradientShader::~SkiaLinearGradientShader() {
@@ -172,6 +192,23 @@ void SkiaLinearGradientShader::describe(ProgramDescription& description,
description.gradientType = ProgramDescription::kGradientLinear;
}
+void SkiaLinearGradientShader::computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView) {
+ screenSpace.loadMultiply(mUnitMatrix, mShaderMatrix);
+ screenSpace.multiply(modelView);
+}
+
+void SkiaLinearGradientShader::updateLocalMatrix(const SkMatrix* matrix) {
+ if (matrix) {
+ mat4 localMatrix(*matrix);
+ mShaderMatrix.loadInverse(localMatrix);
+ }
+}
+
+void SkiaLinearGradientShader::setMatrix(SkMatrix* matrix) {
+ SkiaShader::setMatrix(matrix);
+ updateLocalMatrix(matrix);
+}
+
void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelView,
const Snapshot& snapshot, GLuint* textureUnit) {
GLuint textureSlot = (*textureUnit)++;
@@ -182,34 +219,19 @@ void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelV
texture = mGradientCache->addLinearGradient(mKey, mColors, mPositions, mCount, mTileX);
}
- Rect start(mBounds[0], mBounds[1], mBounds[2], mBounds[3]);
- if (mMatrix) {
- mat4 shaderMatrix(*mMatrix);
- shaderMatrix.mapPoint(start.left, start.top);
- shaderMatrix.mapPoint(start.right, start.bottom);
- }
- snapshot.transform->mapRect(start);
-
- const float gradientX = start.right - start.left;
- const float gradientY = start.bottom - start.top;
-
- mat4 screenSpace(*snapshot.transform);
- screenSpace.multiply(modelView);
+ mat4 screenSpace;
+ computeScreenSpaceMatrix(screenSpace, modelView);
// Uniforms
bindTexture(texture->id, gTileModes[mTileX], gTileModes[mTileY], textureSlot);
glUniform1i(program->getUniform("gradientSampler"), textureSlot);
- glUniform2f(program->getUniform("gradientStart"), start.left, start.top);
- glUniform2f(program->getUniform("gradient"), gradientX, gradientY);
- glUniform1f(program->getUniform("gradientLength"),
- 1.0f / (gradientX * gradientX + gradientY * gradientY));
glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
}
void SkiaLinearGradientShader::updateTransforms(Program* program, const mat4& modelView,
const Snapshot& snapshot) {
- mat4 screenSpace(*snapshot.transform);
- screenSpace.multiply(modelView);
+ mat4 screenSpace;
+ computeScreenSpaceMatrix(screenSpace, modelView);
glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
}
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index 9f8778f..2c1eb35 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -77,7 +77,7 @@ struct SkiaShader {
const Snapshot& snapshot) {
}
- void setMatrix(SkMatrix* matrix) {
+ virtual void setMatrix(SkMatrix* matrix) {
mMatrix = matrix;
}
@@ -139,7 +139,15 @@ struct SkiaLinearGradientShader: public SkiaShader {
GLuint* textureUnit);
void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);
+ void setMatrix(SkMatrix* matrix);
+
private:
+ void updateLocalMatrix(const SkMatrix* matrix);
+ void computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView);
+
+ mat4 mUnitMatrix;
+ mat4 mShaderMatrix;
+
float* mBounds;
uint32_t* mColors;
float* mPositions;
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 062c986..3d74b4c 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -43,7 +43,7 @@ namespace uirenderer {
*/
class Snapshot: public LightRefBase<Snapshot> {
public:
- Snapshot(): flags(0), previous(NULL), layer(NULL) {
+ Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0) {
transform = &mTransformRoot;
clipRect = &mClipRectRoot;
}
@@ -53,7 +53,8 @@ public:
* the previous snapshot.
*/
Snapshot(const sp<Snapshot>& s, int saveFlags):
- flags(0), previous(s), layer(NULL) {
+ flags(0), previous(s), layer(NULL),
+ fbo(s->fbo), viewport(s->viewport), height(s->height) {
if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
mTransformRoot.load(*s->transform);
transform = &mTransformRoot;
@@ -91,9 +92,19 @@ public:
*/
kFlagIsLayer = 0x2,
/**
+ * Indicates that this snapshot is a special type of layer
+ * backed by an FBO. This flag only makes sense when the
+ * flag kFlagIsLayer is also set.
+ */
+ kFlagIsFboLayer = 0x4,
+ /**
* Indicates that the local clip should be recomputed.
*/
- kFlagDirtyLocalClip = 0x4,
+ kFlagDirtyLocalClip = 0x8,
+ /**
+ * Indicates that this snapshot has changed the ortho matrix.
+ */
+ kFlagDirtyOrtho = 0x10,
};
/**
@@ -141,6 +152,7 @@ public:
}
if (clipped) {
+ clipRect->snapToPixelBoundaries();
flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
}
@@ -168,6 +180,17 @@ public:
return mLocalClip;
}
+ void resetTransform(float x, float y, float z) {
+ transform = &mTransformRoot;
+ transform->loadTranslate(x, y, z);
+ }
+
+ void resetClip(float left, float top, float right, float bottom) {
+ clipRect = &mClipRectRoot;
+ clipRect->set(left, top, right, bottom);
+ flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
+ }
+
/**
* Dirty flags.
*/
@@ -184,6 +207,26 @@ public:
Layer* layer;
/**
+ * Only set when the flag kFlagIsFboLayer is set.
+ */
+ GLuint fbo;
+
+ /**
+ * Current viewport.
+ */
+ Rect viewport;
+
+ /**
+ * Height of the framebuffer the snapshot is rendering into.
+ */
+ int height;
+
+ /**
+ * Contains the previous ortho matrix.
+ */
+ mat4 orthoMatrix;
+
+ /**
* Local transformation. Holds the current translation, scale and
* rotation values.
*/
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index adf6ee2..701df83 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -18,6 +18,8 @@
#include <GLES2/gl2.h>
+#include <SkCanvas.h>
+
#include <utils/threads.h>
#include "TextureCache.h"
@@ -192,8 +194,12 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege
// decoding happened
texture->blend = !bitmap->isOpaque();
break;
+ case SkBitmap::kIndex8_Config:
+ uploadPalettedTexture(resize, bitmap, texture->width, texture->height);
+ texture->blend = false;
+ break;
default:
- LOGW("Unsupported bitmap config");
+ LOGW("Unsupported bitmap config: %d", bitmap->getConfig());
break;
}
@@ -204,6 +210,20 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
+void TextureCache::uploadPalettedTexture(bool resize, SkBitmap* bitmap,
+ uint32_t width, uint32_t height) {
+ SkBitmap rgbaBitmap;
+ rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ rgbaBitmap.allocPixels();
+ rgbaBitmap.eraseColor(0);
+
+ SkCanvas canvas(rgbaBitmap);
+ canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL);
+
+ uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), height,
+ GL_UNSIGNED_BYTE, rgbaBitmap.getPixels());
+}
+
void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height,
GLenum type, const GLvoid * data) {
if (resize) {
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 7cf66d9..34c5455 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -79,6 +79,7 @@ private:
*/
void generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate = false);
+ void uploadPalettedTexture(bool resize, SkBitmap* bitmap, uint32_t width, uint32_t height);
void uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height,
GLenum type, const GLvoid * data);
diff --git a/libs/rs/Android.mk b/libs/rs/Android.mk
index 37c418b..05c1a48 100644
--- a/libs/rs/Android.mk
+++ b/libs/rs/Android.mk
@@ -83,7 +83,6 @@ LOCAL_SRC_FILES:= \
rsElement.cpp \
rsFileA3D.cpp \
rsFont.cpp \
- rsLight.cpp \
rsLocklessFifo.cpp \
rsObjectBase.cpp \
rsMatrix.cpp \
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index 13ae1fb..66e27f3 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -40,7 +40,6 @@ typedef void * RsSampler;
typedef void * RsScript;
typedef void * RsMesh;
typedef void * RsType;
-typedef void * RsLight;
typedef void * RsObjectBase;
typedef void * RsProgram;
@@ -242,7 +241,6 @@ enum RsA3DClassID {
RS_A3D_CLASS_ID_PROGRAM_STORE,
RS_A3D_CLASS_ID_SAMPLER,
RS_A3D_CLASS_ID_ANIMATION,
- RS_A3D_CLASS_ID_LIGHT,
RS_A3D_CLASS_ID_ADAPTER_1D,
RS_A3D_CLASS_ID_ADAPTER_2D,
RS_A3D_CLASS_ID_SCRIPT_C
diff --git a/libs/rs/RenderScriptEnv.h b/libs/rs/RenderScriptEnv.h
index c83ece4..b82eaf1 100644
--- a/libs/rs/RenderScriptEnv.h
+++ b/libs/rs/RenderScriptEnv.h
@@ -13,8 +13,6 @@ typedef void * RsMesh;
typedef void * RsType;
typedef void * RsProgramFragment;
typedef void * RsProgramStore;
-typedef void * RsLight;
-
typedef struct {
float m[16];
diff --git a/libs/rs/java/Samples/res/raw/multitexf.glsl b/libs/rs/java/Samples/res/raw/multitexf.glsl
index 91151ad..351ff9b 100644
--- a/libs/rs/java/Samples/res/raw/multitexf.glsl
+++ b/libs/rs/java/Samples/res/raw/multitexf.glsl
@@ -1,4 +1,4 @@
-varying vec4 varTex0;
+varying vec2 varTex0;
void main() {
vec2 t0 = varTex0.xy;
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
index a15c4a1..0990da3 100644
--- a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
@@ -84,8 +84,6 @@ public class RsRenderStatesRS {
private Allocation mTexTransparent;
private Allocation mTexChecker;
- private Allocation mAllocPV;
-
private Mesh mMbyNMesh;
private Mesh mTorus;
@@ -95,7 +93,6 @@ public class RsRenderStatesRS {
Font mFontSerifItalic;
Font mFontSerifBoldItalic;
Font mFontMono;
-
private Allocation mTextAlloc;
private ScriptC_rsrenderstates mScript;
@@ -267,12 +264,15 @@ public class RsRenderStatesRS {
mFontSerifBoldItalic = Font.createFromFamily(mRS, mRes, "serif", Font.Style.BOLD_ITALIC, 8);
mFontMono = Font.createFromFamily(mRS, mRes, "mono", Font.Style.NORMAL, 8);
+ mTextAlloc = Allocation.createFromString(mRS, "String from allocation");
+
mScript.set_gFontSans(mFontSans);
mScript.set_gFontSerif(mFontSerif);
mScript.set_gFontSerifBold(mFontSerifBold);
mScript.set_gFontSerifItalic(mFontSerifItalic);
mScript.set_gFontSerifBoldItalic(mFontSerifBoldItalic);
mScript.set_gFontMono(mFontMono);
+ mScript.set_gTextAlloc(mTextAlloc);
}
private void initMesh() {
diff --git a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
index b471504..77384ef 100644
--- a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
+++ b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
@@ -42,6 +42,7 @@ rs_font gFontSerifBold;
rs_font gFontSerifItalic;
rs_font gFontSerifBoldItalic;
rs_font gFontMono;
+rs_allocation gTextAlloc;
int gDisplayMode;
@@ -70,7 +71,7 @@ rs_program_fragment gProgFragmentMultitex;
#pragma rs export_var(gProgStoreBlendNoneDepth, gProgStoreBlendNone, gProgStoreBlendAlpha, gProgStoreBlendAdd)
#pragma rs export_var(gTexOpaque, gTexTorus, gTexTransparent, gTexChecker)
#pragma rs export_var(gMbyNMesh, gTorusMesh)
-#pragma rs export_var(gFontSans, gFontSerif, gFontSerifBold, gFontSerifItalic, gFontSerifBoldItalic, gFontMono)
+#pragma rs export_var(gFontSans, gFontSerif, gFontSerifBold, gFontSerifItalic, gFontSerifBoldItalic, gFontMono, gTextAlloc)
#pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gMipLinearAniso8, gMipLinearAniso15, gNearestClamp)
#pragma rs export_var(gCullBack, gCullFront, gCullNone)
#pragma rs export_var(gVSConstants, gFSConstants, gVSInputs, gProgVertexCustom, gProgFragmentCustom, gProgFragmentMultitex)
@@ -85,7 +86,7 @@ void init() {
void displayFontSamples() {
rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
- int yPos = 30;
+ int yPos = 100;
rsgBindFont(gFontSans);
rsgDrawText("Sans font sample", 30, yPos);
yPos += 30;
@@ -107,6 +108,47 @@ void displayFontSamples() {
yPos += 30;
rsgBindFont(gFontMono);
rsgDrawText("Monospace font sample", 30, yPos);
+ yPos += 50;
+
+ // Now use text metrics to center the text
+ uint width = rsgGetWidth();
+ uint height = rsgGetHeight();
+ int left = 0, right = 0, top = 0, bottom = 0;
+
+ rsgFontColor(0.9f, 0.9f, 0.95f, 1.0f);
+ rsgBindFont(gFontSerifBoldItalic);
+
+ rsgMeasureText(gTextAlloc, &left, &right, &top, &bottom);
+ int centeredPos = width / 2 - (right - left) / 2;
+ rsgDrawText(gTextAlloc, centeredPos, yPos);
+ yPos += 30;
+
+ const char* text = "Centered Text Sample";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ centeredPos = width / 2 - (right - left) / 2;
+ rsgDrawText(text, centeredPos, yPos);
+ yPos += 30;
+
+ rsgBindFont(gFontSans);
+ text = "More Centered Text Samples";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ centeredPos = width / 2 - (right - left) / 2;
+ rsgDrawText(text, centeredPos, yPos);
+ yPos += 30;
+
+ // Now draw bottom and top right aligned text
+ text = "Top-right aligned text";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ rsgDrawText(text, width - right, top);
+
+ text = "Top-left";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ rsgDrawText(text, -left, top);
+
+ text = "Bottom-right aligned text";
+ rsgMeasureText(text, &left, &right, &top, &bottom);
+ rsgDrawText(text, width - right, height + bottom);
+
}
void bindProgramVertexOrtho() {
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index a4752f4..0da637e 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -280,6 +280,17 @@ AllocationGetType {
ret const void*
}
+AllocationResize1D {
+ param RsAllocation va
+ param uint32_t dimX
+ }
+
+AllocationResize2D {
+ param RsAllocation va
+ param uint32_t dimX
+ param uint32_t dimY
+ }
+
SamplerBegin {
}
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 0356e4d..2e9e0b3 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -488,12 +488,13 @@ void Allocation::sendDirty() const
}
}
-void Allocation::incRefs(const void *ptr, size_t ct) const
+void Allocation::incRefs(const void *ptr, size_t ct, size_t startOff) const
{
const uint8_t *p = static_cast<const uint8_t *>(ptr);
const Element *e = mType->getElement();
uint32_t stride = e->getSizeBytes();
+ p += stride * startOff;
while (ct > 0) {
e->incRefs(p);
ct --;
@@ -501,12 +502,13 @@ void Allocation::incRefs(const void *ptr, size_t ct) const
}
}
-void Allocation::decRefs(const void *ptr, size_t ct) const
+void Allocation::decRefs(const void *ptr, size_t ct, size_t startOff) const
{
const uint8_t *p = static_cast<const uint8_t *>(ptr);
const Element *e = mType->getElement();
uint32_t stride = e->getSizeBytes();
+ p += stride * startOff;
while (ct > 0) {
e->decRefs(p);
ct --;
@@ -514,6 +516,37 @@ void Allocation::decRefs(const void *ptr, size_t ct) const
}
}
+void Allocation::copyRange1D(Context *rsc, const Allocation *src, int32_t srcOff, int32_t destOff, int32_t len)
+{
+}
+
+void Allocation::resize1D(Context *rsc, uint32_t dimX)
+{
+ Type *t = mType->cloneAndResize1D(rsc, dimX);
+
+ uint32_t oldDimX = mType->getDimX();
+ if (dimX == oldDimX) {
+ return;
+ }
+
+ if (dimX < oldDimX) {
+ decRefs(mPtr, oldDimX - dimX, dimX);
+ }
+ mPtr = realloc(mPtr, t->getSizeBytes());
+
+ if (dimX > oldDimX) {
+ const Element *e = mType->getElement();
+ uint32_t stride = e->getSizeBytes();
+ memset(((uint8_t *)mPtr) + stride * oldDimX, 0, stride * (dimX - oldDimX));
+ }
+ mType.set(t);
+}
+
+void Allocation::resize2D(Context *rsc, uint32_t dimX, uint32_t dimY)
+{
+ LOGE("not implemented");
+}
+
/////////////////
//
@@ -822,6 +855,18 @@ void rsi_AllocationRead(Context *rsc, RsAllocation va, void *data)
a->read(data);
}
+void rsi_AllocationResize1D(Context *rsc, RsAllocation va, uint32_t dimX)
+{
+ Allocation *a = static_cast<Allocation *>(va);
+ a->resize1D(rsc, dimX);
+}
+
+void rsi_AllocationResize2D(Context *rsc, RsAllocation va, uint32_t dimX, uint32_t dimY)
+{
+ Allocation *a = static_cast<Allocation *>(va);
+ a->resize2D(rsc, dimX, dimY);
+}
+
const void* rsi_AllocationGetType(Context *rsc, RsAllocation va)
{
Allocation *a = static_cast<Allocation *>(va);
diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h
index ce5372f..24e245f 100644
--- a/libs/rs/rsAllocation.h
+++ b/libs/rs/rsAllocation.h
@@ -55,6 +55,10 @@ public:
void uploadToBufferObject(const Context *rsc);
uint32_t getBufferObjectID() const {return mBufferID;}
+ void copyRange1D(Context *rsc, const Allocation *src, int32_t srcOff, int32_t destOff, int32_t len);
+
+ void resize1D(Context *rsc, uint32_t dimX);
+ void resize2D(Context *rsc, uint32_t dimX, uint32_t dimY);
void data(Context *rsc, const void *data, uint32_t sizeBytes);
void subData(Context *rsc, uint32_t xoff, uint32_t count, const void *data, uint32_t sizeBytes);
@@ -86,8 +90,8 @@ public:
bool getIsTexture() const {return mIsTexture;}
bool getIsBufferObject() const {return mIsVertexBuffer;}
- void incRefs(const void *ptr, size_t ct) const;
- void decRefs(const void *ptr, size_t ct) const;
+ void incRefs(const void *ptr, size_t ct, size_t startOff = 0) const;
+ void decRefs(const void *ptr, size_t ct, size_t startOff = 0) const;
void sendDirty() const;
bool getHasGraphicsMipmaps() const {return mTextureGenMipmap;}
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index 6940033..30add62 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -188,6 +188,9 @@ void Context::timerInit()
mTimeFrame = mTimeLast;
mTimeLastFrame = mTimeLast;
mTimerActive = RS_TIMER_INTERNAL;
+ mAverageFPSFrameCount = 0;
+ mAverageFPSStartTime = mTimeLast;
+ mAverageFPS = 0;
timerReset();
}
@@ -195,6 +198,16 @@ void Context::timerFrame()
{
mTimeLastFrame = mTimeFrame;
mTimeFrame = getTime();
+ // Update average fps
+ const uint64_t averageFramerateInterval = 1000 * 1000000;
+ mAverageFPSFrameCount ++;
+ uint64_t inverval = mTimeFrame - mAverageFPSStartTime;
+ if(inverval >= averageFramerateInterval) {
+ inverval = inverval / 1000000;
+ mAverageFPS = (mAverageFPSFrameCount * 1000) / inverval;
+ mAverageFPSFrameCount = 0;
+ mAverageFPSStartTime = mTimeFrame;
+ }
}
void Context::timerSet(Timers tm)
@@ -218,12 +231,13 @@ void Context::timerPrint()
if (props.mLogTimes) {
- LOGV("RS: Frame (%i), Script %2.1f (%i), Clear & Swap %2.1f (%i), Idle %2.1f (%lli), Internal %2.1f (%lli)",
+ LOGV("RS: Frame (%i), Script %2.1f (%i), Clear & Swap %2.1f (%i), Idle %2.1f (%lli), Internal %2.1f (%lli), Avg fps: %u",
mTimeMSLastFrame,
100.0 * mTimers[RS_TIMER_SCRIPT] / total, mTimeMSLastScript,
100.0 * mTimers[RS_TIMER_CLEAR_SWAP] / total, mTimeMSLastSwap,
100.0 * mTimers[RS_TIMER_IDLE] / total, mTimers[RS_TIMER_IDLE] / 1000000,
- 100.0 * mTimers[RS_TIMER_INTERNAL] / total, mTimers[RS_TIMER_INTERNAL] / 1000000);
+ 100.0 * mTimers[RS_TIMER_INTERNAL] / total, mTimers[RS_TIMER_INTERNAL] / 1000000,
+ mAverageFPS);
}
}
@@ -255,17 +269,17 @@ static bool getProp(const char *str)
void Context::displayDebugStats()
{
char buffer[128];
- sprintf(buffer, "Frame %i ms, Script %i ms", mTimeMSLastFrame, mTimeMSLastScript);
+ sprintf(buffer, "Avg fps %u, Frame %i ms, Script %i ms", mAverageFPS, mTimeMSLastFrame, mTimeMSLastScript);
float oldR, oldG, oldB, oldA;
mStateFont.getFontColor(&oldR, &oldG, &oldB, &oldA);
+ uint32_t bufferLen = strlen(buffer);
- float shadowCol = 0.2f;
+ float shadowCol = 0.1f;
mStateFont.setFontColor(shadowCol, shadowCol, shadowCol, 1.0f);
- mStateFont.renderText(buffer, 5, getHeight() - 5);
+ mStateFont.renderText(buffer, bufferLen, 5, getHeight() - 6);
- float textCol = 0.9f;
- mStateFont.setFontColor(textCol, textCol, textCol, 1.0f);
- mStateFont.renderText(buffer, 4, getHeight() - 6);
+ mStateFont.setFontColor(1.0f, 0.7f, 0.0f, 1.0f);
+ mStateFont.renderText(buffer, bufferLen, 4, getHeight() - 7);
mStateFont.setFontColor(oldR, oldG, oldB, oldA);
}
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index dabe196..8a8b8a8 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -30,7 +30,6 @@
#include "rsAllocation.h"
#include "rsAdapter.h"
#include "rsSampler.h"
-#include "rsLight.h"
#include "rsFont.h"
#include "rsProgramFragment.h"
#include "rsProgramStore.h"
@@ -94,7 +93,6 @@ public:
ProgramStoreState mStateFragmentStore;
ProgramRasterState mStateRaster;
ProgramVertexState mStateVertex;
- LightState mStateLight;
VertexArrayState mStateVertexArray;
FontState mStateFont;
@@ -296,6 +294,9 @@ private:
uint32_t mTimeMSLastFrame;
uint32_t mTimeMSLastScript;
uint32_t mTimeMSLastSwap;
+ uint32_t mAverageFPSFrameCount;
+ uint64_t mAverageFPSStartTime;
+ uint32_t mAverageFPS;
};
}
diff --git a/libs/rs/rsContextHostStub.h b/libs/rs/rsContextHostStub.h
index 06298e8..aa0205d 100644
--- a/libs/rs/rsContextHostStub.h
+++ b/libs/rs/rsContextHostStub.h
@@ -30,7 +30,6 @@
#include "rsAllocation.h"
#include "rsAdapter.h"
#include "rsSampler.h"
-#include "rsLight.h"
#include "rsProgramFragment.h"
#include "rsProgramStore.h"
#include "rsProgramRaster.h"
@@ -68,7 +67,6 @@ public:
ProgramStoreState mStateFragmentStore;
//ProgramRasterState mStateRaster;
//ProgramVertexState mStateVertex;
- LightState mStateLight;
VertexArrayState mStateVertexArray;
//ScriptCState mScriptC;
diff --git a/libs/rs/rsFileA3D.cpp b/libs/rs/rsFileA3D.cpp
index 893598f..c90edc2 100644
--- a/libs/rs/rsFileA3D.cpp
+++ b/libs/rs/rsFileA3D.cpp
@@ -278,9 +278,6 @@ ObjectBase *FileA3D::initializeFromEntry(size_t index) {
case RS_A3D_CLASS_ID_ANIMATION:
entry->mRsObj = Animation::createFromStream(mRSC, mReadStream);
break;
- case RS_A3D_CLASS_ID_LIGHT:
- entry->mRsObj = Light::createFromStream(mRSC, mReadStream);
- break;
case RS_A3D_CLASS_ID_ADAPTER_1D:
entry->mRsObj = Adapter1D::createFromStream(mRSC, mReadStream);
break;
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index c516ea9..a951005 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -84,34 +84,95 @@ void Font::invalidateTextureCache()
}
}
-void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y)
+void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y)
{
FontState *state = &mRSC->mStateFont;
- int nPenX = x + glyph->mBitmapLeft;
- int nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
+ int32_t nPenX = x + glyph->mBitmapLeft;
+ int32_t nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
- state->appendMeshQuad(nPenX, nPenY, 0,
- glyph->mBitmapMinU, glyph->mBitmapMaxV,
+ float u1 = glyph->mBitmapMinU;
+ float u2 = glyph->mBitmapMaxU;
+ float v1 = glyph->mBitmapMinV;
+ float v2 = glyph->mBitmapMaxV;
- nPenX + (int)glyph->mBitmapWidth, nPenY, 0,
- glyph->mBitmapMaxU, glyph->mBitmapMaxV,
+ int32_t width = (int32_t) glyph->mBitmapWidth;
+ int32_t height = (int32_t) glyph->mBitmapHeight;
- nPenX + (int)glyph->mBitmapWidth, nPenY - (int)glyph->mBitmapHeight, 0,
- glyph->mBitmapMaxU, glyph->mBitmapMinV,
+ state->appendMeshQuad(nPenX, nPenY, 0, u1, v2,
+ nPenX + width, nPenY, 0, u2, v2,
+ nPenX + width, nPenY - height, 0, u2, v1,
+ nPenX, nPenY - height, 0, u1, v1);
+}
+
+void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int32_t x, int32_t y,
+ uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+ int32_t nPenX = x + glyph->mBitmapLeft;
+ int32_t nPenY = y + glyph->mBitmapTop;
+
+ uint32_t endX = glyph->mBitmapMinX + glyph->mBitmapWidth;
+ uint32_t endY = glyph->mBitmapMinY + glyph->mBitmapHeight;
+
+ FontState *state = &mRSC->mStateFont;
+ uint32_t cacheWidth = state->getCacheTextureType()->getDimX();
+ const uint8_t* cacheBuffer = state->getTextTextureData();
+
+ uint32_t cacheX = 0, cacheY = 0;
+ int32_t bX = 0, bY = 0;
+ for (cacheX = glyph->mBitmapMinX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
+ for (cacheY = glyph->mBitmapMinY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
+ if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) {
+ LOGE("Skipping invalid index");
+ continue;
+ }
+ uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
+ bitmap[bY * bitmapW + bX] = tempCol;
+ }
+ }
- nPenX, nPenY - (int)glyph->mBitmapHeight, 0,
- glyph->mBitmapMinU, glyph->mBitmapMinV);
}
-void Font::renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyphs, int x, int y)
+void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y, Rect *bounds) {
+ int32_t nPenX = x + glyph->mBitmapLeft;
+ int32_t nPenY = y - glyph->mBitmapTop + glyph->mBitmapHeight;
+
+ int32_t width = (int32_t) glyph->mBitmapWidth;
+ int32_t height = (int32_t) glyph->mBitmapHeight;
+
+ if (bounds->bottom > nPenY) {
+ bounds->bottom = nPenY;
+ }
+ if (bounds->left > nPenX) {
+ bounds->left = nPenX;
+ }
+ if (bounds->right < nPenX + width) {
+ bounds->right = nPenX + width;
+ }
+ if (bounds->top < nPenY + height) {
+ bounds->top = nPenY + height;
+ }
+}
+
+void Font::renderUTF(const char *text, uint32_t len, int32_t x, int32_t y,
+ uint32_t start, int32_t numGlyphs,
+ RenderMode mode, Rect *bounds,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH)
{
if(!mInitialized || numGlyphs == 0 || text == NULL || len == 0) {
return;
}
- int penX = x, penY = y;
- int glyphsLeft = 1;
+ if(mode == Font::MEASURE) {
+ if (bounds == NULL) {
+ LOGE("No return rectangle provided to measure text");
+ return;
+ }
+ // Reset min and max of the bounding box to something large
+ bounds->set(1e6, -1e6, -1e6, 1e6);
+ }
+
+ int32_t penX = x, penY = y;
+ int32_t glyphsLeft = 1;
if(numGlyphs > 0) {
glyphsLeft = numGlyphs;
}
@@ -135,7 +196,17 @@ void Font::renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyp
// If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
if(cachedGlyph->mIsValid) {
- drawCachedGlyph(cachedGlyph, penX, penY);
+ switch(mode) {
+ case FRAMEBUFFER:
+ drawCachedGlyph(cachedGlyph, penX, penY);
+ break;
+ case BITMAP:
+ drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
+ break;
+ case MEASURE:
+ measureCachedGlyph(cachedGlyph, penX, penY, bounds);
+ break;
+ }
}
penX += (cachedGlyph->mAdvance.x >> 6);
@@ -283,7 +354,7 @@ FontState::FontState()
}
// Get the black gamma threshold
- int blackThreshold = DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD;
+ int32_t blackThreshold = DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD;
if (property_get(PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD, property, NULL) > 0) {
LOGD(" Setting text black gamma threshold to %s", property);
blackThreshold = atoi(property);
@@ -294,7 +365,7 @@ FontState::FontState()
mBlackThreshold = (float)(blackThreshold) / 255.0f;
// Get the white gamma threshold
- int whiteThreshold = DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD;
+ int32_t whiteThreshold = DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD;
if (property_get(PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD, property, NULL) > 0) {
LOGD(" Setting text white gamma threshold to %s", property);
whiteThreshold = atoi(property);
@@ -397,13 +468,13 @@ bool FontState::cacheBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *r
uint32_t cacheWidth = getCacheTextureType()->getDimX();
- unsigned char *cacheBuffer = (unsigned char*)mTextTexture->getPtr();
- unsigned char *bitmapBuffer = bitmap->buffer;
+ uint8_t *cacheBuffer = (uint8_t*)mTextTexture->getPtr();
+ uint8_t *bitmapBuffer = bitmap->buffer;
uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
for(cacheX = startX, bX = 0; cacheX < endX; cacheX ++, bX ++) {
for(cacheY = startY, bY = 0; cacheY < endY; cacheY ++, bY ++) {
- unsigned char tempCol = bitmapBuffer[bY * bitmap->width + bX];
+ uint8_t tempCol = bitmapBuffer[bY * bitmap->width + bX];
cacheBuffer[cacheY*cacheWidth + cacheX] = tempCol;
}
}
@@ -426,7 +497,7 @@ bool FontState::cacheBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *r
void FontState::initRenderState()
{
- String8 shaderString("varying vec4 varTex0;\n");
+ String8 shaderString("varying vec2 varTex0;\n");
shaderString.append("void main() {\n");
shaderString.append(" lowp vec4 col = UNI_Color;\n");
shaderString.append(" col.a = texture2D(UNI_Tex0, varTex0.xy).a;\n");
@@ -487,7 +558,7 @@ void FontState::initTextTexture()
mTextTexture->deferedUploadToTexture(mRSC, false, 0);
// Split up our cache texture into lines of certain widths
- int nextLine = 0;
+ int32_t nextLine = 0;
mCacheLines.push(new CacheTextureLine(16, texType->getDimX(), nextLine, 0));
nextLine += mCacheLines.top()->mMaxHeight;
mCacheLines.push(new CacheTextureLine(24, texType->getDimX(), nextLine, 0));
@@ -519,8 +590,8 @@ void FontState::initVertexArrayBuffers()
// Four verts, two triangles , six indices per quad
for(uint32_t i = 0; i < mMaxNumberOfQuads; i ++) {
- int i6 = i * 6;
- int i4 = i * 4;
+ int32_t i6 = i * 6;
+ int32_t i4 = i * 4;
indexPtr[i6 + 0] = i4 + 0;
indexPtr[i6 + 1] = i4 + 1;
@@ -713,7 +784,11 @@ void FontState::precacheLatin(Font *font) {
}
-void FontState::renderText(const char *text, uint32_t len, uint32_t startIndex, int numGlyphs, int x, int y)
+void FontState::renderText(const char *text, uint32_t len, int32_t x, int32_t y,
+ uint32_t startIndex, int32_t numGlyphs,
+ Font::RenderMode mode,
+ Font::Rect *bounds,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH)
{
checkInit();
@@ -730,7 +805,8 @@ void FontState::renderText(const char *text, uint32_t len, uint32_t startIndex,
return;
}
- currentFont->renderUTF(text, len, startIndex, numGlyphs, x, y);
+ currentFont->renderUTF(text, len, x, y, startIndex, numGlyphs,
+ mode, bounds, bitmap, bitmapW, bitmapH);
if(mCurrentQuadIndex != 0) {
issueDrawCommand();
@@ -738,32 +814,8 @@ void FontState::renderText(const char *text, uint32_t len, uint32_t startIndex,
}
}
-void FontState::renderText(const char *text, int x, int y)
-{
- size_t textLen = strlen(text);
- renderText(text, textLen, 0, -1, x, y);
-}
-
-void FontState::renderText(Allocation *alloc, int x, int y)
-{
- if(!alloc) {
- return;
- }
-
- const char *text = (const char *)alloc->getPtr();
- size_t allocSize = alloc->getType()->getSizeBytes();
- renderText(text, allocSize, 0, -1, x, y);
-}
-
-void FontState::renderText(Allocation *alloc, uint32_t start, int len, int x, int y)
-{
- if(!alloc) {
- return;
- }
-
- const char *text = (const char *)alloc->getPtr();
- size_t allocSize = alloc->getType()->getSizeBytes();
- renderText(text, allocSize, start, len, x, y);
+void FontState::measureText(const char *text, uint32_t len, Font::Rect *bounds) {
+ renderText(text, len, 0, 0, 0, -1, Font::MEASURE, bounds);
}
void FontState::setFontColor(float r, float g, float b, float a) {
@@ -773,7 +825,7 @@ void FontState::setFontColor(float r, float g, float b, float a) {
mConstants.mFontColor[3] = a;
mConstants.mGamma = 1.0f;
- const int luminance = (r * 2.0f + g * 5.0f + b) / 8.0f;
+ const float luminance = (r * 2.0f + g * 5.0f + b) / 8.0f;
if (luminance <= mBlackThreshold) {
mConstants.mGamma = mBlackGamma;
} else if (luminance >= mWhiteThreshold) {
diff --git a/libs/rs/rsFont.h b/libs/rs/rsFont.h
index 16009ef..0012b84 100644
--- a/libs/rs/rsFont.h
+++ b/libs/rs/rsFont.h
@@ -45,12 +45,26 @@ class FontState;
class Font : public ObjectBase
{
public:
- ~Font();
+ enum RenderMode {
+ FRAMEBUFFER,
+ BITMAP,
+ MEASURE,
+ };
- // Pointer to the utf data, length of data, where to start, number of glyphs ot read
- // (each glyph may be longer than a char because we are dealing with utf data)
- // Last two variables are the initial pen position
- void renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyphs, int x, int y);
+ struct Rect {
+ int32_t left;
+ int32_t top;
+ int32_t right;
+ int32_t bottom;
+ void set(int32_t l, int32_t r, int32_t t, int32_t b) {
+ left = l;
+ right = r;
+ top = t;
+ bottom = b;
+ }
+ };
+
+ ~Font();
// Currently files do not get serialized,
// but we need to inherit from ObjectBase for ref tracking
@@ -66,6 +80,14 @@ protected:
friend class FontState;
+ // Pointer to the utf data, length of data, where to start, number of glyphs ot read
+ // (each glyph may be longer than a char because we are dealing with utf data)
+ // Last two variables are the initial pen position
+ void renderUTF(const char *text, uint32_t len, int32_t x, int32_t y,
+ uint32_t start, int32_t numGlyphs,
+ RenderMode mode = FRAMEBUFFER, Rect *bounds = NULL,
+ uint8_t *bitmap = NULL, uint32_t bitmapW = 0, uint32_t bitmapH = 0);
+
void invalidateTextureCache();
struct CachedGlyphInfo
{
@@ -106,7 +128,10 @@ protected:
CachedGlyphInfo *cacheGlyph(uint32_t glyph);
void updateGlyphCache(CachedGlyphInfo *glyph);
- void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
+ void measureCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y, Rect *bounds);
+ void drawCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y);
+ void drawCachedGlyph(CachedGlyphInfo *glyph, int32_t x, int32_t y,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
};
class FontState
@@ -121,10 +146,13 @@ public:
ObjectBaseRef<Font> mDefault;
ObjectBaseRef<Font> mLast;
- void renderText(const char *text, uint32_t len, uint32_t startIndex, int numGlyphs, int x, int y);
- void renderText(const char *text, int x, int y);
- void renderText(Allocation *alloc, int x, int y);
- void renderText(Allocation *alloc, uint32_t start, int len, int x, int y);
+ void renderText(const char *text, uint32_t len, int32_t x, int32_t y,
+ uint32_t startIndex = 0, int numGlyphs = -1,
+ Font::RenderMode mode = Font::FRAMEBUFFER,
+ Font::Rect *bounds = NULL,
+ uint8_t *bitmap = NULL, uint32_t bitmapW = 0, uint32_t bitmapH = 0);
+
+ void measureText(const char *text, uint32_t len, Font::Rect *bounds);
void setFontColor(float r, float g, float b, float a);
void getFontColor(float *r, float *g, float *b, float *a) const;
@@ -198,6 +226,9 @@ protected:
// Texture to cache glyph bitmaps
ObjectBaseRef<Allocation> mTextTexture;
void initTextTexture();
+ const uint8_t* getTextTextureData() const {
+ return (uint8_t*)mTextTexture->getPtr();
+ }
bool cacheBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *retOriginY);
const Type* getCacheTextureType() {
diff --git a/libs/rs/rsLight.cpp b/libs/rs/rsLight.cpp
deleted file mode 100644
index eab9a07..0000000
--- a/libs/rs/rsLight.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_RS_BUILD_FOR_HOST
-#include "rsContext.h"
-#include <GLES/gl.h>
-#else
-#include "rsContextHostStub.h"
-#include <OpenGL/gl.h>
-#endif //ANDROID_RS_BUILD_FOR_HOST
-
-using namespace android;
-using namespace android::renderscript;
-
-
-Light::Light(Context *rsc, bool isLocal, bool isMono) : ObjectBase(rsc)
-{
- mAllocFile = __FILE__;
- mAllocLine = __LINE__;
- mIsLocal = isLocal;
- mIsMono = isMono;
-
- mPosition[0] = 0;
- mPosition[1] = 0;
- mPosition[2] = 1;
- mPosition[3] = 0;
-
- mColor[0] = 1.f;
- mColor[1] = 1.f;
- mColor[2] = 1.f;
- mColor[3] = 1.f;
-}
-
-Light::~Light()
-{
-}
-
-void Light::setPosition(float x, float y, float z)
-{
- mPosition[0] = x;
- mPosition[1] = y;
- mPosition[2] = z;
-}
-
-void Light::setColor(float r, float g, float b)
-{
- mColor[0] = r;
- mColor[1] = g;
- mColor[2] = b;
-}
-
-void Light::setupGL(uint32_t num) const
-{
- glLightfv(GL_LIGHT0 + num, GL_DIFFUSE, mColor);
- glLightfv(GL_LIGHT0 + num, GL_SPECULAR, mColor);
- glLightfv(GL_LIGHT0 + num, GL_POSITION, mPosition);
-}
-
-void Light::serialize(OStream *stream) const
-{
-
-}
-
-Light *Light::createFromStream(Context *rsc, IStream *stream)
-{
- return NULL;
-}
-
-////////////////////////////////////////////
-
-LightState::LightState()
-{
- clear();
-}
-
-LightState::~LightState()
-{
-}
-
-void LightState::clear()
-{
- mIsLocal = false;
- mIsMono = false;
-}
-
-
-////////////////////////////////////////////////////
-//
-
-namespace android {
-namespace renderscript {
-
-void rsi_LightBegin(Context *rsc)
-{
- rsc->mStateLight.clear();
-}
-
-void rsi_LightSetLocal(Context *rsc, bool isLocal)
-{
- rsc->mStateLight.mIsLocal = isLocal;
-}
-
-void rsi_LightSetMonochromatic(Context *rsc, bool isMono)
-{
- rsc->mStateLight.mIsMono = isMono;
-}
-
-RsLight rsi_LightCreate(Context *rsc)
-{
- Light *l = new Light(rsc, rsc->mStateLight.mIsLocal,
- rsc->mStateLight.mIsMono);
- l->incUserRef();
- return l;
-}
-
-void rsi_LightSetColor(Context *rsc, RsLight vl, float r, float g, float b)
-{
- Light *l = static_cast<Light *>(vl);
- l->setColor(r, g, b);
-}
-
-void rsi_LightSetPosition(Context *rsc, RsLight vl, float x, float y, float z)
-{
- Light *l = static_cast<Light *>(vl);
- l->setPosition(x, y, z);
-}
-
-
-
-}
-}
diff --git a/libs/rs/rsLight.h b/libs/rs/rsLight.h
deleted file mode 100644
index bd58979..0000000
--- a/libs/rs/rsLight.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_LIGHT_H
-#define ANDROID_LIGHT_H
-
-
-#include "rsObjectBase.h"
-
-// ---------------------------------------------------------------------------
-namespace android {
-namespace renderscript {
-
-
-// An element is a group of Components that occupies one cell in a structure.
-class Light : public ObjectBase
-{
-public:
- Light(Context *, bool isLocal, bool isMono);
- virtual ~Light();
-
- // Values, mutable after creation.
- void setPosition(float x, float y, float z);
- void setColor(float r, float g, float b);
-
- void setupGL(uint32_t num) const;
- virtual void serialize(OStream *stream) const;
- virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_LIGHT; }
- static Light *createFromStream(Context *rsc, IStream *stream);
-
-protected:
- float mColor[4];
- float mPosition[4];
- bool mIsLocal;
- bool mIsMono;
-};
-
-
-class LightState {
-public:
- LightState();
- ~LightState();
-
- void clear();
-
- bool mIsMono;
- bool mIsLocal;
-};
-
-
-}
-}
-#endif //ANDROID_LIGHT_H
-
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index c94f294..81b4fa4 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -190,7 +190,7 @@ void ProgramFragmentState::init(Context *rsc)
{
String8 shaderString(RS_SHADER_INTERNAL);
shaderString.append("varying lowp vec4 varColor;\n");
- shaderString.append("varying vec4 varTex0;\n");
+ shaderString.append("varying vec2 varTex0;\n");
shaderString.append("void main() {\n");
shaderString.append(" lowp vec4 col = UNI_Color;\n");
shaderString.append(" gl_FragColor = col;\n");
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index d3dbfb2..a785262 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -242,6 +242,7 @@ ProgramVertexState::~ProgramVertexState()
void ProgramVertexState::init(Context *rsc)
{
const Element *matrixElem = Element::create(rsc, RS_TYPE_MATRIX_4X4, RS_KIND_USER, false, 1);
+ const Element *f2Elem = Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 2);
const Element *f3Elem = Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 3);
const Element *f4Elem = Element::create(rsc, RS_TYPE_FLOAT_32, RS_KIND_USER, false, 4);
@@ -256,7 +257,7 @@ void ProgramVertexState::init(Context *rsc)
rsc->mStateElement.elementBuilderAdd(f4Elem, "position", 1);
rsc->mStateElement.elementBuilderAdd(f4Elem, "color", 1);
rsc->mStateElement.elementBuilderAdd(f3Elem, "normal", 1);
- rsc->mStateElement.elementBuilderAdd(f4Elem, "texture0", 1);
+ rsc->mStateElement.elementBuilderAdd(f2Elem, "texture0", 1);
const Element *attrElem = rsc->mStateElement.elementBuilderCreate(rsc);
Type *inputType = new Type(rsc);
@@ -266,7 +267,7 @@ void ProgramVertexState::init(Context *rsc)
String8 shaderString(RS_SHADER_INTERNAL);
shaderString.append("varying vec4 varColor;\n");
- shaderString.append("varying vec4 varTex0;\n");
+ shaderString.append("varying vec2 varTex0;\n");
shaderString.append("void main() {\n");
shaderString.append(" gl_Position = UNI_MVP * ATTRIB_position;\n");
shaderString.append(" gl_PointSize = 1.0;\n");
diff --git a/libs/rs/rsScriptC_LibGL.cpp b/libs/rs/rsScriptC_LibGL.cpp
index 88db761..b991cab 100644
--- a/libs/rs/rsScriptC_LibGL.cpp
+++ b/libs/rs/rsScriptC_LibGL.cpp
@@ -351,13 +351,59 @@ static void SC_DrawTextAlloc(RsAllocation va, int x, int y)
CHECK_OBJ(va);
GET_TLS();
Allocation *alloc = static_cast<Allocation *>(va);
- rsc->mStateFont.renderText(alloc, x, y);
+ const char *text = (const char *)alloc->getPtr();
+ size_t allocSize = alloc->getType()->getSizeBytes();
+ rsc->mStateFont.renderText(text, allocSize, x, y);
}
static void SC_DrawText(const char *text, int x, int y)
{
GET_TLS();
- rsc->mStateFont.renderText(text, x, y);
+ size_t textLen = strlen(text);
+ rsc->mStateFont.renderText(text, textLen, x, y);
+}
+
+static void SC_setMetrics(Font::Rect *metrics,
+ int32_t *left, int32_t *right,
+ int32_t *top, int32_t *bottom)
+{
+ if(left) {
+ *left = metrics->left;
+ }
+ if(right) {
+ *right = metrics->right;
+ }
+ if(top) {
+ *top = metrics->top;
+ }
+ if(bottom) {
+ *bottom = metrics->bottom;
+ }
+}
+
+static void SC_MeasureTextAlloc(RsAllocation va,
+ int32_t *left, int32_t *right,
+ int32_t *top, int32_t *bottom)
+{
+ CHECK_OBJ(va);
+ GET_TLS();
+ Allocation *alloc = static_cast<Allocation *>(va);
+ const char *text = (const char *)alloc->getPtr();
+ size_t textLen = alloc->getType()->getSizeBytes();
+ Font::Rect metrics;
+ rsc->mStateFont.measureText(text, textLen, &metrics);
+ SC_setMetrics(&metrics, left, right, top, bottom);
+}
+
+static void SC_MeasureText(const char *text,
+ int32_t *left, int32_t *right,
+ int32_t *top, int32_t *bottom)
+{
+ GET_TLS();
+ size_t textLen = strlen(text);
+ Font::Rect metrics;
+ rsc->mStateFont.measureText(text, textLen, &metrics);
+ SC_setMetrics(&metrics, left, right, top, bottom);
}
static void SC_BindFont(RsFont font)
@@ -432,6 +478,8 @@ static ScriptCState::SymbolTable_t gSyms[] = {
{ "_Z11rsgDrawTextPKcii", (void *)&SC_DrawText },
{ "_Z11rsgDrawText13rs_allocationii", (void *)&SC_DrawTextAlloc },
+ { "_Z14rsgMeasureTextPKcPiS1_S1_S1_", (void *)&SC_MeasureText },
+ { "_Z14rsgMeasureText13rs_allocationPiS0_S0_S0_", (void *)&SC_MeasureTextAlloc },
{ "_Z11rsgBindFont7rs_font", (void *)&SC_BindFont },
{ "_Z12rsgFontColorffff", (void *)&SC_FontColor },
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index fc037a3..8cdb48a 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -274,6 +274,59 @@ bool Type::isEqual(const Type *other) const {
return false;
}
+Type * Type::cloneAndResize1D(Context *rsc, uint32_t dimX) const
+{
+ TypeState * stc = &rsc->mStateType;
+ for (uint32_t ct=0; ct < stc->mTypes.size(); ct++) {
+ Type *t = stc->mTypes[ct];
+ if (t->getElement() != mElement.get()) continue;
+ if (t->getDimX() != dimX) continue;
+ if (t->getDimY() != mDimY) continue;
+ if (t->getDimZ() != mDimZ) continue;
+ if (t->getDimLOD() != mDimLOD) continue;
+ if (t->getDimFaces() != mFaces) continue;
+ t->incUserRef();
+ return t;
+ }
+
+ Type *nt = new Type(rsc);
+ nt->mElement.set(mElement);
+ nt->mDimX = dimX;
+ nt->mDimY = mDimY;
+ nt->mDimZ = mDimZ;
+ nt->mDimLOD = mDimLOD;
+ nt->mFaces = mFaces;
+ nt->compute();
+ return nt;
+}
+
+Type * Type::cloneAndResize2D(Context *rsc, uint32_t dimX, uint32_t dimY) const
+{
+ TypeState * stc = &rsc->mStateType;
+ for (uint32_t ct=0; ct < stc->mTypes.size(); ct++) {
+ Type *t = stc->mTypes[ct];
+ if (t->getElement() != mElement.get()) continue;
+ if (t->getDimX() != dimX) continue;
+ if (t->getDimY() != dimY) continue;
+ if (t->getDimZ() != mDimZ) continue;
+ if (t->getDimLOD() != mDimLOD) continue;
+ if (t->getDimFaces() != mFaces) continue;
+ t->incUserRef();
+ return t;
+ }
+
+ Type *nt = new Type(rsc);
+ nt->mElement.set(mElement);
+ nt->mDimX = dimX;
+ nt->mDimY = dimY;
+ nt->mDimZ = mDimZ;
+ nt->mDimLOD = mDimLOD;
+ nt->mFaces = mFaces;
+ nt->compute();
+ return nt;
+}
+
+
//////////////////////////////////////////////////
//
namespace android {
diff --git a/libs/rs/rsType.h b/libs/rs/rsType.h
index 33faa87..b5548c0 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -79,6 +79,9 @@ public:
bool isEqual(const Type *other) const;
+ Type * cloneAndResize1D(Context *rsc, uint32_t dimX) const;
+ Type * cloneAndResize2D(Context *rsc, uint32_t dimX, uint32_t dimY) const;
+
protected:
struct LOD {
size_t mX;
diff --git a/libs/rs/rsVertexArray.h b/libs/rs/rsVertexArray.h
index bd76d87..dea7d41 100644
--- a/libs/rs/rsVertexArray.h
+++ b/libs/rs/rsVertexArray.h
@@ -62,7 +62,6 @@ public:
}
void add(const Attrib &, uint32_t stride);
- //void addLegacy(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset);
void add(uint32_t type, uint32_t size, uint32_t stride, bool normalized, uint32_t offset, const char *name);
void setupGL2(const Context *rsc, class VertexArrayState *, ShaderCache *) const;
@@ -89,7 +88,7 @@ public:
}
}
-#endif //ANDROID_LIGHT_H
+#endif //ANDROID_VERTEX_ARRAY_H
diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh
index c0b2d2d..ac6f8cc 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -79,6 +79,12 @@ extern void __attribute__((overloadable))
rsgBindFont(rs_font);
extern void __attribute__((overloadable))
rsgFontColor(float, float, float, float);
+// Returns the bounding box of the text relative to (0, 0)
+// Any of left, right, top, bottom could be NULL
+extern void __attribute__((overloadable))
+ rsgMeasureText(const char *, int *left, int *right, int *top, int *bottom);
+extern void __attribute__((overloadable))
+ rsgMeasureText(rs_allocation, int *left, int *right, int *top, int *bottom);
extern void __attribute__((overloadable))
rsgMeshComputeBoundingBox(rs_mesh mesh, float *minX, float *minY, float *minZ,
diff --git a/libs/surfaceflinger_client/SharedBufferStack.cpp b/libs/surfaceflinger_client/SharedBufferStack.cpp
index a43b440..8f583f0 100644
--- a/libs/surfaceflinger_client/SharedBufferStack.cpp
+++ b/libs/surfaceflinger_client/SharedBufferStack.cpp
@@ -285,10 +285,12 @@ ssize_t SharedBufferClient::DequeueUpdate::operator()() {
return NO_ERROR;
}
-SharedBufferClient::UndoDequeueUpdate::UndoDequeueUpdate(SharedBufferBase* sbb)
- : UpdateBase(sbb) {
+SharedBufferClient::CancelUpdate::CancelUpdate(SharedBufferBase* sbb,
+ int tail, int buf)
+ : UpdateBase(sbb), tail(tail), buf(buf) {
}
-ssize_t SharedBufferClient::UndoDequeueUpdate::operator()() {
+ssize_t SharedBufferClient::CancelUpdate::operator()() {
+ stack.index[tail] = buf;
android_atomic_inc(&stack.available);
return NO_ERROR;
}
@@ -319,7 +321,7 @@ ssize_t SharedBufferServer::RetireUpdate::operator()() {
return BAD_VALUE;
// Preventively lock the current buffer before updating queued.
- android_atomic_write(stack.index[head], &stack.inUse);
+ android_atomic_write(stack.headBuf, &stack.inUse);
// Decrement the number of queued buffers
int32_t queued;
@@ -334,7 +336,9 @@ ssize_t SharedBufferServer::RetireUpdate::operator()() {
// the buffer we preventively locked upon entering this function
head = (head + 1) % numBuffers;
- android_atomic_write(stack.index[head], &stack.inUse);
+ const int8_t headBuf = stack.index[head];
+ stack.headBuf = headBuf;
+ android_atomic_write(headBuf, &stack.inUse);
// head is only modified here, so we don't need to use cmpxchg
android_atomic_write(head, &stack.head);
@@ -359,7 +363,7 @@ ssize_t SharedBufferServer::StatusUpdate::operator()() {
SharedBufferClient::SharedBufferClient(SharedClient* sharedClient,
int surface, int num, int32_t identity)
: SharedBufferBase(sharedClient, surface, identity),
- mNumBuffers(num), tail(0), undoDequeueTail(0)
+ mNumBuffers(num), tail(0)
{
SharedBufferStack& stack( *mSharedStack );
tail = computeTail();
@@ -390,7 +394,6 @@ ssize_t SharedBufferClient::dequeue()
DequeueUpdate update(this);
updateCondition( update );
- undoDequeueTail = tail;
int dequeued = stack.index[tail];
tail = ((tail+1 >= mNumBuffers) ? 0 : tail+1);
LOGD_IF(DEBUG_ATOMICS, "dequeued=%d, tail++=%d, %s",
@@ -403,14 +406,19 @@ ssize_t SharedBufferClient::dequeue()
status_t SharedBufferClient::undoDequeue(int buf)
{
+ return cancel(buf);
+}
+
+status_t SharedBufferClient::cancel(int buf)
+{
RWLock::AutoRLock _rd(mLock);
- // TODO: we can only undo the previous dequeue, we should
- // enforce that in the api
- UndoDequeueUpdate update(this);
+ // calculate the new position of the tail index (essentially tail--)
+ int localTail = (tail + mNumBuffers - 1) % mNumBuffers;
+ CancelUpdate update(this, localTail, buf);
status_t err = updateCondition( update );
if (err == NO_ERROR) {
- tail = undoDequeueTail;
+ tail = localTail;
}
return err;
}
diff --git a/libs/surfaceflinger_client/Surface.cpp b/libs/surfaceflinger_client/Surface.cpp
index c77d48e..ebb0cc9 100644
--- a/libs/surfaceflinger_client/Surface.cpp
+++ b/libs/surfaceflinger_client/Surface.cpp
@@ -416,6 +416,7 @@ void Surface::init()
{
ANativeWindow::setSwapInterval = setSwapInterval;
ANativeWindow::dequeueBuffer = dequeueBuffer;
+ ANativeWindow::cancelBuffer = cancelBuffer;
ANativeWindow::lockBuffer = lockBuffer;
ANativeWindow::queueBuffer = queueBuffer;
ANativeWindow::query = query;
@@ -527,6 +528,12 @@ int Surface::dequeueBuffer(ANativeWindow* window,
return self->dequeueBuffer(buffer);
}
+int Surface::cancelBuffer(ANativeWindow* window,
+ android_native_buffer_t* buffer) {
+ Surface* self = getSelf(window);
+ return self->cancelBuffer(buffer);
+}
+
int Surface::lockBuffer(ANativeWindow* window,
android_native_buffer_t* buffer) {
Surface* self = getSelf(window);
@@ -627,6 +634,33 @@ int Surface::dequeueBuffer(android_native_buffer_t** buffer)
return err;
}
+int Surface::cancelBuffer(android_native_buffer_t* buffer)
+{
+ status_t err = validate();
+ switch (err) {
+ case NO_ERROR:
+ // no error, common case
+ break;
+ case INVALID_OPERATION:
+ // legitimate errors here
+ return err;
+ default:
+ // other errors happen because the surface is now invalid,
+ // for instance because it has been destroyed. In this case,
+ // we just fail silently (canceling a buffer is not technically
+ // an error at this point)
+ return NO_ERROR;
+ }
+
+ int32_t bufIdx = getBufferIndex(GraphicBuffer::getSelf(buffer));
+
+ err = mSharedBufferClient->cancel(bufIdx);
+
+ LOGE_IF(err, "error canceling buffer %d (%s)", bufIdx, strerror(-err));
+ return err;
+}
+
+
int Surface::lockBuffer(android_native_buffer_t* buffer)
{
status_t err = validate();
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index bee86b2..9b1f82f 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -508,6 +508,36 @@ bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
unsigned char lfhBuf[kLFHLen];
+#ifdef HAVE_PREAD
+ /*
+ * This file descriptor might be from zygote's preloaded assets,
+ * so we need to do an pread() instead of a lseek() + read() to
+ * guarantee atomicity across the processes with the shared file
+ * descriptors.
+ */
+ ssize_t actual =
+ TEMP_FAILURE_RETRY(pread(mFd, lfhBuf, sizeof(lfhBuf), localHdrOffset));
+
+ if (actual != sizeof(lfhBuf)) {
+ LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
+ return false;
+ }
+
+ if (get4LE(lfhBuf) != kLFHSignature) {
+ LOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
+ "got: data=0x%08lx\n",
+ localHdrOffset, kLFHSignature, get4LE(lfhBuf));
+ return false;
+ }
+#else /* HAVE_PREAD */
+ /*
+ * For hosts don't have pread() we cannot guarantee atomic reads from
+ * an offset in a file. Android should never run on those platforms.
+ * File descriptors inherited from a fork() share file offsets and
+ * there would be nothing to protect from two different processes
+ * calling lseek() concurrently.
+ */
+
{
AutoMutex _l(mFdLock);
@@ -517,7 +547,7 @@ bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
}
ssize_t actual =
- TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
+ TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
if (actual != sizeof(lfhBuf)) {
LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
return false;
@@ -531,6 +561,7 @@ bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
return false;
}
}
+#endif /* HAVE_PREAD */
off_t dataOffset = localHdrOffset + kLFHLen
+ get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
diff --git a/media/java/android/media/videoeditor/AudioTrack.java b/media/java/android/media/videoeditor/AudioTrack.java
index 468ba2a..3ebad00 100755
--- a/media/java/android/media/videoeditor/AudioTrack.java
+++ b/media/java/android/media/videoeditor/AudioTrack.java
@@ -18,23 +18,29 @@ package android.media.videoeditor;
import java.io.IOException;
+import android.util.Log;
+
/**
* This class allows to handle an audio track. This audio file is mixed with the
* audio samples of the MediaItems.
* {@hide}
*/
public class AudioTrack {
+ // Logging
+ private static final String TAG = "AudioTrack";
+
// Instance variables
private final String mUniqueId;
private final String mFilename;
- private final long mDurationMs;
private long mStartTimeMs;
private long mTimelineDurationMs;
private int mVolumePercent;
private long mBeginBoundaryTimeMs;
private long mEndBoundaryTimeMs;
private boolean mLoop;
+ private boolean mMuted;
+ private final long mDurationMs;
private final int mAudioChannels;
private final int mAudioType;
private final int mAudioBitrate;
@@ -47,6 +53,129 @@ public class AudioTrack {
// The audio waveform filename
private String mAudioWaveformFilename;
+ private PlaybackThread mPlaybackThread;
+
+ /**
+ * This listener interface is used by the AudioTrack to emit playback
+ * progress notifications.
+ */
+ public interface PlaybackProgressListener {
+ /**
+ * This method notifies the listener of the current time position while
+ * playing an audio track
+ *
+ * @param audioTrack The audio track
+ * @param timeMs The current playback position (expressed in milliseconds
+ * since the beginning of the audio track).
+ * @param end true if the end of the audio track was reached
+ */
+ public void onProgress(AudioTrack audioTrack, long timeMs, boolean end);
+ }
+
+ /**
+ * The playback thread
+ */
+ private class PlaybackThread extends Thread {
+ // Instance variables
+ private final PlaybackProgressListener mListener;
+ private final long mFromMs, mToMs;
+ private boolean mRun;
+ private final boolean mLoop;
+ private long mPositionMs;
+
+ /**
+ * Constructor
+ *
+ * @param fromMs The time (relative to the beginning of the audio track)
+ * at which the playback will start
+ * @param toMs The time (relative to the beginning of the audio track) at
+ * which the playback will stop. Use -1 to play to the end of
+ * the audio track
+ * @param loop true if the playback should be looped once it reaches the
+ * end
+ * @param listener The listener which will be notified of the playback
+ * progress
+ */
+ public PlaybackThread(long fromMs, long toMs, boolean loop,
+ PlaybackProgressListener listener) {
+ mPositionMs = mFromMs = fromMs;
+ if (toMs < 0) {
+ mToMs = mDurationMs;
+ } else {
+ mToMs = toMs;
+ }
+ mLoop = loop;
+ mListener = listener;
+ mRun = true;
+ }
+
+ /*
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "===> PlaybackThread.run enter");
+ }
+
+ while (mRun) {
+ try {
+ sleep(100);
+ } catch (InterruptedException ex) {
+ break;
+ }
+
+ mPositionMs += 100;
+
+ if (mPositionMs >= mToMs) {
+ if (!mLoop) {
+ if (mListener != null) {
+ mListener.onProgress(AudioTrack.this, mPositionMs, true);
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "PlaybackThread.run playback complete");
+ }
+ break;
+ } else {
+ // Fire a notification for the end of the clip
+ if (mListener != null) {
+ mListener.onProgress(AudioTrack.this, mToMs, false);
+ }
+
+ // Rewind
+ mPositionMs = mFromMs;
+ if (mListener != null) {
+ mListener.onProgress(AudioTrack.this, mPositionMs, false);
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "PlaybackThread.run playback complete");
+ }
+ }
+ } else {
+ if (mListener != null) {
+ mListener.onProgress(AudioTrack.this, mPositionMs, false);
+ }
+ }
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "===> PlaybackThread.run exit");
+ }
+ }
+
+ /**
+ * Stop the playback
+ *
+ * @return The stop position
+ */
+ public long stopPlayback() {
+ mRun = false;
+ try {
+ join();
+ } catch (InterruptedException ex) {
+ }
+ return mPositionMs;
+ }
+ };
/**
* An object of this type cannot be instantiated by using the default
@@ -54,19 +183,22 @@ public class AudioTrack {
*/
@SuppressWarnings("unused")
private AudioTrack() throws IOException {
- this(null, null);
+ this(null, null, null);
}
/**
* Constructor
- * @param audioTrackId The AudioTrack id
+ *
+ * @param editor The video editor reference
+ * @param audioTrackId The audio track id
* @param filename The absolute file name
*
* @throws IOException if file is not found
* @throws IllegalArgumentException if file format is not supported or if
* the codec is not supported
*/
- public AudioTrack(String audioTrackId, String filename) throws IOException {
+ public AudioTrack(VideoEditor editor, String audioTrackId, String filename)
+ throws IOException {
mUniqueId = audioTrackId;
mFilename = filename;
mStartTimeMs = 0;
@@ -89,6 +221,9 @@ public class AudioTrack {
// By default loop is disabled
mLoop = false;
+ // By default the audio track is not muted
+ mMuted = false;
+
// Ducking is enabled by default
mDuckingThreshold = 0;
mDuckingLowVolume = 0;
@@ -99,7 +234,55 @@ public class AudioTrack {
}
/**
- * @return The id of the media item
+ * Constructor
+ *
+ * @param editor The video editor reference
+ * @param audioTrackId The audio track id
+ * @param filename The audio filename
+ * @param startTimeMs the start time in milliseconds (relative to the
+ * timeline)
+ * @param beginMs start time in the audio track in milliseconds (relative to
+ * the beginning of the audio track)
+ * @param endMs end time in the audio track in milliseconds (relative to the
+ * beginning of the audio track)
+ * @param loop true to loop the audio track
+ * @param volume The volume in percentage
+ * @param muted true if the audio track is muted
+ * @param audioWaveformFilename The name of the waveform file
+ *
+ * @throws IOException if file is not found
+ */
+ AudioTrack(VideoEditor editor, String audioTrackId, String filename, long startTimeMs,
+ long beginMs, long endMs, boolean loop, int volume, boolean muted,
+ String audioWaveformFilename) throws IOException {
+ mUniqueId = audioTrackId;
+ mFilename = filename;
+ mStartTimeMs = startTimeMs;
+
+ // TODO: This value represents to the duration of the audio file
+ mDurationMs = 300000;
+
+ // TODO: This value needs to be read from the audio track of the source
+ // file
+ mAudioChannels = 2;
+ mAudioType = MediaProperties.ACODEC_AAC_LC;
+ mAudioBitrate = 128000;
+ mAudioSamplingFrequency = 44100;
+
+ mTimelineDurationMs = endMs - beginMs;
+ mVolumePercent = volume;
+
+ mBeginBoundaryTimeMs = beginMs;
+ mEndBoundaryTimeMs = endMs;
+
+ mLoop = loop;
+ mMuted = muted;
+
+ mAudioWaveformFilename = audioWaveformFilename;
+ }
+
+ /**
+ * @return The id of the audio track
*/
public String getId() {
return mUniqueId;
@@ -169,6 +352,20 @@ public class AudioTrack {
}
/**
+ * @param muted true to mute the audio track
+ */
+ public void setMute(boolean muted) {
+ mMuted = muted;
+ }
+
+ /**
+ * @return true if the audio track is muted
+ */
+ public boolean isMuted() {
+ return mMuted;
+ }
+
+ /**
* Set the start time of this audio track relative to the storyboard
* timeline. Default value is 0.
*
@@ -312,6 +509,50 @@ public class AudioTrack {
}
/**
+ * Start the playback of this audio track. This method does not block (does
+ * not wait for the playback to complete).
+ *
+ * @param fromMs The time (relative to the beginning of the audio track) at
+ * which the playback will start
+ * @param toMs The time (relative to the beginning of the audio track) at
+ * which the playback will stop. Use -1 to play to the end of the
+ * audio track
+ * @param loop true if the playback should be looped once it reaches the end
+ * @param listener The listener which will be notified of the playback
+ * progress
+ * @throws IllegalArgumentException if fromMs or toMs is beyond the playback
+ * duration
+ * @throws IllegalStateException if a playback, preview or an export is
+ * already in progress
+ */
+ public void startPlayback(long fromMs, long toMs, boolean loop,
+ PlaybackProgressListener listener) {
+ if (fromMs >= mDurationMs) {
+ return;
+ }
+ mPlaybackThread = new PlaybackThread(fromMs, toMs, loop, listener);
+ mPlaybackThread.start();
+ }
+
+ /**
+ * Stop the audio track playback. This method blocks until the ongoing
+ * playback is stopped.
+ *
+ * @return The accurate current time when stop is effective expressed in
+ * milliseconds
+ */
+ public long stopPlayback() {
+ final long stopTimeMs;
+ if (mPlaybackThread != null) {
+ stopTimeMs = mPlaybackThread.stopPlayback();
+ mPlaybackThread = null;
+ } else {
+ stopTimeMs = 0;
+ }
+ return stopTimeMs;
+ }
+
+ /**
* This API allows to generate a file containing the sample volume levels of
* this audio track object. This function may take significant time and is
* blocking. The filename can be retrieved using getAudioWaveformFilename().
diff --git a/media/java/android/media/videoeditor/Effect.java b/media/java/android/media/videoeditor/Effect.java
index 038bfc2..f5e6a67 100755
--- a/media/java/android/media/videoeditor/Effect.java
+++ b/media/java/android/media/videoeditor/Effect.java
@@ -124,6 +124,23 @@ public abstract class Effect {
}
/**
+ * Set the start time and duration
+ *
+ * @param startTimeMs start time in milliseconds
+ * @param durationMs The duration in milliseconds
+ */
+ public void setStartTimeAndDuration(long startTimeMs, long durationMs) {
+ if (startTimeMs + durationMs > mMediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Invalid start time or duration");
+ }
+
+ mStartTimeMs = startTimeMs;
+ mDurationMs = durationMs;
+
+ mMediaItem.invalidateTransitions(this);
+ }
+
+ /**
* @return The media item owner
*/
public MediaItem getMediaItem() {
diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java
index ab23119..df3c5fb 100755
--- a/media/java/android/media/videoeditor/MediaImageItem.java
+++ b/media/java/android/media/videoeditor/MediaImageItem.java
@@ -17,7 +17,6 @@
package android.media.videoeditor;
import java.io.IOException;
-import java.util.List;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -54,12 +53,13 @@ public class MediaImageItem extends MediaItem {
*/
@SuppressWarnings("unused")
private MediaImageItem() throws IOException {
- this(null, null, 0, RENDERING_MODE_BLACK_BORDER);
+ this(null, null, null, 0, RENDERING_MODE_BLACK_BORDER);
}
/**
* Constructor
*
+ * @param editor The video editor reference
* @param mediaItemId The media item id
* @param filename The image file name
* @param durationMs The duration of the image on the storyboard
@@ -67,9 +67,10 @@ public class MediaImageItem extends MediaItem {
*
* @throws IOException
*/
- public MediaImageItem(String mediaItemId, String filename, long durationMs, int renderingMode)
+ public MediaImageItem(VideoEditor editor, String mediaItemId, String filename, long durationMs,
+ int renderingMode)
throws IOException {
- super(mediaItemId, filename, renderingMode);
+ super(editor, mediaItemId, filename, renderingMode);
// Determine the dimensions of the image
final BitmapFactory.Options dbo = new BitmapFactory.Options();
@@ -153,55 +154,16 @@ public class MediaImageItem extends MediaItem {
}
/**
- * This method will adjust the duration of bounding transitions if the
- * current duration of the transactions become greater than the maximum
- * allowable duration.
+ * This method will adjust the duration of bounding transitions, effects
+ * and overlays if the current duration of the transactions become greater
+ * than the maximum allowable duration.
*
* @param durationMs The duration of the image in the storyboard timeline
*/
public void setDuration(long durationMs) {
mDurationMs = durationMs;
- // Check if the duration of transitions need to be adjusted
- if (mBeginTransition != null) {
- final long maxDurationMs = mBeginTransition.getMaximumDuration();
- if (mBeginTransition.getDuration() > maxDurationMs) {
- mBeginTransition.setDuration(maxDurationMs);
- }
- }
-
- if (mEndTransition != null) {
- final long maxDurationMs = mEndTransition.getMaximumDuration();
- if (mEndTransition.getDuration() > maxDurationMs) {
- mEndTransition.setDuration(maxDurationMs);
- }
- }
-
- final List<Overlay> overlays = getAllOverlays();
- for (Overlay overlay : overlays) {
- // Adjust the start time if necessary
- if (overlay.getStartTime() < getTimelineDuration()) {
- overlay.setStartTime(0);
- }
-
- // Adjust the duration if necessary
- if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
- overlay.setDuration(getTimelineDuration() - overlay.getStartTime());
- }
- }
-
- final List<Effect> effects = getAllEffects();
- for (Effect effect : effects) {
- // Adjust the start time if necessary
- if (effect.getStartTime() < getTimelineDuration()) {
- effect.setStartTime(0);
- }
-
- // Adjust the duration if necessary
- if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
- effect.setDuration(getTimelineDuration() - effect.getStartTime());
- }
- }
+ adjustElementsDuration();
}
/*
diff --git a/media/java/android/media/videoeditor/MediaItem.java b/media/java/android/media/videoeditor/MediaItem.java
index f4651af..d9c38af 100755
--- a/media/java/android/media/videoeditor/MediaItem.java
+++ b/media/java/android/media/videoeditor/MediaItem.java
@@ -70,6 +70,7 @@ public abstract class MediaItem {
/**
* Constructor
*
+ * @param editor The video editor reference
* @param mediaItemId The MediaItem id
* @param filename name of the media file.
* @param renderingMode The rendering mode
@@ -79,7 +80,8 @@ public abstract class MediaItem {
* supported the exception object contains the unsupported
* capability
*/
- protected MediaItem(String mediaItemId, String filename, int renderingMode) throws IOException {
+ protected MediaItem(VideoEditor editor, String mediaItemId, String filename,
+ int renderingMode) throws IOException {
mUniqueId = mediaItemId;
mFilename = filename;
mRenderingMode = renderingMode;
@@ -472,4 +474,73 @@ public abstract class MediaItem {
}
}
}
+
+ /**
+ * Adjust the duration of effects, overlays and transitions.
+ * This method will be called after a media item duration is changed.
+ */
+ protected void adjustElementsDuration() {
+ // Check if the duration of transitions need to be adjusted
+ if (mBeginTransition != null) {
+ final long maxDurationMs = mBeginTransition.getMaximumDuration();
+ if (mBeginTransition.getDuration() > maxDurationMs) {
+ mBeginTransition.setDuration(maxDurationMs);
+ }
+ }
+
+ if (mEndTransition != null) {
+ final long maxDurationMs = mEndTransition.getMaximumDuration();
+ if (mEndTransition.getDuration() > maxDurationMs) {
+ mEndTransition.setDuration(maxDurationMs);
+ }
+ }
+
+ final List<Overlay> overlays = getAllOverlays();
+ for (Overlay overlay : overlays) {
+ // Adjust the start time if necessary
+ final long overlayStartTimeMs;
+ if (overlay.getStartTime() > getTimelineDuration()) {
+ overlayStartTimeMs = 0;
+ } else {
+ overlayStartTimeMs = overlay.getStartTime();
+ }
+
+ // Adjust the duration if necessary
+ final long overlayDurationMs;
+ if (overlayStartTimeMs + overlay.getDuration() > getTimelineDuration()) {
+ overlayDurationMs = getTimelineDuration() - overlayStartTimeMs;
+ } else {
+ overlayDurationMs = overlay.getDuration();
+ }
+
+ if (overlayStartTimeMs != overlay.getStartTime() ||
+ overlayDurationMs != overlay.getDuration()) {
+ overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs);
+ }
+ }
+
+ final List<Effect> effects = getAllEffects();
+ for (Effect effect : effects) {
+ // Adjust the start time if necessary
+ final long effectStartTimeMs;
+ if (effect.getStartTime() > getTimelineDuration()) {
+ effectStartTimeMs = 0;
+ } else {
+ effectStartTimeMs = effect.getStartTime();
+ }
+
+ // Adjust the duration if necessary
+ final long effectDurationMs;
+ if (effectStartTimeMs + effect.getDuration() > getTimelineDuration()) {
+ effectDurationMs = getTimelineDuration() - effectStartTimeMs;
+ } else {
+ effectDurationMs = effect.getDuration();
+ }
+
+ if (effectStartTimeMs != effect.getStartTime() ||
+ effectDurationMs != effect.getDuration()) {
+ effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs);
+ }
+ }
+ }
}
diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java
index 47d4fa0..dd12336 100755
--- a/media/java/android/media/videoeditor/MediaVideoItem.java
+++ b/media/java/android/media/videoeditor/MediaVideoItem.java
@@ -17,10 +17,8 @@
package android.media.videoeditor;
import java.io.IOException;
-import java.util.List;
import android.graphics.Bitmap;
-import android.media.MediaRecorder;
import android.util.Log;
import android.view.SurfaceHolder;
@@ -50,6 +48,7 @@ public class MediaVideoItem extends MediaItem {
private long mBeginBoundaryTimeMs;
private long mEndBoundaryTimeMs;
private int mVolumePercentage;
+ private boolean mMuted;
private String mAudioWaveformFilename;
private PlaybackThread mPlaybackThread;
@@ -193,42 +192,53 @@ public class MediaVideoItem extends MediaItem {
*/
@SuppressWarnings("unused")
private MediaVideoItem() throws IOException {
- this(null, null, RENDERING_MODE_BLACK_BORDER);
+ this(null, null, null, RENDERING_MODE_BLACK_BORDER);
}
/**
* Constructor
*
+ * @param editor The video editor reference
* @param mediaItemId The MediaItem id
* @param filename The image file name
* @param renderingMode The rendering mode
*
* @throws IOException if the file cannot be opened for reading
*/
- public MediaVideoItem(String mediaItemId, String filename, int renderingMode)
+ public MediaVideoItem(VideoEditor editor, String mediaItemId, String filename,
+ int renderingMode)
throws IOException {
- this(mediaItemId, filename, renderingMode, null);
+ this(editor, mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null);
}
/**
* Constructor
*
+ * @param editor The video editor reference
* @param mediaItemId The MediaItem id
* @param filename The image file name
* @param renderingMode The rendering mode
+ * @param beginMs Start time in milliseconds. Set to 0 to extract from the
+ * beginning
+ * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to
+ * extract until the end
+ * @param volumePercent in %/. 100% means no change; 50% means half value, 200%
+ * means double, 0% means silent.
+ * @param muted true if the audio is muted
* @param audioWaveformFilename The name of the audio waveform file
*
* @throws IOException if the file cannot be opened for reading
*/
- MediaVideoItem(String mediaItemId, String filename, int renderingMode,
+ MediaVideoItem(VideoEditor editor, String mediaItemId, String filename, int renderingMode,
+ long beginMs, long endMs, int volumePercent, boolean muted,
String audioWaveformFilename) throws IOException {
- super(mediaItemId, filename, renderingMode);
+ super(editor, mediaItemId, filename, renderingMode);
// TODO: Set these variables correctly
mWidth = 1080;
mHeight = 720;
mAspectRatio = MediaProperties.ASPECT_RATIO_3_2;
mFileType = MediaProperties.FILE_MP4;
- mVideoType = MediaRecorder.VideoEncoder.H264;
+ mVideoType = MediaProperties.VCODEC_H264BP;
// Do we have predefined values for this variable?
mVideoProfile = 0;
// Can video and audio duration be different?
@@ -240,17 +250,18 @@ public class MediaVideoItem extends MediaItem {
mAudioChannels = 2;
mAudioSamplingFrequency = 16000;
- mBeginBoundaryTimeMs = 0;
- mEndBoundaryTimeMs = mDurationMs;
- mVolumePercentage = 100;
+ mBeginBoundaryTimeMs = beginMs;
+ mEndBoundaryTimeMs = endMs == END_OF_FILE ? mDurationMs : endMs;
+ mVolumePercentage = volumePercent;
+ mMuted = muted;
mAudioWaveformFilename = audioWaveformFilename;
}
/**
* Sets the start and end marks for trimming a video media item.
- * This method will adjust the duration of bounding transitions if the
- * current duration of the transactions become greater than the maximum
- * allowable duration.
+ * This method will adjust the duration of bounding transitions, effects
+ * and overlays if the current duration of the transactions become greater
+ * than the maximum allowable duration.
*
* @param beginMs Start time in milliseconds. Set to 0 to extract from the
* beginning
@@ -284,46 +295,7 @@ public class MediaVideoItem extends MediaItem {
mBeginBoundaryTimeMs = beginMs;
mEndBoundaryTimeMs = endMs;
- // Check if the duration of transitions need to be adjusted
- if (mBeginTransition != null) {
- final long maxDurationMs = mBeginTransition.getMaximumDuration();
- if (mBeginTransition.getDuration() > maxDurationMs) {
- mBeginTransition.setDuration(maxDurationMs);
- }
- }
-
- if (mEndTransition != null) {
- final long maxDurationMs = mEndTransition.getMaximumDuration();
- if (mEndTransition.getDuration() > maxDurationMs) {
- mEndTransition.setDuration(maxDurationMs);
- }
- }
-
- final List<Overlay> overlays = getAllOverlays();
- for (Overlay overlay : overlays) {
- // Adjust the start time if necessary
- if (overlay.getStartTime() < mBeginBoundaryTimeMs) {
- overlay.setStartTime(mBeginBoundaryTimeMs);
- }
-
- // Adjust the duration if necessary
- if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
- overlay.setDuration(getTimelineDuration() - overlay.getStartTime());
- }
- }
-
- final List<Effect> effects = getAllEffects();
- for (Effect effect : effects) {
- // Adjust the start time if necessary
- if (effect.getStartTime() < mBeginBoundaryTimeMs) {
- effect.setStartTime(mBeginBoundaryTimeMs);
- }
-
- // Adjust the duration if necessary
- if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
- effect.setDuration(getTimelineDuration() - effect.getStartTime());
- }
- }
+ adjustElementsDuration();
}
/**
@@ -540,6 +512,20 @@ public class MediaVideoItem extends MediaItem {
}
/**
+ * @param muted true to mute the media item
+ */
+ public void setMute(boolean muted) {
+ mMuted = muted;
+ }
+
+ /**
+ * @return true if the media item is muted
+ */
+ public boolean isMuted() {
+ return mMuted;
+ }
+
+ /**
* @return The video type
*/
public int getVideoType() {
diff --git a/media/java/android/media/videoeditor/Overlay.java b/media/java/android/media/videoeditor/Overlay.java
index d9e7f85..c58b5cb 100755
--- a/media/java/android/media/videoeditor/Overlay.java
+++ b/media/java/android/media/videoeditor/Overlay.java
@@ -126,6 +126,23 @@ public abstract class Overlay {
}
/**
+ * Set the start time and duration
+ *
+ * @param startTimeMs start time in milliseconds
+ * @param durationMs The duration in milliseconds
+ */
+ public void setStartTimeAndDuration(long startTimeMs, long durationMs) {
+ if (startTimeMs + durationMs > mMediaItem.getTimelineDuration()) {
+ throw new IllegalArgumentException("Invalid start time or duration");
+ }
+
+ mStartTimeMs = startTimeMs;
+ mDurationMs = durationMs;
+
+ mMediaItem.invalidateTransitions(this);
+ }
+
+ /**
* @return The media item owner
*/
public MediaItem getMediaItem() {
diff --git a/media/java/android/media/videoeditor/VideoEditorFactory.java b/media/java/android/media/videoeditor/VideoEditorFactory.java
index 8081d76..0a377e2 100755
--- a/media/java/android/media/videoeditor/VideoEditorFactory.java
+++ b/media/java/android/media/videoeditor/VideoEditorFactory.java
@@ -66,11 +66,11 @@ public class VideoEditorFactory {
}
}
- Class<?> cls = Class.forName(className);
- Class<?> partypes[] = new Class[1];
+ final Class<?> cls = Class.forName(className);
+ final Class<?> partypes[] = new Class[1];
partypes[0] = String.class;
- Constructor<?> ct = cls.getConstructor(partypes);
- Object arglist[] = new Object[1];
+ final Constructor<?> ct = cls.getConstructor(partypes);
+ final Object arglist[] = new Object[1];
arglist[0] = projectPath;
return (VideoEditor)ct.newInstance(arglist);
@@ -84,6 +84,7 @@ public class VideoEditorFactory {
* @param projectPath The path where all VideoEditor internal files
* are stored. When a project is deleted the application is
* responsible for deleting the path and its contents.
+ * @param className The implementation class name
* @param generatePreview if set to true the
* {@link MediaEditor#generatePreview()} will be called internally to
* generate any needed transitions.
@@ -96,8 +97,17 @@ public class VideoEditorFactory {
* @throws IllegalStateException if a previous VideoEditor instance has not
* been released
*/
- public static VideoEditor load(String projectPath, boolean generatePreview) throws IOException {
- final VideoEditorTestImpl videoEditor = new VideoEditorTestImpl(projectPath);
+ public static VideoEditor load(String projectPath, String className, boolean generatePreview)
+ throws IOException, ClassNotFoundException, NoSuchMethodException,
+ InvocationTargetException, IllegalAccessException, InstantiationException {
+ final Class<?> cls = Class.forName(className);
+ final Class<?> partypes[] = new Class[1];
+ partypes[0] = String.class;
+ final Constructor<?> ct = cls.getConstructor(partypes);
+ final Object arglist[] = new Object[1];
+ arglist[0] = projectPath;
+
+ final VideoEditor videoEditor = (VideoEditor)ct.newInstance(arglist);
if (generatePreview) {
videoEditor.generatePreview();
}
diff --git a/media/java/android/media/videoeditor/VideoEditorTestImpl.java b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
index 16f9495..c3cb82a 100644
--- a/media/java/android/media/videoeditor/VideoEditorTestImpl.java
+++ b/media/java/android/media/videoeditor/VideoEditorTestImpl.java
@@ -57,6 +57,8 @@ public class VideoEditorTestImpl implements VideoEditor {
private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes";
private static final String TAG_EFFECTS = "effects";
private static final String TAG_EFFECT = "effect";
+ private static final String TAG_AUDIO_TRACKS = "audio_tracks";
+ private static final String TAG_AUDIO_TRACK = "audio_track";
private static final String ATTR_ID = "id";
private static final String ATTR_FILENAME = "filename";
@@ -65,7 +67,8 @@ public class VideoEditorTestImpl implements VideoEditor {
private static final String ATTR_ASPECT_RATIO = "aspect_ratio";
private static final String ATTR_TYPE = "type";
private static final String ATTR_DURATION = "duration";
- private static final String ATTR_BEGIN_TIME = "start_time";
+ private static final String ATTR_START_TIME = "start_time";
+ private static final String ATTR_BEGIN_TIME = "begin_time";
private static final String ATTR_END_TIME = "end_time";
private static final String ATTR_VOLUME = "volume";
private static final String ATTR_BEHAVIOR = "behavior";
@@ -85,6 +88,8 @@ public class VideoEditorTestImpl implements VideoEditor {
private static final String ATTR_END_RECT_T = "end_t";
private static final String ATTR_END_RECT_R = "end_r";
private static final String ATTR_END_RECT_B = "end_b";
+ private static final String ATTR_LOOP = "loop";
+ private static final String ATTR_MUTED = "muted";
// Instance variables
private long mDurationMs;
@@ -537,16 +542,13 @@ public class VideoEditorTestImpl implements VideoEditor {
* {@inheritDoc}
*/
public AudioTrack getAudioTrack(String audioTrackId) {
- if (mPreviewThread != null) {
- throw new IllegalStateException("Previewing is in progress");
- }
-
- final AudioTrack audioTrack = getAudioTrack(audioTrackId);
- if (audioTrack != null) {
- mAudioTracks.remove(audioTrack);
+ for (AudioTrack at : mAudioTracks) {
+ if (at.getId().equals(audioTrackId)) {
+ return at;
+ }
}
- return audioTrack;
+ return null;
}
/*
@@ -581,6 +583,7 @@ public class VideoEditorTestImpl implements VideoEditor {
.attribute("", ATTR_BEGIN_TIME, Long.toString(mvi.getBoundaryBeginTime()));
serializer.attribute("", ATTR_END_TIME, Long.toString(mvi.getBoundaryEndTime()));
serializer.attribute("", ATTR_VOLUME, Integer.toString(mvi.getVolume()));
+ serializer.attribute("", ATTR_MUTED, Boolean.toString(mvi.isMuted()));
if (mvi.getAudioWaveformFilename() != null) {
serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
mvi.getAudioWaveformFilename());
@@ -703,6 +706,26 @@ public class VideoEditorTestImpl implements VideoEditor {
}
serializer.endTag("", TAG_TRANSITIONS);
+ serializer.startTag("", TAG_AUDIO_TRACKS);
+ for (AudioTrack at : mAudioTracks) {
+ serializer.startTag("", TAG_AUDIO_TRACK);
+ serializer.attribute("", ATTR_ID, at.getId());
+ serializer.attribute("", ATTR_FILENAME, at.getFilename());
+ serializer.attribute("", ATTR_START_TIME, Long.toString(at.getStartTime()));
+ serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(at.getBoundaryBeginTime()));
+ serializer.attribute("", ATTR_END_TIME, Long.toString(at.getBoundaryEndTime()));
+ serializer.attribute("", ATTR_VOLUME, Integer.toString(at.getVolume()));
+ serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted()));
+ serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping()));
+ if (at.getAudioWaveformFilename() != null) {
+ serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
+ at.getAudioWaveformFilename());
+ }
+
+ serializer.endTag("", TAG_AUDIO_TRACK);
+ }
+ serializer.endTag("", TAG_AUDIO_TRACKS);
+
serializer.endTag("", TAG_PROJECT);
serializer.endDocument();
@@ -742,13 +765,22 @@ public class VideoEditorTestImpl implements VideoEditor {
if (MediaImageItem.class.getSimpleName().equals(type)) {
final long durationMs = Long.parseLong(parser.getAttributeValue("",
ATTR_DURATION));
- currentMediaItem = new MediaImageItem(mediaItemId, filename,
+ currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
durationMs, renderingMode);
} else if (MediaVideoItem.class.getSimpleName().equals(type)) {
+ final long beginMs = Long.parseLong(parser.getAttributeValue("",
+ ATTR_BEGIN_TIME));
+ final long endMs = Long.parseLong(parser.getAttributeValue("",
+ ATTR_END_TIME));
+ final int volume = Integer.parseInt(parser.getAttributeValue("",
+ ATTR_VOLUME));
+ final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("",
+ ATTR_MUTED));
final String audioWaveformFilename = parser.getAttributeValue("",
ATTR_AUDIO_WAVEFORM_FILENAME);
- currentMediaItem = new MediaVideoItem(mediaItemId, filename,
- renderingMode, audioWaveformFilename);
+ currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
+ renderingMode, beginMs, endMs, volume, muted,
+ audioWaveformFilename);
final long beginTimeMs = Long.parseLong(parser.getAttributeValue("",
ATTR_BEGIN_TIME));
@@ -795,6 +827,11 @@ public class VideoEditorTestImpl implements VideoEditor {
currentMediaItem.addEffect(effect);
}
}
+ } else if (TAG_AUDIO_TRACK.equals(name)) {
+ final AudioTrack audioTrack = parseAudioTrack(parser);
+ if (audioTrack != null) {
+ addAudioTrack(audioTrack);
+ }
}
break;
}
@@ -962,6 +999,33 @@ public class VideoEditorTestImpl implements VideoEditor {
return effect;
}
+ /**
+ * Parse the audio track
+ *
+ * @param parser The parser
+ *
+ * @return The audio track
+ */
+ private AudioTrack parseAudioTrack(XmlPullParser parser) {
+ final String audioTrackId = parser.getAttributeValue("", ATTR_ID);
+ final String filename = parser.getAttributeValue("", ATTR_FILENAME);
+ final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_START_TIME));
+ final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
+ final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
+ final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
+ final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
+ final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP));
+ final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
+ try {
+ final AudioTrack audioTrack = new AudioTrack(this, audioTrackId, filename, startTimeMs,
+ beginMs, endMs, loop, volume, muted, waveformFilename);
+
+ return audioTrack;
+ } catch (IOException ex) {
+ return null;
+ }
+ }
+
public void cancelExport(String filename) {
}
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index bf0c6e17..7a78185 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -966,6 +966,65 @@ void StagefrightRecorder::clipVideoFrameWidth() {
}
}
+/*
+ * Check to see whether the requested video width and height is one
+ * of the supported sizes. It returns true if so; otherwise, it
+ * returns false.
+ */
+bool StagefrightRecorder::isVideoSizeSupported(
+ const Vector<Size>& supportedSizes) const {
+
+ LOGV("isVideoSizeSupported");
+ for (size_t i = 0; i < supportedSizes.size(); ++i) {
+ if (mVideoWidth == supportedSizes[i].width &&
+ mVideoHeight == supportedSizes[i].height) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * If the preview and video output is separate, we only set the
+ * the video size, and applications should set the preview size
+ * to some proper value, and the recording framework will not
+ * change the preview size; otherwise, if the video and preview
+ * output is the same, we need to set the preview to be the same
+ * as the requested video size.
+ *
+ * On return, it also returns whether the setVideoSize() is
+ * supported.
+ */
+status_t StagefrightRecorder::setCameraVideoSize(
+ CameraParameters* params,
+ bool* isSetVideoSizeSupported) {
+ LOGV("setCameraVideoSize: %dx%d", mVideoWidth, mVideoHeight);
+
+ // Check whether the requested video size is supported
+ Vector<Size> sizes;
+ params->getSupportedVideoSizes(sizes);
+ *isSetVideoSizeSupported = true;
+ if (sizes.size() == 0) {
+ LOGD("Camera does not support setVideoSize()");
+ params->getSupportedPreviewSizes(sizes);
+ *isSetVideoSizeSupported = false;
+ }
+ if (!isVideoSizeSupported(sizes)) {
+ LOGE("Camera does not support video size (%dx%d)!",
+ mVideoWidth, mVideoHeight);
+ return BAD_VALUE;
+ }
+
+ // Actually set the video size
+ if (isSetVideoSizeSupported) {
+ params->setVideoSize(mVideoWidth, mVideoHeight);
+ } else {
+ params->setPreviewSize(mVideoWidth, mVideoHeight);
+ }
+
+ return OK;
+}
+
status_t StagefrightRecorder::setupCamera() {
if (!mCaptureTimeLapse) {
// Dont clip for time lapse capture as encoder will have enough
@@ -993,8 +1052,11 @@ status_t StagefrightRecorder::setupCamera() {
// dont change the preview size because time lapse may be using still camera
// as mVideoWidth, mVideoHeight may correspond to HD resolution not
// supported by the video camera.
+ bool isSetVideoSizeSupported = false;
if (!mCaptureTimeLapse) {
- params.setPreviewSize(mVideoWidth, mVideoHeight);
+ if (OK != setCameraVideoSize(&params, &isSetVideoSizeSupported)) {
+ return BAD_VALUE;
+ }
}
params.setPreviewFrameRate(mFrameRate);
@@ -1008,7 +1070,11 @@ status_t StagefrightRecorder::setupCamera() {
// Check on video frame size
int frameWidth = 0, frameHeight = 0;
- newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
+ if (isSetVideoSizeSupported) {
+ newCameraParams.getVideoSize(&frameWidth, &frameHeight);
+ } else {
+ newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
+ }
if (!mCaptureTimeLapse &&
(frameWidth < 0 || frameWidth != mVideoWidth ||
frameHeight < 0 || frameHeight != mVideoHeight)) {
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 02d9a01..f14c704 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -19,6 +19,7 @@
#define STAGEFRIGHT_RECORDER_H_
#include <media/MediaRecorderBase.h>
+#include <camera/CameraParameters.h>
#include <utils/String8.h>
namespace android {
@@ -125,6 +126,9 @@ private:
status_t startRTPRecording();
sp<MediaSource> createAudioSource();
status_t setupCamera();
+ bool isVideoSizeSupported(const Vector<Size>& supportedSizes) const;
+ status_t setCameraVideoSize(CameraParameters* params,
+ bool *isSetVideoSizeSupported);
status_t setupCameraSource(sp<CameraSource> *cameraSource);
status_t setupAudioEncoder(const sp<MediaWriter>& writer);
status_t setupVideoEncoder(
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 8e50d39..31c03ad 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -320,11 +320,17 @@ status_t AwesomePlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
setAudioSource(extractor->getTrack(i));
haveAudio = true;
- sp<MetaData> fileMeta = extractor->getMetaData();
- int32_t loop;
- if (fileMeta != NULL
- && fileMeta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
- mFlags |= AUTO_LOOPING;
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
+ // Only do this for vorbis audio, none of the other audio
+ // formats even support this ringtone specific hack and
+ // retrieving the metadata on some extractors may turn out
+ // to be very expensive.
+ sp<MetaData> fileMeta = extractor->getMetaData();
+ int32_t loop;
+ if (fileMeta != NULL
+ && fileMeta->findInt32(kKeyAutoLoop, &loop) && loop != 0) {
+ mFlags |= AUTO_LOOPING;
+ }
}
}
@@ -481,6 +487,10 @@ void AwesomePlayer::onBufferingUpdate() {
if (eos) {
notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ if (mFlags & PREPARING) {
+ LOGV("cache has reached EOS, prepare is done.");
+ finishAsyncPrepare_l();
+ }
} else {
off_t size;
if (mDurationUs >= 0 && mCachedSource->getSize(&size) == OK) {
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 546df47..6d00d7c 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -40,6 +40,7 @@ namespace android {
static const int64_t kMax32BitFileSize = 0x007fffffffLL;
static const uint8_t kNalUnitTypeSeqParamSet = 0x07;
static const uint8_t kNalUnitTypePicParamSet = 0x08;
+static const int64_t kVideoMediaTimeAdjustPeriodTimeUs = 10000000LL; // 10s
class MPEG4Writer::Track {
public:
@@ -148,6 +149,28 @@ private:
int64_t mPreviousTrackTimeUs;
int64_t mTrackEveryTimeDurationUs;
+ // Has the media time adjustment for video started?
+ bool mIsMediaTimeAdjustmentOn;
+ // The time stamp when previous media time adjustment period starts
+ int64_t mPrevMediaTimeAdjustTimestampUs;
+ // Number of vidoe frames whose time stamp may be adjusted
+ int64_t mMediaTimeAdjustNumFrames;
+ // The sample number when previous meida time adjustmnet period starts
+ int64_t mPrevMediaTimeAdjustSample;
+ // The total accumulated drift time within a period of
+ // kVideoMediaTimeAdjustPeriodTimeUs.
+ int64_t mTotalDriftTimeToAdjustUs;
+ // The total accumalated drift time since the start of the recording
+ // excluding the current time adjustment period
+ int64_t mPrevTotalAccumDriftTimeUs;
+
+ // Update the audio track's drift information.
+ void updateDriftTime(const sp<MetaData>& meta);
+
+ // Adjust the time stamp of the video track according to
+ // the drift time information from the audio track.
+ void adjustMediaTime(int64_t *timestampUs);
+
static void *ThreadWrapper(void *me);
status_t threadEntry();
@@ -189,6 +212,7 @@ MPEG4Writer::MPEG4Writer(const char *filename)
: mFile(fopen(filename, "wb")),
mUse4ByteNalLength(true),
mUse32BitOffset(true),
+ mIsFileSizeLimitExplicitlyRequested(false),
mPaused(false),
mStarted(false),
mOffset(0),
@@ -202,6 +226,7 @@ MPEG4Writer::MPEG4Writer(int fd)
: mFile(fdopen(fd, "wb")),
mUse4ByteNalLength(true),
mUse32BitOffset(true),
+ mIsFileSizeLimitExplicitlyRequested(false),
mPaused(false),
mStarted(false),
mOffset(0),
@@ -299,7 +324,7 @@ int64_t MPEG4Writer::estimateMoovBoxSize(int32_t bitRate) {
static const int64_t MAX_MOOV_BOX_SIZE = (180 * 3000000 * 6LL / 8000);
int64_t size = MIN_MOOV_BOX_SIZE;
- if (mMaxFileSizeLimitBytes != 0) {
+ if (mMaxFileSizeLimitBytes != 0 && mIsFileSizeLimitExplicitlyRequested) {
size = mMaxFileSizeLimitBytes * 4 / 1000;
} else if (mMaxFileDurationLimitUs != 0) {
if (bitRate <= 0) {
@@ -330,6 +355,16 @@ status_t MPEG4Writer::start(MetaData *param) {
return UNKNOWN_ERROR;
}
+ /*
+ * Check mMaxFileSizeLimitBytes at the beginning
+ * since mMaxFileSizeLimitBytes may be implicitly
+ * changed later for 32-bit file offset even if
+ * user does not ask to set it explicitly.
+ */
+ if (mMaxFileSizeLimitBytes != 0) {
+ mIsFileSizeLimitExplicitlyRequested = true;
+ }
+
int32_t use64BitOffset;
if (param &&
param->findInt32(kKey64BitFileOffset, &use64BitOffset) &&
@@ -346,7 +381,7 @@ status_t MPEG4Writer::start(MetaData *param) {
// If file size is set to be larger than the 32 bit file
// size limit, treat it as an error.
if (mMaxFileSizeLimitBytes > kMax32BitFileSize) {
- LOGW("32-bi file size limit (%lld bytes) too big. "
+ LOGW("32-bit file size limit (%lld bytes) too big. "
"It is changed to %lld bytes",
mMaxFileSizeLimitBytes, kMax32BitFileSize);
mMaxFileSizeLimitBytes = kMax32BitFileSize;
@@ -1149,6 +1184,12 @@ status_t MPEG4Writer::Track::start(MetaData *params) {
mNumStscTableEntries = 0;
mNumSttsTableEntries = 0;
mMdatSizeBytes = 0;
+ mIsMediaTimeAdjustmentOn = false;
+ mPrevMediaTimeAdjustTimestampUs = 0;
+ mMediaTimeAdjustNumFrames = 0;
+ mPrevMediaTimeAdjustSample = 0;
+ mTotalDriftTimeToAdjustUs = 0;
+ mPrevTotalAccumDriftTimeUs = 0;
pthread_create(&mThread, &attr, ThreadWrapper, this);
pthread_attr_destroy(&attr);
@@ -1437,6 +1478,145 @@ status_t MPEG4Writer::Track::makeAVCCodecSpecificData(
return OK;
}
+/*
+* The video track's media time adjustment for real-time applications
+* is described as follows:
+*
+* First, the media time adjustment is done for every period of
+* kVideoMediaTimeAdjustPeriodTimeUs. kVideoMediaTimeAdjustPeriodTimeUs
+* is currently a fixed value chosen heuristically. The value of
+* kVideoMediaTimeAdjustPeriodTimeUs should not be very large or very small
+* for two considerations: on one hand, a relatively large value
+* helps reduce large fluctuation of drift time in the audio encoding
+* path; while on the other hand, a relatively small value helps keep
+* restoring synchronization in audio/video more frequently. Note for the
+* very first period of kVideoMediaTimeAdjustPeriodTimeUs, there is
+* no media time adjustment for the video track.
+*
+* Second, the total accumulated audio track time drift found
+* in a period of kVideoMediaTimeAdjustPeriodTimeUs is distributed
+* over a stream of incoming video frames. The number of video frames
+* affected is determined based on the number of recorded video frames
+* within the past kVideoMediaTimeAdjustPeriodTimeUs period.
+* We choose to distribute the drift time over only a portion
+* (rather than all) of the total number of recorded video frames
+* in order to make sure that the video track media time adjustment is
+* completed for the current period before the next video track media
+* time adjustment period starts. Currently, the portion chosen is a
+* half (0.5).
+*
+* Last, various additional checks are performed to ensure that
+* the actual audio encoding path does not have too much drift.
+* In particular, 1) we want to limit the average incremental time
+* adjustment for each video frame to be less than a threshold
+* for a single period of kVideoMediaTimeAdjustPeriodTimeUs.
+* Currently, the threshold is set to 5 ms. If the average incremental
+* media time adjustment for a video frame is larger than the
+* threshold, the audio encoding path has too much time drift.
+* 2) We also want to limit the total time drift in the audio
+* encoding path to be less than a threshold for a period of
+* kVideoMediaTimeAdjustPeriodTimeUs. Currently, the threshold
+* is 0.5% of kVideoMediaTimeAdjustPeriodTimeUs. If the time drift of
+* the audio encoding path is larger than the threshold, the audio
+* encoding path has too much time drift. We treat the large time
+* drift of the audio encoding path as errors, since there is no
+* way to keep audio/video in synchronization for real-time
+* applications if the time drift is too large unless we drop some
+* video frames, which has its own problems that we don't want
+* to get into for the time being.
+*/
+void MPEG4Writer::Track::adjustMediaTime(int64_t *timestampUs) {
+ if (*timestampUs - mPrevMediaTimeAdjustTimestampUs >=
+ kVideoMediaTimeAdjustPeriodTimeUs) {
+
+ LOGV("New media time adjustment period at %lld us", *timestampUs);
+ mIsMediaTimeAdjustmentOn = true;
+ mMediaTimeAdjustNumFrames =
+ (mNumSamples - mPrevMediaTimeAdjustSample) >> 1;
+
+ mPrevMediaTimeAdjustTimestampUs = *timestampUs;
+ mPrevMediaTimeAdjustSample = mNumSamples;
+ int64_t totalAccumDriftTimeUs = mOwner->getDriftTimeUs();
+ mTotalDriftTimeToAdjustUs =
+ totalAccumDriftTimeUs - mPrevTotalAccumDriftTimeUs;
+
+ mPrevTotalAccumDriftTimeUs = totalAccumDriftTimeUs;
+
+ // Check on incremental adjusted time per frame
+ int64_t adjustTimePerFrameUs =
+ mTotalDriftTimeToAdjustUs / mMediaTimeAdjustNumFrames;
+
+ if (adjustTimePerFrameUs < 0) {
+ adjustTimePerFrameUs = -adjustTimePerFrameUs;
+ }
+ if (adjustTimePerFrameUs >= 5000) {
+ LOGE("Adjusted time per video frame is %lld us",
+ adjustTimePerFrameUs);
+ CHECK(!"Video frame time adjustment is too large!");
+ }
+
+ // Check on total accumulated time drift within a period of
+ // kVideoMediaTimeAdjustPeriodTimeUs.
+ int64_t driftPercentage = (mTotalDriftTimeToAdjustUs * 1000)
+ / kVideoMediaTimeAdjustPeriodTimeUs;
+
+ if (driftPercentage < 0) {
+ driftPercentage = -driftPercentage;
+ }
+ if (driftPercentage > 5) {
+ LOGE("Audio track has time drift %lld us over %lld us",
+ mTotalDriftTimeToAdjustUs,
+ kVideoMediaTimeAdjustPeriodTimeUs);
+
+ CHECK(!"The audio track media time drifts too much!");
+ }
+
+ }
+
+ if (mIsMediaTimeAdjustmentOn) {
+ if (mNumSamples - mPrevMediaTimeAdjustSample <=
+ mMediaTimeAdjustNumFrames) {
+
+ // Do media time incremental adjustment
+ int64_t incrementalAdjustTimeUs =
+ (mTotalDriftTimeToAdjustUs *
+ (mNumSamples - mPrevMediaTimeAdjustSample))
+ / mMediaTimeAdjustNumFrames;
+
+ *timestampUs +=
+ (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs);
+
+ LOGV("Incremental video frame media time adjustment: %lld us",
+ (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs));
+ } else {
+ // Within the remaining adjustment period,
+ // no incremental adjustment is needed.
+ *timestampUs +=
+ (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs);
+
+ LOGV("Fixed video frame media time adjustment: %lld us",
+ (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs));
+ }
+ }
+}
+
+/*
+ * Updates the drift time from the audio track so that
+ * the video track can get the updated drift time information
+ * from the file writer. The fluctuation of the drift time of the audio
+ * encoding path is smoothed out with a simple filter by giving a larger
+ * weight to more recently drift time. The filter coefficients, 0.5 and 0.5,
+ * are heuristically determined.
+ */
+void MPEG4Writer::Track::updateDriftTime(const sp<MetaData>& meta) {
+ int64_t driftTimeUs = 0;
+ if (meta->findInt64(kKeyDriftTime, &driftTimeUs)) {
+ int64_t prevDriftTimeUs = mOwner->getDriftTimeUs();
+ int64_t timeUs = (driftTimeUs + prevDriftTimeUs) >> 1;
+ mOwner->setDriftTimeUs(timeUs);
+ }
+}
+
status_t MPEG4Writer::Track::threadEntry() {
int32_t count = 0;
const int64_t interleaveDurationUs = mOwner->interleaveDuration();
@@ -1587,24 +1767,16 @@ status_t MPEG4Writer::Track::threadEntry() {
timestampUs -= previousPausedDurationUs;
CHECK(timestampUs >= 0);
- if (mIsRealTimeRecording && !mIsAudio) {
- // The minor adjustment on the timestamp is heuristic/experimental
- // We are adjusting the timestamp to reduce the fluctuation of the duration
- // of neighboring samples. This in turn helps reduce the track header size,
- // especially, the number of entries in the "stts" box.
- if (mNumSamples > 1) {
- int64_t currDriftTimeUs = mOwner->getDriftTimeUs();
- int64_t durationUs = timestampUs + currDriftTimeUs - lastTimestampUs;
- int64_t diffUs = (durationUs > lastDurationUs)
- ? durationUs - lastDurationUs
- : lastDurationUs - durationUs;
- if (diffUs <= 5000) { // XXX: Magic number 5ms
- timestampUs = lastTimestampUs + lastDurationUs;
- } else {
- timestampUs += currDriftTimeUs;
- }
+
+ // Media time adjustment for real-time applications
+ if (mIsRealTimeRecording) {
+ if (mIsAudio) {
+ updateDriftTime(meta_data);
+ } else {
+ adjustMediaTime(&timestampUs);
}
}
+
CHECK(timestampUs >= 0);
if (mNumSamples > 1) {
if (timestampUs <= lastTimestampUs) {
@@ -1656,12 +1828,6 @@ status_t MPEG4Writer::Track::threadEntry() {
lastDurationUs = timestampUs - lastTimestampUs;
lastDurationTicks = currDurationTicks;
lastTimestampUs = timestampUs;
- if (mIsRealTimeRecording && mIsAudio) {
- int64_t driftTimeUs = 0;
- if (meta_data->findInt64(kKeyDriftTime, &driftTimeUs)) {
- mOwner->setDriftTimeUs(driftTimeUs);
- }
- }
if (isSync != 0) {
addOneStssTableEntry(mNumSamples);
@@ -1735,6 +1901,9 @@ status_t MPEG4Writer::Track::threadEntry() {
mReachedEOS = true;
LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames. - %s",
count, nZeroLengthFrames, mNumSamples, mIsAudio? "audio": "video");
+ if (mIsAudio) {
+ LOGI("Audio track drift time: %lld us", mOwner->getDriftTimeUs());
+ }
if (err == ERROR_END_OF_STREAM) {
return OK;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
index e442c85..3908d71 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
@@ -23,10 +23,13 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
import android.hardware.Camera;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
+import android.os.Handler;
import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
@@ -51,105 +54,90 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
private static final int NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER = 200;
private static final long WAIT_TIME_CAMERA_TEST = 3000; // 3 second
private static final long WAIT_TIME_RECORDER_TEST = 6000; // 6 second
- private static final long WAIT_TIME_RECORD = 10000; // 10 seconds
- private static final long WAIT_TIME_PLAYBACK = 6000; // 6 second
private static final String OUTPUT_FILE = "/sdcard/temp";
private static final String OUTPUT_FILE_EXT = ".3gp";
private static final String MEDIA_STRESS_OUTPUT =
"/sdcard/mediaStressOutput.txt";
- private Looper mCameraLooper = null;
- private Looper mRecorderLooper = null;
- private final Object lock = new Object();
- private final Object recorderlock = new Object();
- private static int WAIT_FOR_COMMAND_TO_COMPLETE = 10000; // Milliseconds.
private final CameraErrorCallback mCameraErrorCallback = new CameraErrorCallback();
private final RecorderErrorCallback mRecorderErrorCallback = new RecorderErrorCallback();
+ private final static int WAIT_TIMEOUT = 10000;
+ private Thread mLooperThread;
+ private Handler mHandler;
+
public MediaRecorderStressTest() {
super("com.android.mediaframeworktest", MediaFrameworkTest.class);
}
protected void setUp() throws Exception {
+ final Semaphore sem = new Semaphore(0);
+ mLooperThread = new Thread() {
+ @Override
+ public void run() {
+ Log.v(TAG, "starting looper");
+ Looper.prepare();
+ mHandler = new Handler();
+ sem.release();
+ Looper.loop();
+ Log.v(TAG, "quit looper");
+ }
+ };
+ mLooperThread.start();
+ if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
+ fail("Failed to start the looper.");
+ }
+
getActivity();
- super.setUp();
+ super.setUp();
}
- private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback {
- public void onError(int error, android.hardware.Camera camera) {
- if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
- assertTrue("Camera test mediaserver died", false);
+ @Override
+ protected void tearDown() throws Exception {
+ if (mHandler != null) {
+ mHandler.getLooper().quit();
+ mHandler = null;
+ }
+ if (mLooperThread != null) {
+ mLooperThread.join(WAIT_TIMEOUT);
+ if (mLooperThread.isAlive()) {
+ fail("Failed to stop the looper.");
}
+ mLooperThread = null;
}
- }
- private final class RecorderErrorCallback implements MediaRecorder.OnErrorListener {
- public void onError(MediaRecorder mr, int what, int extra) {
- // fail the test case no matter what error come up
- assertTrue("mediaRecorder error", false);
- }
+ super.tearDown();
}
- private void initializeCameraMessageLooper() {
- Log.v(TAG, "start looper");
- new Thread() {
+ private void runOnLooper(final Runnable command) throws InterruptedException {
+ final Semaphore sem = new Semaphore(0);
+ mHandler.post(new Runnable() {
@Override
public void run() {
- // Set up a looper to be used by camera.
- Looper.prepare();
- Log.v(TAG, "start loopRun");
- mCameraLooper = Looper.myLooper();
- mCamera = Camera.open();
- synchronized (lock) {
- lock.notify();
+ try {
+ command.run();
+ } finally {
+ sem.release();
}
- Looper.loop();
- Log.v(TAG, "initializeMessageLooper: quit.");
}
- }.start();
+ });
+ if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
+ fail("Failed to run the command on the looper.");
+ }
}
- private void initializeRecorderMessageLooper() {
- Log.v(TAG, "start looper");
- new Thread() {
- @Override
- public void run() {
- Looper.prepare();
- Log.v(TAG, "start loopRun");
- mRecorderLooper = Looper.myLooper();
- mRecorder = new MediaRecorder();
- synchronized (recorderlock) {
- recorderlock.notify();
- }
- Looper.loop(); // Blocks forever until Looper.quit() is called.
- Log.v(TAG, "initializeMessageLooper: quit.");
+ private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback {
+ public void onError(int error, android.hardware.Camera camera) {
+ if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
+ assertTrue("Camera test mediaserver died", false);
}
- }.start();
- }
-
- /*
- * Terminates the message looper thread.
- */
- private void terminateCameraMessageLooper() {
- mCameraLooper.quit();
- try {
- Thread.sleep(1000);
- } catch (Exception e){
- Log.v(TAG, e.toString());
}
- mCamera.release();
}
- /*
- * Terminates the message looper thread.
- */
- private void terminateRecorderMessageLooper() {
- mRecorderLooper.quit();
- try {
- Thread.sleep(1000);
- } catch (Exception e){
- Log.v(TAG, e.toString());
+ private final class RecorderErrorCallback implements MediaRecorder.OnErrorListener {
+ public void onError(MediaRecorder mr, int what, int extra) {
+ // fail the test case no matter what error come up
+ assertTrue("mediaRecorder error", false);
}
- mRecorder.release();
}
//Test case for stressing the camera preview.
@@ -166,21 +154,19 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
Log.v(TAG, "Start preview");
output.write("No of loop: ");
- for (int i = 0; i< NUMBER_OF_CAMERA_STRESS_LOOPS; i++){
- synchronized (lock) {
- initializeCameraMessageLooper();
- try {
- lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
- } catch(Exception e) {
- Log.v(TAG, "wait was interrupted.");
+ for (int i = 0; i< NUMBER_OF_CAMERA_STRESS_LOOPS; i++) {
+ runOnLooper(new Runnable() {
+ @Override
+ public void run() {
+ mCamera = Camera.open();
}
- }
+ });
mCamera.setErrorCallback(mCameraErrorCallback);
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.startPreview();
Thread.sleep(WAIT_TIME_CAMERA_TEST);
mCamera.stopPreview();
- terminateCameraMessageLooper();
+ mCamera.release();
output.write(" ," + i);
}
} catch (Exception e) {
@@ -205,15 +191,13 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
try {
output.write("No of loop: ");
Log.v(TAG, "Start preview");
- for (int i = 0; i < NUMBER_OF_RECORDER_STRESS_LOOPS; i++){
- synchronized (recorderlock) {
- initializeRecorderMessageLooper();
- try {
- recorderlock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
- } catch(Exception e) {
- Log.v(TAG, "wait was interrupted.");
+ for (int i = 0; i < NUMBER_OF_RECORDER_STRESS_LOOPS; i++) {
+ runOnLooper(new Runnable() {
+ @Override
+ public void run() {
+ mRecorder = new MediaRecorder();
}
- }
+ });
Log.v(TAG, "counter = " + i);
filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT;
Log.v(TAG, filename);
@@ -233,7 +217,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
Log.v(TAG, "before release");
Thread.sleep(WAIT_TIME_RECORDER_TEST);
mRecorder.reset();
- terminateRecorderMessageLooper();
+ mRecorder.release();
output.write(", " + i);
}
} catch (Exception e) {
@@ -258,33 +242,29 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
try {
Log.v(TAG, "Start preview");
output.write("No of loop: ");
- for (int i = 0; i < NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER; i++){
- synchronized (lock) {
- initializeCameraMessageLooper();
- try {
- lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
- } catch(Exception e) {
- Log.v(TAG, "wait was interrupted.");
+ for (int i = 0; i < NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER; i++) {
+ runOnLooper(new Runnable() {
+ @Override
+ public void run() {
+ mCamera = Camera.open();
}
- }
+ });
mCamera.setErrorCallback(mCameraErrorCallback);
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.startPreview();
Thread.sleep(WAIT_TIME_CAMERA_TEST);
mCamera.stopPreview();
- terminateCameraMessageLooper();
+ mCamera.release();
mCamera = null;
Log.v(TAG, "release camera");
filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT;
Log.v(TAG, filename);
- synchronized (recorderlock) {
- initializeRecorderMessageLooper();
- try {
- recorderlock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
- } catch(Exception e) {
- Log.v(TAG, "wait was interrupted.");
+ runOnLooper(new Runnable() {
+ @Override
+ public void run() {
+ mRecorder = new MediaRecorder();
}
- }
+ });
mRecorder.setOnErrorListener(mRecorderErrorCallback);
mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
@@ -299,7 +279,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
mRecorder.prepare();
Log.v(TAG, "before release");
Thread.sleep(WAIT_TIME_CAMERA_TEST);
- terminateRecorderMessageLooper();
+ mRecorder.release();
Log.v(TAG, "release video recorder");
output.write(", " + i);
}
@@ -358,14 +338,12 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
for (int i = 0; i < iterations; i++){
filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT;
Log.v(TAG, filename);
- synchronized (recorderlock) {
- initializeRecorderMessageLooper();
- try {
- recorderlock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
- } catch(Exception e) {
- Log.v(TAG, "wait was interrupted.");
+ runOnLooper(new Runnable() {
+ @Override
+ public void run() {
+ mRecorder = new MediaRecorder();
}
- }
+ });
Log.v(TAG, "iterations : " + iterations);
Log.v(TAG, "video_encoder : " + video_encoder);
Log.v(TAG, "audio_encoder : " + audio_encoder);
@@ -391,7 +369,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
Thread.sleep(record_duration);
Log.v(TAG, "Before stop");
mRecorder.stop();
- terminateRecorderMessageLooper();
+ mRecorder.release();
//start the playback
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(filename);
diff --git a/opengl/libagl/egl.cpp b/opengl/libagl/egl.cpp
index 460b74f..239dc05 100644
--- a/opengl/libagl/egl.cpp
+++ b/opengl/libagl/egl.cpp
@@ -1969,7 +1969,7 @@ EGLBoolean eglSwapInterval(EGLDisplay dpy, EGLint interval)
if (egl_display_t::is_valid(dpy) == EGL_FALSE)
return setError(EGL_BAD_DISPLAY, EGL_FALSE);
// TODO: eglSwapInterval()
- return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+ return EGL_TRUE;
}
// ----------------------------------------------------------------------------
diff --git a/packages/SystemUI/res/drawable-hdpi/battery_low_battery.png b/packages/SystemUI/res/drawable-hdpi/battery_low_battery.png
index d894f7b..e74c22f 100644
--- a/packages/SystemUI/res/drawable-hdpi/battery_low_battery.png
+++ b/packages/SystemUI/res/drawable-hdpi/battery_low_battery.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_notification_overlay.9.png b/packages/SystemUI/res/drawable-hdpi/ic_notification_overlay.9.png
index 744178f..d6c8a21 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_notification_overlay.9.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_notification_overlay.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png
index 3e317dd..b16e436 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
index 2f66b1d..b16e436 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png
index 72329f8..1b6ed74 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
index 72329f8..43e35d3 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png
index 558c49c..498adbb 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
index 558c49c..b7e42a0 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png
index 6440bdd..959fc5c 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
index 6440bdd..f905979 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png
index fe20423..6e8e73c 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
index fe20423..b5799c8 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
new file mode 100644
index 0000000..a0def6b
--- /dev/null
+++ b/packages/SystemUI/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources>
+ <drawable name="notification_number_text_color">#ffffffff</drawable>
+</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java
index 027bed4..e87d003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/IconMerger.java
@@ -23,28 +23,34 @@ import android.util.Slog;
import android.view.View;
import android.widget.LinearLayout;
+import com.android.internal.statusbar.StatusBarIcon;
+
import com.android.systemui.R;
public class IconMerger extends LinearLayout {
private static final String TAG = "IconMerger";
+ private int mIconSize;
private StatusBarIconView mMoreView;
+ private StatusBarIcon mMoreIcon = new StatusBarIcon(null, R.drawable.stat_notify_more, 0);
public IconMerger(Context context, AttributeSet attrs) {
super(context, attrs);
- }
- public void addMoreView(StatusBarIconView v, LinearLayout.LayoutParams lp) {
- super.addView(v, lp);
- mMoreView = v;
+ mIconSize = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_icon_size);
+
+ mMoreView = new StatusBarIconView(context, "more");
+ mMoreView.set(mMoreIcon);
+ addView(mMoreView, 0, new LinearLayout.LayoutParams(mIconSize, mIconSize));
}
- public void addView(StatusBarIconView v, int index, LinearLayout.LayoutParams lp) {
+ public void addView(StatusBarIconView v, int index) {
if (index == 0) {
throw new RuntimeException("Attempt to put view before the more view: " + v);
}
- super.addView(v, index, lp);
+ addView(v, index, new LinearLayout.LayoutParams(mIconSize, mIconSize));
}
@Override
@@ -127,28 +133,8 @@ public class IconMerger extends LinearLayout {
}
}
}
-
- // BUG: Updating the text during the layout here doesn't seem to cause
- // the view to be redrawn fully. The text view gets resized correctly, but the
- // text contents aren't drawn properly. To work around this, we post a message
- // and provide the value later. We're the only one changing this value show it
- // should be ordered correctly.
- if (false) {
- // TODO this.moreIcon.update(number);
- } else {
- mBugWorkaroundNumber = number;
- mBugWorkaroundHandler.post(mBugWorkaroundRunnable);
- }
- }
- private int mBugWorkaroundNumber;
- private Handler mBugWorkaroundHandler = new Handler();
- private Runnable mBugWorkaroundRunnable = new Runnable() {
- public void run() {
- /* TODO
- IconMerger.this.moreIcon.update(mBugWorkaroundNumber);
- IconMerger.this.moreIcon.view.invalidate();
- */
- }
- };
+ mMoreIcon.number = number;
+ mMoreView.set(mMoreIcon);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
index e945981..57ebd27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
@@ -274,12 +274,6 @@ public class PhoneStatusBarService extends StatusBarService {
mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
- // the more notifications icon
- StatusBarIconView moreView = new StatusBarIconView(this, "more");
- moreView.set(new StatusBarIcon(null, R.drawable.stat_notify_more, 0));
- mNotificationIcons.addMoreView(moreView,
- new LinearLayout.LayoutParams(mIconSize, mIconSize));
-
// set the inital view visibility
setAreThereNotifications();
mDateView.setVisibility(View.INVISIBLE);
@@ -579,8 +573,7 @@ public class PhoneStatusBarService extends StatusBarService {
parent.addView(row, viewIndex);
// Add the icon.
final int iconIndex = chooseIconIndex(isOngoing, viewIndex);
- mNotificationIcons.addView(iconView, iconIndex,
- new LinearLayout.LayoutParams(mIconSize, mIconSize));
+ mNotificationIcons.addView(iconView, iconIndex);
return iconView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 0ca0572..8419e56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -21,22 +21,37 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
import android.util.Slog;
import android.util.Log;
+import android.view.View;
import android.view.ViewDebug;
import android.widget.FrameLayout;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.R;
+
public class StatusBarIconView extends AnimatedImageView {
private static final String TAG = "StatusBarIconView";
private StatusBarIcon mIcon;
@ViewDebug.ExportedProperty private String mSlot;
+ private Drawable mNumberBackground;
+ private Paint mNumberPain;
+ private int mNumberX;
+ private int mNumberY;
+ private String mNumberText;
public StatusBarIconView(Context context, String slot) {
super(context);
+ final Resources res = context.getResources();
mSlot = slot;
+ mNumberPain = new Paint();
+ mNumberPain.setTextAlign(Paint.Align.CENTER);
+ mNumberPain.setColor(res.getColor(R.drawable.notification_number_text_color));
+ mNumberPain.setAntiAlias(true);
}
private static boolean streq(String a, String b) {
@@ -63,6 +78,9 @@ public class StatusBarIconView extends AnimatedImageView {
&& mIcon.iconLevel == icon.iconLevel;
final boolean visibilityEquals = mIcon != null
&& mIcon.visible == icon.visible;
+ final boolean numberEquals = mIcon != null
+ && mIcon.number == icon.number;
+ mIcon = icon.clone();
if (!iconEquals) {
Drawable drawable = getIcon(icon);
if (drawable == null) {
@@ -74,10 +92,22 @@ public class StatusBarIconView extends AnimatedImageView {
if (!levelEquals) {
setImageLevel(icon.iconLevel);
}
+ if (!numberEquals) {
+ if (icon.number > 0) {
+ if (mNumberBackground == null) {
+ mNumberBackground = getContext().getResources().getDrawable(
+ R.drawable.ic_notification_overlay);
+ }
+ placeNumber();
+ } else {
+ mNumberBackground = null;
+ mNumberText = null;
+ }
+ invalidate();
+ }
if (!visibilityEquals) {
setVisibility(icon.visible ? VISIBLE : GONE);
}
- mIcon = icon.clone();
return true;
}
@@ -126,9 +156,47 @@ public class StatusBarIconView extends AnimatedImageView {
return mIcon;
}
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (mNumberBackground != null) {
+ placeNumber();
+ }
+ }
+
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mNumberBackground != null) {
+ mNumberBackground.draw(canvas);
+ canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain);
+ }
+ }
+
protected void debug(int depth) {
super.debug(depth);
Log.d("View", debugIndent(depth) + "slot=" + mSlot);
Log.d("View", debugIndent(depth) + "icon=" + mIcon);
}
+
+ void placeNumber() {
+ final String str = mNumberText = Integer.toString(mIcon.number);
+ final int w = getWidth();
+ final int h = getHeight();
+ final Rect r = new Rect();
+ mNumberPain.getTextBounds(str, 0, str.length(), r);
+ final int tw = r.right - r.left;
+ final int th = r.bottom - r.top;
+ mNumberBackground.getPadding(r);
+ int dw = r.left + tw + r.right;
+ if (dw < mNumberBackground.getMinimumWidth()) {
+ dw = mNumberBackground.getMinimumWidth();
+ }
+ mNumberX = w-r.right-((dw-r.right-r.left)/2);
+ int dh = r.top + th + r.bottom;
+ if (dh < mNumberBackground.getMinimumWidth()) {
+ dh = mNumberBackground.getMinimumWidth();
+ }
+ mNumberY = h-r.bottom-((dh-r.top-th-r.bottom)/2);
+ mNumberBackground.setBounds(w-dw, h-dh, w, h);
+ }
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index c25df1d..24c9443 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1504,7 +1504,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
sendCloseSystemWindows();
Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- getContext().startActivity(intent);
+ try {
+ getContext().startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "No activity found for android.intent.action.CALL_BUTTON.");
+ }
}
@Override
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c70f5d4..33685ba 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -296,7 +296,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int mLandscapeRotation = -1; // default landscape rotation
int mSeascapeRotation = -1; // "other" landscape rotation, 180 degrees from mLandscapeRotation
- int mPortraitRotation = -1;
+ int mPortraitRotation = -1; // default portrait rotation
+ int mUpsideDownRotation = -1; // "other" portrait rotation
// Nothing to see here, move along...
int mFancyRotationAnimation;
@@ -364,26 +365,25 @@ public class PhoneWindowManager implements WindowManagerPolicy {
boolean useSensorForOrientationLp(int appOrientation) {
// The app says use the sensor.
- if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) {
+ if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
return true;
}
// The user preference says we can rotate, and the app is willing to rotate.
- // Note we include SCREEN_ORIENTATION_LANDSCAPE since we can use the sensor to choose
- // between the two possible landscape rotations.
if (mAccelerometerDefault != 0 &&
(appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER
- || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
- || appOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)) {
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)) {
return true;
}
- // We're in a dock that has a rotation affinity, an the app is willing to rotate.
+ // We're in a dock that has a rotation affinity, and the app is willing to rotate.
if ((mCarDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_CAR)
|| (mDeskDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_DESK)) {
// Note we override the nosensor flag here.
if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER
|| appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
- || appOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
- || appOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
+ || appOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
return true;
}
}
@@ -397,7 +397,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
* screen is switched off.
*/
boolean needSensorRunningLp() {
- if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) {
+ if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
+ || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
// If the application has explicitly requested to follow the
// orientation, then we need to turn the sensor or.
return true;
@@ -2133,21 +2136,42 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (d.getWidth() > d.getHeight()) {
mPortraitRotation = Surface.ROTATION_90;
mLandscapeRotation = Surface.ROTATION_0;
+ mUpsideDownRotation = Surface.ROTATION_270;
mSeascapeRotation = Surface.ROTATION_180;
} else {
mPortraitRotation = Surface.ROTATION_0;
mLandscapeRotation = Surface.ROTATION_90;
+ mUpsideDownRotation = Surface.ROTATION_180;
mSeascapeRotation = Surface.ROTATION_270;
}
}
synchronized (mLock) {
- if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
- //always return portrait if orientation set to portrait
- return mPortraitRotation;
- } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
- return getCurrentLandscapeRotation(lastRotation);
+ switch (orientation) {
+ case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
+ //always return portrait if orientation set to portrait
+ return mPortraitRotation;
+ case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
+ //always return landscape if orientation set to landscape
+ return mLandscapeRotation;
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ //always return portrait if orientation set to portrait
+ return mUpsideDownRotation;
+ case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ //always return seascape if orientation set to reverse landscape
+ return mSeascapeRotation;
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ //return either landscape rotation based on the sensor
+ mOrientationListener.setAllow180Rotation(false);
+ return getCurrentLandscapeRotation(lastRotation);
+ case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ mOrientationListener.setAllow180Rotation(true);
+ return getCurrentPortraitRotation(lastRotation);
}
+
+ mOrientationListener.setAllow180Rotation(
+ orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
+
// case for nosensor meaning ignore sensor and consider only lid
// or orientation sensor disabled
//or case.unspecified
@@ -2167,18 +2191,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private int getCurrentLandscapeRotation(int lastRotation) {
- // landscape-only apps can take either landscape rotation
- if (useSensorForOrientationLp(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)) {
- int sensorRotation = mOrientationListener.getCurrentRotation(lastRotation);
- if (isLandscapeOrSeascape(sensorRotation)) {
- return sensorRotation;
- }
+ int sensorRotation = mOrientationListener.getCurrentRotation(lastRotation);
+ if (isLandscapeOrSeascape(sensorRotation)) {
+ return sensorRotation;
}
// try to preserve the old rotation if it was landscape
if (isLandscapeOrSeascape(lastRotation)) {
return lastRotation;
}
- // default to one of the two landscape rotations
+ // default to one of the primary landscape rotation
return mLandscapeRotation;
}
@@ -2186,6 +2207,23 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return sensorRotation == mLandscapeRotation || sensorRotation == mSeascapeRotation;
}
+ private int getCurrentPortraitRotation(int lastRotation) {
+ int sensorRotation = mOrientationListener.getCurrentRotation(lastRotation);
+ if (isAnyPortrait(sensorRotation)) {
+ return sensorRotation;
+ }
+ // try to preserve the old rotation if it was portrait
+ if (isAnyPortrait(lastRotation)) {
+ return lastRotation;
+ }
+ // default to one of the primary portrait rotations
+ return mPortraitRotation;
+ }
+
+ private boolean isAnyPortrait(int sensorRotation) {
+ return sensorRotation == mPortraitRotation || sensorRotation == mUpsideDownRotation;
+ }
+
public boolean detectSafeMode() {
try {
int menuState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_MENU);
diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java
index e6c32d9..a8ccfc0 100644
--- a/services/java/com/android/server/BatteryService.java
+++ b/services/java/com/android/server/BatteryService.java
@@ -99,6 +99,7 @@ class BatteryService extends Binder {
private int mBatteryTemperature;
private String mBatteryTechnology;
private boolean mBatteryLevelCritical;
+ private boolean mInvalidCharger;
private int mLastBatteryStatus;
private int mLastBatteryHealth;
@@ -107,6 +108,7 @@ class BatteryService extends Binder {
private int mLastBatteryVoltage;
private int mLastBatteryTemperature;
private boolean mLastBatteryLevelCritical;
+ private boolean mLastInvalidCharger;
private int mLowBatteryWarningLevel;
private int mLowBatteryCloseWarningLevel;
@@ -128,7 +130,12 @@ class BatteryService extends Binder {
mLowBatteryCloseWarningLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryCloseWarningLevel);
- mUEventObserver.startObserving("SUBSYSTEM=power_supply");
+ mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply");
+
+ // watch for invalid charger messages if the invalid_charger switch exists
+ if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
+ mInvalidChargerObserver.startObserving("DEVPATH=/devices/virtual/switch/invalid_charger");
+ }
// set initial status
update();
@@ -162,13 +169,24 @@ class BatteryService extends Binder {
return mPlugType;
}
- private UEventObserver mUEventObserver = new UEventObserver() {
+ private UEventObserver mPowerSupplyObserver = new UEventObserver() {
@Override
public void onUEvent(UEventObserver.UEvent event) {
update();
}
};
+ private UEventObserver mInvalidChargerObserver = new UEventObserver() {
+ @Override
+ public void onUEvent(UEventObserver.UEvent event) {
+ boolean invalidCharger = "1".equals(event.get("SWITCH_STATE"));
+ if (mInvalidCharger != invalidCharger) {
+ mInvalidCharger = invalidCharger;
+ update();
+ }
+ }
+ };
+
// returns battery level as a percentage
final int getBatteryLevel() {
return mBatteryLevel;
@@ -237,7 +255,8 @@ class BatteryService extends Binder {
mBatteryLevel != mLastBatteryLevel ||
mPlugType != mLastPlugType ||
mBatteryVoltage != mLastBatteryVoltage ||
- mBatteryTemperature != mLastBatteryTemperature) {
+ mBatteryTemperature != mLastBatteryTemperature ||
+ mInvalidCharger != mLastInvalidCharger) {
if (mPlugType != mLastPlugType) {
if (mLastPlugType == BATTERY_PLUGGED_NONE) {
@@ -334,6 +353,7 @@ class BatteryService extends Binder {
mLastBatteryVoltage = mBatteryVoltage;
mLastBatteryTemperature = mBatteryTemperature;
mLastBatteryLevelCritical = mBatteryLevelCritical;
+ mLastInvalidCharger = mInvalidCharger;
}
}
@@ -355,6 +375,7 @@ class BatteryService extends Binder {
intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage);
intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature);
intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology);
+ intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
if (false) {
Slog.d(TAG, "updateBattery level:" + mBatteryLevel +
@@ -364,7 +385,7 @@ class BatteryService extends Binder {
" temperature: " + mBatteryTemperature +
" technology: " + mBatteryTechnology +
" AC powered:" + mAcOnline + " USB powered:" + mUsbOnline +
- " icon:" + icon );
+ " icon:" + icon + " invalid charger:" + mInvalidCharger);
}
ActivityManagerNative.broadcastStickyIntent(intent, null);
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index 308c9c0..bdf313c 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -16,32 +16,81 @@
package com.android.server;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.IClipboard;
import android.content.IOnPrimaryClipChangedListener;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.util.HashSet;
/**
* Implementation of the clipboard for copy and paste.
*/
public class ClipboardService extends IClipboard.Stub {
- private ClipData mPrimaryClip;
+ private final Context mContext;
+ private final IActivityManager mAm;
+ private final PackageManager mPm;
+ private final IBinder mPermissionOwner;
+
private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
= new RemoteCallbackList<IOnPrimaryClipChangedListener>();
+ private ClipData mPrimaryClip;
+
+ private final HashSet<String> mActivePermissionOwners
+ = new HashSet<String>();
+
/**
* Instantiates the clipboard.
*/
- public ClipboardService(Context context) { }
+ public ClipboardService(Context context) {
+ mContext = context;
+ mAm = ActivityManagerNative.getDefault();
+ mPm = context.getPackageManager();
+ IBinder permOwner = null;
+ try {
+ permOwner = mAm.newUriPermissionOwner("clipboard");
+ } catch (RemoteException e) {
+ Slog.w("clipboard", "AM dead", e);
+ }
+ mPermissionOwner = permOwner;
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ Slog.w("clipboard", "Exception: ", e);
+ throw e;
+ }
+
+ }
public void setPrimaryClip(ClipData clip) {
synchronized (this) {
if (clip != null && clip.getItemCount() <= 0) {
throw new IllegalArgumentException("No items");
}
+ checkDataOwnerLocked(clip, Binder.getCallingUid());
+ clearActiveOwnersLocked();
mPrimaryClip = clip;
final int n = mPrimaryClipListeners.beginBroadcast();
for (int i = 0; i < n; i++) {
@@ -57,8 +106,9 @@ public class ClipboardService extends IClipboard.Stub {
}
}
- public ClipData getPrimaryClip() {
+ public ClipData getPrimaryClip(String pkg) {
synchronized (this) {
+ addActiveOwnerLocked(Binder.getCallingUid(), pkg);
return mPrimaryClip;
}
}
@@ -96,4 +146,110 @@ public class ClipboardService extends IClipboard.Stub {
return false;
}
}
+
+ private final void checkUriOwnerLocked(Uri uri, int uid) {
+ if (!"content".equals(uri.getScheme())) {
+ return;
+ }
+ long ident = Binder.clearCallingIdentity();
+ boolean allowed = false;
+ try {
+ // This will throw SecurityException for us.
+ mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
+ if (item.getUri() != null) {
+ checkUriOwnerLocked(item.getUri(), uid);
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ checkUriOwnerLocked(intent.getData(), uid);
+ }
+ }
+
+ private final void checkDataOwnerLocked(ClipData data, int uid) {
+ final int N = data.getItemCount();
+ for (int i=0; i<N; i++) {
+ checkItemOwnerLocked(data.getItem(i), uid);
+ }
+ }
+
+ private final void grantUriLocked(Uri uri, String pkg) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private final void grantItemLocked(ClipData.Item item, String pkg) {
+ if (item.getUri() != null) {
+ grantUriLocked(item.getUri(), pkg);
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ grantUriLocked(intent.getData(), pkg);
+ }
+ }
+
+ private final void addActiveOwnerLocked(int uid, String pkg) {
+ PackageInfo pi;
+ try {
+ pi = mPm.getPackageInfo(pkg, 0);
+ if (pi.applicationInfo.uid != uid) {
+ throw new SecurityException("Calling uid " + uid
+ + " does not own package " + pkg);
+ }
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown package " + pkg, e);
+ }
+ if (!mActivePermissionOwners.contains(pkg)) {
+ final int N = mPrimaryClip.getItemCount();
+ for (int i=0; i<N; i++) {
+ grantItemLocked(mPrimaryClip.getItem(i), pkg);
+ }
+ mActivePermissionOwners.add(pkg);
+ }
+ }
+
+ private final void revokeUriLocked(Uri uri) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private final void revokeItemLocked(ClipData.Item item) {
+ if (item.getUri() != null) {
+ revokeUriLocked(item.getUri());
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ revokeUriLocked(intent.getData());
+ }
+ }
+
+ private final void clearActiveOwnersLocked() {
+ mActivePermissionOwners.clear();
+ if (mPrimaryClip == null) {
+ return;
+ }
+ final int N = mPrimaryClip.getItemCount();
+ for (int i=0; i<N; i++) {
+ revokeItemLocked(mPrimaryClip.getItem(i));
+ }
+ }
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 880befd..6095117 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -68,7 +68,7 @@ import java.net.UnknownHostException;
*/
public class ConnectivityService extends IConnectivityManager.Stub {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String TAG = "ConnectivityService";
// how long to wait before switching back to a radio's default network
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index a960097..fe306b3 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -342,7 +342,6 @@ public class InputManager {
if (toChannel == null) {
throw new IllegalArgumentException("toChannel must not be null.");
}
- Slog.d(TAG, "transferring touch focus");
return nativeTransferTouchFocus(fromChannel, toChannel);
}
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index de28375..dc4194c 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -25,6 +25,7 @@ import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.InputBindResult;
+import com.android.internal.view.InputMethodAndSubtypeEnabler;
import com.android.server.StatusBarManagerService;
@@ -97,6 +98,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
static final int MSG_SHOW_IM_PICKER = 1;
static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
+ static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
static final int MSG_UNBIND_INPUT = 1000;
static final int MSG_BIND_INPUT = 1010;
@@ -117,6 +119,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final Context mContext;
final Handler mHandler;
+ final InputMethodSettings mSettings;
final SettingsObserver mSettingsObserver;
final StatusBarManagerService mStatusBar;
final IWindowManager mIWindowManager;
@@ -126,13 +129,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// All known input methods. mMethodMap also serves as the global
// lock for this class.
- final ArrayList<InputMethodInfo> mMethodList
- = new ArrayList<InputMethodInfo>();
- final HashMap<String, InputMethodInfo> mMethodMap
- = new HashMap<String, InputMethodInfo>();
-
- final TextUtils.SimpleStringSplitter mStringColonSplitter
- = new TextUtils.SimpleStringSplitter(':');
+ final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
+ final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
class SessionState {
final ClientState client;
@@ -483,27 +481,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mStatusBar = statusBar;
statusBar.setIconVisibility("ime", false);
+ // mSettings should be created before buildInputMethodListLocked
+ mSettings = new InputMethodSettings(context.getContentResolver(), mMethodMap, mMethodList);
buildInputMethodListLocked(mMethodList, mMethodMap);
+ mSettings.enableAllIMEsIfThereIsNoEnabledIME();
- final String enabledStr = Settings.Secure.getString(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- Slog.i(TAG, "Enabled input methods: " + enabledStr);
- final String defaultIme = Settings.Secure.getString(mContext
- .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
- if (enabledStr == null || TextUtils.isEmpty(defaultIme)) {
- Slog.i(TAG, "Enabled input methods or default IME has not been set, enabling all");
+ if (TextUtils.isEmpty(Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
InputMethodInfo defIm = null;
- StringBuilder sb = new StringBuilder(256);
- final int N = mMethodList.size();
- for (int i=0; i<N; i++) {
- InputMethodInfo imi = mMethodList.get(i);
- Slog.i(TAG, "Adding: " + imi.getId());
- if (i > 0) sb.append(':');
- sb.append(imi.getId());
+ for (InputMethodInfo imi: mMethodList) {
if (defIm == null && imi.getIsDefaultResourceId() != 0) {
try {
- Resources res = mContext.createPackageContext(
+ Resources res = context.createPackageContext(
imi.getPackageName(), 0).getResources();
if (res.getBoolean(imi.getIsDefaultResourceId())) {
defIm = imi;
@@ -514,12 +503,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
}
- if (defIm == null && N > 0) {
+ if (defIm == null && mMethodList.size() > 0) {
defIm = mMethodList.get(0);
Slog.i(TAG, "No default found, using " + defIm.getId());
}
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, sb.toString());
if (defIm != null) {
Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId());
@@ -567,29 +554,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
public List<InputMethodInfo> getEnabledInputMethodList() {
synchronized (mMethodMap) {
- return getEnabledInputMethodListLocked();
- }
- }
-
- List<InputMethodInfo> getEnabledInputMethodListLocked() {
- final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
-
- final String enabledStr = Settings.Secure.getString(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- if (enabledStr != null) {
- final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
- splitter.setString(enabledStr);
-
- while (splitter.hasNext()) {
- InputMethodInfo info = mMethodMap.get(splitter.next());
- if (info != null) {
- res.add(info);
- }
- }
+ return mSettings.getEnabledInputMethodListLocked();
}
-
- return res;
}
public void addClient(IInputMethodClient client,
@@ -1261,7 +1227,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
- Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid "
+ Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
+ Binder.getCallingUid() + ": " + client);
}
@@ -1273,13 +1239,26 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
- Slog.w(TAG, "Ignoring showInputSubtypeMethodDialogFromClient of: " + client);
+ Slog.w(TAG, "Ignoring showInputMethodSubtypePickerFromClient of: " + client);
}
mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
}
}
+ public void showInputMethodAndSubtypeEnablerFromClient(
+ IInputMethodClient client, String topId) {
+ // TODO: Handle topId for setting the top position of the list activity
+ synchronized (mMethodMap) {
+ if (mCurClient == null || client == null
+ || mCurClient.client.asBinder() != client.asBinder()) {
+ Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
+ }
+
+ mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_ENABLER);
+ }
+ }
+
public void setInputMethod(IBinder token, String id) {
setInputMethodWithSubtype(token, id, NOT_A_SUBTYPE_ID);
}
@@ -1372,6 +1351,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
showInputMethodSubtypeMenu();
return true;
+ case MSG_SHOW_IM_SUBTYPE_ENABLER:
+ showInputMethodAndSubtypeEnabler();
+ return true;
+
// ---------------------------------------------------------
case MSG_UNBIND_INPUT:
@@ -1471,7 +1454,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
private boolean chooseNewDefaultIMELocked() {
- List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
+ List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
if (enabled != null && enabled.size() > 0) {
// We'd prefer to fall back on a system IME, since that is safer.
int i=enabled.size();
@@ -1564,6 +1547,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
showInputMethodMenuInternal(true);
}
+ private void showInputMethodAndSubtypeEnabler() {
+ Intent intent = new Intent();
+ intent.setClassName("android", InputMethodAndSubtypeEnabler.class.getCanonicalName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ mContext.startActivity(intent);
+ }
+
private void showInputMethodMenuInternal(boolean showSubtypes) {
if (DEBUG) Slog.v(TAG, "Show switching menu");
@@ -1650,7 +1641,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
hideInputMethodMenu();
}
};
-
+
TypedArray a = context.obtainStyledAttributes(null,
com.android.internal.R.styleable.DialogPreference,
com.android.internal.R.attr.alertDialogStyle, 0);
@@ -1664,7 +1655,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
.setIcon(a.getDrawable(
com.android.internal.R.styleable.DialogPreference_dialogTitle));
a.recycle();
-
+
mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@@ -1738,81 +1729,45 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// Make sure this is a valid input method.
InputMethodInfo imm = mMethodMap.get(id);
if (imm == null) {
- if (imm == null) {
- throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
- }
+ throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
}
- StringBuilder builder = new StringBuilder(256);
+ List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+ .getEnabledInputMethodsAndSubtypeListLocked();
- boolean removed = false;
- String firstId = null;
-
- // Look through the currently enabled input methods.
- String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- if (enabledStr != null) {
- final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
- splitter.setString(enabledStr);
- while (splitter.hasNext()) {
- String curId = splitter.next();
- if (curId.equals(id)) {
- if (enabled) {
- // We are enabling this input method, but it is
- // already enabled. Nothing to do. The previous
- // state was enabled.
- return true;
- }
- // We are disabling this input method, and it is
- // currently enabled. Skip it to remove from the
- // new list.
- removed = true;
- } else if (!enabled) {
- // We are building a new list of input methods that
- // doesn't contain the given one.
- if (firstId == null) firstId = curId;
- if (builder.length() > 0) builder.append(':');
- builder.append(curId);
+ if (enabled) {
+ for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
+ if (pair.first.equals(id)) {
+ // We are enabling this input method, but it is already enabled.
+ // Nothing to do. The previous state was enabled.
+ return true;
}
}
- }
-
- if (!enabled) {
- if (!removed) {
- // We are disabling the input method but it is already
- // disabled. Nothing to do. The previous state was
- // disabled.
+ mSettings.appendAndPutEnabledInputMethodLocked(id, false);
+ // Previous state was disabled.
+ return false;
+ } else {
+ StringBuilder builder = new StringBuilder();
+ if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ builder, enabledInputMethodsList, id)) {
+ // Disabled input method is currently selected, switch to another one.
+ String selId = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ if (id.equals(selId)) {
+ Settings.Secure.putString(
+ mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD,
+ enabledInputMethodsList.size() > 0
+ ? enabledInputMethodsList.get(0).first : "");
+ resetSelectedInputMethodSubtype();
+ }
+ // Previous state was enabled.
+ return true;
+ } else {
+ // We are disabling the input method but it is already disabled.
+ // Nothing to do. The previous state was disabled.
return false;
}
- // Update the setting with the new list of input methods.
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
- // We the disabled input method is currently selected, switch
- // to another one.
- String selId = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
- if (id.equals(selId)) {
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD,
- firstId != null ? firstId : "");
- resetSelectedInputMethodSubtype();
- }
- // Previous state was enabled.
- return true;
- }
-
- // Add in the newly enabled input method.
- if (enabledStr == null || enabledStr.length() == 0) {
- enabledStr = id;
- } else {
- enabledStr = enabledStr + ':' + id;
}
-
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
-
- // Previous state was disabled.
- return false;
}
private void resetSelectedInputMethodSubtype() {
@@ -1862,6 +1817,161 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return mCurrentSubtype;
}
+ /**
+ * Utility class for putting and getting settings for InputMethod
+ * TODO: Move all putters and getters of settings to this class.
+ */
+ private static class InputMethodSettings {
+ // The string for enabled input method is saved as follows:
+ // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
+ private static final char INPUT_METHOD_SEPARATER = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
+ private final TextUtils.SimpleStringSplitter mStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
+
+ private final TextUtils.SimpleStringSplitter mStringSemiColonSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
+
+ private final ContentResolver mResolver;
+ private final HashMap<String, InputMethodInfo> mMethodMap;
+ private final ArrayList<InputMethodInfo> mMethodList;
+
+ private String mEnabledInputMethodsStrCache;
+
+ private static void buildEnabledInputMethodsSettingString(
+ StringBuilder builder, Pair<String, ArrayList<String>> pair) {
+ String id = pair.first;
+ ArrayList<String> subtypes = pair.second;
+ builder.append(id);
+ if (subtypes.size() > 0) {
+ builder.append(subtypes.get(0));
+ for (int i = 1; i < subtypes.size(); ++i) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypes.get(i));
+ }
+ }
+ }
+
+ public InputMethodSettings(
+ ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap,
+ ArrayList<InputMethodInfo> methodList) {
+ mResolver = resolver;
+ mMethodMap = methodMap;
+ mMethodList = methodList;
+ }
+
+ public List<InputMethodInfo> getEnabledInputMethodListLocked() {
+ return createEnabledInputMethodListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked());
+ }
+
+ // At the initial boot, the settings for input methods are not set,
+ // so we need to enable IME in that case.
+ public void enableAllIMEsIfThereIsNoEnabledIME() {
+ if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
+ StringBuilder sb = new StringBuilder();
+ final int N = mMethodList.size();
+ for (int i = 0; i < N; i++) {
+ InputMethodInfo imi = mMethodList.get(i);
+ Slog.i(TAG, "Adding: " + imi.getId());
+ if (i > 0) sb.append(':');
+ sb.append(imi.getId());
+ }
+ putEnabledInputMethodsStr(sb.toString());
+ }
+ }
+
+ public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+ ArrayList<Pair<String, ArrayList<String>>> imsList
+ = new ArrayList<Pair<String, ArrayList<String>>>();
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
+ }
+ mStringColonSplitter.setString(enabledInputMethodsStr);
+ while (mStringColonSplitter.hasNext()) {
+ String nextImsStr = mStringColonSplitter.next();
+ mStringSemiColonSplitter.setString(nextImsStr);
+ if (mStringSemiColonSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<String>();
+ // The first element is ime id.
+ String imeId = mStringSemiColonSplitter.next();
+ while (mStringSemiColonSplitter.hasNext()) {
+ subtypeHashes.add(mStringSemiColonSplitter.next());
+ }
+ imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
+ }
+
+ public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
+ if (reloadInputMethodStr) {
+ getEnabledInputMethodsStr();
+ }
+ if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
+ // Add in the newly enabled input method.
+ putEnabledInputMethodsStr(id);
+ } else {
+ putEnabledInputMethodsStr(
+ mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
+ }
+ }
+
+ /**
+ * Build and put a string of EnabledInputMethods with removing specified Id.
+ * @return the specified id was removed or not.
+ */
+ public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+ boolean isRemoved = false;
+ boolean needsAppendSeparator = false;
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ String curId = ims.first;
+ if (curId.equals(id)) {
+ // We are disabling this input method, and it is
+ // currently enabled. Skip it to remove from the
+ // new list.
+ isRemoved = true;
+ } else {
+ if (needsAppendSeparator) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ } else {
+ needsAppendSeparator = true;
+ }
+ buildEnabledInputMethodsSettingString(builder, ims);
+ }
+ }
+ if (isRemoved) {
+ // Update the setting with the new list of input methods.
+ putEnabledInputMethodsStr(builder.toString());
+ }
+ return isRemoved;
+ }
+
+ private List<InputMethodInfo> createEnabledInputMethodListLocked(
+ List<Pair<String, ArrayList<String>>> imsList) {
+ final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null) {
+ res.add(info);
+ }
+ }
+ return res;
+ }
+
+ private void putEnabledInputMethodsStr(String str) {
+ Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
+ mEnabledInputMethodsStrCache = str;
+ }
+
+ private String getEnabledInputMethodsStr() {
+ mEnabledInputMethodsStrCache = Settings.Secure.getString(
+ mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
+ return mEnabledInputMethodsStrCache;
+ }
+ }
+
// ----------------------------------------------------------------------
@Override
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 1f97bee..c4d2d4d 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -5466,7 +5466,7 @@ class PackageManagerService extends IPackageManager.Stub {
deletePackageLI(
pkgName, false,
dataDirExists ? PackageManager.DONT_DELETE_DATA : 0,
- res.removedInfo);
+ res.removedInfo, true);
}
}
}
@@ -5511,7 +5511,7 @@ class PackageManagerService extends IPackageManager.Stub {
// First delete the existing package while retaining the data directory
if (!deletePackageLI(pkgName, true, PackageManager.DONT_DELETE_DATA,
- res.removedInfo)) {
+ res.removedInfo, true)) {
// If the existing package was'nt successfully deleted
res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
deletedPkg = false;
@@ -5541,7 +5541,7 @@ class PackageManagerService extends IPackageManager.Stub {
deletePackageLI(
pkgName, true,
PackageManager.DONT_DELETE_DATA,
- res.removedInfo);
+ res.removedInfo, true);
}
// Since we failed to install the new package we need to restore the old
// package that we deleted.
@@ -6009,7 +6009,7 @@ class PackageManagerService extends IPackageManager.Stub {
synchronized (mInstallLock) {
res = deletePackageLI(packageName, deleteCodeAndResources,
- flags | REMOVE_CHATTY, info);
+ flags | REMOVE_CHATTY, info, true);
}
if(res && sendBroadCast) {
@@ -6070,7 +6070,7 @@ class PackageManagerService extends IPackageManager.Stub {
* delete a partially installed application.
*/
private void removePackageDataLI(PackageParser.Package p, PackageRemovedInfo outInfo,
- int flags) {
+ int flags, boolean writeSettings) {
String packageName = p.packageName;
if (outInfo != null) {
outInfo.removedPackage = packageName;
@@ -6123,8 +6123,10 @@ class PackageManagerService extends IPackageManager.Stub {
mSettings.mPreferredActivities.removeFilter(pa);
}
}
- // Save settings now
- mSettings.writeLP();
+ if (writeSettings) {
+ // Save settings now
+ mSettings.writeLP();
+ }
}
}
@@ -6132,7 +6134,7 @@ class PackageManagerService extends IPackageManager.Stub {
* Tries to delete system package.
*/
private boolean deleteSystemPackageLI(PackageParser.Package p,
- int flags, PackageRemovedInfo outInfo) {
+ int flags, PackageRemovedInfo outInfo, boolean writeSettings) {
ApplicationInfo applicationInfo = p.applicationInfo;
//applicable for non-partially installed applications only
if (applicationInfo == null) {
@@ -6164,7 +6166,8 @@ class PackageManagerService extends IPackageManager.Stub {
deleteCodeAndResources = false;
flags |= PackageManager.DONT_DELETE_DATA;
}
- boolean ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo);
+ boolean ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo,
+ writeSettings);
if (!ret) {
return false;
}
@@ -6185,13 +6188,16 @@ class PackageManagerService extends IPackageManager.Stub {
}
synchronized (mPackages) {
updatePermissionsLP(newPkg.packageName, newPkg, true, true, false);
- mSettings.writeLP();
+ if (writeSettings) {
+ mSettings.writeLP();
+ }
}
return true;
}
private boolean deleteInstalledPackageLI(PackageParser.Package p,
- boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) {
+ boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo,
+ boolean writeSettings) {
ApplicationInfo applicationInfo = p.applicationInfo;
if (applicationInfo == null) {
Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo.");
@@ -6202,7 +6208,7 @@ class PackageManagerService extends IPackageManager.Stub {
}
// Delete package data from internal structures and also remove data if flag is set
- removePackageDataLI(p, outInfo, flags);
+ removePackageDataLI(p, outInfo, flags, writeSettings);
// Delete application code and resources
if (deleteCodeAndResources) {
@@ -6219,7 +6225,8 @@ class PackageManagerService extends IPackageManager.Stub {
* This method handles package deletion in general
*/
private boolean deletePackageLI(String packageName,
- boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo) {
+ boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo,
+ boolean writeSettings) {
if (packageName == null) {
Slog.w(TAG, "Attempt to delete null packageName.");
return false;
@@ -6246,7 +6253,7 @@ class PackageManagerService extends IPackageManager.Stub {
if (dataOnly) {
// Delete application data first
- removePackageDataLI(p, outInfo, flags);
+ removePackageDataLI(p, outInfo, flags, writeSettings);
return true;
}
// At this point the package should have ApplicationInfo associated with it
@@ -6259,12 +6266,13 @@ class PackageManagerService extends IPackageManager.Stub {
Log.i(TAG, "Removing system package:"+p.packageName);
// When an updated system application is deleted we delete the existing resources as well and
// fall back to existing code in system partition
- ret = deleteSystemPackageLI(p, flags, outInfo);
+ ret = deleteSystemPackageLI(p, flags, outInfo, writeSettings);
} else {
Log.i(TAG, "Removing non-system package:"+p.packageName);
// Kill application pre-emptively especially for apps on sd.
killApplication(packageName, p.applicationInfo.uid);
- ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo);
+ ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo,
+ writeSettings);
}
return ret;
}
@@ -9739,7 +9747,7 @@ class PackageManagerService extends IPackageManager.Stub {
PackageRemovedInfo outInfo = new PackageRemovedInfo();
synchronized (mInstallLock) {
boolean res = deletePackageLI(pkgName, false,
- PackageManager.DONT_DELETE_DATA, outInfo);
+ PackageManager.DONT_DELETE_DATA, outInfo, false);
if (res) {
pkgList.add(pkgName);
} else {
@@ -9748,6 +9756,13 @@ class PackageManagerService extends IPackageManager.Stub {
}
}
}
+
+ synchronized (mPackages) {
+ // We didn't update the settings after removing each package;
+ // write them now for all packages.
+ mSettings.writeLP();
+ }
+
// We have to absolutely send UPDATED_MEDIA_STATUS only
// after confirming that all the receivers processed the ordered
// broadcast when packages get disabled, force a gc to clean things up.
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 717f63c..80dcc98 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -31,6 +31,7 @@ import android.content.ContentResolver;
import android.content.ContentService;
import android.content.Context;
import android.content.pm.IPackageManager;
+import android.content.res.Configuration;
import android.database.ContentObserver;
import android.media.AudioService;
import android.os.Build;
@@ -44,6 +45,9 @@ import android.provider.Settings;
import android.server.BluetoothA2dpService;
import android.server.BluetoothService;
import android.server.search.SearchManagerService;
+import android.view.Display;
+import android.view.WindowManager;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
@@ -496,6 +500,16 @@ class ServerThread extends Thread {
statusBar.systemReady();
}
wm.systemReady();
+
+ // Update the configuration for this context by hand, because we're going
+ // to start using it before the config change done in wm.systemReady() will
+ // propagate to it.
+ Configuration config = wm.computeNewConfiguration();
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ w.getDefaultDisplay().getMetrics(metrics);
+ context.getResources().updateConfiguration(config, metrics);
+
power.systemReady();
try {
pm.systemReady();
diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java
index a33b7c2..2b4845b 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -584,9 +584,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (linkProperties != null) {
intent.putExtra(Phone.DATA_LINK_PROPERTIES_KEY, linkProperties);
- NetworkInterface iface = linkProperties.getInterface();
+ String iface = linkProperties.getInterfaceName();
if (iface != null) {
- intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface.getName());
+ intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface);
}
}
if (linkCapabilities != null) {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 19f56a8..540c7fe 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -881,7 +881,6 @@ public class WifiService extends IWifiManager.Stub {
* the already-set timer.
*/
int pluggedType = intent.getIntExtra("plugged", 0);
- Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType);
if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) &&
!shouldWifiStayAwake(stayAwakeConditions, pluggedType)) {
long triggerTime = System.currentTimeMillis() + idleMillis;
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 0727a79..59f7434 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -505,7 +505,7 @@ public class WindowManagerService extends IWindowManager.Stub
InputChannel mServerChannel, mClientChannel;
WindowState mTargetWindow;
ArrayList<WindowState> mNotifiedWindows;
- boolean mDragEnded;
+ boolean mDragInProgress;
private final Rect tmpRect = new Rect();
@@ -562,6 +562,7 @@ public class WindowManagerService extends IWindowManager.Stub
// works correctly in calling out to the apps.
mDataDescription = new ClipDescription(mData);
mNotifiedWindows.clear();
+ mDragInProgress = true;
if (DEBUG_DRAG) {
Slog.d(TAG, "broadcasting DRAG_STARTED of " + mDataDescription);
@@ -569,8 +570,10 @@ public class WindowManagerService extends IWindowManager.Stub
DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
mDataDescription, null);
- for (WindowState ws : mWindows) {
- sendDragStartedLw(ws, evt);
+ final int N = mWindows.size();
+ for (int i = 0; i < N; i++) {
+ // sendDragStartedLw() clones evt for local-process dispatch
+ sendDragStartedLw(mWindows.get(i), evt);
}
evt.recycle();
}
@@ -579,10 +582,17 @@ public class WindowManagerService extends IWindowManager.Stub
* designated window is potentially a drop recipient. There are race situations
* around DRAG_ENDED broadcast, so we make sure that once we've declared that
* the drag has ended, we never send out another DRAG_STARTED for this drag action.
+ *
+ * This method clones the 'event' parameter if it's being delivered to the same
+ * process, so it's safe for the caller to call recycle() on the event afterwards.
*/
- private void sendDragStartedLw(WindowState newWin, final DragEvent event) {
- if (!mDragEnded && newWin.isPotentialDragTarget()) {
+ private void sendDragStartedLw(WindowState newWin, DragEvent event) {
+ if (mDragInProgress && newWin.isPotentialDragTarget()) {
try {
+ // clone for local callees since dispatch will recycle the event
+ if (Process.myPid() == newWin.mSession.mPid) {
+ event = DragEvent.obtain(event);
+ }
newWin.mClient.dispatchDragEvent(event);
// track each window that we've notified that the drag is starting
mNotifiedWindows.add(newWin);
@@ -597,19 +607,22 @@ public class WindowManagerService extends IWindowManager.Stub
* was begun. This is a rare case.
*/
private void sendDragStartedIfNeededLw(WindowState newWin) {
- // If we have sent the drag-started, we needn't do so again
- for (WindowState ws : mNotifiedWindows) {
- if (ws == newWin) {
- return;
+ if (mDragInProgress) {
+ // If we have sent the drag-started, we needn't do so again
+ for (WindowState ws : mNotifiedWindows) {
+ if (ws == newWin) {
+ return;
+ }
}
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DRAG_STARTED to new window " + newWin);
+ }
+ DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
+ mDataDescription, null);
+ // sendDragStartedLw() clones 'event' if the window is process-local
+ sendDragStartedLw(newWin, event);
+ event.recycle();
}
- if (DEBUG_DRAG) {
- Slog.d(TAG, "sending DRAG_STARTED to new window " + newWin);
- }
- DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED, 0, 0,
- mDataDescription, null);
- sendDragStartedLw(newWin, event);
- event.recycle();
}
void broadcastDragEnded() {
@@ -626,12 +639,20 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
mNotifiedWindows.clear();
- mDragEnded = true;
+ mDragInProgress = false;
}
evt.recycle();
}
void notifyMoveLw(float x, float y) {
+ final int myPid = Process.myPid();
+
+ // Move the surface to the given touch
+ mSurface.openTransaction();
+ mSurface.setPosition((int)(x - mThumbOffsetX), (int)(y - mThumbOffsetY));
+ mSurface.closeTransaction();
+
+ // Tell the affected window
WindowState touchedWin = getTouchedWinAtPointLw(x, y);
try {
// have we dragged over a new window?
@@ -643,7 +664,9 @@ public class WindowManagerService extends IWindowManager.Stub
DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED,
0, 0, null, null);
mTargetWindow.mClient.dispatchDragEvent(evt);
- evt.recycle();
+ if (myPid != mTargetWindow.mSession.mPid) {
+ evt.recycle();
+ }
}
if (touchedWin != null) {
if (DEBUG_DRAG) {
@@ -652,7 +675,9 @@ public class WindowManagerService extends IWindowManager.Stub
DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION,
x, y, null, null);
touchedWin.mClient.dispatchDragEvent(evt);
- evt.recycle();
+ if (myPid != touchedWin.mSession.mPid) {
+ evt.recycle();
+ }
}
} catch (RemoteException e) {
Slog.w(TAG, "can't send drag notification to windows");
@@ -667,13 +692,16 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_DRAG) {
Slog.d(TAG, "sending DROP to " + touchedWin);
}
+ final int myPid = Process.myPid();
DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP, x, y, null, mData);
try {
touchedWin.mClient.dispatchDragEvent(evt);
} catch (RemoteException e) {
Slog.w(TAG, "can't send drop notification to win " + touchedWin);
}
- evt.recycle();
+ if (myPid != touchedWin.mSession.mPid) {
+ evt.recycle();
+ }
}
}
@@ -749,13 +777,7 @@ public class WindowManagerService extends IWindowManager.Stub
case MotionEvent.ACTION_MOVE: {
synchronized (mWindowMap) {
- // move the surface to the latest touch point
- mDragState.mSurface.openTransaction();
- mDragState.mSurface.setPosition((int)(newX - mDragState.mThumbOffsetX),
- (int)(newY - mDragState.mThumbOffsetY));
- mDragState.mSurface.closeTransaction();
-
- // tell the involved window(s) where we are
+ // move the surface and tell the involved window(s) where we are
mDragState.notifyMoveLw(newX, newY);
}
} break;
@@ -3368,11 +3390,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
// If this application has requested an explicit orientation,
// then use it.
- if (or == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ||
- or == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT ||
- or == ActivityInfo.SCREEN_ORIENTATION_SENSOR ||
- or == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR ||
- or == ActivityInfo.SCREEN_ORIENTATION_USER) {
+ if (or != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
return or;
}
findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND);
@@ -5403,7 +5422,6 @@ public class WindowManagerService extends IWindowManager.Stub
mDragState.reset();
mDragState = null;
}
-
}
}
} finally {
@@ -5535,7 +5553,7 @@ public class WindowManagerService extends IWindowManager.Stub
inputWindow.frameTop = 0;
inputWindow.frameRight = mDisplay.getWidth();
inputWindow.frameBottom = mDisplay.getHeight();
-
+
inputWindow.visibleFrameLeft = inputWindow.frameLeft;
inputWindow.visibleFrameTop = inputWindow.frameTop;
inputWindow.visibleFrameRight = inputWindow.frameRight;
@@ -6195,9 +6213,8 @@ public class WindowManagerService extends IWindowManager.Stub
mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
- // !!! TODO: call into the input monitor to sever the current touch event flow
- // and redirect to the drag "window"; also extract the current touch (x, y)
- // in screen coordinates
+ // !!! TODO: extract the current touch (x, y) in screen coordinates. That
+ // will let us eliminate the (touchX,touchY) parameters from the API.
mDragState.register();
mInputMonitor.updateInputWindowsLw();
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 1eab7fc..d008c90 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4350,8 +4350,10 @@ public final class ActivityManagerService extends ActivityManagerNative
return -1;
}
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Checking grant " + targetPkg + " permission to " + uri);
+ if (targetPkg != null) {
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ "Checking grant " + targetPkg + " permission to " + uri);
+ }
final IPackageManager pm = AppGlobals.getPackageManager();
@@ -4380,23 +4382,45 @@ public final class ActivityManagerService extends ActivityManagerNative
}
int targetUid;
- try {
- targetUid = pm.getPackageUid(targetPkg);
- if (targetUid < 0) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Can't grant URI permission no uid for: " + targetPkg);
+ if (targetPkg != null) {
+ try {
+ targetUid = pm.getPackageUid(targetPkg);
+ if (targetUid < 0) {
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ "Can't grant URI permission no uid for: " + targetPkg);
+ return -1;
+ }
+ } catch (RemoteException ex) {
return -1;
}
- } catch (RemoteException ex) {
- return -1;
+ } else {
+ targetUid = -1;
}
- // First... does the target actually need this permission?
- if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
- // No need to grant the target this permission.
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Target " + targetPkg + " already has full permission to " + uri);
- return -1;
+ if (targetUid >= 0) {
+ // First... does the target actually need this permission?
+ if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
+ // No need to grant the target this permission.
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ "Target " + targetPkg + " already has full permission to " + uri);
+ return -1;
+ }
+ } else {
+ // First... there is no target package, so can anyone access it?
+ boolean allowed = pi.exported;
+ if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+ if (pi.readPermission != null) {
+ allowed = false;
+ }
+ }
+ if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+ if (pi.writePermission != null) {
+ allowed = false;
+ }
+ }
+ if (allowed) {
+ return -1;
+ }
}
// Second... is the provider allowing granting of URI permissions?
@@ -4426,16 +4450,25 @@ public final class ActivityManagerService extends ActivityManagerNative
// Third... does the caller itself have permission to access
// this uri?
- if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
- if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
- throw new SecurityException("Uid " + callingUid
- + " does not have permission to uri " + uri);
+ if (callingUid != Process.myUid()) {
+ if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+ if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
+ throw new SecurityException("Uid " + callingUid
+ + " does not have permission to uri " + uri);
+ }
}
}
return targetUid;
}
+ public int checkGrantUriPermission(int callingUid, String targetPkg,
+ Uri uri, int modeFlags) {
+ synchronized(this) {
+ return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
+ }
+ }
+
void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg,
Uri uri, int modeFlags, UriPermissionOwner owner) {
modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -4478,6 +4511,10 @@ public final class ActivityManagerService extends ActivityManagerNative
void grantUriPermissionLocked(int callingUid,
String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
+ if (targetPkg == null) {
+ throw new NullPointerException("targetPkg");
+ }
+
int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
if (targetUid < 0) {
return;
@@ -4496,6 +4533,10 @@ public final class ActivityManagerService extends ActivityManagerNative
+ " from " + intent + "; flags=0x"
+ Integer.toHexString(intent != null ? intent.getFlags() : 0));
+ if (targetPkg == null) {
+ throw new NullPointerException("targetPkg");
+ }
+
if (intent == null) {
return -1;
}
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 30395c0..9ed1242 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -515,7 +515,7 @@ public class ActivityStack {
r.info, r.icicle, results, newIntents, !andResume,
mService.isNextTransitionForward());
- if ((app.info.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) {
+ if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Note that the package
// manager will ensure that only activity can run in the main
// process of the .apk, which is the only thing that will be
@@ -2442,7 +2442,7 @@ public class ActivityStack {
final long origId = Binder.clearCallingIdentity();
if (mMainStack && aInfo != null &&
- (aInfo.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) {
+ (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Check to see if we already
// have another, different heavy-weight process running.
if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) {
diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java
index 99c82e6..68a2e0f 100644
--- a/services/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/java/com/android/server/am/UriPermissionOwner.java
@@ -45,7 +45,7 @@ class UriPermissionOwner {
}
Binder getExternalTokenLocked() {
- if (externalToken != null) {
+ if (externalToken == null) {
externalToken = new ExternalToken();
}
return externalToken;
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index 39ce0b6..e9eb4f0 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -46,6 +46,10 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.WorkSource;
import android.provider.Settings;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.TelephonyManager;
+import android.telephony.gsm.GsmCellLocation;
+import android.telephony.SmsMessage;
import android.util.Log;
import android.util.SparseIntArray;
@@ -53,6 +57,9 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.telephony.Phone;
import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.util.HexDump;
import java.io.File;
import java.io.FileInputStream;
@@ -153,6 +160,24 @@ public class GpsLocationProvider implements LocationProviderInterface {
private static final int REMOVE_LISTENER = 9;
private static final int REQUEST_SINGLE_SHOT = 10;
+ // Request setid
+ private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1;
+ private static final int AGPS_RIL_REQUEST_SETID_MSISDN = 2;
+
+ // Request ref location
+ private static final int AGPS_RIL_REQUEST_REFLOC_CELLID = 1;
+ private static final int AGPS_RIL_REQUEST_REFLOC_MAC = 2;
+
+ // ref. location info
+ private static final int AGPS_REF_LOCATION_TYPE_GSM_CELLID = 1;
+ private static final int AGPS_REF_LOCATION_TYPE_UMTS_CELLID = 2;
+ private static final int AGPS_REG_LOCATION_TYPE_MAC = 3;
+
+ // set id info
+ private static final int AGPS_SETID_TYPE_NONE = 0;
+ private static final int AGPS_SETID_TYPE_IMSI = 1;
+ private static final int AGPS_SETID_TYPE_MSISDN = 2;
+
private static final String PROPERTIES_FILE = "/etc/gps.conf";
private int mLocationFlags = LOCATION_INVALID;
@@ -328,10 +353,27 @@ public class GpsLocationProvider implements LocationProviderInterface {
} else if (action.equals(ALARM_TIMEOUT)) {
if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT");
hibernate();
- }
+ } else if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) {
+ checkSmsSuplInit(intent);
+ } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) {
+ checkWapSuplInit(intent);
+ }
}
};
+ private void checkSmsSuplInit(Intent intent) {
+ SmsMessage[] messages = Intents.getMessagesFromIntent(intent);
+ for (int i=0; i <messages.length; i++) {
+ byte[] supl_init = messages[i].getUserData();
+ native_agps_ni_message(supl_init,supl_init.length);
+ }
+ }
+
+ private void checkWapSuplInit(Intent intent) {
+ byte[] supl_init = (byte[]) intent.getExtra("data");
+ native_agps_ni_message(supl_init,supl_init.length);
+ }
+
public static boolean isSupported() {
return native_is_supported();
}
@@ -352,6 +394,21 @@ public class GpsLocationProvider implements LocationProviderInterface {
mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0);
mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
+ intentFilter.addDataScheme("sms");
+ intentFilter.addDataAuthority("localhost","7275");
+ context.registerReceiver(mBroadcastReciever, intentFilter);
+
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
+ try {
+ intentFilter.addDataType("application/vnd.omaloc-supl-init");
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ Log.w(TAG, "Malformed SUPL init mime type");
+ }
+ context.registerReceiver(mBroadcastReciever, intentFilter);
+
mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
// Battery statistics service to be notified when GPS turns on or off
@@ -1255,22 +1312,20 @@ public class GpsLocationProvider implements LocationProviderInterface {
//=============================================================
// NI Client support
- //=============================================================
+ //=============================================================
private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() {
- // Sends a response for an NI reqeust to HAL.
- public boolean sendNiResponse(int notificationId, int userResponse)
- {
- // TODO Add Permission check
-
- StringBuilder extrasBuf = new StringBuilder();
-
- if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId +
- ", response: " + userResponse);
-
- native_send_ni_response(notificationId, userResponse);
-
- return true;
- }
+ // Sends a response for an NI reqeust to HAL.
+ public boolean sendNiResponse(int notificationId, int userResponse)
+ {
+ // TODO Add Permission check
+
+ StringBuilder extrasBuf = new StringBuilder();
+
+ if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId +
+ ", response: " + userResponse);
+ native_send_ni_response(notificationId, userResponse);
+ return true;
+ }
};
public INetInitiatedListener getNetInitiatedListener() {
@@ -1278,70 +1333,132 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
// Called by JNI function to report an NI request.
- @SuppressWarnings("deprecation")
- public void reportNiNotification(
- int notificationId,
- int niType,
- int notifyFlags,
- int timeout,
- int defaultResponse,
- String requestorId,
- String text,
- int requestorIdEncoding,
- int textEncoding,
- String extras // Encoded extra data
+ public void reportNiNotification(
+ int notificationId,
+ int niType,
+ int notifyFlags,
+ int timeout,
+ int defaultResponse,
+ String requestorId,
+ String text,
+ int requestorIdEncoding,
+ int textEncoding,
+ String extras // Encoded extra data
)
- {
- Log.i(TAG, "reportNiNotification: entered");
- Log.i(TAG, "notificationId: " + notificationId +
- ", niType: " + niType +
- ", notifyFlags: " + notifyFlags +
- ", timeout: " + timeout +
- ", defaultResponse: " + defaultResponse);
-
- Log.i(TAG, "requestorId: " + requestorId +
- ", text: " + text +
- ", requestorIdEncoding: " + requestorIdEncoding +
- ", textEncoding: " + textEncoding);
-
- GpsNiNotification notification = new GpsNiNotification();
-
- notification.notificationId = notificationId;
- notification.niType = niType;
- notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0;
- notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0;
- notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0;
- notification.timeout = timeout;
- notification.defaultResponse = defaultResponse;
- notification.requestorId = requestorId;
- notification.text = text;
- notification.requestorIdEncoding = requestorIdEncoding;
- notification.textEncoding = textEncoding;
-
- // Process extras, assuming the format is
- // one of more lines of "key = value"
- Bundle bundle = new Bundle();
-
- if (extras == null) extras = "";
- Properties extraProp = new Properties();
-
- try {
- extraProp.load(new StringBufferInputStream(extras));
- }
- catch (IOException e)
- {
- Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras);
- }
-
- for (Entry<Object, Object> ent : extraProp.entrySet())
- {
- bundle.putString((String) ent.getKey(), (String) ent.getValue());
- }
-
- notification.extras = bundle;
-
- mNIHandler.handleNiNotification(notification);
- }
+ {
+ Log.i(TAG, "reportNiNotification: entered");
+ Log.i(TAG, "notificationId: " + notificationId +
+ ", niType: " + niType +
+ ", notifyFlags: " + notifyFlags +
+ ", timeout: " + timeout +
+ ", defaultResponse: " + defaultResponse);
+
+ Log.i(TAG, "requestorId: " + requestorId +
+ ", text: " + text +
+ ", requestorIdEncoding: " + requestorIdEncoding +
+ ", textEncoding: " + textEncoding);
+
+ GpsNiNotification notification = new GpsNiNotification();
+
+ notification.notificationId = notificationId;
+ notification.niType = niType;
+ notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0;
+ notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0;
+ notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0;
+ notification.timeout = timeout;
+ notification.defaultResponse = defaultResponse;
+ notification.requestorId = requestorId;
+ notification.text = text;
+ notification.requestorIdEncoding = requestorIdEncoding;
+ notification.textEncoding = textEncoding;
+
+ // Process extras, assuming the format is
+ // one of more lines of "key = value"
+ Bundle bundle = new Bundle();
+
+ if (extras == null) extras = "";
+ Properties extraProp = new Properties();
+
+ try {
+ extraProp.load(new StringBufferInputStream(extras));
+ }
+ catch (IOException e)
+ {
+ Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras);
+ }
+
+ for (Entry<Object, Object> ent : extraProp.entrySet())
+ {
+ bundle.putString((String) ent.getKey(), (String) ent.getValue());
+ }
+
+ notification.extras = bundle;
+
+ mNIHandler.handleNiNotification(notification);
+ }
+
+ /**
+ * Called from native code to request set id info.
+ * We should be careful about receiving null string from the TelephonyManager,
+ * because sending null String to JNI function would cause a crash.
+ */
+
+ private void requestSetID(int flags) {
+ TelephonyManager phone = (TelephonyManager)
+ mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ int type = AGPS_SETID_TYPE_NONE;
+ String data = "";
+
+ if ((flags & AGPS_RIL_REQUEST_SETID_IMSI) == AGPS_RIL_REQUEST_SETID_IMSI) {
+ String data_temp = phone.getSubscriberId();
+ if (data_temp == null) {
+ // This means the framework does not have the SIM card ready.
+ } else {
+ // This means the framework has the SIM card.
+ data = data_temp;
+ type = AGPS_SETID_TYPE_IMSI;
+ }
+ }
+ else if ((flags & AGPS_RIL_REQUEST_SETID_MSISDN) == AGPS_RIL_REQUEST_SETID_MSISDN) {
+ String data_temp = phone.getLine1Number();
+ if (data_temp == null) {
+ // This means the framework does not have the SIM card ready.
+ } else {
+ // This means the framework has the SIM card.
+ data = data_temp;
+ type = AGPS_SETID_TYPE_MSISDN;
+ }
+ }
+ native_agps_set_id(type, data);
+ }
+
+ /**
+ * Called from native code to request reference location info
+ */
+
+ private void requestRefLocation(int flags) {
+ TelephonyManager phone = (TelephonyManager)
+ mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+ GsmCellLocation gsm_cell = (GsmCellLocation) phone.getCellLocation();
+ if ((gsm_cell != null) && (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM)
+ && (phone.getNetworkOperator().length() > 3)) {
+ int type;
+ int mcc = Integer.parseInt(phone.getNetworkOperator().substring(0,3));
+ int mnc = Integer.parseInt(phone.getNetworkOperator().substring(3));
+ if (phone.getNetworkType() == TelephonyManager.NETWORK_TYPE_UMTS)
+ type = AGPS_REF_LOCATION_TYPE_UMTS_CELLID;
+ else
+ type = AGPS_REF_LOCATION_TYPE_GSM_CELLID;
+ native_agps_set_ref_location_cellid(type, mcc, mnc,
+ gsm_cell.getLac(), gsm_cell.getCid());
+ }
+ else
+ Log.e(TAG,"Error getting cell location info.");
+ }
+ else
+ Log.e(TAG,"CDMA not supported.");
+ }
private void sendMessage(int message, int arg, Object obj) {
// hold a wake lock while messages are pending
@@ -1472,8 +1589,14 @@ public class GpsLocationProvider implements LocationProviderInterface {
private native void native_agps_data_conn_open(String apn);
private native void native_agps_data_conn_closed();
private native void native_agps_data_conn_failed();
+ private native void native_agps_ni_message(byte [] msg, int length);
private native void native_set_agps_server(int type, String hostname, int port);
// Network-initiated (NI) Support
private native void native_send_ni_response(int notificationId, int userResponse);
+
+ // AGPS ril suport
+ private native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc,
+ int lac, int cid);
+ private native void native_agps_set_id(int type, String setid);
}
diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
index 93068e6..71c7aba 100755
--- a/services/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -40,12 +40,15 @@ static jmethodID method_reportNmea;
static jmethodID method_setEngineCapabilities;
static jmethodID method_xtraDownloadRequest;
static jmethodID method_reportNiNotification;
+static jmethodID method_requestRefLocation;
+static jmethodID method_requestSetID;
static const GpsInterface* sGpsInterface = NULL;
static const GpsXtraInterface* sGpsXtraInterface = NULL;
static const AGpsInterface* sAGpsInterface = NULL;
static const GpsNiInterface* sGpsNiInterface = NULL;
static const GpsDebugInterface* sGpsDebugInterface = NULL;
+static const AGpsRilInterface* sAGpsRilInterface = NULL;
// temporary storage for GPS callbacks
static GpsSvStatus sGpsSvStatus;
@@ -193,17 +196,30 @@ GpsNiCallbacks sGpsNiCallbacks = {
create_thread_callback,
};
-static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
- method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V");
- method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
- method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V");
- method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II)V");
- method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
- method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V");
- method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
- method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");
+static void agps_request_set_id(uint32_t flags)
+{
+ LOGD("agps_request_set_id: flags (%d)", flags);
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mCallbacksObj, method_requestSetID, flags);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+static void agps_request_ref_location(uint32_t flags)
+{
+ LOGD("agps_ref_location: flags (%d)", flags);
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mCallbacksObj, method_requestRefLocation, flags);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
+AGpsRilCallbacks sAGpsRilCallbacks = {
+ agps_request_set_id,
+ agps_request_ref_location,
+ create_thread_callback,
+};
+
static const GpsInterface* get_gps_interface() {
int err;
hw_module_t* module;
@@ -222,6 +238,64 @@ static const GpsInterface* get_gps_interface() {
return interface;
}
+static const AGpsInterface* GetAGpsInterface()
+{
+ if (!sGpsInterface)
+ sGpsInterface = get_gps_interface();
+ if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+ return NULL;
+
+ if (!sAGpsInterface) {
+ sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
+ if (sAGpsInterface)
+ sAGpsInterface->init(&sAGpsCallbacks);
+ }
+ return sAGpsInterface;
+}
+
+static const GpsNiInterface* GetNiInterface()
+{
+ if (!sGpsInterface)
+ sGpsInterface = get_gps_interface();
+ if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+ return NULL;
+
+ if (!sGpsNiInterface) {
+ sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+ if (sGpsNiInterface)
+ sGpsNiInterface->init(&sGpsNiCallbacks);
+ }
+ return sGpsNiInterface;
+}
+
+static const AGpsRilInterface* GetAGpsRilInterface()
+{
+ if (!sGpsInterface)
+ sGpsInterface = get_gps_interface();
+ if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+ return NULL;
+
+ if (!sAGpsRilInterface) {
+ sAGpsRilInterface = (const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE);
+ if (sAGpsRilInterface)
+ sAGpsRilInterface->init(&sAGpsRilCallbacks);
+ }
+ return sAGpsRilInterface;
+}
+
+static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
+ method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V");
+ method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
+ method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V");
+ method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II)V");
+ method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
+ method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V");
+ method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
+ method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");
+ method_requestRefLocation = env->GetMethodID(clazz,"requestRefLocation","(I)V");
+ method_requestSetID = env->GetMethodID(clazz,"requestSetID","(I)V");
+}
+
static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass clazz) {
if (!sGpsInterface)
sGpsInterface = get_gps_interface();
@@ -239,16 +313,6 @@ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject o
if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
return false;
- if (!sAGpsInterface)
- sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
- if (sAGpsInterface)
- sAGpsInterface->init(&sAGpsCallbacks);
-
- if (!sGpsNiInterface)
- sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
- if (sGpsNiInterface)
- sGpsNiInterface->init(&sGpsNiCallbacks);
-
if (!sGpsDebugInterface)
sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
@@ -313,6 +377,64 @@ static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, job
return num_svs;
}
+static void android_location_GpsLocationProvider_agps_set_reference_location_cellid(JNIEnv* env,
+ jobject obj, jint type, jint mcc, jint mnc, jint lac, jint cid)
+{
+ AGpsRefLocation location;
+ const AGpsRilInterface* interface = GetAGpsRilInterface();
+ if (!interface) {
+ LOGE("no AGPS RIL interface in agps_set_reference_location_cellid");
+ return;
+ }
+
+ switch(type) {
+ case AGPS_REF_LOCATION_TYPE_GSM_CELLID:
+ case AGPS_REF_LOCATION_TYPE_UMTS_CELLID:
+ location.type = type;
+ location.u.cellID.mcc = mcc;
+ location.u.cellID.mnc = mnc;
+ location.u.cellID.lac = lac;
+ location.u.cellID.cid = cid;
+ break;
+ default:
+ LOGE("Neither a GSM nor a UMTS cellid (%s:%d).",__FUNCTION__,__LINE__);
+ return;
+ break;
+ }
+ interface->set_ref_location(&location, sizeof(location));
+}
+
+static void android_location_GpsLocationProvider_agps_send_ni_message(JNIEnv* env,
+ jobject obj, jbyteArray ni_msg, jint size)
+{
+ size_t sz;
+ const AGpsRilInterface* interface = GetAGpsRilInterface();
+ if (!interface) {
+ LOGE("no AGPS RIL interface in send_ni_message");
+ return;
+ }
+ if (size < 0)
+ return;
+ sz = (size_t)size;
+ jbyte* b = env->GetByteArrayElements(ni_msg, 0);
+ interface->ni_message((uint8_t *)b,sz);
+ env->ReleaseByteArrayElements(ni_msg,b,0);
+}
+
+static void android_location_GpsLocationProvider_agps_set_id(JNIEnv *env,
+ jobject obj, jint type, jstring setid_string)
+{
+ const AGpsRilInterface* interface = GetAGpsRilInterface();
+ if (!interface) {
+ LOGE("no AGPS RIL interface in agps_set_id");
+ return;
+ }
+
+ const char *setid = env->GetStringUTFChars(setid_string, NULL);
+ interface->set_set_id(type, setid);
+ env->ReleaseStringUTFChars(setid_string, setid);
+}
+
static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj,
jbyteArray nmeaArray, jint buffer_size)
{
@@ -363,60 +485,63 @@ static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, j
static void android_location_GpsLocationProvider_agps_data_conn_open(JNIEnv* env, jobject obj, jstring apn)
{
- if (!sAGpsInterface) {
- sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
+ const AGpsInterface* interface = GetAGpsInterface();
+ if (!interface) {
+ LOGE("no AGPS interface in agps_data_conn_open");
+ return;
}
- if (sAGpsInterface) {
- if (apn == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
- return;
- }
- const char *apnStr = env->GetStringUTFChars(apn, NULL);
- sAGpsInterface->data_conn_open(apnStr);
- env->ReleaseStringUTFChars(apn, apnStr);
+ if (apn == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
}
+ const char *apnStr = env->GetStringUTFChars(apn, NULL);
+ interface->data_conn_open(apnStr);
+ env->ReleaseStringUTFChars(apn, apnStr);
}
static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* env, jobject obj)
{
- if (!sAGpsInterface) {
- sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
- }
- if (sAGpsInterface) {
- sAGpsInterface->data_conn_closed();
+ const AGpsInterface* interface = GetAGpsInterface();
+ if (!interface) {
+ LOGE("no AGPS interface in agps_data_conn_open");
+ return;
}
+ interface->data_conn_closed();
}
static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* env, jobject obj)
{
- if (!sAGpsInterface) {
- sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
- }
- if (sAGpsInterface) {
- sAGpsInterface->data_conn_failed();
+ const AGpsInterface* interface = GetAGpsInterface();
+ if (!interface) {
+ LOGE("no AGPS interface in agps_data_conn_open");
+ return;
}
+ interface->data_conn_failed();
}
static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jobject obj,
jint type, jstring hostname, jint port)
{
- if (!sAGpsInterface) {
- sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
- }
- if (sAGpsInterface) {
- const char *c_hostname = env->GetStringUTFChars(hostname, NULL);
- sAGpsInterface->set_server(type, c_hostname, port);
- env->ReleaseStringUTFChars(hostname, c_hostname);
+ const AGpsInterface* interface = GetAGpsInterface();
+ if (!interface) {
+ LOGE("no AGPS interface in agps_data_conn_open");
+ return;
}
+ const char *c_hostname = env->GetStringUTFChars(hostname, NULL);
+ interface->set_server(type, c_hostname, port);
+ env->ReleaseStringUTFChars(hostname, c_hostname);
}
static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
jint notifId, jint response)
{
- if (!sGpsNiInterface)
- sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
- if (sGpsNiInterface)
- sGpsNiInterface->respond(notifId, response);
+ const GpsNiInterface* interface = GetNiInterface();
+ if (!interface) {
+ LOGE("no NI interface in send_ni_response");
+ return;
+ }
+
+ interface->respond(notifId, response);
}
static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj)
@@ -452,8 +577,11 @@ static JNINativeMethod sMethods[] = {
{"native_agps_data_conn_open", "(Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open},
{"native_agps_data_conn_closed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed},
{"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed},
+ {"native_agps_set_id","(ILjava/lang/String;)V",(void*)android_location_GpsLocationProvider_agps_set_id},
+ {"native_agps_set_ref_location_cellid","(IIIII)V",(void*)android_location_GpsLocationProvider_agps_set_reference_location_cellid},
{"native_set_agps_server", "(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server},
{"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response},
+ {"native_agps_ni_message", "([BI)V", (void *)android_location_GpsLocationProvider_agps_send_ni_message},
{"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state},
};
diff --git a/telephony/java/com/android/internal/telephony/Connection.java b/telephony/java/com/android/internal/telephony/Connection.java
index 0d983b5..07f90cd 100644
--- a/telephony/java/com/android/internal/telephony/Connection.java
+++ b/telephony/java/com/android/internal/telephony/Connection.java
@@ -40,6 +40,7 @@ public abstract class Connection {
MMI, /* not presently used; dial() returns null */
INVALID_NUMBER, /* invalid dial string */
NUMBER_UNREACHABLE, /* cannot reach the peer */
+ SERVER_UNREACHABLE, /* cannot reach the server */
INVALID_CREDENTIALS, /* invalid credentials */
OUT_OF_NETWORK, /* calling from out of network is not allowed */
SERVER_ERROR, /* server error */
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 3030481..185d413 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -21,6 +21,7 @@ import com.android.internal.telephony.gsm.ApnSetting;
import com.android.internal.util.HierarchicalState;
import com.android.internal.util.HierarchicalStateMachine;
+import android.net.LinkAddress;
import android.net.LinkCapabilities;
import android.net.LinkProperties;
import android.os.AsyncResult;
@@ -29,10 +30,10 @@ import android.os.SystemProperties;
import android.util.EventLog;
import java.net.InetAddress;
+import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
-import java.util.HashMap;
/**
* {@hide}
@@ -68,7 +69,7 @@ import java.util.HashMap;
* EVENT_GET_LAST_FAIL_DONE,
* EVENT_DEACTIVATE_DONE.
* }
- * ++ # mInactiveState
+ * ++ # mInactiveState
* e(doNotifications)
* x(clearNotifications) {
* EVENT_RESET { notifiyDisconnectCompleted }.
@@ -428,26 +429,25 @@ public abstract class DataConnection extends HierarchicalStateMachine {
try {
String prefix = "net." + interfaceName + ".";
- linkProperties.setInterface(NetworkInterface.getByName(interfaceName));
+ NetworkInterface networkInterface = NetworkInterface.getByName(interfaceName);
+ linkProperties.setInterfaceName(interfaceName);
// TODO: Get gateway and dns via RIL interface not property?
String gatewayAddress = SystemProperties.get(prefix + "gw");
linkProperties.setGateway(InetAddress.getByName(gatewayAddress));
- if (response.length > 2) {
- String ipAddress = response[2];
- linkProperties.addAddress(InetAddress.getByName(ipAddress));
-
- // TODO: Get gateway and dns via RIL interface not property?
- String dnsServers[] = new String[2];
- dnsServers[0] = SystemProperties.get(prefix + "dns1");
- dnsServers[1] = SystemProperties.get(prefix + "dns2");
- if (isDnsOk(dnsServers)) {
- linkProperties.addDns(InetAddress.getByName(dnsServers[0]));
- linkProperties.addDns(InetAddress.getByName(dnsServers[1]));
- } else {
- result = SetupResult.ERR_BadDns;
- }
+ for (InterfaceAddress addr : networkInterface.getInterfaceAddresses()) {
+ linkProperties.addLinkAddress(new LinkAddress(addr));
+ }
+ // TODO: Get gateway and dns via RIL interface not property?
+ String dnsServers[] = new String[2];
+ dnsServers[0] = SystemProperties.get(prefix + "dns1");
+ dnsServers[1] = SystemProperties.get(prefix + "dns2");
+ if (isDnsOk(dnsServers)) {
+ linkProperties.addDns(InetAddress.getByName(dnsServers[0]));
+ linkProperties.addDns(InetAddress.getByName(dnsServers[1]));
+ } else {
+ result = SetupResult.ERR_BadDns;
}
} catch (UnknownHostException e1) {
log("onSetupCompleted: UnknowHostException " + e1);
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index 55e3002..6ed9295 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -874,6 +874,9 @@ public class SipPhone extends SipPhoneBase {
public void onError(SipAudioCall call, int errorCode,
String errorMessage) {
switch (errorCode) {
+ case SipErrorCode.SERVER_UNREACHABLE:
+ onError(Connection.DisconnectCause.SERVER_UNREACHABLE);
+ break;
case SipErrorCode.PEER_NOT_REACHABLE:
onError(Connection.DisconnectCause.NUMBER_UNREACHABLE);
break;
diff --git a/tests/CoreTests/android/core/MiscRegressionTest.java b/tests/CoreTests/android/core/MiscRegressionTest.java
index 8281db0..7734397 100644
--- a/tests/CoreTests/android/core/MiscRegressionTest.java
+++ b/tests/CoreTests/android/core/MiscRegressionTest.java
@@ -66,37 +66,6 @@ public class MiscRegressionTest extends TestCase {
}
}
- // Regression test for #1061945: negative Shorts do not
- // serialize/deserialize correctly
- @SmallTest
- public void testShortSerialization() throws Exception {
- // create an instance of ObjectInputStream
- String x = new String("serialize_foobar");
- java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
- (new java.io.ObjectOutputStream(baos)).writeObject(x);
- ObjectInputStream ois = new java.io.ObjectInputStream(
- new java.io.ByteArrayInputStream(baos.toByteArray()));
-
- // get the setField(...,, short val) method in question
- Class<ObjectInputStream> oClass = ObjectInputStream.class;
- Method m = oClass.getDeclaredMethod("setField", new Class[] { Object.class, Class.class, String.class, short.class});
- // compose args
- short start = 123;
- short origval = -1; // 0xffff
- Short obj = new Short(start);
- Class<Short> declaringClass = Short.class;
- String fieldDescName = "value";
-
- // test the initial value
- assertEquals(obj.shortValue(), start);
- // invoke native method to set the field "value" of type short to the newval
- m.setAccessible(true); // since the method is private
- m.invoke(ois, new Object[]{ obj, declaringClass, fieldDescName, new Short(origval)} );
- // test the set value
- short res = obj.shortValue();
- assertEquals("Read and written values must be equal", origval, res);
- }
-
// Regression test for #951285: Suitable LogHandler should be chosen
// depending on the environment.
@MediumTest
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
index b70f3a9..769bfdd 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
@@ -24,7 +24,10 @@ import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.os.Bundle;
+import android.view.Gravity;
import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.SeekBar;
@SuppressWarnings({"UnusedDeclaration"})
public class GradientsActivity extends Activity {
@@ -32,7 +35,59 @@ public class GradientsActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(new ShadersView(this));
+ final FrameLayout layout = new FrameLayout(this);
+ final ShadersView shadersView = new ShadersView(this);
+ final GradientView gradientView = new GradientView(this);
+ final SeekBar rotateView = new SeekBar(this);
+ rotateView.setMax(360);
+ rotateView.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ gradientView.setRotationY((float)progress);
+ }
+ });
+
+ layout.addView(shadersView);
+ layout.addView(gradientView, new FrameLayout.LayoutParams(
+ 200, 200, Gravity.CENTER));
+ layout.addView(rotateView, new FrameLayout.LayoutParams(
+ 300, FrameLayout.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM));
+
+ setContentView(layout);
+ }
+
+ static class GradientView extends View {
+ private final Paint mPaint;
+
+ GradientView(Context c) {
+ super(c);
+
+ LinearGradient gradient = new LinearGradient(0, 0, 200, 0, 0xFF000000, 0,
+ Shader.TileMode.CLAMP);
+ mPaint = new Paint();
+ mPaint.setShader(gradient);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(200, 200);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mPaint);
+ }
}
static class ShadersView extends View {
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
index 4430533..0dc836d 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
@@ -101,6 +101,8 @@ public class LinesActivity extends Activity {
mSmallPaint.setAntiAlias(false);
canvas.drawLine(0.0f, 0.0f, 400.0f, 0.0f, mSmallPaint);
mSmallPaint.setAntiAlias(true);
+ canvas.drawLine(0.0f, 0.0f, 0.0f, 400.0f, mSmallPaint);
+ canvas.drawLine(0.0f, 400.0f, 400.0f, 400.0f, mSmallPaint);
canvas.translate(120.0f, 0.0f);
mAlphaPaint.setShader(mShader);
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 2d7ee13..e30cf4a 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -727,7 +727,7 @@ public class NotificationTestList extends TestActivity
}
},
- new Test("Persistent with numbers 222") {
+ new Test("Persistent with numbers 22") {
public void run() {
mNM.notify(1, notificationWithNumbers(22));
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 90a6256..1d6b18d 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -782,7 +782,6 @@ status_t compileResourceFile(Bundle* bundle,
const String16 translatable16("translatable");
const String16 formatted16("formatted");
const String16 false16("false");
- const String16 product16("product");
const String16 myPackage(assets->getPackage());
@@ -830,7 +829,6 @@ status_t compileResourceFile(Bundle* bundle,
bool curIsStyled = false;
bool curIsPseudolocalizable = false;
bool curIsFormatted = fileIsTranslatable;
- String16 curProduct;
bool localHasErrors = false;
if (strcmp16(block.getElementName(&len), skip16.string()) == 0) {
@@ -1228,8 +1226,6 @@ status_t compileResourceFile(Bundle* bundle,
translatable.setTo(block.getAttributeStringValue(i, &length));
} else if (strcmp16(attr, formatted16.string()) == 0) {
formatted.setTo(block.getAttributeStringValue(i, &length));
- } else if (strcmp16(attr, product16.string()) == 0) {
- curProduct.setTo(block.getAttributeStringValue(i, &length));
}
}
@@ -1356,6 +1352,12 @@ status_t compileResourceFile(Bundle* bundle,
hasErrors = localHasErrors = true;
}
+ String16 product;
+ identIdx = block.indexOfAttribute(NULL, "product");
+ if (identIdx >= 0) {
+ product = String16(block.getAttributeStringValue(identIdx, &len));
+ }
+
String16 comment(block.getComment(&len) ? block.getComment(&len) : nulStr);
if (curIsBag) {
@@ -1447,7 +1449,7 @@ status_t compileResourceFile(Bundle* bundle,
err = parseAndAddBag(bundle, in, &block, curParams, myPackage, curType,
ident, parentIdent, itemIdent, curFormat, curIsFormatted,
- curProduct, false, overwrite, outTable);
+ product, false, overwrite, outTable);
if (err == NO_ERROR) {
if (curIsPseudolocalizable && localeIsDefined(curParams)
&& bundle->getPseudolocalize()) {
@@ -1456,7 +1458,7 @@ status_t compileResourceFile(Bundle* bundle,
block.setPosition(parserPosition);
err = parseAndAddBag(bundle, in, &block, pseudoParams, myPackage,
curType, ident, parentIdent, itemIdent, curFormat,
- curIsFormatted, curProduct, true, overwrite, outTable);
+ curIsFormatted, product, true, overwrite, outTable);
#endif
}
}
@@ -1480,7 +1482,7 @@ status_t compileResourceFile(Bundle* bundle,
err = parseAndAddEntry(bundle, in, &block, curParams, myPackage, curType, ident,
*curTag, curIsStyled, curFormat, curIsFormatted,
- curProduct, false, overwrite, outTable);
+ product, false, overwrite, outTable);
if (err < NO_ERROR) { // Why err < NO_ERROR instead of err != NO_ERROR?
hasErrors = localHasErrors = true;
@@ -1492,7 +1494,7 @@ status_t compileResourceFile(Bundle* bundle,
block.setPosition(parserPosition);
err = parseAndAddEntry(bundle, in, &block, pseudoParams, myPackage, curType,
ident, *curTag, curIsStyled, curFormat,
- curIsFormatted, curProduct,
+ curIsFormatted, product,
true, overwrite, outTable);
if (err != NO_ERROR) {
hasErrors = localHasErrors = true;
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index c59e20d..65a64cd 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -195,5 +195,22 @@ example, the inner class Paint$Style in the Paint class should be discarded and
bridge will provide its own implementation.
+- References -
+--------------
+
+
+The JVM Specification 2nd edition:
+ http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
+
+Understanding bytecode:
+ http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
+
+Bytecode opcode list:
+ http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
+
+ASM user guide:
+ http://download.forge.objectweb.org/asm/asm-guide.pdf
+
+
--
end
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
new file mode 100644
index 0000000..9a48ea6
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a method that has been converted to a delegate by layoutlib_create.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LayoutlibDelegate {
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 7b55ed3e..590923f 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -28,9 +28,9 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
-import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
@@ -60,38 +60,55 @@ public class AsmGenerator {
* old-FQCN to rename and they get erased as they get renamed. At the end, classes still
* left here are not in the code base anymore and thus were not renamed. */
private HashSet<String> mClassesNotRenamed;
- /** A map { FQCN => map { list of return types to delete from the FQCN } }. */
+ /** A map { FQCN => set { list of return types to delete from the FQCN } }. */
private HashMap<String, Set<String>> mDeleteReturns;
+ /** A map { FQCN => set { method names } } of methods to rewrite as delegates.
+ * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
+ private final HashMap<String, Set<String>> mDelegateMethods;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
- *
+ *
* @param log Output logger.
* @param osDestJar The path of the destination JAR to create.
- * @param injectClasses The list of class from layoutlib_create to inject in layoutlib.
- * @param stubMethods The list of methods to stub out. Each entry must be in the form
- * "package.package.OuterClass$InnerClass#MethodName".
- * @param renameClasses The list of classes to rename, must be an even list: the binary FQCN
- * of class to replace followed by the new FQCN.
- * @param deleteReturns List of classes for which the methods returning them should be deleted.
- * The array contains a list of null terminated section starting with the name of the class
- * to rename in which the methods are deleted, followed by a list of return types identifying
- * the methods to delete.
+ * @param createInfo Creation parameters. Must not be null.
*/
- public AsmGenerator(Log log, String osDestJar,
- Class<?>[] injectClasses,
- String[] stubMethods,
- String[] renameClasses, String[] deleteReturns) {
+ public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
mLog = log;
mOsDestJar = osDestJar;
- mInjectClasses = injectClasses != null ? injectClasses : new Class<?>[0];
- mStubMethods = stubMethods != null ? new HashSet<String>(Arrays.asList(stubMethods)) :
- new HashSet<String>();
+ mInjectClasses = createInfo.getInjectedClasses();
+ mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
+
+ // Create the map/set of methods to change to delegates
+ mDelegateMethods = new HashMap<String, Set<String>>();
+ for (String signature : createInfo.getDelegateMethods()) {
+ int pos = signature.indexOf('#');
+ if (pos <= 0 || pos >= signature.length() - 1) {
+ continue;
+ }
+ String className = binaryToInternalClassName(signature.substring(0, pos));
+ String methodName = signature.substring(pos + 1);
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(methodName);
+ }
+ for (String className : createInfo.getDelegateClassNatives()) {
+ Set<String> methods = mDelegateMethods.get(className);
+ if (methods == null) {
+ methods = new HashSet<String>();
+ mDelegateMethods.put(className, methods);
+ }
+ methods.add(DelegateClassAdapter.ALL_NATIVES);
+ }
// Create the map of classes to rename.
mRenameClasses = new HashMap<String, String>();
mClassesNotRenamed = new HashSet<String>();
- int n = renameClasses == null ? 0 : renameClasses.length;
+ String[] renameClasses = createInfo.getRenamedClasses();
+ int n = renameClasses.length;
for (int i = 0; i < n; i += 2) {
assert i + 1 < n;
// The ASM class names uses "/" separators, whereas regular FQCN use "."
@@ -100,38 +117,37 @@ public class AsmGenerator {
mRenameClasses.put(oldFqcn, newFqcn);
mClassesNotRenamed.add(oldFqcn);
}
-
+
// create the map of renamed class -> return type of method to delete.
mDeleteReturns = new HashMap<String, Set<String>>();
- if (deleteReturns != null) {
- Set<String> returnTypes = null;
- String renamedClass = null;
- for (String className : deleteReturns) {
- // if we reach the end of a section, add it to the main map
- if (className == null) {
- if (returnTypes != null) {
- mDeleteReturns.put(renamedClass, returnTypes);
- }
-
- renamedClass = null;
- continue;
- }
-
- // if the renamed class is null, this is the beginning of a section
- if (renamedClass == null) {
- renamedClass = binaryToInternalClassName(className);
- continue;
- }
-
- // just a standard return type, we add it to the list.
- if (returnTypes == null) {
- returnTypes = new HashSet<String>();
+ String[] deleteReturns = createInfo.getDeleteReturns();
+ Set<String> returnTypes = null;
+ String renamedClass = null;
+ for (String className : deleteReturns) {
+ // if we reach the end of a section, add it to the main map
+ if (className == null) {
+ if (returnTypes != null) {
+ mDeleteReturns.put(renamedClass, returnTypes);
}
- returnTypes.add(binaryToInternalClassName(className));
+
+ renamedClass = null;
+ continue;
}
+
+ // if the renamed class is null, this is the beginning of a section
+ if (renamedClass == null) {
+ renamedClass = binaryToInternalClassName(className);
+ continue;
+ }
+
+ // just a standard return type, we add it to the list.
+ if (returnTypes == null) {
+ returnTypes = new HashSet<String>();
+ }
+ returnTypes.add(binaryToInternalClassName(className));
}
}
-
+
/**
* Returns the list of classes that have not been renamed yet.
* <p/>
@@ -163,12 +179,12 @@ public class AsmGenerator {
public void setDeps(Map<String, ClassReader> deps) {
mDeps = deps;
}
-
+
/** Gets the map of classes to output as-is, except if they have native methods */
public Map<String, ClassReader> getKeep() {
return mKeep;
}
-
+
/** Gets the map of dependencies that must be completely stubbed */
public Map<String, ClassReader> getDeps() {
return mDeps;
@@ -177,7 +193,7 @@ public class AsmGenerator {
/** Generates the final JAR */
public void generate() throws FileNotFoundException, IOException {
TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
-
+
for (Class<?> clazz : mInjectClasses) {
String name = classToEntryPath(clazz);
InputStream is = ClassLoader.getSystemResourceAsStream(name);
@@ -186,7 +202,7 @@ public class AsmGenerator {
name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
-
+
for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
ClassReader cr = entry.getValue();
byte[] b = transform(cr, true /* stubNativesOnly */);
@@ -211,8 +227,8 @@ public class AsmGenerator {
/**
* Writes the JAR file.
- *
- * @param outStream The file output stream were to write the JAR.
+ *
+ * @param outStream The file output stream were to write the JAR.
* @param all The map of all classes to output.
* @throws IOException if an I/O error has occurred
*/
@@ -236,7 +252,7 @@ public class AsmGenerator {
String classNameToEntryPath(String className) {
return className.replaceAll("\\.", "/").concat(".class");
}
-
+
/**
* Utility method to get the JAR entry path from a Class name.
* e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
@@ -248,30 +264,32 @@ public class AsmGenerator {
name = "$" + clazz.getSimpleName() + name;
clazz = parent;
}
- return classNameToEntryPath(clazz.getCanonicalName() + name);
+ return classNameToEntryPath(clazz.getCanonicalName() + name);
}
/**
* Transforms a class.
* <p/>
* There are 3 kind of transformations:
- *
+ *
* 1- For "mock" dependencies classes, we want to remove all code from methods and replace
* by a stub. Native methods must be implemented with this stub too. Abstract methods are
* left intact. Modified classes must be overridable (non-private, non-final).
* Native methods must be made non-final, non-private.
- *
+ *
* 2- For "keep" classes, we want to rewrite all native methods as indicated above.
* If a class has native methods, it must also be made non-private, non-final.
- *
+ *
* Note that unfortunately static methods cannot be changed to non-static (since static and
* non-static are invoked differently.)
*/
byte[] transform(ClassReader cr, boolean stubNativesOnly) {
boolean hasNativeMethods = hasNativeMethods(cr);
+
+ // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
String className = cr.getClassName();
-
+
String newName = transformName(className);
// transformName returns its input argument if there's no need to rename the class
if (newName != className) {
@@ -288,13 +306,24 @@ public class AsmGenerator {
// Rewrite the new class from scratch, without reusing the constant pool from the
// original class reader.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-
+
ClassVisitor rv = cw;
if (newName != className) {
rv = new RenameClassAdapter(cw, className, newName);
}
-
- TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
+
+ Set<String> delegateMethods = mDelegateMethods.get(className);
+ if (delegateMethods != null && !delegateMethods.isEmpty()) {
+ // If delegateMethods only contains one entry ALL_NATIVES and the class is
+ // known to have no native methods, just skip this step.
+ if (hasNativeMethods ||
+ !(delegateMethods.size() == 1 &&
+ delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
+ rv = new DelegateClassAdapter(mLog, rv, className, delegateMethods);
+ }
+ }
+
+ TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
mDeleteReturns.get(className),
newName, rv,
stubNativesOnly, stubNativesOnly || hasNativeMethods);
@@ -323,7 +352,7 @@ public class AsmGenerator {
return newName + className.substring(pos);
}
}
-
+
return className;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 2ed8641..92892784 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -16,11 +16,70 @@
package com.android.tools.layoutlib.create;
-public class CreateInfo {
+/**
+ * Describes the work to be done by {@link AsmGenerator}.
+ */
+public final class CreateInfo implements ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ public Class<?>[] getInjectedClasses() {
+ return INJECTED_CLASSES;
+ }
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDelegateMethods() {
+ return DELEGATE_METHODS;
+ }
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDelegateClassNatives() {
+ return DELEGATE_CLASS_NATIVES;
+ }
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ */
+ public String[] getOverriddenMethods() {
+ return OVERRIDDEN_METHODS;
+ }
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ public String[] getRenamedClasses() {
+ return RENAMED_CLASSES;
+ }
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ public String[] getDeleteReturns() {
+ return DELETE_RETURNS;
+ }
+
+ //-----
+
/**
* The list of class from layoutlib_create to inject in layoutlib.
*/
- public final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
+ private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
OverrideMethod.class,
MethodListener.class,
MethodAdapter.class,
@@ -28,19 +87,37 @@ public class CreateInfo {
};
/**
+ * The list of methods to rewrite as delegates.
+ */
+ private final static String[] DELEGATE_METHODS = new String[] {
+ // TODO: comment out once DelegateClass is working
+ // "android.view.View#isInEditMode",
+ // "android.content.res.Resources$Theme#obtainStyledAttributes",
+ };
+
+ /**
+ * The list of classes on which to delegate all native methods.
+ */
+ private final static String[] DELEGATE_CLASS_NATIVES = new String[] {
+ // TODO: comment out once DelegateClass is working
+ // "android.graphics.Paint"
+ };
+
+ /**
* The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
*/
- public final static String[] OVERRIDDEN_METHODS = new String[] {
- "android.view.View#isInEditMode",
- "android.content.res.Resources$Theme#obtainStyledAttributes",
- };
+ private final static String[] OVERRIDDEN_METHODS = new String[] {
+ // TODO: remove once DelegateClass is working
+ "android.view.View#isInEditMode",
+ "android.content.res.Resources$Theme#obtainStyledAttributes",
+ };
/**
* The list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
*/
- public final static String[] RENAMED_CLASSES =
+ private final static String[] RENAMED_CLASSES =
new String[] {
"android.graphics.Bitmap", "android.graphics._Original_Bitmap",
"android.graphics.BitmapFactory", "android.graphics._Original_BitmapFactory",
@@ -69,7 +146,7 @@ public class CreateInfo {
* to rename in which the methods are deleted, followed by a list of return types identifying
* the methods to delete.
*/
- public final static String[] REMOVED_METHODS =
+ private final static String[] DELETE_RETURNS =
new String[] {
"android.graphics.Paint", // class to delete methods from
"android.graphics.Paint$Align", // list of type identifying methods to delete
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
new file mode 100644
index 0000000..9cba8a0
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Set;
+
+/**
+ * A {@link DelegateClassAdapter} can transform some methods from a class into
+ * delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ */
+public class DelegateClassAdapter extends ClassAdapter {
+
+ public final static String ALL_NATIVES = "<<all_natives>>";
+
+ private final String mClassName;
+ private final Set<String> mDelegateMethods;
+ private final Log mLog;
+
+ /**
+ * Creates a new {@link DelegateClassAdapter} that can transform some methods
+ * from a class into delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ *
+ * @param log The logger object. Must not be null.
+ * @param cv the class visitor to which this adapter must delegate calls.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param delegateMethods The set of method names to modify and/or the
+ * special constant {@link #ALL_NATIVES} to convert all native methods.
+ */
+ public DelegateClassAdapter(Log log,
+ ClassVisitor cv,
+ String className,
+ Set<String> delegateMethods) {
+ super(cv);
+ mLog = log;
+ mClassName = className;
+ mDelegateMethods = delegateMethods;
+ }
+
+ //----------------------------------
+ // Methods from the ClassAdapter
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+
+ boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
+ mDelegateMethods.contains(name);
+
+ if (useDelegate) {
+ // remove native
+ access = access & ~Opcodes.ACC_NATIVE;
+ }
+
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ if (useDelegate) {
+ DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName,
+ name, desc, isStatic);
+ if (isNative) {
+ // A native has no code to visit, so we need to generate it directly.
+ a.generateCode();
+ } else {
+ return a;
+ }
+ }
+ return mw;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
new file mode 100644
index 0000000..21d6682
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -0,0 +1,319 @@
+/*
+ * 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.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * This method adapter rewrites a method by discarding the original code and generating
+ * a call to a delegate. Original annotations are passed along unchanged.
+ * <p/>
+ * Calls are delegated to a class named <code>&lt;className&gt;_Delegate</code> with
+ * static methods matching the methods to be overridden here. The methods have the
+ * same return type. The argument type list is the same except the "this" reference is
+ * passed first for non-static methods.
+ * <p/>
+ * A new annotation is added.
+ * <p/>
+ * Note that native methods have, by definition, no code so there's nothing a visitor
+ * can visit. That means the caller must call {@link #generateCode()} directly for
+ * a native and use the visitor pattern for non-natives.
+ * <p/>
+ * Instances of this class are not re-usable. You need a new instance for each method.
+ */
+class DelegateMethodAdapter implements MethodVisitor {
+
+ /**
+ * Suffix added to delegate classes.
+ */
+ public static final String DELEGATE_SUFFIX = "_Delegate";
+
+ private static String CONSTRUCTOR = "<init>";
+ private static String CLASS_INIT = "<clinit>";
+
+ /** The parent method writer */
+ private MethodVisitor mParentVisitor;
+ /** Flag to output the first line number. */
+ private boolean mOutputFirstLineNumber = true;
+ /** The original method descriptor (return type + argument types.) */
+ private String mDesc;
+ /** True if the original method is static. */
+ private final boolean mIsStatic;
+ /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
+ private final String mClassName;
+ /** The method name. */
+ private final String mMethodName;
+ /** Logger object. */
+ private final Log mLog;
+ /** True if {@link #visitCode()} has been invoked. */
+ private boolean mVisitCodeCalled;
+
+ /**
+ * Creates a new {@link DelegateMethodAdapter} that will transform this method
+ * into a delegate call.
+ * <p/>
+ * See {@link DelegateMethodAdapter} for more details.
+ *
+ * @param log The logger object. Must not be null.
+ * @param mv the method visitor to which this adapter must delegate calls.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param methodName The simple name of the method.
+ * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
+ * {@link Type#getArgumentTypes(String)})
+ * @param isStatic True if the method is declared static.
+ */
+ public DelegateMethodAdapter(Log log,
+ MethodVisitor mv,
+ String className,
+ String methodName,
+ String desc,
+ boolean isStatic) {
+ mLog = log;
+ mParentVisitor = mv;
+ mClassName = className;
+ mMethodName = methodName;
+ mDesc = desc;
+ mIsStatic = isStatic;
+
+ if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
+ // We're going to simplify by not supporting constructors.
+ // The only trick with a constructor is to find the proper super constructor
+ // and call it (and deciding if we should mirror the original method call to
+ // a custom constructor or call a default one.)
+ throw new UnsupportedOperationException(
+ String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
+ className, methodName, desc));
+ }
+ }
+
+ /**
+ * Generates the new code for the method.
+ * <p/>
+ * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
+ * (since they have no code to visit).
+ * <p/>
+ * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
+ * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
+ * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
+ * this method will be invoked from {@link MethodVisitor#visitEnd()}.
+ */
+ public void generateCode() {
+ /*
+ * The goal is to generate a call to a static delegate method.
+ * If this method is not-static, the first parameter will be this.
+ * All the parameters must be passed and then the eventual return type returned.
+ *
+ * Example, let's say we have a method such as
+ * public void method_1(int a, Object b, ArrayList<String> c) { ... }
+ *
+ * We'll want to create a body that calls a delegate method like this:
+ * TheClass_Delegate.method_1(this, a, b, c);
+ *
+ * The generated class name is the current class name with "_Delegate" appended to it.
+ * One thing to realize is that we don't care about generics -- since generic types
+ * are erased at runtime, they have no influence on the method being called.
+ */
+
+ // Add our annotation
+ AnnotationVisitor aw = mParentVisitor.visitAnnotation(
+ Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
+ true); // visible at runtime
+ aw.visitEnd();
+
+ if (!mVisitCodeCalled) {
+ // If this is a direct call to generateCode() as done by DelegateClassAdapter
+ // for natives, visitCode() hasn't been called yet.
+ mParentVisitor.visitCode();
+ mVisitCodeCalled = true;
+ }
+
+ int numVars = 0;
+
+ // Push "this" for an instance method, which is always ALOAD 0
+ if (!mIsStatic) {
+ mParentVisitor.visitVarInsn(Opcodes.ALOAD, numVars++);
+ }
+
+ // Push all other arguments
+ Type[] argTypes = Type.getArgumentTypes(mDesc);
+ for (Type t : argTypes) {
+ int size = t.getSize();
+ mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), numVars);
+ numVars += size;
+ }
+
+ // Construct the descriptor of the delegate. For a static method, it's the same
+ // however for an instance method we need to pass the 'this' reference first
+ String desc = mDesc;
+ if (!mIsStatic && argTypes.length > 0) {
+ Type[] argTypes2 = new Type[argTypes.length + 1];
+
+ argTypes2[0] = Type.getObjectType(mClassName);
+ System.arraycopy(argTypes, 0, argTypes2, 1, argTypes.length);
+
+ desc = Type.getMethodDescriptor(Type.getReturnType(mDesc), argTypes2);
+ }
+
+ String delegateClassName = mClassName + DELEGATE_SUFFIX;
+
+ // Invoke the static delegate
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ delegateClassName,
+ mMethodName,
+ desc);
+
+ Type returnType = Type.getReturnType(mDesc);
+ mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+ mParentVisitor.visitMaxs(numVars, numVars);
+ mParentVisitor.visitEnd();
+
+ // For debugging now. Maybe we should collect these and store them in
+ // a text file for helping create the delegates. We could also compare
+ // the text file to a golden and break the build on unsupported changes
+ // or regressions. Even better we could fancy-print something that looks
+ // like the expected Java method declaration.
+ mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
+ }
+
+ /* Pass down to visitor writer. In this implementation, either do nothing. */
+ public void visitCode() {
+ mVisitCodeCalled = true;
+ mParentVisitor.visitCode();
+ }
+
+ /*
+ * visitMaxs is called just before visitEnd if there was any code to rewrite.
+ * Skip the original.
+ */
+ public void visitMaxs(int maxStack, int maxLocals) {
+ }
+
+ /**
+ * End of visiting. Generate the messaging code.
+ */
+ public void visitEnd() {
+ generateCode();
+ }
+
+ /* Writes all annotation from the original method. */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return mParentVisitor.visitAnnotation(desc, visible);
+ }
+
+ /* Writes all annotation default values from the original method. */
+ public AnnotationVisitor visitAnnotationDefault() {
+ return mParentVisitor.visitAnnotationDefault();
+ }
+
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ /* Writes all attributes from the original method. */
+ public void visitAttribute(Attribute attr) {
+ mParentVisitor.visitAttribute(attr);
+ }
+
+ /*
+ * Only writes the first line number present in the original code so that source
+ * viewers can direct to the correct method, even if the content doesn't match.
+ */
+ public void visitLineNumber(int line, Label start) {
+ if (mOutputFirstLineNumber) {
+ mParentVisitor.visitLineNumber(line, start);
+ mOutputFirstLineNumber = false;
+ }
+ }
+
+ public void visitInsn(int opcode) {
+ // Skip original code.
+ }
+
+ public void visitLabel(Label label) {
+ // Skip original code.
+ }
+
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ // Skip original code.
+ }
+
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ // Skip original code.
+ }
+
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ // Skip original code.
+ }
+
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ // Skip original code.
+ }
+
+ public void visitIincInsn(int var, int increment) {
+ // Skip original code.
+ }
+
+ public void visitIntInsn(int opcode, int operand) {
+ // Skip original code.
+ }
+
+ public void visitJumpInsn(int opcode, Label label) {
+ // Skip original code.
+ }
+
+ public void visitLdcInsn(Object cst) {
+ // Skip original code.
+ }
+
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ // Skip original code.
+ }
+
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ // Skip original code.
+ }
+
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ // Skip original code.
+ }
+
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ // Skip original code.
+ }
+
+ public void visitTypeInsn(int opcode, String type) {
+ // Skip original code.
+ }
+
+ public void visitVarInsn(int opcode, int var) {
+ // Skip original code.
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
new file mode 100644
index 0000000..40c1706
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+/**
+ * Interface describing the work to be done by {@link AsmGenerator}.
+ */
+public interface ICreateInfo {
+
+ /**
+ * Returns the list of class from layoutlib_create to inject in layoutlib.
+ * The list can be empty but must not be null.
+ */
+ public abstract Class<?>[] getInjectedClasses();
+
+ /**
+ * Returns the list of methods to rewrite as delegates.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateMethods();
+
+ /**
+ * Returns the list of classes on which to delegate all native methods.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDelegateClassNatives();
+
+ /**
+ * Returns The list of methods to stub out. Each entry must be in the form
+ * "package.package.OuterClass$InnerClass#MethodName".
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getOverriddenMethods();
+
+ /**
+ * Returns the list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getRenamedClasses();
+
+ /**
+ * Returns the list of classes for which the methods returning them should be deleted.
+ * The array contains a list of null terminated section starting with the name of the class
+ * to rename in which the methods are deleted, followed by a list of return types identifying
+ * the methods to delete.
+ * The list can be empty but must not be null.
+ */
+ public abstract String[] getDeleteReturns();
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index 303f097..4adaff9 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -21,7 +21,28 @@ import java.util.ArrayList;
import java.util.Set;
-
+/**
+ * Entry point for the layoutlib_create tool.
+ * <p/>
+ * The tool does not currently rely on any external configuration file.
+ * Instead the configuration is mostly done via the {@link CreateInfo} class.
+ * <p/>
+ * For a complete description of the tool and its implementation, please refer to
+ * the "README.txt" file at the root of this project.
+ * <p/>
+ * For a quick test, invoke this as follows:
+ * <pre>
+ * $ make layoutlib
+ * </pre>
+ * which does:
+ * <pre>
+ * $ make layoutlib_create &lt;bunch of framework jars&gt;
+ * $ out/host/linux-x86/framework/bin/layoutlib_create \
+ * out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \
+ * out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
+ * </pre>
+ */
public class Main {
public static void main(String[] args) {
@@ -42,12 +63,7 @@ public class Main {
}
try {
- AsmGenerator agen = new AsmGenerator(log, osDestJar[0],
- CreateInfo.INJECTED_CLASSES,
- CreateInfo.OVERRIDDEN_METHODS,
- CreateInfo.RENAMED_CLASSES,
- CreateInfo.REMOVED_METHODS
- );
+ AsmGenerator agen = new AsmGenerator(log, osDestJar[0], new CreateInfo());
AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
new String[] { "android.view.View" }, // derived from
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index e294d56..f2d9755 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -26,7 +26,7 @@ import org.objectweb.asm.Type;
import java.util.Set;
/**
- * Class adapter that can stub some or all of the methods of the class.
+ * Class adapter that can stub some or all of the methods of the class.
*/
class TransformClassAdapter extends ClassAdapter {
@@ -41,12 +41,12 @@ class TransformClassAdapter extends ClassAdapter {
/**
* Creates a new class adapter that will stub some or all methods.
- * @param logger
- * @param stubMethods
+ * @param logger
+ * @param stubMethods list of method signatures to always stub out
* @param deleteReturns list of types that trigger the deletion of methods returning them.
* @param className The name of the class being modified
* @param cv The parent class writer visitor
- * @param stubNativesOnly True if only native methods should be stubbed. False if all
+ * @param stubNativesOnly True if only native methods should be stubbed. False if all
* methods should be stubbed.
* @param hasNative True if the method has natives, in which case its access should be
* changed.
@@ -67,10 +67,10 @@ class TransformClassAdapter extends ClassAdapter {
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
-
+
// This class might be being renamed.
name = mClassName;
-
+
// remove protected or private and set as public
access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
access |= Opcodes.ACC_PUBLIC;
@@ -82,7 +82,7 @@ class TransformClassAdapter extends ClassAdapter {
mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
super.visit(version, access, name, signature, superName, interfaces);
}
-
+
/* Visits the header of an inner class. */
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
@@ -101,7 +101,7 @@ class TransformClassAdapter extends ClassAdapter {
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
-
+
if (mDeleteReturns != null) {
Type t = Type.getReturnType(desc);
if (t.getSort() == Type.OBJECT) {
@@ -130,16 +130,16 @@ class TransformClassAdapter extends ClassAdapter {
(mStubAll ||
(access & Opcodes.ACC_NATIVE) != 0) ||
mStubMethods.contains(methodSignature)) {
-
+
boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
// remove abstract, final and native
access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
-
+
String invokeSignature = methodSignature + desc;
mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
-
+
MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
isStatic, isNative);
@@ -149,7 +149,7 @@ class TransformClassAdapter extends ClassAdapter {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
-
+
/* Visits a field. Makes it public. */
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature,
@@ -157,7 +157,7 @@ class TransformClassAdapter extends ClassAdapter {
// change access to public
access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
access |= Opcodes.ACC_PUBLIC;
-
+
return super.visitField(access, name, desc, signature, value);
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index 603284e..d6dba6a 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -22,7 +22,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
import org.junit.After;
import org.junit.Before;
@@ -46,9 +45,9 @@ public class AsmAnalyzerTest {
@Before
public void setUp() throws Exception {
- mLog = new LogTest.MockLog();
+ mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
+
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
@@ -69,9 +68,9 @@ public class AsmAnalyzerTest {
"mock_android.dummy.InnerTest$DerivingClass",
"mock_android.dummy.InnerTest$MyGenerics1",
"mock_android.dummy.InnerTest$MyIntEnum",
- "mock_android.dummy.InnerTest$MyStaticInnerClass",
- "mock_android.dummy.InnerTest$NotStaticInner1",
- "mock_android.dummy.InnerTest$NotStaticInner2",
+ "mock_android.dummy.InnerTest$MyStaticInnerClass",
+ "mock_android.dummy.InnerTest$NotStaticInner1",
+ "mock_android.dummy.InnerTest$NotStaticInner2",
"mock_android.view.View",
"mock_android.view.ViewGroup",
"mock_android.view.ViewGroup$LayoutParams",
@@ -83,7 +82,7 @@ public class AsmAnalyzerTest {
},
map.keySet().toArray());
}
-
+
@Test
public void testFindClass() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -91,7 +90,7 @@ public class AsmAnalyzerTest {
ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
zipClasses, found);
-
+
assertNotNull(cr);
assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
@@ -172,14 +171,14 @@ public class AsmAnalyzerTest {
"mock_android.widget.TableLayout",
},
found.keySet().toArray());
-
+
for (String key : found.keySet()) {
ClassReader value = found.get(key);
assertNotNull("No value for " + key, value);
assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
}
}
-
+
@Test
public void testDependencyVisitor() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -190,7 +189,7 @@ public class AsmAnalyzerTest {
ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep);
DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
-
+
// get first level dependencies
cr.accept(visitor, 0 /* flags */);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 7cdf79a..f4ff389 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -20,8 +20,6 @@ package com.android.tools.layoutlib.create;
import static org.junit.Assert.assertArrayEquals;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -44,9 +42,9 @@ public class AsmGeneratorTest {
@Before
public void setUp() throws Exception {
- mLog = new LogTest.MockLog();
+ mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
+
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
@@ -65,16 +63,41 @@ public class AsmGeneratorTest {
@Test
public void testClassRenaming() throws IOException, LogAbortException {
-
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar,
- null, // classes to inject in the final JAR
- null, // methods to force override
- new String[] { // classes to rename (so that we can replace them)
- "mock_android.view.View", "mock_android.view._Original_View",
- "not.an.actual.ClassName", "anoter.fake.NewClassName",
- },
- null // methods deleted from their return type.
- );
+
+ ICreateInfo ci = new ICreateInfo() {
+ public Class<?>[] getInjectedClasses() {
+ // classes to inject in the final JAR
+ return new Class<?>[0];
+ }
+
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[] {
+ "mock_android.view.View", "mock_android.view._Original_View",
+ "not.an.actual.ClassName", "anoter.fake.NewClassName",
+ };
+ }
+
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
null, // derived from
@@ -83,7 +106,7 @@ public class AsmGeneratorTest {
});
aa.analyze();
agen.generate();
-
+
Set<String> notRenamed = agen.getClassesNotRenamed();
assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
index d6916ae..0135c40 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -33,8 +33,9 @@ public class ClassHasNativeVisitorTest {
@Test
public void testHasNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- ClassReader cr = new ClassReader(
- "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithNative");
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
@@ -44,14 +45,17 @@ public class ClassHasNativeVisitorTest {
@Test
public void testHasNoNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
- ClassReader cr = new ClassReader(
- "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithoutNative");
+ String className =
+ this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName();
+ ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[0], cv.getMethodsFound());
assertFalse(cv.hasNativeMethods());
}
+ //-------
+
/**
* Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
*/
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
new file mode 100644
index 0000000..9ad2e6e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+
+public class DelegateClassAdapterTest {
+
+ private MockLog mLog;
+
+ private static final String CLASS_NAME =
+ DelegateClassAdapterTest.class.getCanonicalName() + "$" +
+ ClassWithNative.class.getSimpleName();
+
+ @Before
+ public void setUp() throws Exception {
+ mLog = new MockLog();
+ mLog.setVerbose(true); // capture debug error too
+ }
+
+ /**
+ * Tests that a class not being modified still works.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testNoOp() throws Exception {
+ // create an instance of the class that will be modified
+ // (load the class in a distinct class loader so that we can trash its definition later)
+ ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
+ Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(CLASS_NAME);
+ ClassWithNative instance1 = clazz1.newInstance();
+ assertEquals(42, instance1.add(20, 22));
+ try {
+ instance1.callNativeInstance(10, 3.1415, new Object[0] );
+ fail("Test should have failed to invoke callTheNativeMethod [1]");
+ } catch (UnsatisfiedLinkError e) {
+ // This is expected to fail since the native method is not implemented.
+ }
+
+ // Now process it but tell the delegate to not modify any method
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ String internalClassName = CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it again
+ final byte[] bytes = cw.toByteArray();
+
+ ClassLoader2 cl2 = new ClassLoader2(bytes) {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ try {
+ callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
+ fail("Test should have failed to invoke callTheNativeMethod [2]");
+ } catch (InvocationTargetException e) {
+ // This is expected to fail since the native method has NOT been
+ // overridden here.
+ assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
+ }
+
+ // Check that the native method does NOT have the new annotation
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertTrue(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals(0, a.length);
+ }
+ };
+ cl2.testModifiedInstance();
+ }
+
+ /**
+ * {@link DelegateMethodAdapter} does not support overriding constructors yet,
+ * so this should fail with an {@link UnsupportedOperationException}.
+ *
+ * Although not tested here, the message of the exception should contain the
+ * constructor signature.
+ */
+ @Test(expected=UnsupportedOperationException.class)
+ public void testConstructorsNotSupported() throws IOException {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+ String internalClassName = CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("<init>");
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+ }
+
+ @Test
+ public void testDelegateNative() throws Exception {
+ ClassWriter cw = new ClassWriter(0 /*flags*/);
+ String internalClassName = CLASS_NAME.replace('.', '/');
+
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
+ DelegateClassAdapter cv = new DelegateClassAdapter(
+ mLog, cw, internalClassName, delegateMethods);
+
+ ClassReader cr = new ClassReader(CLASS_NAME);
+ cr.accept(cv, 0 /* flags */);
+
+ // Load the generated class in a different class loader and try it
+ final byte[] bytes = cw.toByteArray();
+
+ try {
+ ClassLoader2 cl2 = new ClassLoader2(bytes) {
+ @Override
+ public void testModifiedInstance() throws Exception {
+ Class<?> clazz2 = loadClass(CLASS_NAME);
+ Object i2 = clazz2.newInstance();
+ assertNotNull(i2);
+
+ // Use reflection to access inner methods
+ assertEquals(42, callAdd(i2, 20, 22));
+
+ Object[] objResult = new Object[] { null };
+ int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
+ assertEquals((int)(10 + 3.1415), result);
+ assertSame(i2, objResult[0]);
+
+ // Check that the native method now has the new annotation and is not native
+ Method[] m = clazz2.getDeclaredMethods();
+ assertEquals("native_instance", m[2].getName());
+ assertFalse(Modifier.isNative(m[2].getModifiers()));
+ Annotation[] a = m[2].getAnnotations();
+ assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
+ }
+ };
+ cl2.testModifiedInstance();
+
+ } catch (Throwable t) {
+ // For debugging, dump the bytecode of the class in case of unexpected error.
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ TraceClassVisitor tcv = new TraceClassVisitor(pw);
+
+ ClassReader cr2 = new ClassReader(bytes);
+ cr2.accept(tcv, 0 /* flags */);
+
+ String msg = "\n" + t.getClass().getCanonicalName();
+ if (t.getMessage() != null) {
+ msg += ": " + t.getMessage();
+ }
+ msg = msg + "\nBytecode dump:\n" + sw.toString();
+
+ // Re-throw exception with new message
+ RuntimeException ex = new RuntimeException(msg, t);
+ throw ex;
+ }
+ }
+
+ //-------
+
+ /**
+ * A class loader than can define and instantiate our dummy {@link ClassWithNative}.
+ * <p/>
+ * The trick here is that this class loader will test our modified version of ClassWithNative.
+ * Trying to do so in the original class loader generates all sort of link issues because
+ * there are 2 different definitions of the same class name. This class loader will
+ * define and load the class when requested by name and provide helpers to access the
+ * instance methods via reflection.
+ */
+ private abstract class ClassLoader2 extends ClassLoader {
+ private final byte[] mClassWithNative;
+
+ public ClassLoader2(byte[] classWithNative) {
+ super(null);
+ mClassWithNative = classWithNative;
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ try {
+ return super.findClass(name);
+ } catch (ClassNotFoundException e) {
+
+ if (CLASS_NAME.equals(name)) {
+ // Load the modified ClassWithNative from its bytes representation.
+ return defineClass(CLASS_NAME, mClassWithNative, 0, mClassWithNative.length);
+ }
+
+ try {
+ // Load everything else from the original definition into the new class loader.
+ ClassReader cr = new ClassReader(name);
+ ClassWriter cw = new ClassWriter(0);
+ cr.accept(cw, 0);
+ byte[] bytes = cw.toByteArray();
+ return defineClass(name, bytes, 0, bytes.length);
+
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#add(int, int)} via reflection.
+ */
+ public int callAdd(Object instance, int a, int b) throws Exception {
+ Method m = instance.getClass().getMethod("add",
+ new Class<?>[] { int.class, int.class });
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
+ * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])}
+ * via reflection.
+ */
+ public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
+ throws Exception {
+ Method m = instance.getClass().getMethod("callNativeInstance",
+ new Class<?>[] { int.class, double.class, Object[].class });
+
+ Object result = m.invoke(instance, new Object[] { a, d, o });
+ return ((Integer) result).intValue();
+ }
+
+ public abstract void testModifiedInstance() throws Exception;
+ }
+
+ /**
+ * Dummy test class with a native method.
+ * The native method is not defined and any attempt to invoke it will
+ * throw an {@link UnsatisfiedLinkError}.
+ */
+ public static class ClassWithNative {
+ public ClassWithNative() {
+ }
+
+ public int add(int a, int b) {
+ return a + b;
+ }
+
+ public int callNativeInstance(int a, double d, Object[] o) {
+ return native_instance(a, d, o);
+ }
+
+ private native int native_instance(int a, double d, Object[] o);
+ }
+
+ /**
+ * The delegate that receives the call to {@link ClassWithNative}'s overridden methods.
+ */
+ public static class ClassWithNative_Delegate {
+ public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
+ if (o != null && o.length > 0) {
+ o[0] = instance;
+ }
+ return (int)(a + d);
+ }
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
index 3f13158..1a5f653 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
@@ -24,33 +24,8 @@ import org.junit.Test;
public class LogTest {
- public static class MockLog extends Log {
- StringBuilder mOut = new StringBuilder();
- StringBuilder mErr = new StringBuilder();
-
- public String getOut() {
- return mOut.toString();
- }
-
- public String getErr() {
- return mErr.toString();
- }
-
- @Override
- protected void outPrintln(String msg) {
- mOut.append(msg);
- mOut.append('\n');
- }
-
- @Override
- protected void errPrintln(String msg) {
- mErr.append(msg);
- mErr.append('\n');
- }
- }
-
private MockLog mLog;
-
+
@Before
public void setUp() throws Exception {
mLog = new MockLog();
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
new file mode 100644
index 0000000..de750a3
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+
+public class MockLog extends Log {
+ StringBuilder mOut = new StringBuilder();
+ StringBuilder mErr = new StringBuilder();
+
+ public String getOut() {
+ return mOut.toString();
+ }
+
+ public String getErr() {
+ return mErr.toString();
+ }
+
+ @Override
+ protected void outPrintln(String msg) {
+ mOut.append(msg);
+ mOut.append('\n');
+ }
+
+ @Override
+ protected void errPrintln(String msg) {
+ mErr.append(msg);
+ mErr.append('\n');
+ }
+}
diff --git a/voip/java/android/net/sip/ISipService.aidl b/voip/java/android/net/sip/ISipService.aidl
index 6c68213..3250bf9 100644
--- a/voip/java/android/net/sip/ISipService.aidl
+++ b/voip/java/android/net/sip/ISipService.aidl
@@ -16,6 +16,7 @@
package android.net.sip;
+import android.app.PendingIntent;
import android.net.sip.ISipSession;
import android.net.sip.ISipSessionListener;
import android.net.sip.SipProfile;
@@ -26,7 +27,7 @@ import android.net.sip.SipProfile;
interface ISipService {
void open(in SipProfile localProfile);
void open3(in SipProfile localProfile,
- String incomingCallBroadcastAction,
+ in PendingIntent incomingCallPendingIntent,
in ISipSessionListener listener);
void close(in String localProfileUri);
boolean isOpened(String localProfileUri);
diff --git a/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java
index eb7a1ae..a55ab25 100644
--- a/voip/java/android/net/sip/SipErrorCode.java
+++ b/voip/java/android/net/sip/SipErrorCode.java
@@ -61,6 +61,9 @@ public class SipErrorCode {
/** Cross-domain authentication required. */
public static final int CROSS_DOMAIN_AUTHENTICATION = -11;
+ /** When the server is not reachable. */
+ public static final int SERVER_UNREACHABLE = -12;
+
public static String toString(int errorCode) {
switch (errorCode) {
case NO_ERROR:
@@ -87,6 +90,8 @@ public class SipErrorCode {
return "DATA_CONNECTION_LOST";
case CROSS_DOMAIN_AUTHENTICATION:
return "CROSS_DOMAIN_AUTHENTICATION";
+ case SERVER_UNREACHABLE:
+ return "SERVER_UNREACHABLE";
default:
return "UNKNOWN";
}
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index bd859e8..80c35fb 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -16,6 +16,7 @@
package android.net.sip;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -34,7 +35,7 @@ import java.text.ParseException;
* <li>open a {@link SipProfile} to get ready for making outbound calls or have
* the background SIP service listen to incoming calls and broadcast them
* with registered command string. See
- * {@link #open(SipProfile, String, SipRegistrationListener)},
+ * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)},
* {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and
* {@link #isRegistered}. It also facilitates handling of the incoming call
* broadcast intent. See
@@ -51,6 +52,19 @@ import java.text.ParseException;
*/
public class SipManager {
/**
+ * The result code to be sent back with the incoming call
+ * {@link PendingIntent}.
+ * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
+ */
+ public static final int INCOMING_CALL_RESULT_CODE = 101;
+
+ /** Part of the incoming call intent. */
+ public static final String EXTRA_CALL_ID = "android:sipCallID";
+
+ /** Part of the incoming call intent. */
+ public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
+
+ /**
* Action string for the incoming call intent for the Phone app.
* Internal use only.
* @hide
@@ -78,12 +92,6 @@ public class SipManager {
*/
public static final String EXTRA_LOCAL_URI = "android:localSipUri";
- /** Part of the incoming call intent. */
- public static final String EXTRA_CALL_ID = "android:sipCallID";
-
- /** Part of the incoming call intent. */
- public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
-
private static final String TAG = "SipManager";
private ISipService mSipService;
@@ -142,7 +150,8 @@ public class SipManager {
/**
* Opens the profile for making calls. The caller may make subsequent calls
* through {@link #makeAudioCall}. If one also wants to receive calls on the
- * profile, use {@link #open(SipProfile, String, SipRegistrationListener)}
+ * profile, use
+ * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}
* instead.
*
* @param localProfile the SIP profile to make calls from
@@ -165,17 +174,29 @@ public class SipManager {
* in order to receive calls from the provider.
*
* @param localProfile the SIP profile to receive incoming calls for
- * @param incomingCallBroadcastAction the action to be broadcast when an
- * incoming call is received
+ * @param incomingCallPendingIntent When an incoming call is received, the
+ * SIP service will call
+ * {@link PendingIntent#send(Context, int, Intent)} to send back the
+ * intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the
+ * result code and the intent to fill in the call ID and session
+ * description information. It cannot be null.
* @param listener to listen to registration events; can be null
+ * @see #getCallId
+ * @see #getOfferSessionDescription
+ * @see #takeAudioCall
+ * @throws NullPointerException if {@code incomingCallPendingIntent} is null
* @throws SipException if the profile contains incorrect settings or
* calling the SIP service results in an error
*/
public void open(SipProfile localProfile,
- String incomingCallBroadcastAction,
+ PendingIntent incomingCallPendingIntent,
SipRegistrationListener listener) throws SipException {
+ if (incomingCallPendingIntent == null) {
+ throw new NullPointerException(
+ "incomingCallPendingIntent cannot be null");
+ }
try {
- mSipService.open3(localProfile, incomingCallBroadcastAction,
+ mSipService.open3(localProfile, incomingCallPendingIntent,
createRelay(listener, localProfile.getUriString()));
} catch (RemoteException e) {
throw new SipException("open()", e);
@@ -184,7 +205,8 @@ public class SipManager {
/**
* Sets the listener to listen to registration events. No effect if the
- * profile has not been opened to receive calls (see {@link #open}).
+ * profile has not been opened to receive calls (see
+ * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}).
*
* @param localProfileUri the URI of the profile
* @param listener to listen to registration events; can be null
@@ -282,9 +304,9 @@ public class SipManager {
}
/**
- * Creates a {@link SipAudioCall} to make a call. To use this method, one
- * must call {@link #open(SipProfile)} first. The attempt will be timed out
- * if the call is not established within {@code timeout} seconds and
+ * Creates a {@link SipAudioCall} to make an audio call. The attempt will be
+ * timed out if the call is not established within {@code timeout} seconds
+ * and
* {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
* will be called.
*
@@ -416,9 +438,11 @@ public class SipManager {
/**
* Manually registers the profile to the corresponding SIP provider for
- * receiving calls. {@link #open(SipProfile, String, SipRegistrationListener)}
- * is still needed to be called at least once in order for the SIP service
- * to broadcast an intent when an incoming call is received.
+ * receiving calls.
+ * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is
+ * still needed to be called at least once in order for the SIP service to
+ * notify the caller with the {@code PendingIntent} when an incoming call is
+ * received.
*
* @param localProfile the SIP profile to register with
* @param expiryTime registration expiration time (in seconds)
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index 405dff8..a7c61e5 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -134,14 +134,6 @@ public final class SipService extends ISipService.Stub {
public void open(SipProfile localProfile) {
localProfile.setCallingUid(Binder.getCallingUid());
- if (localProfile.getAutoRegistration() && isCallerRadio()) {
- openToReceiveCalls(localProfile);
- } else {
- openToMakeCalls(localProfile);
- }
- }
-
- private void openToMakeCalls(SipProfile localProfile) {
try {
createGroup(localProfile);
} catch (SipException e) {
@@ -150,28 +142,20 @@ public final class SipService extends ISipService.Stub {
}
}
- private void openToReceiveCalls(SipProfile localProfile) {
- open3(localProfile, SipManager.ACTION_SIP_INCOMING_CALL, null);
- }
-
public synchronized void open3(SipProfile localProfile,
- String incomingCallBroadcastAction, ISipSessionListener listener) {
+ PendingIntent incomingCallPendingIntent,
+ ISipSessionListener listener) {
localProfile.setCallingUid(Binder.getCallingUid());
- if (TextUtils.isEmpty(incomingCallBroadcastAction)) {
- Log.w(TAG, "empty broadcast action for incoming call");
- return;
- }
- if (incomingCallBroadcastAction.equals(
- SipManager.ACTION_SIP_INCOMING_CALL) && !isCallerRadio()) {
- Log.w(TAG, "failed to open the profile; "
- + "the action string is reserved");
+ if (incomingCallPendingIntent == null) {
+ Log.w(TAG, "incomingCallPendingIntent cannot be null; "
+ + "the profile is not opened");
return;
}
if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": "
- + incomingCallBroadcastAction + ": " + listener);
+ + incomingCallPendingIntent + ": " + listener);
try {
SipSessionGroupExt group = createGroup(localProfile,
- incomingCallBroadcastAction, listener);
+ incomingCallPendingIntent, listener);
if (localProfile.getAutoRegistration()) {
group.openToReceiveCalls();
if (isWifiOn()) grabWifiLock();
@@ -287,20 +271,19 @@ public final class SipService extends ISipService.Stub {
}
private SipSessionGroupExt createGroup(SipProfile localProfile,
- String incomingCallBroadcastAction, ISipSessionListener listener)
- throws SipException {
+ PendingIntent incomingCallPendingIntent,
+ ISipSessionListener listener) throws SipException {
String key = localProfile.getUriString();
SipSessionGroupExt group = mSipGroups.get(key);
if (group != null) {
if (!isCallerCreator(group)) {
throw new SipException("only creator can access the profile");
}
- group.setIncomingCallBroadcastAction(
- incomingCallBroadcastAction);
+ group.setIncomingCallPendingIntent(incomingCallPendingIntent);
group.setListener(listener);
} else {
group = new SipSessionGroupExt(localProfile,
- incomingCallBroadcastAction, listener);
+ incomingCallPendingIntent, listener);
mSipGroups.put(key, group);
notifyProfileAdded(localProfile);
}
@@ -405,19 +388,19 @@ public final class SipService extends ISipService.Stub {
private class SipSessionGroupExt extends SipSessionAdapter {
private SipSessionGroup mSipGroup;
- private String mIncomingCallBroadcastAction;
+ private PendingIntent mIncomingCallPendingIntent;
private boolean mOpened;
private AutoRegistrationProcess mAutoRegistration =
new AutoRegistrationProcess();
public SipSessionGroupExt(SipProfile localProfile,
- String incomingCallBroadcastAction,
+ PendingIntent incomingCallPendingIntent,
ISipSessionListener listener) throws SipException {
String password = localProfile.getPassword();
SipProfile p = duplicate(localProfile);
mSipGroup = createSipSessionGroup(mLocalIp, p, password);
- mIncomingCallBroadcastAction = incomingCallBroadcastAction;
+ mIncomingCallPendingIntent = incomingCallPendingIntent;
mAutoRegistration.setListener(listener);
}
@@ -458,8 +441,8 @@ public final class SipService extends ISipService.Stub {
mAutoRegistration.setListener(listener);
}
- public void setIncomingCallBroadcastAction(String action) {
- mIncomingCallBroadcastAction = action;
+ public void setIncomingCallPendingIntent(PendingIntent pIntent) {
+ mIncomingCallPendingIntent = pIntent;
}
public void openToReceiveCalls() throws SipException {
@@ -469,7 +452,7 @@ public final class SipService extends ISipService.Stub {
mAutoRegistration.start(mSipGroup);
}
if (DEBUG) Log.d(TAG, " openToReceiveCalls: " + getUri() + ": "
- + mIncomingCallBroadcastAction);
+ + mIncomingCallPendingIntent);
}
public void onConnectivityChanged(boolean connected)
@@ -481,7 +464,7 @@ public final class SipService extends ISipService.Stub {
} else {
// close mSipGroup but remember mOpened
if (DEBUG) Log.d(TAG, " close auto reg temporarily: "
- + getUri() + ": " + mIncomingCallBroadcastAction);
+ + getUri() + ": " + mIncomingCallPendingIntent);
mSipGroup.close();
mAutoRegistration.stop();
}
@@ -508,7 +491,7 @@ public final class SipService extends ISipService.Stub {
mSipGroup.close();
mAutoRegistration.stop();
if (DEBUG) Log.d(TAG, " close: " + getUri() + ": "
- + mIncomingCallBroadcastAction);
+ + mIncomingCallPendingIntent);
}
public ISipSession createSession(ISipSessionListener listener) {
@@ -516,8 +499,10 @@ public final class SipService extends ISipService.Stub {
}
@Override
- public void onRinging(ISipSession session, SipProfile caller,
+ public void onRinging(ISipSession s, SipProfile caller,
String sessionDescription) {
+ SipSessionGroup.SipSessionImpl session =
+ (SipSessionGroup.SipSessionImpl) s;
synchronized (SipService.this) {
try {
if (!isRegistered()) {
@@ -528,15 +513,15 @@ public final class SipService extends ISipService.Stub {
// send out incoming call broadcast
addPendingSession(session);
Intent intent = SipManager.createIncomingCallBroadcast(
- session.getCallId(), sessionDescription)
- .setAction(mIncomingCallBroadcastAction);
+ session.getCallId(), sessionDescription);
if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
+ caller.getUri() + ": " + session.getCallId()
- + " " + mIncomingCallBroadcastAction);
- mContext.sendBroadcast(intent);
- } catch (RemoteException e) {
- // should never happen with a local call
- Log.e(TAG, "processCall()", e);
+ + " " + mIncomingCallPendingIntent);
+ mIncomingCallPendingIntent.send(mContext,
+ SipManager.INCOMING_CALL_RESULT_CODE, intent);
+ } catch (PendingIntent.CanceledException e) {
+ Log.w(TAG, "pendingIntent is canceled, drop incoming call");
+ session.endCall();
}
}
}
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index bc377cf..37fffa8 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -480,7 +480,7 @@ class SipSessionGroup implements SipListener {
public void run() {
try {
processCommand(command);
- } catch (SipException e) {
+ } catch (Throwable e) {
Log.w(TAG, "command error: " + command, e);
onError(e);
}
@@ -1218,7 +1218,7 @@ class SipSessionGroup implements SipListener {
private int getErrorCode(Throwable exception) {
String message = exception.getMessage();
if (exception instanceof UnknownHostException) {
- return SipErrorCode.INVALID_REMOTE_URI;
+ return SipErrorCode.SERVER_UNREACHABLE;
} else if (exception instanceof IOException) {
return SipErrorCode.SOCKET_ERROR;
} else {
diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp
index f3ecac2..84c7166 100644
--- a/voip/jni/rtp/AmrCodec.cpp
+++ b/voip/jni/rtp/AmrCodec.cpp
@@ -73,7 +73,7 @@ int AmrCodec::set(int sampleRate, const char *fmtp)
}
// Handle mode-set and octet-align.
- char *modes = strcasestr(fmtp, "mode-set=");
+ const char *modes = strcasestr(fmtp, "mode-set=");
if (modes) {
mMode = 0;
mModeSet = 0;
diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk
index 5909c0d..76c43ba 100644
--- a/voip/jni/rtp/Android.mk
+++ b/voip/jni/rtp/Android.mk
@@ -22,6 +22,7 @@ LOCAL_MODULE := librtp_jni
LOCAL_SRC_FILES := \
AudioCodec.cpp \
AudioGroup.cpp \
+ EchoSuppressor.cpp \
RtpStream.cpp \
util.cpp \
rtp_jni.cpp
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index 5214518..9da560a 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -44,6 +44,7 @@
#include "JNIHelp.h"
#include "AudioCodec.h"
+#include "EchoSuppressor.h"
extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss);
@@ -766,7 +767,9 @@ bool AudioGroup::DeviceThread::threadLoop()
}
LOGD("latency: output %d, input %d", track.latency(), record.latency());
- // TODO: initialize echo canceler here.
+ // Initialize echo canceler.
+ EchoSuppressor echo(sampleRate, sampleCount, sampleCount * 2 +
+ (track.latency() + record.latency()) * sampleRate / 1000);
// Give device socket a reasonable buffer size.
setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output));
@@ -839,7 +842,7 @@ bool AudioGroup::DeviceThread::threadLoop()
if (mode == NORMAL) {
send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
} else {
- // TODO: Echo canceller runs here.
+ echo.run(output, input);
send(deviceSocket, input, sizeof(input), MSG_DONTWAIT);
}
}
diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp
new file mode 100644
index 0000000..2ceebdc
--- /dev/null
+++ b/voip/jni/rtp/EchoSuppressor.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyrightm (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <math.h>
+
+#define LOG_TAG "Echo"
+#include <utils/Log.h>
+
+#include "EchoSuppressor.h"
+
+EchoSuppressor::EchoSuppressor(int sampleRate, int sampleCount, int tailLength)
+{
+ int scale = 1;
+ while (tailLength > 200 * scale) {
+ scale <<= 1;
+ }
+ if (scale > sampleCount) {
+ scale = sampleCount;
+ }
+
+ mScale = scale;
+ mSampleCount = sampleCount;
+ mWindowSize = sampleCount / scale;
+ mTailLength = (tailLength + scale - 1) / scale;
+ mRecordLength = (sampleRate + sampleCount - 1) / sampleCount;
+ mRecordOffset = 0;
+
+ mXs = new float[mTailLength + mWindowSize];
+ memset(mXs, 0, sizeof(float) * (mTailLength + mWindowSize));
+ mXYs = new float[mTailLength];
+ memset(mXYs, 0, sizeof(float) * mTailLength);
+ mXXs = new float[mTailLength];
+ memset(mXYs, 0, sizeof(float) * mTailLength);
+ mYY = 0;
+
+ mXYRecords = new float[mRecordLength * mTailLength];
+ memset(mXYRecords, 0, sizeof(float) * mRecordLength * mTailLength);
+ mXXRecords = new float[mRecordLength * mWindowSize];
+ memset(mXXRecords, 0, sizeof(float) * mRecordLength * mWindowSize);
+ mYYRecords = new float[mRecordLength];
+ memset(mYYRecords, 0, sizeof(float) * mRecordLength);
+
+ mLastX = 0;
+ mLastY = 0;
+}
+
+EchoSuppressor::~EchoSuppressor()
+{
+ delete [] mXs;
+ delete [] mXYs;
+ delete [] mXXs;
+ delete [] mXYRecords;
+ delete [] mXXRecords;
+ delete [] mYYRecords;
+}
+
+void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded)
+{
+ float *records;
+
+ // Update Xs.
+ for (int i = 0; i < mTailLength; ++i) {
+ mXs[i] = mXs[mWindowSize + i];
+ }
+ for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) {
+ float sum = 0;
+ for (int k = 0; k < mScale; ++k) {
+ float x = playbacked[j + k] >> 8;
+ mLastX += x;
+ sum += (mLastX >= 0) ? mLastX : -mLastX;
+ mLastX = 0.005f * mLastX - x;
+ }
+ mXs[mTailLength - 1 + i] = sum;
+ }
+
+ // Update XXs and XXRecords.
+ for (int i = 0; i < mTailLength - mWindowSize; ++i) {
+ mXXs[i] = mXXs[mWindowSize + i];
+ }
+ records = &mXXRecords[mRecordOffset * mWindowSize];
+ for (int i = 0, j = mTailLength - mWindowSize; i < mWindowSize; ++i, ++j) {
+ float xx = mXs[mTailLength - 1 + i] * mXs[mTailLength - 1 + i];
+ mXXs[j] = mXXs[j - 1] + xx - records[i];
+ records[i] = xx;
+ if (mXXs[j] < 0) {
+ mXXs[j] = 0;
+ }
+ }
+
+ // Compute Ys.
+ float ys[mWindowSize];
+ for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) {
+ float sum = 0;
+ for (int k = 0; k < mScale; ++k) {
+ float y = recorded[j + k] >> 8;
+ mLastY += y;
+ sum += (mLastY >= 0) ? mLastY : -mLastY;
+ mLastY = 0.005f * mLastY - y;
+ }
+ ys[i] = sum;
+ }
+
+ // Update YY and YYRecords.
+ float yy = 0;
+ for (int i = 0; i < mWindowSize; ++i) {
+ yy += ys[i] * ys[i];
+ }
+ mYY += yy - mYYRecords[mRecordOffset];
+ mYYRecords[mRecordOffset] = yy;
+ if (mYY < 0) {
+ mYY = 0;
+ }
+
+ // Update XYs and XYRecords.
+ records = &mXYRecords[mRecordOffset * mTailLength];
+ for (int i = 0; i < mTailLength; ++i) {
+ float xy = 0;
+ for (int j = 0;j < mWindowSize; ++j) {
+ xy += mXs[i + j] * ys[j];
+ }
+ mXYs[i] += xy - records[i];
+ records[i] = xy;
+ if (mXYs[i] < 0) {
+ mXYs[i] = 0;
+ }
+ }
+
+ // Computes correlations from XYs, XXs, and YY.
+ float weight = 1.0f / (mYY + 1);
+ float correlation = 0;
+ int latency = 0;
+ for (int i = 0; i < mTailLength; ++i) {
+ float c = mXYs[i] * mXYs[i] * weight / (mXXs[i] + 1);
+ if (c > correlation) {
+ correlation = c;
+ latency = i;
+ }
+ }
+
+ correlation = sqrtf(correlation);
+ if (correlation > 0.3f) {
+ float factor = 1.0f - correlation;
+ factor *= factor;
+ for (int i = 0; i < mSampleCount; ++i) {
+ recorded[i] *= factor;
+ }
+ }
+// LOGI("latency %5d, correlation %.10f", latency, correlation);
+
+
+ // Increase RecordOffset.
+ ++mRecordOffset;
+ if (mRecordOffset == mRecordLength) {
+ mRecordOffset = 0;
+ }
+}
diff --git a/voip/jni/rtp/EchoSuppressor.h b/voip/jni/rtp/EchoSuppressor.h
new file mode 100644
index 0000000..85decf5
--- /dev/null
+++ b/voip/jni/rtp/EchoSuppressor.h
@@ -0,0 +1,51 @@
+/*
+ * Copyrightm (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ECHO_SUPPRESSOR_H__
+#define __ECHO_SUPPRESSOR_H__
+
+#include <stdint.h>
+
+class EchoSuppressor
+{
+public:
+ // The sampleCount must be power of 2.
+ EchoSuppressor(int sampleRate, int sampleCount, int tailLength);
+ ~EchoSuppressor();
+ void run(int16_t *playbacked, int16_t *recorded);
+
+private:
+ int mScale;
+ int mSampleCount;
+ int mWindowSize;
+ int mTailLength;
+ int mRecordLength;
+ int mRecordOffset;
+
+ float *mXs;
+ float *mXYs;
+ float *mXXs;
+ float mYY;
+
+ float *mXYRecords;
+ float *mXXRecords;
+ float *mYYRecords;
+
+ float mLastX;
+ float mLastY;
+};
+
+#endif
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index 7ea4872..9634157 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -20,19 +20,24 @@ import android.app.ActivityManagerNative;
import android.content.Context;
import android.content.Intent;
import android.net.DhcpInfo;
+import android.net.ProxyProperties;
import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiConfiguration.ProxySettings;
import android.net.wifi.WifiConfiguration.Status;
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
-import java.io.BufferedWriter;
-import java.io.File;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.FileInputStream;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
@@ -46,8 +51,31 @@ import java.util.List;
* It deals with the following
* - Add/update/remove a WifiConfiguration
* The configuration contains two types of information.
- * = IP configuration that is handled by WifiConfigStore and
+ * = IP and proxy configuration that is handled by WifiConfigStore and
* is saved to disk on any change.
+ *
+ * The format of configuration file is as follows:
+ * <version>
+ * <netA_key1><netA_value1><netA_key2><netA_value2>...<EOS>
+ * <netB_key1><netB_value1><netB_key2><netB_value2>...<EOS>
+ * ..
+ *
+ * (key, value) pairs for a given network are grouped together and can
+ * be in any order. A "EOS" at the end of a set of (key, value) pairs
+ * indicates that the next set of (key, value) pairs are for a new
+ * network. A network is identified by a unique "id". If there is no
+ * "id" key in the (key, value) pairs, the data is discarded. An IP
+ * configuration includes the keys - "ipAssignment", "ipAddress", "gateway",
+ * "netmask", "dns1" and "dns2". A proxy configuration includes "proxySettings",
+ * "proxyHost", "proxyPort" and "exclusionList"
+ *
+ * An invalid version on read would result in discarding the contents of
+ * the file. On the next write, the latest version is written to file.
+ *
+ * Any failures during read or write to the configuration file are ignored
+ * without reporting to the user since the likelihood of these errors are
+ * low and the impact on connectivity is low.
+ *
* = SSID & security details that is pushed to the supplicant.
* supplicant saves these details to the disk on calling
* saveConfigCommand().
@@ -59,10 +87,9 @@ import java.util.List;
* to the disk. (TODO: deprecate these calls in WifiManager)
* > The new API calls - selectNetwork(), saveNetwork() & forgetNetwork().
* These calls persist the supplicant config to disk.
+ *
* - Maintain a list of configured networks for quick access
*
- * TODO:
- * - handle proxy per configuration
*/
class WifiConfigStore {
@@ -110,7 +137,7 @@ class WifiConfigStore {
List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
synchronized (sConfiguredNetworks) {
for(WifiConfiguration config : sConfiguredNetworks.values()) {
- networks.add(config.clone());
+ networks.add(new WifiConfiguration(config));
}
}
return networks;
@@ -229,6 +256,7 @@ class WifiConfigStore {
synchronized (sConfiguredNetworks) {
sConfiguredNetworks.remove(netId);
}
+ writeIpAndProxyConfigurations();
sendConfigChangeBroadcast();
} else {
Log.e(TAG, "Failed to remove network " + netId);
@@ -353,6 +381,19 @@ class WifiConfigStore {
}
/**
+ * Fetch the proxy properties for a given network id
+ */
+ static ProxyProperties getProxyProperties(int netId) {
+ synchronized (sConfiguredNetworks) {
+ WifiConfiguration config = sConfiguredNetworks.get(netId);
+ if (config != null && config.proxySettings == ProxySettings.STATIC) {
+ return new ProxyProperties(config.proxyProperties);
+ }
+ }
+ return null;
+ }
+
+ /**
* Return if the specified network is using static IP
*/
static boolean isUsingStaticIp(int netId) {
@@ -411,7 +452,7 @@ class WifiConfigStore {
sNetworkIds.put(configKey(config), config.networkId);
}
}
- readIpConfigurations();
+ readIpAndProxyConfigurations();
sendConfigChangeBroadcast();
}
@@ -430,38 +471,89 @@ class WifiConfigStore {
markAllNetworksDisabledExcept(INVALID_NETWORK_ID);
}
- private static void writeIpConfigurations() {
- StringBuilder builder = new StringBuilder();
- BufferedWriter out = null;
+ private static void writeIpAndProxyConfigurations() {
- builder.append(IPCONFIG_FILE_VERSION);
- builder.append("\n");
+ DataOutputStream out = null;
+ try {
+ out = new DataOutputStream(new BufferedOutputStream(
+ new FileOutputStream(ipConfigFile)));
- synchronized (sConfiguredNetworks) {
- for(WifiConfiguration config : sConfiguredNetworks.values()) {
- if (config.ipAssignment == WifiConfiguration.IpAssignment.STATIC) {
- builder.append("id=" + configKey(config));
- builder.append(":");
- builder.append("ip=" + config.ipConfig.ipAddress);
- builder.append(":");
- builder.append("gateway=" + config.ipConfig.gateway);
- builder.append(":");
- builder.append("netmask=" + config.ipConfig.netmask);
- builder.append(":");
- builder.append("dns1=" + config.ipConfig.dns1);
- builder.append(":");
- builder.append("dns2=" + config.ipConfig.dns2);
- builder.append("\n");
+ out.writeInt(IPCONFIG_FILE_VERSION);
+
+ synchronized (sConfiguredNetworks) {
+ for(WifiConfiguration config : sConfiguredNetworks.values()) {
+ boolean writeToFile = false;
+
+ switch (config.ipAssignment) {
+ case STATIC:
+ out.writeUTF("ipAssignment");
+ out.writeUTF(config.ipAssignment.toString());
+ out.writeUTF("ipAddress");
+ out.writeInt(config.ipConfig.ipAddress);
+ out.writeUTF("gateway");
+ out.writeInt(config.ipConfig.gateway);
+ out.writeUTF("netmask");
+ out.writeInt(config.ipConfig.netmask);
+ out.writeUTF("dns1");
+ out.writeInt(config.ipConfig.dns1);
+ out.writeUTF("dns2");
+ out.writeInt(config.ipConfig.dns2);
+ writeToFile = true;
+ break;
+ case DHCP:
+ out.writeUTF("ipAssignment");
+ out.writeUTF(config.ipAssignment.toString());
+ writeToFile = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ Log.e(TAG, "Ignore invalid ip assignment while writing");
+ break;
+ }
+
+ switch (config.proxySettings) {
+ case STATIC:
+ out.writeUTF("proxySettings");
+ out.writeUTF(config.proxySettings.toString());
+ InetSocketAddress proxy = config.proxyProperties.getSocketAddress();
+ if (proxy != null) {
+ out.writeUTF("proxyHost");
+ out.writeUTF(proxy.getHostName());
+ out.writeUTF("proxyPort");
+ out.writeInt(proxy.getPort());
+ String exclusionList = config.proxyProperties.getExclusionList();
+ if (exclusionList != null && exclusionList.length() > 0) {
+ out.writeUTF("exclusionList");
+ out.writeUTF(exclusionList);
+ }
+ }
+ writeToFile = true;
+ break;
+ case NONE:
+ out.writeUTF("proxySettings");
+ out.writeUTF(config.proxySettings.toString());
+ writeToFile = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ Log.e(TAG, "Ignore invalid proxy settings while writing");
+ break;
+ }
+
+ if (writeToFile) {
+ out.writeUTF("id");
+ out.writeInt(configKey(config));
+ out.writeUTF("EOS");
+ }
}
}
- }
- try {
- out = new BufferedWriter(new FileWriter(ipConfigFile), builder.length());
- out.write(builder.toString());
} catch (IOException e) {
Log.e(TAG, "Error writing data file");
- return;
} finally {
if (out != null) {
try {
@@ -471,80 +563,116 @@ class WifiConfigStore {
}
}
- private static void readIpConfigurations() {
- File f = new File(ipConfigFile);
- byte[] buffer;
- FileInputStream s = null;
- try {
- buffer = new byte[(int)f.length()];
- s = new FileInputStream(f);
- s.read(buffer);
- } catch (IOException e) {
- Log.e(TAG, "Error reading data file");
- return;
- } finally {
- if (s != null) {
- try {
- s.close();
- } catch (Exception e) {}
- }
- }
+ private static void readIpAndProxyConfigurations() {
- String data = new String(buffer);
- if (data == null || data.length() == 0) {
- Log.d(TAG, "IP configuration file empty");
- return;
- }
-
- String[] parsed = data.split("\n");
+ DataInputStream in = null;
try {
- if (Integer.parseInt(parsed[0]) != IPCONFIG_FILE_VERSION) {
+ in = new DataInputStream(new BufferedInputStream(new FileInputStream(
+ ipConfigFile)));
+
+ if (in.readInt() != IPCONFIG_FILE_VERSION) {
Log.e(TAG, "Bad version on IP configuration file, ignore read");
return;
}
- for (String line : parsed) {
- int hashKey = -1;
+ while (true) {
+ int id = -1;
+ IpAssignment ipAssignment = IpAssignment.UNASSIGNED;
DhcpInfo ipConfig = new DhcpInfo();
- String[] keyVals = line.split(":");
-
- for (String keyVal : keyVals) {
- String[] keyValPair = keyVal.split("=");
- if (keyValPair[0].equals("id")) {
- hashKey = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("ip")) {
- ipConfig.ipAddress = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("gateway")) {
- ipConfig.gateway = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("netmask")) {
- ipConfig.netmask = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("dns1")) {
- ipConfig.dns1 = Integer.parseInt(keyValPair[1]);
- } else if (keyValPair[0].equals("dns2")) {
- ipConfig.dns2 = Integer.parseInt(keyValPair[1]);
+ ProxySettings proxySettings = ProxySettings.UNASSIGNED;
+ String proxyHost = null;
+ int proxyPort = -1;
+ String exclusionList = null;
+ String key;
+
+ do {
+ key = in.readUTF();
+ if (key.equals("id")) {
+ id = in.readInt();
+ } else if (key.equals("ipAssignment")) {
+ ipAssignment = IpAssignment.valueOf(in.readUTF());
+ } else if (key.equals("ipAddress")) {
+ ipConfig.ipAddress = in.readInt();
+ } else if (key.equals("gateway")) {
+ ipConfig.gateway = in.readInt();
+ } else if (key.equals("netmask")) {
+ ipConfig.netmask = in.readInt();
+ } else if (key.equals("dns1")) {
+ ipConfig.dns1 = in.readInt();
+ } else if (key.equals("dns2")) {
+ ipConfig.dns2 = in.readInt();
+ } else if (key.equals("proxySettings")) {
+ proxySettings = ProxySettings.valueOf(in.readUTF());
+ } else if (key.equals("proxyHost")) {
+ proxyHost = in.readUTF();
+ } else if (key.equals("proxyPort")) {
+ proxyPort = in.readInt();
+ } else if (key.equals("exclusionList")) {
+ exclusionList = in.readUTF();
+ } else if (key.equals("EOS")) {
+ break;
} else {
- Log.w(TAG, "Ignoring " + keyVal);
+ Log.e(TAG, "Ignore unknown key " + key + "while reading");
}
- }
+ } while (true);
- if (hashKey != -1) {
+ if (id != -1) {
synchronized (sConfiguredNetworks) {
WifiConfiguration config = sConfiguredNetworks.get(
- sNetworkIds.get(hashKey));
+ sNetworkIds.get(id));
if (config == null) {
- Log.e(TAG, "IP configuration found for missing network, ignored");
+ Log.e(TAG, "configuration found for missing network, ignored");
} else {
- config.ipAssignment = WifiConfiguration.IpAssignment.STATIC;
- config.ipConfig = ipConfig;
+ switch (ipAssignment) {
+ case STATIC:
+ config.ipAssignment = ipAssignment;
+ config.ipConfig = ipConfig;
+ break;
+ case DHCP:
+ config.ipAssignment = ipAssignment;
+ break;
+ case UNASSIGNED:
+ //Ignore
+ break;
+ default:
+ Log.e(TAG, "Ignore invalid ip assignment while reading");
+ break;
+ }
+
+ switch (proxySettings) {
+ case STATIC:
+ config.proxySettings = proxySettings;
+ ProxyProperties proxyProperties = new ProxyProperties();
+ proxyProperties.setSocketAddress(
+ new InetSocketAddress(proxyHost, proxyPort));
+ proxyProperties.setExclusionList(exclusionList);
+ config.proxyProperties = proxyProperties;
+ break;
+ case NONE:
+ config.proxySettings = proxySettings;
+ break;
+ case UNASSIGNED:
+ //Ignore
+ break;
+ default:
+ Log.e(TAG, "Ignore invalid proxy settings while reading");
+ break;
+ }
}
}
} else {
- Log.e(TAG,"Missing id while parsing configuration" + line);
+ Log.e(TAG,"Missing id while parsing configuration");
}
}
- } catch (NumberFormatException e) {
+ } catch (IOException e) {
Log.e(TAG, "Error parsing configuration");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (Exception e) {}
+ }
}
}
@@ -759,21 +887,68 @@ class WifiConfigStore {
}
}
readNetworkVariables(sConfig);
+ writeIpAndProxyConfigurationsOnChange(sConfig, config);
+ return netId;
+ }
- if (config.ipAssignment != IpAssignment.UNASSIGNED) {
+ /* Compare current and new configuration and write to file on change */
+ private static void writeIpAndProxyConfigurationsOnChange(WifiConfiguration currentConfig,
+ WifiConfiguration newConfig) {
+ boolean newNetwork = (newConfig.networkId == INVALID_NETWORK_ID);
+ boolean writeConfigToFile = false;
+
+ if (newConfig.ipAssignment != IpAssignment.UNASSIGNED) {
if (newNetwork ||
- (sConfig.ipAssignment != config.ipAssignment) ||
- (sConfig.ipConfig.ipAddress != config.ipConfig.ipAddress) ||
- (sConfig.ipConfig.gateway != config.ipConfig.gateway) ||
- (sConfig.ipConfig.netmask != config.ipConfig.netmask) ||
- (sConfig.ipConfig.dns1 != config.ipConfig.dns1) ||
- (sConfig.ipConfig.dns2 != config.ipConfig.dns2)) {
- sConfig.ipAssignment = config.ipAssignment;
- sConfig.ipConfig = config.ipConfig;
- writeIpConfigurations();
+ (currentConfig.ipAssignment != newConfig.ipAssignment) ||
+ (currentConfig.ipConfig.ipAddress != newConfig.ipConfig.ipAddress) ||
+ (currentConfig.ipConfig.gateway != newConfig.ipConfig.gateway) ||
+ (currentConfig.ipConfig.netmask != newConfig.ipConfig.netmask) ||
+ (currentConfig.ipConfig.dns1 != newConfig.ipConfig.dns1) ||
+ (currentConfig.ipConfig.dns2 != newConfig.ipConfig.dns2)) {
+ currentConfig.ipAssignment = newConfig.ipAssignment;
+ currentConfig.ipConfig = newConfig.ipConfig;
+ writeConfigToFile = true;
}
}
- return netId;
+
+ if (newConfig.proxySettings != ProxySettings.UNASSIGNED) {
+ InetSocketAddress newSockAddr = newConfig.proxyProperties.getSocketAddress();
+ String newExclusionList = newConfig.proxyProperties.getExclusionList();
+
+ InetSocketAddress currentSockAddr = currentConfig.proxyProperties.getSocketAddress();
+ String currentExclusionList = currentConfig.proxyProperties.getExclusionList();
+
+ boolean socketAddressDiffers = false;
+ boolean exclusionListDiffers = false;
+
+ if (newSockAddr != null && currentSockAddr != null ) {
+ socketAddressDiffers = !currentSockAddr.equals(newSockAddr);
+ } else if (newSockAddr != null || currentSockAddr != null) {
+ socketAddressDiffers = true;
+ }
+
+ if (newExclusionList != null && currentExclusionList != null) {
+ exclusionListDiffers = currentExclusionList.equals(newExclusionList);
+ } else if (newExclusionList != null || currentExclusionList != null) {
+ exclusionListDiffers = true;
+ }
+
+ if (newNetwork ||
+ (currentConfig.proxySettings != newConfig.proxySettings) ||
+ socketAddressDiffers ||
+ exclusionListDiffers) {
+ currentConfig.proxySettings = newConfig.proxySettings;
+ currentConfig.proxyProperties = newConfig.proxyProperties;
+ Log.d(TAG, "proxy change SSID = " + currentConfig.SSID + " proxyProperties: " +
+ currentConfig.proxyProperties.toString());
+ writeConfigToFile = true;
+ }
+ }
+
+ if (writeConfigToFile) {
+ writeIpAndProxyConfigurations();
+ sendConfigChangeBroadcast();
+ }
}
/**
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 57e9bad..c4a1310 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -17,6 +17,7 @@
package android.net.wifi;
import android.net.DhcpInfo;
+import android.net.ProxyProperties;
import android.os.Parcelable;
import android.os.Parcel;
@@ -301,8 +302,13 @@ public class WifiConfiguration implements Parcelable {
* @hide
*/
public enum IpAssignment {
+ /* Use statically configured IP settings. Configuration can be accessed
+ * with ipConfig */
STATIC,
+ /* Use dynamically configured IP settigns */
DHCP,
+ /* no IP details are assigned, this is used to indicate
+ * that any existing IP settings should be retained */
UNASSIGNED
}
/**
@@ -314,6 +320,29 @@ public class WifiConfiguration implements Parcelable {
*/
public DhcpInfo ipConfig;
+ /**
+ * @hide
+ */
+ public enum ProxySettings {
+ /* No proxy is to be used. Any existing proxy settings
+ * should be cleared. */
+ NONE,
+ /* Use statically configured proxy. Configuration can be accessed
+ * with proxyProperties */
+ STATIC,
+ /* no proxy details are assigned, this is used to indicate
+ * that any existing proxy settings should be retained */
+ UNASSIGNED
+ }
+ /**
+ * @hide
+ */
+ public ProxySettings proxySettings;
+ /**
+ * @hide
+ */
+ public ProxyProperties proxyProperties;
+
public WifiConfiguration() {
networkId = INVALID_NETWORK_ID;
SSID = null;
@@ -333,6 +362,8 @@ public class WifiConfiguration implements Parcelable {
}
ipAssignment = IpAssignment.UNASSIGNED;
ipConfig = new DhcpInfo();
+ proxySettings = ProxySettings.UNASSIGNED;
+ proxyProperties = new ProxyProperties();
}
public String toString() {
@@ -419,6 +450,12 @@ public class WifiConfiguration implements Parcelable {
sbuf.append(" ").append(ipConfig);
}
sbuf.append('\n');
+
+ if (proxySettings == ProxySettings.STATIC) {
+ sbuf.append(" ").append("Proxy configuration:").append('\n');
+ sbuf.append(" ").append(proxyProperties);
+ }
+ sbuf.append('\n');
return sbuf.toString();
}
@@ -458,38 +495,36 @@ public class WifiConfiguration implements Parcelable {
return 0;
}
- /**
- * Returns a copy of this WifiConfiguration.
- *
- * @return a copy of this WifiConfiguration.
- * @hide
- */
- public WifiConfiguration clone() {
- WifiConfiguration config = new WifiConfiguration();
- config.networkId = networkId;
- config.status = status;
- config.SSID = SSID;
- config.BSSID = BSSID;
- config.preSharedKey = preSharedKey;
-
- for (int i = 0; i < wepKeys.length; i++)
- config.wepKeys[i] = wepKeys[i];
-
- config.wepTxKeyIndex = wepTxKeyIndex;
- config.priority = priority;
- config.hiddenSSID = hiddenSSID;
- config.allowedKeyManagement = (BitSet) allowedKeyManagement.clone();
- config.allowedProtocols = (BitSet) allowedProtocols.clone();
- config.allowedAuthAlgorithms = (BitSet) allowedAuthAlgorithms.clone();
- config.allowedPairwiseCiphers = (BitSet) allowedPairwiseCiphers.clone();
- config.allowedGroupCiphers = (BitSet) allowedGroupCiphers.clone();
-
- for (int i = 0; i < enterpriseFields.length; i++) {
- config.enterpriseFields[i].setValue(enterpriseFields[i].value());
+ /** copy constructor {@hide} */
+ public WifiConfiguration(WifiConfiguration source) {
+ if (source != null) {
+ networkId = source.networkId;
+ status = source.status;
+ SSID = source.SSID;
+ BSSID = source.BSSID;
+ preSharedKey = source.preSharedKey;
+
+ wepKeys = new String[4];
+ for (int i = 0; i < wepKeys.length; i++)
+ wepKeys[i] = source.wepKeys[i];
+
+ wepTxKeyIndex = source.wepTxKeyIndex;
+ priority = source.priority;
+ hiddenSSID = source.hiddenSSID;
+ allowedKeyManagement = (BitSet) source.allowedKeyManagement.clone();
+ allowedProtocols = (BitSet) source.allowedProtocols.clone();
+ allowedAuthAlgorithms = (BitSet) source.allowedAuthAlgorithms.clone();
+ allowedPairwiseCiphers = (BitSet) source.allowedPairwiseCiphers.clone();
+ allowedGroupCiphers = (BitSet) source.allowedGroupCiphers.clone();
+
+ for (int i = 0; i < source.enterpriseFields.length; i++) {
+ enterpriseFields[i].setValue(source.enterpriseFields[i].value());
+ }
+ ipAssignment = source.ipAssignment;
+ ipConfig = new DhcpInfo(source.ipConfig);
+ proxySettings = source.proxySettings;
+ proxyProperties = new ProxyProperties(source.proxyProperties);
}
- config.ipAssignment = ipAssignment;
- config.ipConfig = new DhcpInfo(ipConfig);
- return config;
}
/** Implement the Parcelable interface {@hide} */
@@ -522,6 +557,8 @@ public class WifiConfiguration implements Parcelable {
dest.writeInt(ipConfig.dns2);
dest.writeInt(ipConfig.serverAddress);
dest.writeInt(ipConfig.leaseDuration);
+ dest.writeString(proxySettings.name());
+ dest.writeParcelable(proxyProperties, flags);
}
/** Implement the Parcelable interface {@hide} */
@@ -557,6 +594,8 @@ public class WifiConfiguration implements Parcelable {
config.ipConfig.dns2 = in.readInt();
config.ipConfig.serverAddress = in.readInt();
config.ipConfig.leaseDuration = in.readInt();
+ config.proxySettings = ProxySettings.valueOf(in.readString());
+ config.proxyProperties = in.readParcelable(null);
return config;
}
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index e82c003..572abc0 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -38,12 +38,14 @@ import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
import android.app.ActivityManagerNative;
+import android.net.LinkAddress;
import android.net.NetworkInfo;
import android.net.DhcpInfo;
import android.net.NetworkUtils;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.DetailedState;
import android.net.LinkProperties;
+import android.net.ProxyProperties;
import android.net.wifi.WifiConfiguration.Status;
import android.os.Binder;
import android.os.Message;
@@ -1251,24 +1253,25 @@ public class WifiStateMachine extends HierarchicalStateMachine {
}
private void configureLinkProperties() {
- try {
- mLinkProperties.setInterface(NetworkInterface.getByName(mInterfaceName));
- } catch (SocketException e) {
- Log.e(TAG, "SocketException creating NetworkInterface from " + mInterfaceName +
- ". e=" + e);
- return;
- } catch (NullPointerException e) {
- Log.e(TAG, "NPE creating NetworkInterface. e=" + e);
- return;
- }
+
+ mLinkProperties.setInterfaceName(mInterfaceName);
+
// TODO - fix this for v6
synchronized (mDhcpInfo) {
- mLinkProperties.addAddress(NetworkUtils.intToInetAddress(mDhcpInfo.ipAddress));
+ mLinkProperties.addLinkAddress(new LinkAddress(
+ NetworkUtils.intToInetAddress(mDhcpInfo.ipAddress),
+ NetworkUtils.intToInetAddress(mDhcpInfo.netmask)));
mLinkProperties.setGateway(NetworkUtils.intToInetAddress(mDhcpInfo.gateway));
mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns1));
mLinkProperties.addDns(NetworkUtils.intToInetAddress(mDhcpInfo.dns2));
}
- // TODO - add proxy info
+
+ ProxyProperties proxyProperties = WifiConfigStore.getProxyProperties(mLastNetworkId);
+ if (proxyProperties != null) {
+ mLinkProperties.setHttpProxy(proxyProperties);
+ Log.d(TAG, "netId=" + mLastNetworkId + " proxy configured: "
+ + proxyProperties.toString());
+ }
}
private int getMaxDhcpRetries() {