From b798689749c64baba81f02e10cf2157c747d6b46 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Fri, 9 Jan 2009 17:51:23 -0800 Subject: [PATCH] auto import from //branches/cupcake/...@125939 --- api/current.xml | 828 +++++++++++++++++- camera/libcameraservice/CameraHardwareStub.cpp | 4 + camera/libcameraservice/CameraHardwareStub.h | 1 + camera/libcameraservice/CameraService.cpp | 62 +- camera/libcameraservice/CameraService.h | 19 +- cmds/dumpstate/Android.mk | 17 +- cmds/dumpstate/dumpstate | 114 --- cmds/dumpstate/dumpstate.c | 291 +++++++ cmds/dumpstate/dumpstate.h | 120 +++ cmds/dumpstate/utils.c | 230 +++++ cmds/ime/Android.mk | 15 + cmds/ime/MODULE_LICENSE_APACHE2 | 0 cmds/ime/NOTICE | 190 ++++ cmds/ime/ime | 7 + cmds/ime/src/com/android/commands/ime/Ime.java | 248 ++++++ cmds/pm/src/com/android/commands/pm/Pm.java | 61 ++ core/java/android/app/Activity.java | 2 - core/java/android/app/SearchDialog.java | 960 +++++++++------------ core/java/android/app/SearchManager.java | 8 + core/java/android/bluetooth/BluetoothA2dp.java | 4 +- core/java/android/content/AbstractTableMerger.java | 12 +- core/java/android/hardware/Camera.java | 106 ++- .../IInputMethodSessionWrapper.java | 9 +- .../inputmethodservice/InputMethodService.java | 177 +++- core/java/android/inputmethodservice/Keyboard.java | 10 +- .../android/inputmethodservice/KeyboardView.java | 50 +- core/java/android/os/HandlerState.java | 33 + core/java/android/os/HandlerStateMachine.java | 290 +++++++ core/java/android/os/IPowerManager.aidl | 1 + core/java/android/os/Power.java | 18 +- .../android/preference/PreferenceGroupAdapter.java | 5 +- core/java/android/provider/Browser.java | 10 +- core/java/android/provider/Contacts.java | 30 +- core/java/android/provider/MediaStore.java | 46 +- core/java/android/provider/Settings.java | 12 +- core/java/android/provider/Sync.java | 30 +- core/java/android/server/BluetoothA2dpService.java | 31 +- .../java/android/server/search/SearchableInfo.java | 18 + core/java/android/speech/RecognizerIntent.java | 106 +++ core/java/android/text/InputType.java | 32 +- core/java/android/text/Layout.java | 4 +- core/java/android/text/format/DateUtils.java | 201 ++++- .../text/method/ArrowKeyMovementMethod.java | 36 +- .../android/text/method/MetaKeyKeyListener.java | 239 ++++- .../android/text/style/BackgroundColorSpan.java | 2 +- core/java/android/text/style/CharacterStyle.java | 4 +- core/java/android/text/style/ClickableSpan.java | 2 +- .../android/text/style/DynamicDrawableSpan.java | 50 +- .../android/text/style/ForegroundColorSpan.java | 3 +- core/java/android/text/style/ImageSpan.java | 46 +- core/java/android/text/style/MaskFilterSpan.java | 3 +- core/java/android/text/style/RasterizerSpan.java | 3 +- .../java/android/text/style/StrikethroughSpan.java | 2 +- core/java/android/text/style/UnderlineSpan.java | 2 +- core/java/android/text/style/UpdateAppearance.java | 10 + core/java/android/text/style/UpdateLayout.java | 7 +- core/java/android/util/PrintStreamPrinter.java | 40 + core/java/android/view/Menu.java | 3 - core/java/android/view/SurfaceView.java | 62 +- core/java/android/view/View.java | 29 +- core/java/android/view/ViewRoot.java | 66 +- core/java/android/view/animation/Animation.java | 37 +- core/java/android/view/animation/package.html | 236 +---- .../view/inputmethod/BaseInputConnection.java | 21 +- .../view/inputmethod/DefaultInputMethod.java | 6 +- .../android/view/inputmethod/InputConnection.java | 11 + .../view/inputmethod/InputConnectionWrapper.java | 4 + .../java/android/view/inputmethod/InputMethod.java | 18 +- .../view/inputmethod/InputMethodManager.java | 232 ++++- .../view/inputmethod/InputMethodSession.java | 7 +- core/java/android/view/inputmethod/package.html | 3 +- core/java/android/webkit/CookieManager.java | 85 +- core/java/android/webkit/TextDialog.java | 59 +- core/java/android/webkit/WebView.java | 81 +- core/java/android/webkit/WebViewCore.java | 79 +- core/java/android/widget/AbsListView.java | 23 +- core/java/android/widget/AbsSeekBar.java | 31 +- core/java/android/widget/AutoCompleteTextView.java | 22 +- core/java/android/widget/Gallery.java | 52 +- core/java/android/widget/ProgressBar.java | 112 +-- core/java/android/widget/RatingBar.java | 44 +- .../java/android/widget/ResourceCursorAdapter.java | 12 +- core/java/android/widget/SeekBar.java | 29 +- core/java/android/widget/SimpleCursorAdapter.java | 43 +- core/java/android/widget/TextView.java | 64 +- .../internal/app/RingtonePickerActivity.java | 16 +- .../com/android/internal/os/HandlerCaller.java | 14 + .../internal/view/IInputConnectionWrapper.java | 9 + .../com/android/internal/view/IInputContext.aidl | 2 + .../android/internal/view/IInputMethodManager.aidl | 2 + .../android/internal/view/IInputMethodSession.aidl | 3 +- .../internal/view/InputConnectionWrapper.java | 9 + .../android/internal/view/menu/MenuItemImpl.java | 14 +- .../internal/widget/EditableInputConnection.java | 93 +- .../android/gdata/client/AndroidGDataClient.java | 6 +- .../com/google/android/net/GoogleHttpClient.java | 34 +- core/jni/Android.mk | 7 +- core/jni/AndroidRuntime.cpp | 2 + core/jni/android/graphics/Bitmap.cpp | 2 +- core/jni/android/graphics/Canvas.cpp | 2 +- core/jni/android/graphics/Shader.cpp | 2 +- core/jni/android/opengl/util.cpp | 2 +- core/jni/android_hardware_Camera.cpp | 34 + core/jni/android_media_AudioTrack.cpp | 41 +- core/jni/android_media_JetPlayer.cpp | 485 +++++++++++ core/jni/android_os_Power.cpp | 18 +- core/jni/android_view_ViewRoot.cpp | 8 +- core/jni/com_google_android_gles_jni_EGLImpl.cpp | 4 +- core/res/AndroidManifest.xml | 2 +- core/res/res/drawable/ic_menu_login.png | Bin 0 -> 2466 bytes core/res/res/drawable/ic_menu_notifications.png | Bin 0 -> 1771 bytes core/res/res/drawable/ic_menu_refresh.png | Bin 0 -> 2450 bytes core/res/res/drawable/ratingbar_full.xml | 6 +- core/res/res/drawable/ratingbar_full_empty.xml | 34 + core/res/res/drawable/ratingbar_full_filled.xml | 34 + core/res/res/drawable/seek_thumb.xml | 34 + .../{seek_thumb.png => seek_thumb_normal.png} | Bin core/res/res/drawable/seek_thumb_pressed.png | Bin 0 -> 970 bytes core/res/res/drawable/seek_thumb_selected.png | Bin 0 -> 970 bytes core/res/res/layout/search_bar.xml | 27 +- core/res/res/values-fr/arrays.xml | 4 + core/res/res/values-fr/strings.xml | 723 ++++++++++++++++ core/res/res/values-it/arrays.xml | 4 + core/res/res/values-it/strings.xml | 723 ++++++++++++++++ core/res/res/values-mcc204-fr/strings.xml | 6 + core/res/res/values-mcc204-it/strings.xml | 6 + core/res/res/values-mcc204-zh-rCN/strings.xml | 6 + core/res/res/values-mcc230-fr/strings.xml | 6 + core/res/res/values-mcc230-it/strings.xml | 6 + core/res/res/values-mcc230-zh-rCN/strings.xml | 6 + core/res/res/values-mcc232-fr/strings.xml | 6 + core/res/res/values-mcc232-it/strings.xml | 6 + core/res/res/values-mcc232-zh-rCN/strings.xml | 6 + core/res/res/values-mcc234-fr/strings.xml | 6 + core/res/res/values-mcc234-it/strings.xml | 6 + core/res/res/values-mcc234-zh-rCN/strings.xml | 6 + core/res/res/values-mcc260-fr/strings.xml | 6 + core/res/res/values-mcc260-it/strings.xml | 6 + core/res/res/values-mcc260-zh-rCN/strings.xml | 6 + core/res/res/values-mcc262-fr/strings.xml | 6 + core/res/res/values-mcc262-it/strings.xml | 6 + core/res/res/values-mcc262-zh-rCN/strings.xml | 6 + core/res/res/values-zh-rCN/arrays.xml | 4 + core/res/res/values-zh-rCN/strings.xml | 723 ++++++++++++++++ core/res/res/values/attrs.xml | 24 +- core/res/res/values/ids.xml | 2 + core/res/res/values/public.xml | 3 + core/res/res/values/strings.xml | 62 ++ core/res/res/values/styles.xml | 1 + core/res/res/xml-en/autotext.xml | 195 +++++ core/res/res/xml/autotext.xml | 175 ---- data/etc/Android.mk | 8 +- data/etc/{permissions.xml => platform.xml} | 7 +- data/localization/import-from-xtb | 4 +- docs/html/community/index.jd | 2 +- docs/html/images/linearlayout.png | Bin 13576 -> 10100 bytes .../graphics/drawable/AnimationDrawable.java | 30 +- .../android/graphics/drawable/BitmapDrawable.java | 18 + .../android/graphics/drawable/ClipDrawable.java | 10 +- .../android/graphics/drawable/ColorDrawable.java | 8 +- .../graphics/drawable/GradientDrawable.java | 32 +- .../android/graphics/drawable/InsetDrawable.java | 9 + .../android/graphics/drawable/LayerDrawable.java | 16 +- .../graphics/drawable/LevelListDrawable.java | 14 +- .../android/graphics/drawable/RotateDrawable.java | 11 +- .../android/graphics/drawable/ScaleDrawable.java | 11 +- .../graphics/drawable/StateListDrawable.java | 56 ++ .../graphics/drawable/TransitionDrawable.java | 16 +- include/media/JetPlayer.h | 101 +++ include/media/ToneGenerator.h | 1 + include/media/mediametadataretriever.h | 5 + include/ui/Camera.h | 5 + include/ui/CameraHardwareInterface.h | 5 + include/ui/EGLNativeSurface.h | 13 +- include/ui/ICamera.h | 9 + include/ui/IOverlay.h | 2 - include/ui/ISurface.h | 4 +- include/ui/Overlay.h | 78 +- include/ui/PixelFormat.h | 4 +- include/ui/Region.h | 2 +- include/utils/MemoryHeapBase.h | 5 +- include/utils/RefBase.h | 24 + libs/audioflinger/A2dpAudioInterface.cpp | 49 +- libs/audioflinger/A2dpAudioInterface.h | 12 +- libs/audioflinger/AudioDumpInterface.cpp | 16 +- libs/audioflinger/AudioDumpInterface.h | 2 +- libs/audioflinger/AudioFlinger.cpp | 135 ++- libs/audioflinger/AudioFlinger.h | 3 +- libs/audioflinger/AudioHardwareGeneric.cpp | 12 +- libs/audioflinger/AudioHardwareGeneric.h | 2 +- libs/audioflinger/AudioHardwareStub.cpp | 10 +- libs/audioflinger/AudioHardwareStub.h | 2 +- libs/surfaceflinger/BootAnimation.cpp | 4 +- .../DisplayHardware/DisplayHardware.cpp | 4 +- .../DisplayHardware/DisplayHardware.h | 6 +- libs/surfaceflinger/Layer.cpp | 4 +- libs/surfaceflinger/LayerBase.cpp | 17 +- libs/surfaceflinger/LayerBase.h | 79 +- libs/surfaceflinger/LayerBlur.cpp | 2 +- libs/surfaceflinger/LayerBuffer.cpp | 617 ++++++++----- libs/surfaceflinger/LayerBuffer.h | 134 ++- libs/surfaceflinger/LayerScreenshot.cpp | 2 +- libs/surfaceflinger/SurfaceFlinger.cpp | 24 +- libs/surfaceflinger/SurfaceFlinger.h | 14 +- libs/surfaceflinger/Transform.h | 2 +- libs/ui/Camera.cpp | 19 + libs/ui/ICamera.cpp | 45 +- libs/ui/IOverlay.cpp | 15 - libs/ui/ISurface.cpp | 8 +- libs/ui/Overlay.cpp | 122 ++- libs/ui/Region.cpp | 2 - libs/utils/MemoryHeapBase.cpp | 25 +- .../internal/location/GpsLocationProvider.java | 58 +- .../internal/location/GpsXtraDownloader.java | 14 +- .../android/internal/location/LocationCache.java | 18 +- .../internal/location/LocationMasfClient.java | 10 + media/java/android/media/AudioManager.java | 29 + media/java/android/media/AudioTrack.java | 2 +- media/java/android/media/JetPlayer.java | 334 +++++++ .../java/android/media/MediaMetadataRetriever.java | 18 +- media/java/android/media/ResampleInputStream.java | 153 ++++ media/jni/Android.mk | 3 +- media/jni/android_media_MediaMetadataRetriever.cpp | 2 +- media/jni/android_media_MediaPlayer.cpp | 6 + media/jni/android_media_ResampleInputStream.cpp | 143 +++ media/libmedia/Android.mk | 5 +- media/libmedia/AudioTrack.cpp | 22 +- media/libmedia/IMediaMetadataRetriever.cpp | 2 +- media/libmedia/JetPlayer.cpp | 428 +++++++++ media/libmedia/ToneGenerator.cpp | 264 +++--- media/libmedia/mediarecorder.cpp | 2 - preloaded-classes | 69 +- .../java/com/android/server/HeadsetObserver.java | 19 +- .../android/server/InputMethodManagerService.java | 104 ++- .../com/android/server/LocationManagerService.java | 2 +- services/java/com/android/server/MountService.java | 6 +- .../com/android/server/PackageManagerService.java | 42 +- .../com/android/server/PowerManagerService.java | 143 ++- services/java/com/android/server/Watchdog.java | 197 ++--- services/java/com/android/server/WifiService.java | 147 ++-- .../android/server/am/ActivityManagerService.java | 5 +- .../com/android/server/am/PendingIntentRecord.java | 4 +- .../telephony/gsm/ServiceStateTracker.java | 7 +- .../telephony/gsm/stk/CommandParamsFactory.java | 7 +- .../internal/telephony/gsm/stk/IconLoader.java | 8 +- .../telephony/gsm/stk/ImageDescriptor.java | 6 +- .../telephony/gsm/stk/RilMessageDecoder.java | 138 ++- .../internal/telephony/gsm/stk/Service.java | 22 +- .../internal/telephony/gsm/stk/ValueParser.java | 6 +- test-runner/android/test/TouchUtils.java | 348 +++++++- .../android/unit_tests/GoogleHttpClientTest.java | 6 +- .../unit_tests/os/HandlerStateMachineTest.java | 119 +++ tests/CoreTests/android/AndroidManifest.xml | 1 + tests/CoreTests/android/webkit/CookieTest.java | 4 + tests/FrameworkTest/AndroidManifest.xml | 8 + .../res/layout/autocompletetextview_simple.xml | 34 + .../android/widget/AutoCompleteTextViewSimple.java | 122 +++ .../widget/AutoCompleteTextViewCallbacks.java | 110 +++ .../android/widget/SimpleCursorAdapterTest.java | 104 ++- tests/SmokeTest/Android.mk | 16 + tests/SmokeTest/AndroidManifest.xml | 32 + tests/SmokeTest/README | 8 + .../com/android/smoketest/SmokeTestActivity.java | 41 + tests/SmokeTest/tests/Android.mk | 21 + tests/SmokeTest/tests/AndroidManifest.xml | 39 + .../com/android/smoketest/ProcessErrorsTest.java | 117 +++ tools/aapt/Images.cpp | 222 ++++- tools/aapt/Package.cpp | 2 +- tools/aapt/ResourceTable.cpp | 3 +- tools/aidl/aidl.cpp | 2 + tools/aidl/generate_java.cpp | 2 + tools/aidl/options.cpp | 4 + tools/aidl/search_path.cpp | 1 + tools/aidl/search_path.h | 1 + .../src/com/android/layoutlib/bridge/Bridge.java | 96 ++- .../tools/layoutlib/create/AsmGenerator.java | 3 +- .../com/android/tools/layoutlib/create/Main.java | 4 +- .../tools/layoutlib/create/MethodAdapter.java | 91 ++ .../tools/layoutlib/create/MethodListener.java | 76 ++ .../tools/layoutlib/create/OverrideMethod.java | 102 ++- .../tools/layoutlib/create/StubMethodAdapter.java | 97 ++- tools/localize/Perforce.cpp | 2 + tools/localize/XLIFFFile.cpp | 1 + tools/localize/XMLHandler.cpp | 1 + tools/localize/file_utils.cpp | 3 + tools/localize/localize.cpp | 1 + wifi/java/android/net/wifi/IWifiManager.aidl | 2 +- wifi/java/android/net/wifi/WifiManager.java | 81 +- wifi/java/android/net/wifi/WifiStateTracker.java | 176 ++-- 289 files changed, 13302 insertions(+), 3173 deletions(-) delete mode 100755 cmds/dumpstate/dumpstate create mode 100644 cmds/dumpstate/dumpstate.c create mode 100644 cmds/dumpstate/dumpstate.h create mode 100644 cmds/dumpstate/utils.c create mode 100644 cmds/ime/Android.mk create mode 100644 cmds/ime/MODULE_LICENSE_APACHE2 create mode 100644 cmds/ime/NOTICE create mode 100755 cmds/ime/ime create mode 100644 cmds/ime/src/com/android/commands/ime/Ime.java create mode 100644 core/java/android/os/HandlerState.java create mode 100644 core/java/android/os/HandlerStateMachine.java create mode 100644 core/java/android/speech/RecognizerIntent.java create mode 100644 core/java/android/text/style/UpdateAppearance.java create mode 100644 core/java/android/util/PrintStreamPrinter.java create mode 100644 core/jni/android_media_JetPlayer.cpp create mode 100644 core/res/res/drawable/ic_menu_login.png create mode 100644 core/res/res/drawable/ic_menu_notifications.png create mode 100644 core/res/res/drawable/ic_menu_refresh.png create mode 100644 core/res/res/drawable/ratingbar_full_empty.xml create mode 100644 core/res/res/drawable/ratingbar_full_filled.xml create mode 100644 core/res/res/drawable/seek_thumb.xml rename core/res/res/drawable/{seek_thumb.png => seek_thumb_normal.png} (100%) create mode 100644 core/res/res/drawable/seek_thumb_pressed.png create mode 100644 core/res/res/drawable/seek_thumb_selected.png create mode 100644 core/res/res/values-fr/arrays.xml create mode 100644 core/res/res/values-fr/strings.xml create mode 100644 core/res/res/values-it/arrays.xml create mode 100644 core/res/res/values-it/strings.xml create mode 100644 core/res/res/values-mcc204-fr/strings.xml create mode 100644 core/res/res/values-mcc204-it/strings.xml create mode 100644 core/res/res/values-mcc204-zh-rCN/strings.xml create mode 100644 core/res/res/values-mcc230-fr/strings.xml create mode 100644 core/res/res/values-mcc230-it/strings.xml create mode 100644 core/res/res/values-mcc230-zh-rCN/strings.xml create mode 100644 core/res/res/values-mcc232-fr/strings.xml create mode 100644 core/res/res/values-mcc232-it/strings.xml create mode 100644 core/res/res/values-mcc232-zh-rCN/strings.xml create mode 100644 core/res/res/values-mcc234-fr/strings.xml create mode 100644 core/res/res/values-mcc234-it/strings.xml create mode 100644 core/res/res/values-mcc234-zh-rCN/strings.xml create mode 100644 core/res/res/values-mcc260-fr/strings.xml create mode 100644 core/res/res/values-mcc260-it/strings.xml create mode 100644 core/res/res/values-mcc260-zh-rCN/strings.xml create mode 100644 core/res/res/values-mcc262-fr/strings.xml create mode 100644 core/res/res/values-mcc262-it/strings.xml create mode 100644 core/res/res/values-mcc262-zh-rCN/strings.xml create mode 100644 core/res/res/values-zh-rCN/arrays.xml create mode 100644 core/res/res/values-zh-rCN/strings.xml create mode 100644 core/res/res/xml-en/autotext.xml rename data/etc/{permissions.xml => platform.xml} (96%) create mode 100644 include/media/JetPlayer.h create mode 100644 media/java/android/media/JetPlayer.java create mode 100644 media/java/android/media/ResampleInputStream.java create mode 100644 media/jni/android_media_ResampleInputStream.cpp create mode 100644 media/libmedia/JetPlayer.cpp create mode 100644 tests/AndroidTests/src/com/android/unit_tests/os/HandlerStateMachineTest.java create mode 100644 tests/FrameworkTest/res/layout/autocompletetextview_simple.xml create mode 100644 tests/FrameworkTest/src/android/widget/AutoCompleteTextViewSimple.java create mode 100644 tests/FrameworkTest/tests/src/android/widget/AutoCompleteTextViewCallbacks.java create mode 100644 tests/SmokeTest/Android.mk create mode 100644 tests/SmokeTest/AndroidManifest.xml create mode 100644 tests/SmokeTest/README create mode 100644 tests/SmokeTest/src/com/android/smoketest/SmokeTestActivity.java create mode 100644 tests/SmokeTest/tests/Android.mk create mode 100644 tests/SmokeTest/tests/AndroidManifest.xml create mode 100644 tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java create mode 100644 tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodAdapter.java create mode 100644 tools/layoutlib/create/src/com/android/tools/layoutlib/create/MethodListener.java diff --git a/api/current.xml b/api/current.xml index 880b6e82136c..13ca37036b72 100644 --- a/api/current.xml +++ b/api/current.xml @@ -6044,7 +6044,7 @@ value="16843269" static="true" final="true" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > @@ -8992,6 +8992,17 @@ visibility="public" > + + + + + + + + - + + + + + + + + + + + + @@ -93808,7 +93882,7 @@ synchronized="false" static="true" final="false" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > @@ -93824,6 +93898,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -93847,7 +93972,9 @@ deprecated="not deprecated" visibility="public" > - + + + @@ -93871,6 +93998,27 @@ + + + + + + + + + + + + @@ -93892,6 +94040,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -93934,7 +94171,7 @@ deprecated="not deprecated" visibility="public" > - + @@ -93949,7 +94186,7 @@ deprecated="not deprecated" visibility="public" > - + @@ -93963,7 +94200,7 @@ synchronized="false" static="true" final="false" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > @@ -93975,7 +94212,7 @@ - + + + + + + + + + + @@ -93994,6 +94250,25 @@ + + + + + + + + + + @@ -94009,6 +94284,36 @@ + + + + + + + + + + + + + + + + + + + + @@ -94034,7 +94356,9 @@ deprecated="not deprecated" visibility="public" > - + + + @@ -98115,6 +98439,17 @@ visibility="public" > + + - - - + + + + + + + + + + + + + + + + + + + + + + @@ -102247,7 +102622,7 @@ type="int" transient="false" volatile="false" - value="114688" + value="524288" static="true" final="true" deprecated="not deprecated" @@ -102265,6 +102640,17 @@ visibility="public" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -106165,6 +106725,18 @@ > + + + + + + @@ -106175,10 +106747,38 @@ deprecated="not deprecated" visibility="public" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + - + @@ -130793,7 +131513,7 @@ return="void" abstract="false" native="false" - synchronized="true" + synchronized="false" static="false" final="false" deprecated="not deprecated" @@ -130804,7 +131524,7 @@ return="void" abstract="false" native="false" - synchronized="true" + synchronized="false" static="false" final="false" deprecated="not deprecated" @@ -130815,7 +131535,7 @@ return="void" abstract="false" native="false" - synchronized="true" + synchronized="false" static="false" final="false" deprecated="not deprecated" @@ -144252,7 +144972,7 @@ - + @@ -144992,6 +145712,19 @@ + + + + - + + + + + + + + + CameraService::connect(const sp& cameraClient) } // create a new Client object - sp client = new Client(this, cameraClient); + sp client = new Client(this, cameraClient, IPCThreadState::self()->getCallingPid()); mClient = client; #if DEBUG_CLIENT_REFERENCES // Enable tracking for this object, and track increments and decrements of @@ -151,10 +151,12 @@ void CameraService::removeClient(const sp& cameraClient) } CameraService::Client::Client(const sp& cameraService, - const sp& cameraClient) : - mCameraService(cameraService), mCameraClient(cameraClient), mHardware(0) + const sp& cameraClient, pid_t clientPid) { LOGD("Client E constructor"); + mCameraService = cameraService; + mCameraClient = cameraClient; + mClientPid = clientPid; mHardware = openCameraHardware(); // Callback is disabled by default @@ -162,12 +164,36 @@ CameraService::Client::Client(const sp& cameraService, LOGD("Client X constructor"); } +status_t CameraService::Client::checkPid() +{ + // zero means the interface is not locked down + if (mClientPid == 0) return NO_ERROR; + return (int) mClientPid == IPCThreadState::self()->getCallingPid() ? NO_ERROR : -EBUSY; +} + +status_t CameraService::Client::lock() +{ + // lock camera to this client + status_t result = checkPid(); + if (result == NO_ERROR) mClientPid = IPCThreadState::self()->getCallingPid(); + return result; +} + +status_t CameraService::Client::unlock() +{ + // allow anyone to use camera + status_t result = checkPid(); + if (result == NO_ERROR) mClientPid = 0; + return result; +} + status_t CameraService::Client::connect(const sp& client) { - // remvoe old client - LOGD("connect (new client)"); + // remove old client + LOGV("connect new client to existing camera"); Mutex::Autolock _l(mLock); mCameraClient = client; + mClientPid = IPCThreadState::self()->getCallingPid(); mFrameCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; return NO_ERROR; } @@ -209,6 +235,7 @@ void CameraService::Client::disconnect() { LOGD("Client E disconnect"); Mutex::Autolock lock(mLock); + if (checkPid() != NO_ERROR) return; mCameraService->removeClient(mCameraClient); if (mHardware != 0) { // Before destroying mHardware, we must make sure it's in the @@ -228,6 +255,8 @@ status_t CameraService::Client::setPreviewDisplay(const sp& surface) { LOGD("setPreviewDisplay(%p)", surface.get()); Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; Mutex::Autolock surfaceLock(mSurfaceLock); // asBinder() is safe on NULL (returns NULL) if (surface->asBinder() != mSurface->asBinder()) { @@ -245,6 +274,7 @@ status_t CameraService::Client::setPreviewDisplay(const sp& surface) void CameraService::Client::setFrameCallbackFlag(int frame_callback_flag) { Mutex::Autolock lock(mLock); + if (checkPid() != NO_ERROR) return; mFrameCallbackFlag = frame_callback_flag; } @@ -258,6 +288,8 @@ status_t CameraService::Client::startPreview() */ Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; if (mHardware == 0) { LOGE("mHardware is NULL, returning."); @@ -269,14 +301,15 @@ status_t CameraService::Client::startPreview() return INVALID_OPERATION; } + // do nothing if preview is already started + if (mHardware->previewEnabled()) return NO_ERROR; + // XXX: This needs to be improved. remove all hardcoded stuff int w, h; CameraParameters params(mHardware->getParameters()); params.getPreviewSize(&w, &h); - mSurface->unregisterBuffers(); - #if DEBUG_DUMP_PREVIEW_FRAME_TO_FILE debug_frame_cnt = 0; #endif @@ -284,6 +317,7 @@ status_t CameraService::Client::startPreview() status_t ret = mHardware->startPreview(previewCallback, mCameraService.get()); if (ret == NO_ERROR) { + mSurface->unregisterBuffers(); mSurface->registerBuffers(w,h,w,h, PIXEL_FORMAT_YCbCr_420_SP, mHardware->getPreviewHeap()); @@ -300,6 +334,7 @@ void CameraService::Client::stopPreview() LOGD("stopPreview()"); Mutex::Autolock lock(mLock); + if (checkPid() != NO_ERROR) return; if (mHardware == 0) { LOGE("mHardware is NULL, returning."); @@ -315,6 +350,13 @@ void CameraService::Client::stopPreview() mPreviewBuffer.clear(); } +bool CameraService::Client::previewEnabled() +{ + Mutex::Autolock lock(mLock); + if (mHardware == 0) return false; + return mHardware->previewEnabled(); +} + // Safely retrieves a strong pointer to the client during a hardware callback. sp CameraService::Client::getClientFromCookie(void* user) { @@ -424,6 +466,8 @@ status_t CameraService::Client::autoFocus() LOGV("autoFocus"); Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; if (mHardware == 0) { LOGE("mHardware is NULL, returning."); @@ -440,6 +484,8 @@ status_t CameraService::Client::takePicture() LOGD("takePicture"); Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; if (mHardware == 0) { LOGE("mHardware is NULL, returning."); @@ -580,6 +626,8 @@ status_t CameraService::Client::setParameters(const String8& params) LOGD("setParameters(%s)", params.string()); Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; if (mHardware == 0) { LOGE("mHardware is NULL, returning."); diff --git a/camera/libcameraservice/CameraService.h b/camera/libcameraservice/CameraService.h index b225aa9f3449..cd8c1e9a41cf 100644 --- a/camera/libcameraservice/CameraService.h +++ b/camera/libcameraservice/CameraService.h @@ -73,6 +73,12 @@ private: // connect new client with existing camera remote virtual status_t connect(const sp& client); + // prevent other processes from using this ICamera interface + virtual status_t lock(); + + // allow other processes to use this ICamera interface + virtual status_t unlock(); + // pass the buffered ISurface to the camera service virtual status_t setPreviewDisplay(const sp& surface); @@ -86,6 +92,9 @@ private: // stop preview mode virtual void stopPreview(); + // get preview state + virtual bool previewEnabled(); + // auto focus virtual status_t autoFocus(); @@ -103,11 +112,14 @@ private: private: friend class CameraService; - Client( const sp& cameraService, - const sp& cameraClient); + Client(const sp& cameraService, + const sp& cameraClient, + pid_t clientPid); Client(); virtual ~Client(); + status_t checkPid(); + static void previewCallback(const sp& mem, void* user); static void shutterCallback(void *user); static void yuvPictureCallback(const sp& mem, void* user); @@ -132,7 +144,7 @@ private: // by the CameraHardwareInterface callback, and needs to // access mSurface. It cannot hold mLock, however, because // stopPreview() may be holding that lock while attempting - // top stop preview, and stopPreview itself will block waiting + // to stop preview, and stopPreview itself will block waiting // for a callback from CameraHardwareInterface. If this // happens, it will cause a deadlock. mutable Mutex mSurfaceLock; @@ -146,6 +158,7 @@ private: // they don't need to be protected by a lock sp mCameraClient; sp mHardware; + pid_t mClientPid; }; // ---------------------------------------------------------------------------- diff --git a/cmds/dumpstate/Android.mk b/cmds/dumpstate/Android.mk index e101aebf7c1e..f61d7ecb3f5a 100644 --- a/cmds/dumpstate/Android.mk +++ b/cmds/dumpstate/Android.mk @@ -1,11 +1,18 @@ +ifneq ($(TARGET_SIMULATOR),true) + LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -ALL_PREBUILT += $(TARGET_OUT)/bin/dumpstate -$(TARGET_OUT)/bin/dumpstate : $(LOCAL_PATH)/dumpstate | $(ACP) - $(transform-prebuilt-to-target) +LOCAL_SRC_FILES:= dumpstate.c utils.c + +LOCAL_MODULE:= dumpstate + +LOCAL_SHARED_LIBRARIES := libcutils -SYMLINKS := $(TARGET_OUT_EXECUTABLES)/dumpcrash +include $(BUILD_EXECUTABLE) + +COMMANDS = dumpcrash bugreport +SYMLINKS := $(addprefix $(TARGET_OUT_EXECUTABLES)/,$(COMMANDS)) $(SYMLINKS): DUMPSTATE_BINARY := dumpstate $(SYMLINKS): $(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk @echo "Symlink: $@ -> $(DUMPSTATE_BINARY)" @@ -14,3 +21,5 @@ $(SYMLINKS): $(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk $(hide) ln -sf $(DUMPSTATE_BINARY) $@ ALL_DEFAULT_INSTALLED_MODULES += $(SYMLINKS) + +endif diff --git a/cmds/dumpstate/dumpstate b/cmds/dumpstate/dumpstate deleted file mode 100755 index 8dd7d140dcba..000000000000 --- a/cmds/dumpstate/dumpstate +++ /dev/null @@ -1,114 +0,0 @@ -#!/system/bin/sh - -# Dumps to /tmp/state by default. -# Specify "-" to dump to stdout, or any file name to dump to that file. - -crashmode=0 -case $0 in - dumpcrash|*/dumpcrash) crashmode=1 ;; -esac - -case $1 in - "") echo "DUMPING STATE TO /tmp/state"; exec 1>/tmp/state ;; - -) ;; - *) echo "DUMPING STATE TO $1"; exec 1>$1 ;; -esac - -case $crashmode in 0) - echo "========================================================" - echo "== dumpstate" - echo "========================================================" - echo "------ SYSTEM LOG ------" - logcat -v time -d '*:v' - echo "------ VM TRACES ------" - cat /data/anr/traces.txt - echo "------ EVENT LOG TAGS ------" - cat /etc/event-log-tags - echo "------ EVENT LOG ------" - logcat -b events -v time -d '*:v' - echo "------ RADIO LOG ------" - logcat -b radio -v time -d '*:v' - echo "------ NETWORK STATE ------" - echo "Interfaces:" - netcfg - echo "" - echo "Routes:" - cat /proc/net/route - echo "------ SYSTEM PROPERTIES ------" - getprop - echo "------ KERNEL LOG ------" - dmesg - echo "------ KERNEL WAKELOCKS ------" - cat /proc/wakelocks - echo "" - echo "------ PROCESSES ------" - ps - echo "------ PROCESSES AND THREADS ------" - ps -t -p - echo "------ MEMORY INFO ------" - cat /proc/meminfo - echo "------ PSS INFO ------" - top -n 1 -d 0 -m 15 -s pss - echo "------ PROCRANK ------" - procrank - echo "------ LIBRANK ------" - librank - echo "------ VIRTUAL MEMORY STATS ------" - cat /proc/vmstat - echo "------ SLAB INFO ------" - cat /proc/slabinfo - echo "------ ZONEINFO ------" - cat /proc/zoneinfo - echo "------ BINDER FAILED TRANSACTION LOG ------" - cat /proc/binder/failed_transaction_log - echo "" - echo "------ BINDER TRANSACTION LOG ------" - cat /proc/binder/transaction_log - echo "" - echo "------ BINDER TRANSACTIONS ------" - cat /proc/binder/transactions - echo "" - echo "------ BINDER STATS ------" - cat /proc/binder/stats - echo "" - for i in `ls /proc/binder/proc`; do - echo "------ BINDER PROCESS STATE: $i ------" - cat /proc/binder/proc/$i - echo "" - done - echo "------ FILESYSTEMS ------" - df - echo "------ PACKAGE SETTINGS ------" - cat /data/system/packages.xml - echo "------ PACKAGE UID ERRORS ------" - cat /data/system/uiderrors.txt - echo "------ LAST KERNEL LOG ------" - cat /proc/last_kmsg -;; esac - -echo "========================================================" -echo "== build.prop" -echo "========================================================" - -# the crash server parses key-value pairs between the VERSION INFO and -# END lines so we can aggregate crash reports based on this data. -echo "------ VERSION INFO ------" -echo "currenttime=`date`" -echo "kernel.version=`cat /proc/version`" -echo "kernel.cmdline=`cat /proc/cmdline`" -cat /system/build.prop -echo "gsm.version.ril-impl=`getprop gsm.version.ril-impl`" -echo "gsm.version.baseband=`getprop gsm.version.baseband`" -echo "gsm.imei=`getprop gsm.imei`" -echo "gsm.sim.operator.numeric=`getprop gsm.sim.operator.numeric`" -echo "gsm.operator.alpha=`getprop gsm.operator.alpha`" -echo "------ END ------" - -case $crashmode in 0) - echo "========================================================" - echo "== dumpsys" - echo "========================================================" - dumpsys -;; esac - -exit 0 diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c new file mode 100644 index 000000000000..dea269dcf3be --- /dev/null +++ b/cmds/dumpstate/dumpstate.c @@ -0,0 +1,291 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "private/android_filesystem_config.h" + +#include "dumpstate.h" + +static char* const gzip_args[] = { "gzip", "-6", 0 }; +static int start_pattern[] = { 150, 0 }; +static int end_pattern[] = { 75, 50, 75, 50, 75, 0 }; + +static struct tm now; + +/* dumps the current system state to stdout */ +static void dumpstate(int full) { + if (full) { + PRINT("========================================================"); + PRINT("== dumpstate"); + PRINT("========================================================"); + PRINT("------ SYSTEM LOG ------"); + EXEC4("logcat", "-v", "time", "-d", "*:v"); + PRINT("------ VM TRACES ------"); + DUMP("/data/anr/traces.txt"); + PRINT("------ EVENT LOG TAGS ------"); + DUMP("/etc/event-log-tags"); + PRINT("------ EVENT LOG ------"); + EXEC6("logcat", "-b", "events", "-v", "time", "-d", "*:v"); + PRINT("------ RADIO LOG ------"); + EXEC6("logcat", "-b", "radio", "-v", "time", "-d", "*:v"); + PRINT("------ NETWORK STATE ------"); + PRINT("Interfaces:"); + EXEC("netcfg"); + PRINT(""); + PRINT("Routes:"); + DUMP("/proc/net/route"); + PRINT("------ SYSTEM PROPERTIES ------"); + print_properties(); + PRINT("------ KERNEL LOG ------"); + EXEC("dmesg"); + PRINT("------ KERNEL WAKELOCKS ------"); + DUMP("/proc/wakelocks"); + PRINT(""); + PRINT("------ PROCESSES ------"); + EXEC("ps"); + PRINT("------ PROCESSES AND THREADS ------"); + EXEC2("ps", "-t", "-p"); + PRINT("------ MEMORY INFO ------"); + DUMP("/proc/meminfo"); + PRINT("------ PSS INFO ------"); + EXEC8("top", "-n", "1", "-d", "0", "-m", "15", "-s", "pss"); + PRINT("------ PROCRANK ------"); + EXEC("procrank"); + PRINT("------ LIBRANK ------"); + EXEC("librank"); + PRINT("------ VIRTUAL MEMORY STATS ------"); + DUMP("/proc/vmstat"); + PRINT("------ SLAB INFO ------"); + DUMP("/proc/slabinfo"); + PRINT("------ ZONEINFO ------"); + DUMP("/proc/zoneinfo"); + PRINT("------ BINDER FAILED TRANSACTION LOG ------"); + DUMP("/proc/binder/failed_transaction_log"); + PRINT(""); + PRINT("------ BINDER TRANSACTION LOG ------"); + DUMP("/proc/binder/transaction_log"); + PRINT(""); + PRINT("------ BINDER TRANSACTIONS ------"); + DUMP("/proc/binder/transactions"); + PRINT(""); + PRINT("------ BINDER STATS ------"); + DUMP("/proc/binder/stats"); + PRINT(""); + PRINT("------ BINDER PROCESS STATE: $i ------"); + DUMP_FILES("/proc/binder/proc"); + PRINT("------ FILESYSTEMS ------"); + EXEC("df"); + PRINT("------ PACKAGE SETTINGS ------"); + DUMP("/data/system/packages.xml"); + PRINT("------ PACKAGE UID ERRORS ------"); + DUMP("/data/system/uiderrors.txt"); + PRINT("------ LAST KERNEL LOG ------"); + DUMP("/proc/last_kmsg"); + } + PRINT("========================================================"); + PRINT("== build.prop"); + PRINT("========================================================"); + + /* the crash server parses key-value pairs between the VERSION INFO and + * END lines so we can aggregate crash reports based on this data. + */ + PRINT("------ VERSION INFO ------"); + print_date("currenttime=", &now); + DUMP_PROMPT("kernel.version=", "/proc/version"); + DUMP_PROMPT("kernel.cmdline=", "/proc/cmdline"); + DUMP("/system/build.prop"); + PROPERTY("gsm.version.ril-impl"); + PROPERTY("gsm.version.baseband"); + PROPERTY("gsm.imei"); + PROPERTY("gsm.sim.operator.numeric"); + PROPERTY("gsm.operator.alpha"); + PRINT("------ END ------"); + + if (full) { + PRINT("========================================================"); + PRINT("== dumpsys"); + PRINT("========================================================"); + EXEC("dumpsys"); + } +} + +/* used to check the file name passed via argv[0] */ +static int check_command_name(const char* name, const char* test) { + int name_length, test_length; + + if (!strcmp(name, test)) + return 1; + + name_length = strlen(name); + test_length = strlen(test); + + if (name_length > test_length + 2) { + name += (name_length - test_length); + if (name[-1] != '/') + return 0; + if (!strcmp(name, test)) + return 1; + } + + return 0; +} + +int main(int argc, char *argv[]) { + int dumpcrash = check_command_name(argv[0], "dumpcrash"); + int bugreport = check_command_name(argv[0], "bugreport"); + int add_date = 0; + char* outfile = 0; + int vibrate = 0; + int compress = 0; + int c, fd, vibrate_fd, fds[2]; + char path[PATH_MAX]; + pid_t pid; + + /* set as high priority, and protect from OOM killer */ + setpriority(PRIO_PROCESS, 0, -20); + protect_from_oom_killer(); + + get_time(&now); + + if (bugreport) { + do { + c = getopt(argc, argv, "do:vz"); + if (c == EOF) + break; + switch (c) { + case 'd': + add_date = 1; + break; + case 'o': + outfile = optarg; + break; + case 'v': + vibrate = 1; + break; + case 'z': + compress = 1; + break; + case '?': + fprintf(stderr, "%s: invalid option -%c\n", + argv[0], optopt); + exit(1); + } + } while (1); + } + + /* open vibrator before switching user */ + if (vibrate) { + vibrate_fd = open("/sys/class/timed_output/vibrator/enable", O_WRONLY); + if (vibrate_fd > 0) + fcntl(vibrate_fd, F_SETFD, FD_CLOEXEC); + } else + vibrate_fd = -1; + + /* switch to non-root user and group */ + setgid(AID_LOG); + setuid(AID_SHELL); + + /* make it safe to use both printf and STDOUT_FILENO */ + setvbuf(stdout, 0, _IONBF, 0); + + if (outfile) { + if (strlen(outfile) > sizeof(path) - 100) + exit(1); + + strcpy(path, outfile); + if (add_date) { + char date[260]; + strftime(date, sizeof(date), + "-%Y-%m-%d-%H-%M-%S", + &now); + strcat(path, date); + } + if (compress) + strcat(path, ".gz"); + + /* ensure that all directories in the path exist */ + create_directories(path); + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) + return fd; + + if (compress) { + pipe(fds); + + /* redirect our stdout to the pipe */ + dup2(fds[1], STDOUT_FILENO); + close(fds[1]); + + if ((pid = fork()) < 0) + { + fprintf(stderr, "fork error\n"); + exit(1); + } + + if (pid) { + /* parent case */ + + /* close our copy of the input to gzip */ + close(fds[0]); + /* close our copy of the output file */ + close(fd); + } else { + /* child case */ + + /* redirect our input pipe to stdin */ + dup2(fds[0], STDIN_FILENO); + close(fds[0]); + + /* redirect stdout to the output file */ + dup2(fd, STDOUT_FILENO); + close(fd); + + /* run gzip to postprocess our output */ + execv("/system/bin/gzip", gzip_args); + fprintf(stderr, "execv returned\n"); + } + } else { + /* redirect stdout to the output file */ + dup2(fd, STDOUT_FILENO); + close(fd); + } + } + /* else everything will print to stdout */ + + if (vibrate) { + vibrate_pattern(vibrate_fd, start_pattern); + } + dumpstate(!dumpcrash); + if (vibrate) { + vibrate_pattern(vibrate_fd, end_pattern); + close(vibrate_fd); + } + + /* so gzip will terminate */ + close(STDOUT_FILENO); + + return 0; +} + diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h new file mode 100644 index 000000000000..b956f99ce9db --- /dev/null +++ b/cmds/dumpstate/dumpstate.h @@ -0,0 +1,120 @@ +/* + * 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. + */ + +#ifndef _DUMPSTATE_H_ +#define _DUMPSTATE_H_ + +#include + +// Commands time out after 15 seconds +#define TIMEOUT 15 + +#define PRINT(s) printf("%s\n", s) + +#define DUMP(file) dump_file(file) + +#define DUMP_FILES(path) dump_files(path) + +#define DUMP_PROMPT(prompt, file) \ +{ \ + printf(prompt); \ + dump_file(file); \ +} + +#define EXEC(cmd) \ +{ \ + static struct Command c = { \ + "/system/bin/" cmd, \ + { cmd, 0 } \ + }; \ + run_command(&c, TIMEOUT); \ +} + +#define EXEC2(cmd, a1, a2) \ +{ \ + static struct Command c = { \ + "/system/bin/" cmd, \ + { cmd, a1, a2, 0 } \ + }; \ + run_command(&c, TIMEOUT); \ +} + +#define EXEC4(cmd, a1, a2, a3, a4) \ +{ \ + static struct Command c = { \ + "/system/bin/" cmd, \ + { cmd, a1, a2, a3, a4, 0 } \ + }; \ + run_command(&c, TIMEOUT); \ +} + +#define EXEC6(cmd, a1, a2, a3, a4, a5, a6) \ +{ \ + static struct Command c = { \ + "/system/bin/" cmd, \ + { cmd, a1, a2, a3, a4, a5, a6, 0 } \ + }; \ + run_command(&c, TIMEOUT); \ +} + +#define EXEC8(cmd, a1, a2, a3, a4, a5, a6, a7, a8) \ +{ \ + static struct Command c = { \ + "/system/bin/" cmd, \ + { cmd, a1, a2, a3, a4, a5, a6, a7, a8, 0 } \ + }; \ + run_command(&c, TIMEOUT); \ +} + +#define PROPERTY(name) print_property(name) + +struct Command { + const char* path; + char* const args[]; +}; +typedef struct Command Command; + +/* prints the contents of a file */ +int dump_file(const char* path); + +/* prints the contents of all files in a directory */ +void dump_files(const char* path); + +/* forks a command and waits for it to finish */ +int run_command(struct Command* cmd, int timeout); + +/* reads the current time into tm */ +void get_time(struct tm *tm); + +/* prints the date in tm */ +void print_date(const char* prompt, struct tm *tm); + +/* prints the name and value of a system property */ +int print_property(const char* name); + +/* prints all the system properties */ +void print_properties(); + +/* creates directories as needed for the given path */ +void create_directories(char *path); + +/* runs the vibrator using the given pattern */ +void vibrate_pattern(int fd, int* pattern); + +/* prevents the OOM killer from killing us */ +void protect_from_oom_killer(); + +#endif /* _DUMPSTATE_H_ */ diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c new file mode 100644 index 000000000000..60d845f61bf3 --- /dev/null +++ b/cmds/dumpstate/utils.c @@ -0,0 +1,230 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dumpstate.h" + + +/* prints the contents of a file */ +int dump_file(const char* path) { + char buffer[32768]; + int fd, amount_read; + int ret = 0; + + fd = open(path, O_RDONLY); + if (fd < 0) + return fd; + + do { + ret = read(fd, buffer, sizeof(buffer)); + if (ret > 0) + ret = write(STDOUT_FILENO, buffer, ret); + } while (ret > 0); + + buffer[0] = '\n'; + write(STDOUT_FILENO, buffer, 1); + + close(fd); + return ret; +} + +/* prints the contents of all files in a directory */ +void dump_files(const char* path) { + DIR* dir; + struct dirent* entry; + char buffer[PATH_MAX]; + + dir = opendir(path); + if (!dir) { + fprintf(stderr, "could not open directory %s\n", path); + return; + } + + while ((entry = readdir(dir))) { + if (entry->d_type == DT_REG) { + snprintf(buffer, sizeof(buffer), "%s/%s", path, entry->d_name); + dump_file(path); + printf("\n"); + } + } + + closedir(dir); +} + +/* prints the name and value of a system property */ +int print_property(const char* name) { + char value[PROP_VALUE_MAX]; + + __system_property_get(name, value); + printf("%s=%s\n", name, value); + return 0; +} + +static pid_t alarm_pid = 0; +static int timed_out = 0; +static void sig_alarm(int sig) +{ + if (alarm_pid) { + kill(alarm_pid, SIGKILL); + timed_out = 1; + alarm_pid = 0; + } +} + +/* forks a command and waits for it to finish */ +int run_command(struct Command* cmd, int timeout) { + struct sigaction sa; + pid_t pid; + int status; + + pid = fork(); + /* handle error case */ + if (pid < 0) + return pid; + + /* handle child case */ + if (pid == 0) { + int ret = execv(cmd->path, cmd->args); + if (ret) + fprintf(stderr, "execv %s returned %d\n", cmd->path, ret); + exit(ret); + } + + /* handle parent case */ + timed_out = 0; + if (timeout) { + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_RESETHAND; + sa.sa_handler = sig_alarm; + sigaction(SIGALRM, &sa, NULL); + + /* set an alarm so we don't hang forever */ + alarm_pid = pid; + alarm(timeout); + } + + waitpid(pid, &status, 0); + + if (timed_out) + printf("ERROR: command %s timed out\n", cmd->path); + + return status; +} + +/* reads the current time into tm */ +void get_time(struct tm *tm) { + time_t t; + + tzset(); + time(&t); + localtime_r(&t, tm); +} + +/* prints the date in tm */ +void print_date(const char* prompt, struct tm *tm) { + char strbuf[260]; + + strftime(strbuf, sizeof(strbuf), + "%a %b %e %H:%M:%S %Z %Y", + tm); + printf("%s%s\n", prompt, strbuf); +} + + +static void print_prop(const char *key, const char *name, + void *user __attribute__((unused))) +{ + printf("[%s]: [%s]\n", key, name); +} + +/* prints all the system properties */ +void print_properties() { + property_list(print_prop, NULL); +} + +/* creates directories as needed for the given path */ +void create_directories(char *path) +{ + char *chp = path; + + /* skip initial slash */ + if (chp[0] == '/') + chp++; + + while (chp && chp[0]) { + chp = strchr(chp, '/'); + if (chp) { + *chp = 0; + mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + *chp = '/'; + chp++; + } + } +} + +/* runs the vibrator using the given pattern */ +void vibrate_pattern(int fd, int* pattern) +{ + struct timespec tm; + char buffer[10]; + + while (*pattern) { + /* read vibrate on time */ + int on_time = *pattern++; + snprintf(buffer, sizeof(buffer), "%d", on_time); + write(fd, buffer, strlen(buffer)); + + /* read vibrate off time */ + int delay = *pattern++; + if (delay) { + delay += on_time; + + tm.tv_sec = delay / 1000; + tm.tv_nsec = (delay % 1000) * 1000000; + nanosleep(&tm, NULL); + } else + break; + } +} + +/* prevents the OOM killer from killing us */ +void protect_from_oom_killer() +{ + int fd; + + fd = open("/proc/self/oom_adj", O_WRONLY); + if (fd >= 0) { + // -17 should make us immune to OOM + const char* text = "-17"; + write(fd, text, strlen(text)); + close(fd); + } +} diff --git a/cmds/ime/Android.mk b/cmds/ime/Android.mk new file mode 100644 index 000000000000..90b1c9106cbc --- /dev/null +++ b/cmds/ime/Android.mk @@ -0,0 +1,15 @@ +# Copyright 2007 The Android Open Source Project +# +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_MODULE := ime +include $(BUILD_JAVA_LIBRARY) + + +include $(CLEAR_VARS) +ALL_PREBUILT += $(TARGET_OUT)/bin/ime +$(TARGET_OUT)/bin/ime : $(LOCAL_PATH)/ime | $(ACP) + $(transform-prebuilt-to-target) + diff --git a/cmds/ime/MODULE_LICENSE_APACHE2 b/cmds/ime/MODULE_LICENSE_APACHE2 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cmds/ime/NOTICE b/cmds/ime/NOTICE new file mode 100644 index 000000000000..c5b1efa7aac7 --- /dev/null +++ b/cmds/ime/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/cmds/ime/ime b/cmds/ime/ime new file mode 100755 index 000000000000..96c56d3bde0a --- /dev/null +++ b/cmds/ime/ime @@ -0,0 +1,7 @@ +# Script to start "pm" on the device, which has a very rudimentary +# shell. +# +base=/system +export CLASSPATH=$base/framework/ime.jar +exec app_process $base/bin com.android.commands.ime.Ime "$@" + diff --git a/cmds/ime/src/com/android/commands/ime/Ime.java b/cmds/ime/src/com/android/commands/ime/Ime.java new file mode 100644 index 000000000000..72a0af6c2d99 --- /dev/null +++ b/cmds/ime/src/com/android/commands/ime/Ime.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.ime; + +import com.android.internal.view.IInputMethodManager; + +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.PrintStreamPrinter; +import android.util.Printer; +import android.view.inputmethod.InputMethodInfo; + +import java.util.List; + +public final class Ime { + IInputMethodManager mImm; + + private String[] mArgs; + private int mNextArg; + private String mCurArgData; + + private static final String IMM_NOT_RUNNING_ERR = + "Error: Could not access the Input Method Manager. Is the system running?"; + + public static void main(String[] args) { + new Ime().run(args); + } + + public void run(String[] args) { + if (args.length < 1) { + showUsage(); + return; + } + + mImm = IInputMethodManager.Stub.asInterface(ServiceManager.getService("input_method")); + if (mImm == null) { + System.err.println(IMM_NOT_RUNNING_ERR); + return; + } + + mArgs = args; + String op = args[0]; + mNextArg = 1; + + if ("list".equals(op)) { + runList(); + return; + } + + if ("enable".equals(op)) { + runSetEnabled(true); + return; + } + + if ("disable".equals(op)) { + runSetEnabled(false); + return; + } + + if ("set".equals(op)) { + runSet(); + return; + } + + if (op != null) { + System.err.println("Error: unknown command '" + op + "'"); + } + showUsage(); + } + + /** + * Execute the list sub-command. + */ + private void runList() { + String opt; + boolean all = false; + boolean brief = false; + while ((opt=nextOption()) != null) { + if (opt.equals("-a")) { + all = true; + } else if (opt.equals("-s")) { + brief = true; + } else { + System.err.println("Error: Unknown option: " + opt); + showUsage(); + return; + } + } + + + List methods; + if (!all) { + try { + methods = mImm.getEnabledInputMethodList(); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(IMM_NOT_RUNNING_ERR); + return; + } + } else { + try { + methods = mImm.getInputMethodList(); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(IMM_NOT_RUNNING_ERR); + return; + } + } + + if (methods != null) { + Printer pr = new PrintStreamPrinter(System.out); + for (int i=0; i= mArgs.length) { + return null; + } + String arg = mArgs[mNextArg]; + if (!arg.startsWith("-")) { + return null; + } + mNextArg++; + if (arg.equals("--")) { + return null; + } + if (arg.length() > 1 && arg.charAt(1) != '-') { + if (arg.length() > 2) { + mCurArgData = arg.substring(2); + return arg.substring(0, 2); + } else { + mCurArgData = null; + return arg; + } + } + mCurArgData = null; + return arg; + } + + private String nextOptionData() { + if (mCurArgData != null) { + return mCurArgData; + } + if (mNextArg >= mArgs.length) { + return null; + } + String data = mArgs[mNextArg]; + mNextArg++; + return data; + } + + private String nextArg() { + if (mNextArg >= mArgs.length) { + return null; + } + String arg = mArgs[mNextArg]; + mNextArg++; + return arg; + } + + private static void showUsage() { + System.err.println("usage: ime list [-a] [-s]"); + System.err.println(" ime enable ID"); + System.err.println(" ime disable ID"); + System.err.println(" ime set ID"); + System.err.println(""); + System.err.println("The list command prints all enabled input methods. Use"); + System.err.println("the -a option to see all input methods. Use"); + System.err.println("the -s option to see only a single summary line of each."); + System.err.println(""); + System.err.println("The enable command allows the given input method ID to be used."); + System.err.println(""); + System.err.println("The disable command disallows the given input method ID from use."); + System.err.println(""); + System.err.println("The set command switches to the given input method ID."); + } +} diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 09a140b02192..c2d8da519f75 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -94,6 +94,16 @@ public final class Pm { return; } + if ("enable".equals(op)) { + runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + return; + } + + if ("disable".equals(op)) { + runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED); + return; + } + try { if (args.length == 1) { if (args[0].equalsIgnoreCase("-l")) { @@ -111,6 +121,9 @@ public final class Pm { } } finally { if (validCommand == false) { + if (op != null) { + System.err.println("Error: unknown command '" + op + "'"); + } showUsage(); } } @@ -662,6 +675,49 @@ public final class Pm { return obs.result; } + private static String enabledSettingToString(int state) { + switch (state) { + case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: + return "default"; + case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: + return "enabled"; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: + return "disabled"; + } + return "unknown"; + } + + private void runSetEnabledSetting(int state) { + String pkg = nextArg(); + if (pkg == null) { + System.err.println("Error: no package or component specified"); + showUsage(); + return; + } + ComponentName cn = ComponentName.unflattenFromString(pkg); + if (cn == null) { + try { + mPm.setApplicationEnabledSetting(pkg, state, 0); + System.err.println("Package " + pkg + " new state: " + + enabledSettingToString( + mPm.getApplicationEnabledSetting(pkg))); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } else { + try { + mPm.setComponentEnabledSetting(cn, state, 0); + System.err.println("Component " + cn.toShortString() + " new state: " + + enabledSettingToString( + mPm.getComponentEnabledSetting(cn))); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(PM_NOT_RUNNING_ERR); + } + } + } + /** * Displays the package file for a package. * @param pckg @@ -752,6 +808,8 @@ public final class Pm { System.err.println(" pm path PACKAGE"); System.err.println(" pm install [-l] [-r] PATH"); System.err.println(" pm uninstall [-k] PACKAGE"); + System.err.println(" pm enable PACKAGE_OR_COMPONENT"); + System.err.println(" pm disable PACKAGE_OR_COMPONENT"); System.err.println(""); System.err.println("The list packages command prints all packages. Use"); System.err.println("the -f option to see their associated file."); @@ -780,5 +838,8 @@ public final class Pm { System.err.println("The uninstall command removes a package from the system. Use"); System.err.println("the -k option to keep the data and cache directories around"); System.err.println("after the package removal."); + System.err.println(""); + System.err.println("The enable and disable commands change the enabled state of"); + System.err.println("a given package or component (written as \"package/class\")."); } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index eafb0488490f..c98cf1bbd356 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2232,8 +2232,6 @@ public class Activity extends ContextThemeWrapper /** * Programmatically closes the most recently opened context menu, if showing. - * - * @hide pending API council */ public void closeContextMenu() { mWindow.closePanel(Window.FEATURE_CONTEXT_MENU); diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 2ce2db996b89..f1c604cd247e 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -16,14 +16,11 @@ package android.app; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; @@ -38,6 +35,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.server.search.SearchableInfo; import android.text.Editable; +import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; @@ -45,21 +43,13 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; -import android.view.View.OnFocusChangeListener; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; +import android.widget.AutoCompleteTextView; +import android.widget.Button; import android.widget.CursorAdapter; -import android.widget.EditText; -import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListAdapter; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.TextView; @@ -67,8 +57,7 @@ import android.widget.WrapperListAdapter; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; -import java.util.List; -import java.util.concurrent.LinkedBlockingQueue; +import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicLong; /** @@ -77,7 +66,7 @@ import java.util.concurrent.atomic.AtomicLong; * * @hide */ -public class SearchDialog extends Dialog { +public class SearchDialog extends Dialog implements OnItemClickListener, OnItemSelectedListener { // Debugging support final static String LOG_TAG = "SearchDialog"; @@ -87,7 +76,6 @@ public class SearchDialog extends Dialog { // interaction with runtime IntentFilter mCloseDialogsFilter; IntentFilter mPackageFilter; - private final Handler mHandler = new Handler(); // why isn't Dialog.mHandler shared? private static final String INSTANCE_KEY_COMPONENT = "comp"; private static final String INSTANCE_KEY_APPDATA = "data"; @@ -102,15 +90,10 @@ public class SearchDialog extends Dialog { private static final int INSTANCE_SELECTED_QUERY = -1; // views & widgets - private View mSearchBarLayout; private TextView mBadgeLabel; - private LinearLayout mSearchEditLayout; - private EditText mSearchTextField; - private ImageButton mGoButton; - private ListView mSuggestionsList; + private AutoCompleteTextView mSearchTextField; + private Button mGoButton; - private ViewTreeObserver mViewTreeObserver = null; - // interaction with searchable application private ComponentName mLaunchComponent; private Bundle mAppSearchData; @@ -121,22 +104,20 @@ public class SearchDialog extends Dialog { private SearchableInfo mSearchable; // support for suggestions - private SuggestionsRunner mSuggestionsRunner; private String mUserQuery = null; private int mUserQuerySelStart; private int mUserQuerySelEnd; - private boolean mNonUserQuery = false; private boolean mLeaveJammedQueryOnRefocus = false; private String mPreviousSuggestionQuery = null; - private Context mProviderContext; - private Animation mSuggestionsEntry; - private Animation mSuggestionsExit; - private boolean mSkipNextAnimate; private int mPresetSelection = -1; private String mSuggestionAction = null; private Uri mSuggestionData = null; private String mSuggestionQuery = null; + // support for AutoCompleteTextView suggestions display + private SuggestionsAdapter mSuggestionsAdapter; + + /** * Constructor - fires it up and makes it look like the search UI. * @@ -167,38 +148,25 @@ public class SearchDialog extends Dialog { theWindow.setAttributes(lp); // get the view elements for local access - mSearchBarLayout = findViewById(com.android.internal.R.id.search_bar); mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge); - mSearchEditLayout = (LinearLayout)findViewById(com.android.internal.R.id.search_edit_frame); - mSearchTextField = (EditText) findViewById(com.android.internal.R.id.search_src_text); - mGoButton = (ImageButton) findViewById(com.android.internal.R.id.search_go_btn); - mSuggestionsList = (ListView) findViewById(com.android.internal.R.id.search_suggest_list); + mSearchTextField = (AutoCompleteTextView) + findViewById(com.android.internal.R.id.search_src_text); + mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn); // attach listeners mSearchTextField.addTextChangedListener(mTextWatcher); mSearchTextField.setOnKeyListener(mTextKeyListener); mGoButton.setOnClickListener(mGoButtonClickListener); mGoButton.setOnKeyListener(mButtonsKeyListener); - mSuggestionsList.setOnItemClickListener(mSuggestionsListItemClickListener); - mSuggestionsList.setOnKeyListener(mSuggestionsKeyListener); - mSuggestionsList.setOnFocusChangeListener(mSuggestFocusListener); - mSuggestionsList.setOnItemSelectedListener(mSuggestSelectedListener); // pre-hide all the extraneous elements mBadgeLabel.setVisibility(View.GONE); - mSuggestionsList.setVisibility(View.GONE); // Additional adjustments to make Dialog work for Search // Touching outside of the search dialog will dismiss it setCanceledOnTouchOutside(true); - // Preload animations - mSuggestionsEntry = AnimationUtils.loadAnimation(getContext(), - com.android.internal.R.anim.grow_fade_in); - mSuggestionsExit = AnimationUtils.loadAnimation(getContext(), - com.android.internal.R.anim.fade_out); - // Set up broadcast filters mCloseDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -238,18 +206,10 @@ public class SearchDialog extends Dialog { } // OK, we're going to show ourselves - if (mSuggestionsList != null) { - mSuggestionsList.setVisibility(View.GONE); // prevent any flicker if was visible - } - super.show(); setupSearchableInfo(); - // start the suggestions thread (which will mainly idle) - mSuggestionsRunner = new SuggestionsRunner(); - new Thread(mSuggestionsRunner, "SearchSuggestions").start(); - mLaunchComponent = componentName; mAppSearchData = appSearchData; mGlobalSearchMode = globalSearch; @@ -258,26 +218,21 @@ public class SearchDialog extends Dialog { getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter); getContext().registerReceiver(mBroadcastReceiver, mPackageFilter); - mViewTreeObserver = mSearchBarLayout.getViewTreeObserver(); - mViewTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener); + // configure the autocomplete aspects of the input box + mSearchTextField.setOnItemClickListener(this); + mSearchTextField.setOnItemSelectedListener(this); + + // attach the suggestions adapter + mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable); + mSearchTextField.setAdapter(mSuggestionsAdapter); // finally, load the user's initial text (which may trigger suggestions) - mNonUserQuery = false; + mSuggestionsAdapter.setNonUserQuery(false); if (initialQuery == null) { initialQuery = ""; // This forces the preload to happen, triggering suggestions } mSearchTextField.setText(initialQuery); - // If it is not for global search, that means the search dialog is - // launched to input a web address. - if (!globalSearch) { - mSearchTextField.setRawInputType(EditorInfo.TYPE_CLASS_TEXT - | EditorInfo.TYPE_TEXT_VARIATION_URI); - } else { - mSearchTextField.setRawInputType(EditorInfo.TYPE_CLASS_TEXT - | EditorInfo.TYPE_TEXT_VARIATION_NORMAL); - } - if (selectInitialQuery) { mSearchTextField.selectAll(); } else { @@ -315,21 +270,7 @@ public class SearchDialog extends Dialog { // This is OK - it just means we didn't have any registered } - // ignore layout notifications - try { - if (mViewTreeObserver != null) { - mViewTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); - } - } catch (RuntimeException e) { - // This is OK - none registered or observer "dead" - } - mViewTreeObserver = null; - // dump extra memory we're hanging on to - if (mSuggestionsRunner != null) { - mSuggestionsRunner.cancelSuggestions(); - mSuggestionsRunner = null; - } mLaunchComponent = null; mAppSearchData = null; mSearchable = null; @@ -337,7 +278,6 @@ public class SearchDialog extends Dialog { mSuggestionData = null; mSuggestionQuery = null; mActivityContext = null; - mProviderContext = null; mPreviousSuggestionQuery = null; mUserQuery = null; } @@ -366,9 +306,8 @@ public class SearchDialog extends Dialog { int selectedElement = INSTANCE_SELECTED_QUERY; if (mGoButton.isFocused()) { selectedElement = INSTANCE_SELECTED_BUTTON; - } else if ((mSuggestionsList.getVisibility() == View.VISIBLE) && - mSuggestionsList.isFocused()) { - selectedElement = mSuggestionsList.getSelectedItemPosition(); // 0..n + } else if (mSearchTextField.isPopupShowing()) { + selectedElement = 0; // TODO mSearchTextField.getListSelection() // 0..n } bundle.putInt(INSTANCE_KEY_SELECTED_ELEMENT, selectedElement); @@ -403,11 +342,13 @@ public class SearchDialog extends Dialog { // for some reason, we couldn't re-instantiate return; } - mSkipNextAnimate = true; - mNonUserQuery = true; + mSuggestionsAdapter.setNonUserQuery(true); mSearchTextField.setText(displayQuery); - mNonUserQuery = false; + // TODO because the new query is (not) processed in another thread, we can't just + // take away this flag (yet). The better solution here is going to require a new API + // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions. +// mSuggestionsAdapter.setNonUserQuery(false); // clean up the selection state switch (selectedElement) { @@ -425,6 +366,7 @@ public class SearchDialog extends Dialog { default: // defer selecting a list element until suggestion list appears mPresetSelection = selectedElement; + // TODO mSearchTextField.setListSelection(selectedElement) break; } } @@ -436,6 +378,7 @@ public class SearchDialog extends Dialog { public void onConfigurationChanged(Configuration newConfig) { if (isShowing()) { // Redraw (resources may have changed) + updateSearchButton(); updateSearchBadge(); updateQueryHint(); } @@ -448,10 +391,28 @@ public class SearchDialog extends Dialog { private void setupSearchableInfo() { if (mSearchable != null) { mActivityContext = mSearchable.getActivityContext(getContext()); - mProviderContext = mSearchable.getProviderContext(getContext(), mActivityContext); + updateSearchButton(); updateSearchBadge(); updateQueryHint(); + + // In order to properly configure the input method (if one is being used), we + // need to let it know if we'll be providing suggestions. Although it would be + // difficult/expensive to know if every last detail has been configured properly, we + // can at least see if a suggestions provider has been configured, and use that + // as our trigger. + int inputType = mSearchable.getInputType(); + // We only touch this if the input type is set up for text (which it almost certainly + // should be, in the case of search!) + if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { + // The existence of a suggestions authority is the proxy for "suggestions + // are available here" + inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; + if (mSearchable.getSuggestAuthority() != null) { + inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; + } + } + mSearchTextField.setInputType(inputType); } } @@ -468,6 +429,24 @@ public class SearchDialog extends Dialog { cancel(); } + /** + * Update the text in the search button. Note: This is deprecated functionality, for + * 1.0 compatibility only. + */ + private void updateSearchButton() { + String textLabel = null; + Drawable iconLabel = null; + int textId = mSearchable.getSearchButtonText(); + if (textId != 0) { + textLabel = mActivityContext.getResources().getString(textId); + } else { + iconLabel = getContext().getResources(). + getDrawable(com.android.internal.R.drawable.ic_btn_search); + } + mGoButton.setText(textLabel); + mGoButton.setCompoundDrawablesWithIntrinsicBounds(iconLabel, null, null, null); + } + /** * Setup the search "Badge" if request by mode flags. */ @@ -559,8 +538,8 @@ public class SearchDialog extends Dialog { } updateWidgetState(); // Only do suggestions if actually typed by user - if (!mNonUserQuery) { - updateSuggestions(); + if (mSuggestionsAdapter.getNonUserQuery()) { + mPreviousSuggestionQuery = s.toString(); mUserQuery = mSearchTextField.getText().toString(); mUserQuerySelStart = mSearchTextField.getSelectionStart(); mUserQuerySelEnd = mSearchTextField.getSelectionEnd(); @@ -582,156 +561,6 @@ public class SearchDialog extends Dialog { mGoButton.setFocusable(enabled); } - /** - * In response to a change in the query text, update the suggestions - */ - private void updateSuggestions() { - final String queryText = mSearchTextField.getText().toString(); - mPreviousSuggestionQuery = queryText; - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions()"); - } - - mSuggestionsRunner.requestSuggestions(mSearchable, queryText); - - // For debugging purposes, put in a lot of strings (really fast typist) - if (DBG_JAM_THREADING > 0) { - for (int ii = 1; ii < DBG_JAM_THREADING; ++ii) { - final String jamQuery = queryText + ii; - mSuggestionsRunner.requestSuggestions(mSearchable, jamQuery); - } - // one final (correct) string for cleanup - mSuggestionsRunner.requestSuggestions(mSearchable, queryText); - } - } - - /** - * This class defines a queued message structure for processing user keystrokes, and a - * thread that allows the suggestions to be gathered out-of-band, and allows us to skip - * over multiple keystrokes if the typist is faster than the content provider. - */ - private class SuggestionsRunner implements Runnable { - - private class Request { - final SearchableInfo mSearchableInfo; // query will set these - final String mQueryText; - final boolean cancelRequest; // cancellation will set this - - // simple constructors - Request(final SearchableInfo searchable, final String queryText) { - mSearchableInfo = searchable; - mQueryText = queryText; - cancelRequest = false; - } - - Request() { - mSearchableInfo = null; - mQueryText = null; - cancelRequest = true; - } - } - - private final LinkedBlockingQueue mSuggestionsQueue = - new LinkedBlockingQueue(); - - /** - * Queue up a suggestions request (non-blocking - can safely call from UI thread) - */ - public void requestSuggestions(final SearchableInfo searchable, final String queryText) { - Request request = new Request(searchable, queryText); - try { - mSuggestionsQueue.put(request); - } catch (InterruptedException e) { - // discard the request. - } - } - - /** - * Cancel blocking suggestions, discard any results, and shut down the thread. - * (non-blocking - can safely call from UI thread) - */ - private void cancelSuggestions() { - Request request = new Request(); - try { - mSuggestionsQueue.put(request); - } catch (InterruptedException e) { - // discard the request. - // TODO can we do better here? - } - } - - /** - * This runnable implements the logic for decoupling keystrokes from suggestions. - * The logic isn't quite obvious here, so I'll try to describe it. - * - * Normally we simply sleep waiting for a keystroke. When a keystroke arrives, - * we immediately dispatch a request to gather suggestions. - * - * But this can take a while, so by the time it comes back, more keystrokes may have - * arrived. If anything happened while we were gathering the suggestion, we discard its - * results, and then use the most recent keystroke to start the next suggestions request. - * - * Any request containing cancelRequest == true will cause the thread to immediately - * terminate. - */ - public void run() { - // outer blocking loop simply waits for a suggestion - while (true) { - try { - Request request = mSuggestionsQueue.take(); - if (request.cancelRequest) { - return; - } - - // since we were idle, what we're really interested is the final element - // in the queue. So keep pulling until we get the last element. - // TODO Could we just do some sort of takeHead() here? - while (! mSuggestionsQueue.isEmpty()) { - request = mSuggestionsQueue.take(); - if (request.cancelRequest) { - return; - } - } - final Request useRequest = request; - - // now process the final element (unless it's a cancel - that can be discarded) - - if (useRequest.mSearchableInfo != null) { - - // go get the cursor. this is what takes time. - final Cursor c = getSuggestions(useRequest.mSearchableInfo, - useRequest.mQueryText); - - // We now have a suggestions result. But, if any new requests have arrived, - // we're going to discard them - we don't want to waste time displaying - // out-of-date results, we just want to get going on the next set. - // Note, null cursor is a valid result (no suggestions). This logic also - // supports the need to discard the results *and* stop the thread if a kill - // request arrives during a query. - if (mSuggestionsQueue.size() > 0) { - if (c != null) { - c.close(); - } - } else { - mHandler.post(new Runnable() { - public void run() { - updateSuggestionsWithCursor(c, useRequest.mSearchableInfo); - } - }); - } - } - } catch (InterruptedException e) { - // loop back for more - } - // At this point the queue may contain zero-to-many new requests; We simply - // loop back to handle them (or, block until new requests arrive) - } - } - } - - /** - * Back in the UI thread, handle incoming cursors - */ private final static String[] ONE_LINE_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1 }; private final static String[] ONE_LINE_ICONS_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_ICON_1, @@ -755,209 +584,6 @@ public class SearchDialog extends Dialog { com.android.internal.R.id.icon2}; /** - * A new cursor (with suggestions) is ready for use. Update the UI. - */ - void updateSuggestionsWithCursor(Cursor c, final SearchableInfo searchable) { - ListAdapter adapter = null; - - // first, check for various conditions that disqualify this cursor - if ((c == null) || (c.getCount() == 0)) { - // no cursor, or cursor with no data - } else if ((searchable != mSearchable) || !isShowing()) { - // race condition (suggestions arrived after conditions changed) - } else { - // check cursor before trying to create list views from it - int colId = c.getColumnIndex("_id"); - int col1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); - int col2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); - int colIc1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); - int colIc2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); - - boolean minimal = (colId >= 0) && (col1 >= 0); - boolean hasIcons = (colIc1 >= 0) && (colIc2 >= 0); - boolean has2Lines = col2 >= 0; - - if (minimal) { - int layout; - String[] from; - int[] to; - - if (hasIcons) { - if (has2Lines) { - layout = com.android.internal.R.layout.search_dropdown_item_icons_2line; - from = TWO_LINE_ICONS_FROM; - to = TWO_LINE_ICONS_TO; - } else { - layout = com.android.internal.R.layout.search_dropdown_item_icons_1line; - from = ONE_LINE_ICONS_FROM; - to = ONE_LINE_ICONS_TO; - } - } else { - if (has2Lines) { - layout = com.android.internal.R.layout.search_dropdown_item_2line; - from = TWO_LINE_FROM; - to = TWO_LINE_TO; - } else { - layout = com.android.internal.R.layout.search_dropdown_item_1line; - from = ONE_LINE_FROM; - to = ONE_LINE_TO; - } - } - try { - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions(3)"); - } - adapter = new SuggestionsCursorAdapter(getContext(), layout, c, from, to, - mProviderContext); - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions(4)"); - } - } catch (RuntimeException e) { - Log.e(LOG_TAG, "Exception while creating SuggestionsCursorAdapter", e); - } - } - - // Provide some help for developers instead of just silently discarding - if ((colIc1 >= 0) != (colIc2 >= 0)) { - Log.w(LOG_TAG, "Suggestion icon column(s) discarded, must be 0 or 2 columns."); - } else if (adapter == null) { - Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns."); - } - } - - // if we have a cursor but we're not using it (e.g. disqualified), close it now - if ((c != null) && (adapter == null)) { - c.close(); - c = null; - } - - // we only made an adapter if there were 1+ suggestions. Now, based on the existence - // of the adapter, we'll also show/hide the list. - discardListCursor(mSuggestionsList); - if (adapter == null) { - showSuggestions(false, !mSkipNextAnimate); - } else { - layoutSuggestionsList(); - showSuggestions(true, !mSkipNextAnimate); - } - mSkipNextAnimate = false; - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions(5)"); - } - mSuggestionsList.setAdapter(adapter); - // now that we have an adapter, we can actually adjust the selection & scroll positions - if (mPresetSelection >= 0) { - boolean bTouchMode = mSuggestionsList.isInTouchMode(); - mSuggestionsList.setSelection(mPresetSelection); - mPresetSelection = -1; - } - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions(6)"); - } - } - - /** - * Utility for showing & hiding the suggestions list. This is also responsible for triggering - * animation, if any, at the right time. - * - * @param visible If true, show the suggestions, if false, hide them. - * @param animate If true, use animation. If false, "just do it." - */ - private void showSuggestions(boolean visible, boolean animate) { - if (visible) { - if (animate && (mSuggestionsList.getVisibility() != View.VISIBLE)) { - mSuggestionsList.startAnimation(mSuggestionsEntry); - } - mSuggestionsList.setVisibility(View.VISIBLE); - } else { - if (animate && (mSuggestionsList.getVisibility() != View.GONE)) { - mSuggestionsList.startAnimation(mSuggestionsExit); - } - mSuggestionsList.setVisibility(View.GONE); - } - } - - /** - * This helper class supports the suggestions list by allowing 3rd party (e.g. app) resources - * to be used in suggestions - */ - private static class SuggestionsCursorAdapter extends SimpleCursorAdapter { - - private Resources mProviderResources; - - public SuggestionsCursorAdapter(Context context, int layout, Cursor c, - String[] from, int[] to, Context providerContext) { - super(context, layout, c, from, to); - mProviderResources = providerContext.getResources(); - } - - /** - * Overriding this allows us to affect the way that an icon is loaded. Specifically, - * we can be more controlling about the resource path (and allow icons to come from other - * packages). - * - * @param v ImageView to receive an image - * @param value the value retrieved from the cursor - */ - @Override - public void setViewImage(ImageView v, String value) { - int resID; - Drawable img = null; - - try { - resID = Integer.parseInt(value); - if (resID != 0) { - img = mProviderResources.getDrawable(resID); - } - } catch (NumberFormatException nfe) { - // img = null; - } catch (NotFoundException e2) { - // img = null; - } - - // finally, set the image to whatever we've gotten - v.setImageDrawable(img); - } - - /** - * This method is overridden purely to provide a bit of protection against - * flaky content providers. - */ - @Override - /** - * @see android.widget.ListAdapter#getView(int, View, ViewGroup) - */ - public View getView(int position, View convertView, ViewGroup parent) { - try { - return super.getView(position, convertView, parent); - } catch (RuntimeException e) { - Log.w(LOG_TAG, "Search Suggestions cursor returned exception " + e.toString()); - // what can I return here? - View v = newView(mContext, mCursor, parent); - if (v != null) { - TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1); - tv.setText(e.toString()); - } - return v; - } - } - } - - /** - * Cleanly close the cursor being used by a ListView. Do this before replacing the adapter - * or before closing the ListView. - */ - private void discardListCursor(ListView list) { - CursorAdapter ca = getSuggestionsAdapter(list); - if (ca != null) { - Cursor c = ca.getCursor(); - if (c != null) { - ca.changeCursor(null); - } - } - } - - /** * Safely retrieve the suggestions cursor adapter from the ListView * * @param adapterView The ListView containing our adapter @@ -977,58 +603,6 @@ public class SearchDialog extends Dialog { } /** - * Get the query cursor for the search suggestions. - * - * @param query The search text entered (so far) - * @return Returns a cursor with suggestions, or null if no suggestions - */ - private Cursor getSuggestions(final SearchableInfo searchable, final String query) { - Cursor cursor = null; - if (searchable.getSuggestAuthority() != null) { - try { - StringBuilder uriStr = new StringBuilder("content://"); - uriStr.append(searchable.getSuggestAuthority()); - - // if content path provided, insert it now - final String contentPath = searchable.getSuggestPath(); - if (contentPath != null) { - uriStr.append('/'); - uriStr.append(contentPath); - } - - // append standard suggestion query path - uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY); - - // inject query, either as selection args or inline - String[] selArgs = null; - if (searchable.getSuggestSelection() != null) { // if selection provided, use it - selArgs = new String[] {query}; - } else { - uriStr.append('/'); // no sel, use REST pattern - uriStr.append(Uri.encode(query)); - } - - // finally, make the query - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("getSuggestions(1)"); - } - cursor = getContext().getContentResolver().query( - Uri.parse(uriStr.toString()), null, - searchable.getSuggestSelection(), selArgs, - null); - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("getSuggestions(2)"); - } - } catch (RuntimeException e) { - Log.w(LOG_TAG, "Search Suggestions query returned exception " + e.toString()); - cursor = null; - } - } - - return cursor; - } - - /** * React to typing in the GO search button by refocusing to EditText. * Continue typing the query. */ @@ -1094,7 +668,10 @@ public class SearchDialog extends Dialog { * React to the user typing while the suggestions are focused. First, check for action * keys. If not handled, try refocusing regular characters into the EditText. In this case, * replace the query text (start typing fresh text). + * + * TODO: Move this code into mTextKeyListener, testing for a list entry being hilited */ + /* View.OnKeyListener mSuggestionsKeyListener = new View.OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { boolean handled = false; @@ -1108,6 +685,7 @@ public class SearchDialog extends Dialog { return handled; } }; + */ /** * Per UI design, we're going to "steer" any typed keystrokes back into the EditText @@ -1140,6 +718,9 @@ public class SearchDialog extends Dialog { /** * Update query text based on transitions in and out of suggestions list. */ + /* + * TODO - figure out if this logic is required for the autocomplete text view version + OnFocusChangeListener mSuggestFocusListener = new OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { // also guard against possible race conditions (late arrival after dismiss) @@ -1170,24 +751,9 @@ public class SearchDialog extends Dialog { } }; + */ /** - * Update query text based on movement of selection in/out of suggestion list - */ - OnItemSelectedListener mSuggestSelectedListener = new OnItemSelectedListener() { - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Update query text while user navigates through suggestions list - // also guard against possible race conditions (late arrival after dismiss) - if (mSearchable != null && position >= 0 && mSuggestionsList.isFocused()) { - jamSuggestionQuery(true, parent, position); - } - } - - // No action needed on this callback - public void onNothingSelected(AdapterView parent) { } - }; - - /** * This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent. It's an indication that * we should close ourselves immediately, in order to allow a higher-priority UI to take over * (e.g. phone call received). @@ -1225,20 +791,6 @@ public class SearchDialog extends Dialog { }; /** - * Listener for layout changes in the main layout. I use this to dynamically clean up - * the layout of the dropdown and make it "pixel perfect." - */ - private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener - = new ViewTreeObserver.OnGlobalLayoutListener() { - - // It's very important that layoutSuggestionsList() does not reset - // the values more than once, or this becomes an infinite loop. - public void onGlobalLayout() { - layoutSuggestionsList(); - } - }; - - /** * Various ways to launch searches */ @@ -1333,7 +885,7 @@ public class SearchDialog extends Dialog { * @param jamQuery True means to set the query, false means to reset it to the user's choice */ private void jamSuggestionQuery(boolean jamQuery, AdapterView parent, int position) { - mNonUserQuery = true; // disables any suggestions processing + mSuggestionsAdapter.setNonUserQuery(true); // disables any suggestions processing if (jamQuery) { CursorAdapter ca = getSuggestionsAdapter(parent); Cursor c = ca.getCursor(); @@ -1356,7 +908,10 @@ public class SearchDialog extends Dialog { } if (jamText != null) { mSearchTextField.setText(jamText); - mSearchTextField.selectAll(); + /* mSearchTextField.selectAll(); */ // this didn't work anyway in the old UI + // TODO this is only needed in the model where we have a selection in the ACTV + // and in the dropdown at the same time. + mSearchTextField.setSelection(jamText.length()); } } } else { @@ -1372,7 +927,10 @@ public class SearchDialog extends Dialog { mSearchTextField.selectAll(); } } - mNonUserQuery = false; + // TODO because the new query is (not) processed in another thread, we can't just + // take away this flag (yet). The better solution here is going to require a new API + // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions. +// mSuggestionsAdapter.setNonUserQuery(false); } /** @@ -1418,18 +976,6 @@ public class SearchDialog extends Dialog { } /** - * Handler for clicks in the suggestions list - */ - private OnItemClickListener mSuggestionsListItemClickListener = new OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) { - // this guard protects against possible race conditions (late arrival of click) - if (mSearchable != null) { - launchSuggestion(parent, position); - } - } - }; - - /** * Shared code for launching a query from a suggestion. * * @param av The AdapterView (really a ListView) containing the suggestions @@ -1439,6 +985,16 @@ public class SearchDialog extends Dialog { */ private boolean launchSuggestion(AdapterView av, int position) { CursorAdapter ca = getSuggestionsAdapter(av); + return launchSuggestion(ca, position); + } + + /** + * Shared code for launching a query from a suggestion. + * @param ca The cursor adapter containing the suggestions + * @param position The suggestion we'll be launching from + * @return true if a successful launch, false if could not (e.g. bad position) + */ + private boolean launchSuggestion(CursorAdapter ca, int position) { Cursor c = ca.getCursor(); if ((c != null) && c.moveToPosition(position)) { setupSuggestionIntent(c, mSearchable); @@ -1457,33 +1013,6 @@ public class SearchDialog extends Dialog { } /** - * Manually adjust suggestions list into its perfectly-tweaked position. - * - * NOTE: This MUST not adjust the parameters if they are already set correctly, - * or you create an infinite loop via the ViewTreeObserver.OnGlobalLayoutListener callback. - */ - private void layoutSuggestionsList() { - final int FUDGE_SUGG_X = 1; - final int FUDGE_SUGG_WIDTH = 2; - - int[] itemLoc = new int[2]; - mSearchTextField.getLocationOnScreen(itemLoc); - int x,width; - x = itemLoc[0] + FUDGE_SUGG_X; - width = mSearchTextField.getMeasuredWidth() + FUDGE_SUGG_WIDTH; - - // now set params and relayout - ViewGroup.MarginLayoutParams lp; - lp = (ViewGroup.MarginLayoutParams) mSuggestionsList.getLayoutParams(); - boolean changing = (lp.width != width) || (lp.leftMargin != x); - if (changing) { - lp.leftMargin = x; - lp.width = width; - mSuggestionsList.setLayoutParams(lp); - } - } - - /** * When a particular suggestion has been selected, perform the various lookups required * to use the suggestion. This includes checking the cursor for suggestion-specific data, * and/or falling back to the XML for defaults; It also creates REST style Uri data when @@ -1585,6 +1114,301 @@ public class SearchDialog extends Dialog { } return result; } + + /** + * Support for AutoCompleteTextView-based suggestions + */ + /** + * This class provides the filtering-based interface to suggestions providers. + * It is hardwired in a couple of places to support GoogleSearch - for example, it supports + * two-line suggestions, but it does not support icons. + */ + private static class SuggestionsAdapter extends SimpleCursorAdapter { + private final String TAG = "SuggestionsAdapter"; + + SearchableInfo mSearchable; + private Resources mProviderResources; + + // These private variables are shared by the filter thread and must be protected + private WeakReference mRecentCursor = new WeakReference(null); + private boolean mNonUserQuery = false; + + public SuggestionsAdapter(Context context, SearchableInfo searchable) { + super(context, -1, null, null, null); + mSearchable = searchable; + + // set up provider resources (gives us icons, etc.) + Context activityContext = mSearchable.getActivityContext(mContext); + Context providerContext = mSearchable.getProviderContext(mContext, activityContext); + mProviderResources = providerContext.getResources(); + } + + /** + * Set this field (temporarily!) to disable suggestions updating. This allows us + * to change the string in the text view without changing the suggestions list. + */ + public void setNonUserQuery(boolean nonUserQuery) { + synchronized (this) { + mNonUserQuery = nonUserQuery; + } + } + + public boolean getNonUserQuery() { + synchronized (this) { + return mNonUserQuery; + } + } + + /** + * Use the search suggestions provider to obtain a live cursor. This will be called + * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions). + * The results will be processed in the UI thread and changeCursor() will be called. + * + * In order to provide the Search Mgr functionality of seeing your query change as you + * scroll through the list, we have to be able to jam new text into the string without + * retriggering the suggestions. We do that here via the "nonUserQuery" flag. In that + * case we simply return the existing cursor. + * + * TODO: Dianne suggests that this should simply be promoted into an AutoCompleteTextView + * behavior (perhaps optionally). + * + * TODO: The "nonuserquery" logic has a race condition because it happens in another thread. + * This also needs to be fixed. + */ + @Override + public Cursor runQueryOnBackgroundThread(CharSequence constraint) { + String query = (constraint == null) ? "" : constraint.toString(); + Cursor c = null; + synchronized (this) { + if (mNonUserQuery) { + c = mRecentCursor.get(); + mNonUserQuery = false; + } + } + if (c == null) { + c = getSuggestions(mSearchable, query); + synchronized (this) { + mRecentCursor = new WeakReference(c); + } + } + return c; + } + + /** + * Overriding changeCursor() allows us to change not only the cursor, but by sampling + * the cursor's columns, the actual display characteristics of the list. + */ + @Override + public void changeCursor(Cursor c) { + + // first, check for various conditions that disqualify this cursor + if ((c == null) || (c.getCount() == 0)) { + // no cursor, or cursor with no data + changeCursorAndColumns(null, null, null); + if (c != null) { + c.close(); + } + return; + } + + // check cursor before trying to create list views from it + int colId = c.getColumnIndex("_id"); + int col1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); + int col2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); + int colIc1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); + int colIc2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); + + boolean minimal = (colId >= 0) && (col1 >= 0); + boolean hasIcons = (colIc1 >= 0) && (colIc2 >= 0); + boolean has2Lines = col2 >= 0; + + if (minimal) { + int layout; + String[] from; + int[] to; + + if (hasIcons) { + if (has2Lines) { + layout = com.android.internal.R.layout.search_dropdown_item_icons_2line; + from = TWO_LINE_ICONS_FROM; + to = TWO_LINE_ICONS_TO; + } else { + layout = com.android.internal.R.layout.search_dropdown_item_icons_1line; + from = ONE_LINE_ICONS_FROM; + to = ONE_LINE_ICONS_TO; + } + } else { + if (has2Lines) { + layout = com.android.internal.R.layout.search_dropdown_item_2line; + from = TWO_LINE_FROM; + to = TWO_LINE_TO; + } else { + layout = com.android.internal.R.layout.search_dropdown_item_1line; + from = ONE_LINE_FROM; + to = ONE_LINE_TO; + } + } + // Now actually set up the cursor, columns, and the list view + changeCursorAndColumns(c, from, to); + setViewResource(layout); + } else { + // Provide some help for developers instead of just silently discarding + Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns."); + changeCursorAndColumns(null, null, null); + c.close(); + } + if ((colIc1 >= 0) != (colIc2 >= 0)) { + Log.w(LOG_TAG, "Suggestion icon column(s) discarded, must be 0 or 2 columns."); + } + } + + /** + * Overriding this allows us to write the selected query back into the box. + * NOTE: This is a vastly simplified version of SearchDialog.jamQuery() and does + * not universally support the search API. But it is sufficient for Google Search. + */ + @Override + public CharSequence convertToString(Cursor cursor) { + CharSequence result = null; + if (cursor != null) { + int column = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY); + if (column >= 0) { + final String query = cursor.getString(column); + if (query != null) { + result = query; + } + } + } + return result; + } + + /** + * Get the query cursor for the search suggestions. + * + * TODO this is functionally identical to the version in SearchDialog.java. Perhaps it + * could be hoisted into SearchableInfo or some other shared spot. + * + * @param query The search text entered (so far) + * @return Returns a cursor with suggestions, or null if no suggestions + */ + private Cursor getSuggestions(final SearchableInfo searchable, final String query) { + Cursor cursor = null; + if (searchable.getSuggestAuthority() != null) { + try { + StringBuilder uriStr = new StringBuilder("content://"); + uriStr.append(searchable.getSuggestAuthority()); + + // if content path provided, insert it now + final String contentPath = searchable.getSuggestPath(); + if (contentPath != null) { + uriStr.append('/'); + uriStr.append(contentPath); + } + + // append standard suggestion query path + uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY); + + // inject query, either as selection args or inline + String[] selArgs = null; + if (searchable.getSuggestSelection() != null) { // use selection if provided + selArgs = new String[] {query}; + } else { + uriStr.append('/'); // no sel, use REST pattern + uriStr.append(Uri.encode(query)); + } + + // finally, make the query + cursor = mContext.getContentResolver().query( + Uri.parse(uriStr.toString()), null, + searchable.getSuggestSelection(), selArgs, + null); + } catch (RuntimeException e) { + Log.w(TAG, "Search Suggestions query returned exception " + e.toString()); + cursor = null; + } + } + + return cursor; + } + + /** + * Overriding this allows us to affect the way that an icon is loaded. Specifically, + * we can be more controlling about the resource path (and allow icons to come from other + * packages). + * + * TODO: This is 100% identical to the version in SearchDialog.java + * + * @param v ImageView to receive an image + * @param value the value retrieved from the cursor + */ + @Override + public void setViewImage(ImageView v, String value) { + int resID; + Drawable img = null; + + try { + resID = Integer.parseInt(value); + if (resID != 0) { + img = mProviderResources.getDrawable(resID); + } + } catch (NumberFormatException nfe) { + // img = null; + } catch (NotFoundException e2) { + // img = null; + } + + // finally, set the image to whatever we've gotten + v.setImageDrawable(img); + } + + /** + * This method is overridden purely to provide a bit of protection against + * flaky content providers. + * + * TODO: This is 100% identical to the version in SearchDialog.java + * + * @see android.widget.ListAdapter#getView(int, View, ViewGroup) + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + try { + return super.getView(position, convertView, parent); + } catch (RuntimeException e) { + Log.w(TAG, "Search Suggestions cursor returned exception " + e.toString()); + // what can I return here? + View v = newView(mContext, mCursor, parent); + if (v != null) { + TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1); + tv.setText(e.toString()); + } + return v; + } + } + + } + + /** + * Implements OnItemClickListener + */ + public void onItemClick(AdapterView parent, View view, int position, long id) { +// Log.d(LOG_TAG, "onItemClick() position " + position); + launchSuggestion(mSuggestionsAdapter, position); + } + + /** + * Implements OnItemSelectedListener + */ + public void onItemSelected(AdapterView parent, View view, int position, long id) { +// Log.d(LOG_TAG, "onItemSelected() position " + position); + jamSuggestionQuery(true, parent, position); + } + + /** + * Implements OnItemSelectedListener + */ + public void onNothingSelected(AdapterView parent) { +// Log.d(LOG_TAG, "onNothingSelected()"); + } /** * Debugging Support diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 5f25b9060167..0a37e8132022 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -739,6 +739,14 @@ import android.view.KeyEvent; * No * * + * android:inputType + * If provided, supplies a hint about the type of search text the user will be + * entering. For most searches, in which free form text is expected, this attribute + * need not be provided. Suitable values for this attribute are described in the + * inputType attribute. + * No + * + * * * * diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index d6ea889e8c91..022a87cd443c 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -90,7 +90,7 @@ public class BluetoothA2dp { } /** Initiate a connection to an A2DP sink. - * Listen for A2DP_SINK_STATE_CHANGED_ACTION to find out when the + * Listen for SINK_STATE_CHANGED_ACTION to find out when the * connection is completed. * @param address Remote BT address. * @return Result code, negative indicates an immediate error. @@ -106,7 +106,7 @@ public class BluetoothA2dp { } /** Initiate disconnect from an A2DP sink. - * Listen for A2DP_SINK_STATE_CHANGED_ACTION to find out when + * Listen for SINK_STATE_CHANGED_ACTION to find out when * disconnect is completed. * @param address Remote BT address. * @return Result code, negative indicates an immediate error. diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java index e1a484e3363d..5511ff6931e7 100644 --- a/core/java/android/content/AbstractTableMerger.java +++ b/core/java/android/content/AbstractTableMerger.java @@ -477,7 +477,7 @@ public abstract class AbstractTableMerger private void fullyDeleteRowsWithSyncId(String syncId, String account, SyncResult syncResult) { final String[] selectionArgs = new String[]{syncId, account}; // delete the rows explicitly so that the delete operation can be overridden - Cursor c = mDb.query(mTable, new String[]{"_id"}, SELECT_BY_ID_AND_ACCOUNT, + Cursor c = mDb.query(mTable, getDeleteRowProjection(), SELECT_BY_ID_AND_ACCOUNT, selectionArgs, null, null, null); try { c.moveToFirst(); @@ -494,6 +494,16 @@ public abstract class AbstractTableMerger } /** + * Provides the projection used by + * {@link AbstractTableMerger#deleteRow(android.database.Cursor)}. + * This should be overridden if the deleteRow implementation requires + * additional columns. + */ + protected String[] getDeleteRowProjection() { + return new String[]{"_id"}; + } + + /** * Converts cursor into a Map, using the correct types for the values. */ protected void cursorRowToContentValues(Cursor cursor, ContentValues map) { diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index dc757480d9a8..e2d7097af3b7 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -100,10 +100,12 @@ public class Camera { /** * Reconnect to the camera after passing it to MediaRecorder. To save - * setup/teardown time, a client of Camara can pass an initialized Camera + * setup/teardown time, a client of Camera can pass an initialized Camera * object to a MediaRecorder to use for video recording. Once the * MediaRecorder is done with the Camera, this method can be used to - * re-establish a connection with the camera hardware. + * re-establish a connection with the camera hardware. NOTE: The Camera + * object must first be unlocked by the process that owns it before it + * can be connected to another proces. * * @throws IOException if the method fails. * @@ -113,6 +115,34 @@ public class Camera { public native final void reconnect() throws IOException; /** + * Lock the camera to prevent other processes from accessing it. To save + * setup/teardown time, a client of Camera can pass an initialized Camera + * object to another process. This method is used to re-lock the Camera + * object prevent other processes from accessing it. By default, the + * Camera object is locked. Locking it again from the same process will + * have no effect. Attempting to lock it from another process if it has + * not been unlocked will fail. + * Returns 0 if lock was successful. + * + * FIXME: Unhide after approval + * @hide + */ + public native final int lock(); + + /** + * Unlock the camera to allow aother process to access it. To save + * setup/teardown time, a client of Camera can pass an initialized Camera + * object to another process. This method is used to unlock the Camera + * object before handing off the Camera object to the other process. + + * Returns 0 if unlock was successful. + * + * FIXME: Unhide after approval + * @hide + */ + public native final int unlock(); + + /** * Sets the SurfaceHolder to be used for a picture preview. If the surface * changed since the last call, the screen will blank. Nothing happens * if the same surface is re-set. @@ -152,6 +182,14 @@ public class Camera { public native final void stopPreview(); /** + * Return current preview state. + * + * FIXME: Unhide before release + * @hide + */ + public native final boolean previewEnabled(); + + /** * Can be called at any time to instruct the camera to use a callback for * each preview frame in addition to displaying it. * @@ -242,7 +280,9 @@ public class Camera { }; /** - * Registers a callback to be invoked when the auto focus responds. + * Starts auto-focus function and registers a callback function to + * run when camera is focused. Only valid after startPreview() has + * been called. * * @param cb the callback to run */ @@ -525,6 +565,58 @@ public class Camera { } /** + * Sets the dimensions for EXIF thumbnails. + * + * @param width the width of the thumbnail, in pixels + * @param height the height of the thumbnail, in pixels + * + * FIXME: unhide before release + * @hide + */ + public void setThumbnailSize(int width, int height) { + set("jpeg-thumbnail-width", width); + set("jpeg-thumbnail-height", height); + } + + /** + * Returns the dimensions for EXIF thumbnail + * + * @return a Size object with the height and width setting + * for the EXIF thumbnails + * + * FIXME: unhide before release + * @hide + */ + public Size getThumbnailSize() { + return new Size(getInt("jpeg-thumbnail-width"), + getInt("jpeg-thumbnail-height")); + } + + /** + * Sets the quality of the EXIF thumbnail + * + * @param quality the JPEG quality of the EXIT thumbnail + * + * FIXME: unhide before release + * @hide + */ + public void setThumbnailQuality(int quality) { + set("jpeg-thumbnail-quality", quality); + } + + /** + * Returns the quality setting for the EXIF thumbnail + * + * @return the JPEG quality setting of the EXIF thumbnail + * + * FIXME: unhide before release + * @hide + */ + public int getThumbnailQuality() { + return getInt("jpeg-thumbnail-quality"); + } + + /** * Sets the rate at which preview frames are received. * * @param fps the frame rate (frames per second) @@ -547,7 +639,7 @@ public class Camera { * Sets the image format for preview pictures. * * @param pixel_format the desired preview picture format - * (PixelFormat.YCbCr_422_SP, + * (PixelFormat.YCbCr_420_SP, * PixelFormat.RGB_565, or * PixelFormat.JPEG) * @see android.graphics.PixelFormat @@ -604,7 +696,7 @@ public class Camera { * Sets the image format for pictures. * * @param pixel_format the desired picture format - * (PixelFormat.YCbCr_422_SP, + * (PixelFormat.YCbCr_420_SP, * PixelFormat.RGB_565, or * PixelFormat.JPEG) * @see android.graphics.PixelFormat @@ -630,6 +722,7 @@ public class Camera { private String cameraFormatForPixelFormat(int pixel_format) { switch(pixel_format) { case PixelFormat.YCbCr_422_SP: return "yuv422sp"; + case PixelFormat.YCbCr_420_SP: return "yuv420sp"; case PixelFormat.RGB_565: return "rgb565"; case PixelFormat.JPEG: return "jpeg"; default: return null; @@ -643,6 +736,9 @@ public class Camera { if (format.equals("yuv422sp")) return PixelFormat.YCbCr_422_SP; + if (format.equals("yuv420sp")) + return PixelFormat.YCbCr_420_SP; + if (format.equals("rgb565")) return PixelFormat.RGB_565; diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index 40c03cd898f0..5a85c66f4290 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -91,7 +91,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub case DO_UPDATE_SELECTION: { HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; mInputMethodSession.updateSelection(args.argi1, args.argi2, - args.argi3, args.argi4); + args.argi3, args.argi4, args.argi5, args.argi6); mCaller.recycleArgs(args); return; } @@ -135,9 +135,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub } public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { - mCaller.executeOrSendMessage(mCaller.obtainMessageIIII(DO_UPDATE_SELECTION, - oldSelStart, oldSelEnd, newSelStart, newSelEnd)); + int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { + mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION, + oldSelStart, oldSelEnd, newSelStart, newSelEnd, + candidatesStart, candidatesEnd)); } public void updateCursor(Rect newCursor) { diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 9ebf1271ee67..0588beada6cf 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -48,6 +48,132 @@ import android.widget.FrameLayout; * which final implementations can derive from and customize. See the * base class {@link AbstractInputMethodService} and the {@link InputMethod} * interface for more information on the basics of writing input methods. + * + *

An input method has significant discretion in how it goes about its + * work: the {@link android.inputmethodservice.InputMethodService} provides + * a basic framework for standard UI elements (input view, candidates view, + * and running in fullscreen mode), but it is up to a particular implementor + * to decide how to use them. For example, one input method could implement + * an input area with a keyboard, another could allow the user to draw text, + * while a third could have no input area (and thus not be visible to the + * user) but instead listen to audio and perform text to speech conversion.

+ * + *

In the implementation provided here, all of these elements are placed + * together in a single window managed by the InputMethodService. It will + * execute callbacks as it needs information about them, and provides APIs for + * programmatic control over them. They layout of these elements is explicitly + * defined:

+ * + *
    + *
  • The soft input view, if available, is placed at the bottom of the + * screen. + *
  • The candidates view, if currently shown, is placed above the soft + * input view. + *
  • If not running fullscreen, the application is moved or resized to be + * above these views; if running fullscreen, the window will completely cover + * the application and its top part will contain the extract text of what is + * currently being edited by the application. + *
+ * + * + * + *

Soft Input View

+ * + *

Central to most input methods is the soft input view. This is where most + * user interaction occurs: pressing on soft keys, drawing characters, or + * however else your input method wants to generate text. Most implementations + * will simply have their own view doing all of this work, and return a new + * instance of it when {@link #onCreateInputView()} is called. At that point, + * as long as the input view is visible, you will see user interaction in + * that view and can call back on the InputMethodService to interact with the + * application as appropriate.

+ * + *

There are some situations where you want to decide whether or not your + * soft input view should be shown to the user. This is done by implementing + * the {@link #onEvaluateInputViewShown()} to return true or false based on + * whether it should be shown in the current environment. If any of your + * state has changed that may impact this, call + * {@link #updateInputViewShown()} to have it re-evaluated. The default + * implementation always shows the input view unless there is a hard + * keyboard available, which is the appropriate behavior for most input + * methods.

+ * + * + * + *

Candidates View

+ * + *

Often while the user is generating raw text, an input method wants to + * provide them with a list of possible interpretations of that text that can + * be selected for use. This is accomplished with the candidates view, and + * like the soft input view you implement {@link #onCreateCandidatesView()} + * to instantiate your own view implementing your candidates UI.

+ * + *

Management of the candidates view is a little different than the input + * view, because the candidates view tends to be more transient, being shown + * only when there are possible candidates for the current text being entered + * by the user. To control whether the candidates view is shown, you use + * {@link #setCandidatesViewShown(boolean)}. Note that because the candidate + * view tends to be shown and hidden a lot, it does not impact the application + * UI in the same way as the soft input view: it will never cause application + * windows to resize, only cause them to be panned if needed for the user to + * see the current focus.

+ * + * + * + *

Fullscreen Mode

+ * + *

Sometimes your input method UI is too large to integrate with the + * application UI, so you just want to take over the screen. This is + * accomplished by switching to full-screen mode, causing the input method + * window to fill the entire screen and add its own "extracted text" editor + * showing the user the text that is being typed. Unlike the other UI elements, + * there is a standard implementation for the extract editor that you should + * not need to change. The editor is placed at the top of the IME, above the + * input and candidates views.

+ * + *

Similar to the input view, you control whether the IME is running in + * fullscreen mode by implementing {@link #onEvaluateFullscreenMode()} + * to return true or false based on + * whether it should be fullscreen in the current environment. If any of your + * state has changed that may impact this, call + * {@link #updateFullscreenMode()} to have it re-evaluated. The default + * implementation selects fullscreen mode when the screen is in a landscape + * orientation, which is appropriate behavior for most input methods that have + * a significant input area.

+ * + *

When in fullscreen mode, you have some special requirements because the + * user can not see the application UI. In particular, you should implement + * {@link #onDisplayCompletions(CompletionInfo[])} to show completions + * generated by your application, typically in your candidates view like you + * would normally show candidates. + * + * + * + *

Generating Text

+ * + *

The key part of an IME is of course generating text for the application. + * This is done through calls to the + * {@link android.view.inputmethod.InputConnection} interface to the + * application, which can be retrieved from {@link #getCurrentInputConnection()}. + * This interface allows you to generate raw key events or, if the target + * supports it, directly edit in strings of candidates and committed text.

+ * + *

Information about what the target is expected and supports can be found + * through the {@link android.view.inputmethod.EditorInfo} class, which is + * retrieved with {@link #getCurrentInputEditorInfo()} method. The most + * important part of this is {@link android.view.inputmethod.EditorInfo#inputType + * EditorInfo.inputType}; in particular, if this is + * {@link android.view.inputmethod.EditorInfo#TYPE_NULL EditorInfo.TYPE_NULL}, + * then the target does not support complex edits and you need to only deliver + * raw key events to it. An input method will also want to look at other + * values here, to for example detect password mode, auto complete text views, + * phone number entry, etc.

+ * + *

When the user switches between input targets, you will receive calls to + * {@link #onFinishInput()} and {@link #onStartInput(EditorInfo, boolean)}. + * You can use these to reset and initialize your input state for the current + * target. For example, you will often want to clear any input state, and + * update a soft keyboard to be appropriate for the new inputType.

*/ public class InputMethodService extends AbstractInputMethodService { static final String TAG = "InputMethodService"; @@ -68,7 +194,7 @@ public class InputMethodService extends AbstractInputMethodService { InputBinding mInputBinding; InputConnection mInputConnection; boolean mInputStarted; - EditorInfo mInputInfo; + EditorInfo mInputEditorInfo; boolean mShowInputRequested; boolean mShowCandidatesRequested; @@ -210,12 +336,13 @@ public class InputMethodService extends AbstractInputMethodService { * InputMethodService.onUpdateSelection()}. */ public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd) { if (!isEnabled()) { return; } InputMethodService.this.onUpdateSelection(oldSelStart, oldSelEnd, - newSelStart, newSelEnd); + newSelStart, newSelEnd, candidatesStart, candidatesEnd); } /** @@ -303,6 +430,7 @@ public class InputMethodService extends AbstractInputMethodService { Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this); initViews(); + mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT); } void initViews() { @@ -384,8 +512,8 @@ public class InputMethodService extends AbstractInputMethodService { return mInputStarted; } - public EditorInfo getCurrentInputInfo() { - return mInputInfo; + public EditorInfo getCurrentInputEditorInfo() { + return mInputEditorInfo; } /** @@ -459,14 +587,14 @@ public class InputMethodService extends AbstractInputMethodService { int[] loc = mTmpLocation; if (mInputFrame.getVisibility() == View.VISIBLE) { mInputFrame.getLocationInWindow(loc); - outInsets.contentTopInsets = loc[1]; + } else { + loc[1] = 0; } + outInsets.contentTopInsets = loc[1]; if (mCandidatesFrame.getVisibility() == View.VISIBLE) { mCandidatesFrame.getLocationInWindow(loc); - outInsets.visibleTopInsets = loc[1]; - } else { - outInsets.visibleTopInsets = loc[1]; } + outInsets.visibleTopInsets = loc[1]; outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE; } @@ -712,7 +840,7 @@ public class InputMethodService extends AbstractInputMethodService { if (doShowInput) { if (mInputStarted) { if (DEBUG) Log.v(TAG, "showWindow: starting input view"); - onStartInputView(mInputInfo, false); + onStartInputView(mInputEditorInfo, false); } startExtractingText(); } @@ -744,11 +872,11 @@ public class InputMethodService extends AbstractInputMethodService { void doStartInput(EditorInfo attribute, boolean restarting) { mInputStarted = true; - mInputInfo = attribute; + mInputEditorInfo = attribute; onStartInput(attribute, restarting); if (mWindowVisible) { if (mWindowCreated) { - onStartInputView(mInputInfo, restarting); + onStartInputView(mInputEditorInfo, restarting); } startExtractingText(); } @@ -795,7 +923,8 @@ public class InputMethodService extends AbstractInputMethodService { * the extract text, if it is being shown. */ public void onUpdateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd) { if (mExtractEditText != null && mExtractedText != null) { final int off = mExtractedText.startOffset; mExtractEditText.setSelection(newSelStart-off, newSelEnd-off); @@ -821,10 +950,22 @@ public class InputMethodService extends AbstractInputMethodService { } public boolean onKeyDown(int keyCode, KeyEvent event) { - if (mWindowVisible && event.getKeyCode() == KeyEvent.KEYCODE_BACK + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { - dismissSoftInput(); - return true; + if (mShowInputRequested) { + // If the soft input area is shown, back closes it and we + // consume the back key. + dismissSoftInput(); + return true; + } + if (mShowCandidatesRequested) { + // If the candidates are shown, we just want to make sure + // they are now hidden but otherwise let the app execute + // the back. + // XXX this needs better interaction with the soft input + // implementation. + //setCandidatesViewShown(false); + } } return false; } @@ -857,8 +998,8 @@ public class InputMethodService extends AbstractInputMethodService { if (mExtractedText != null) { mExtractEditText.setExtractedText(mExtractedText); } - mExtractEditText.setInputType(getCurrentInputInfo().inputType); - mExtractEditText.setHint(mInputInfo.hintText); + mExtractEditText.setInputType(getCurrentInputEditorInfo().inputType); + mExtractEditText.setHint(mInputEditorInfo.hintText); } } } diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java index 75a2911bcaea..cfd3188bec4d 100755 --- a/core/java/android/inputmethodservice/Keyboard.java +++ b/core/java/android/inputmethodservice/Keyboard.java @@ -438,7 +438,6 @@ public class Keyboard { } } - /** * Returns the square of the distance between the center of the key and the given point. * @param x the x-coordinate of the point @@ -446,9 +445,9 @@ public class Keyboard { * @return the square of the distance of the point from the center of the key */ public int squaredDistanceFrom(int x, int y) { - float xDist = Math.abs((this.x + this.x + width) / 2f - x); - float yDist = Math.abs((this.y + this.y + height) / 2f - y); - return (int) (xDist * xDist + yDist * yDist); + int xDist = this.x + width / 2 - x; + int yDist = this.y + height / 2 - y; + return xDist * xDist + yDist * yDist; } /** @@ -749,7 +748,8 @@ public class Keyboard { if (value.type == TypedValue.TYPE_DIMENSION) { return a.getDimensionPixelOffset(index, defValue); } else if (value.type == TypedValue.TYPE_FRACTION) { - return (int) a.getFraction(index, base, base, defValue); + // Round it to avoid values like 47.9999 from getting truncated + return Math.round(a.getFraction(index, base, base, defValue)); } return defValue; } diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 56473da35899..3b5d741aba5f 100755 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -24,6 +24,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.Typeface; import android.graphics.Paint.Align; import android.graphics.drawable.Drawable; import android.inputmethodservice.Keyboard.Key; @@ -37,6 +38,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.PopupWindow; @@ -132,6 +134,7 @@ public class KeyboardView extends View implements View.OnClickListener { private static final int MSG_REMOVE_PREVIEW = 1; private static final int MSG_REPEAT = 2; + private static final int MSG_LONGPRESS = 3; private int mVerticalCorrection; private int mProximityThreshold; @@ -178,6 +181,7 @@ public class KeyboardView extends View implements View.OnClickListener { private static final int REPEAT_INTERVAL = 50; // ~20 keys per second private static final int REPEAT_START_DELAY = 400; + private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); private Vibrator mVibrator; private long[] mVibratePattern = new long[] {1, 20}; @@ -206,6 +210,9 @@ public class KeyboardView extends View implements View.OnClickListener { sendMessageDelayed(repeat, REPEAT_INTERVAL); } break; + case MSG_LONGPRESS: + openPopupIfRequired((MotionEvent) msg.obj); + break; } } @@ -308,27 +315,28 @@ public class KeyboardView extends View implements View.OnClickListener { @Override public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) { - if (velocityX > 400 && Math.abs(velocityY) < 400) { + final float absX = Math.abs(velocityX); + final float absY = Math.abs(velocityY); + if (velocityX > 500 && absY < absX) { swipeRight(); return true; - } else if (velocityX < -400 && Math.abs(velocityY) < 400) { + } else if (velocityX < -500 && absY < absX) { swipeLeft(); return true; - } else if (velocityY < -400 && Math.abs(velocityX) < 400) { + } else if (velocityY < -500 && absX < absY) { swipeUp(); return true; - } else if (velocityY > 400 && Math.abs(velocityX) < 400) { + } else if (velocityY > 500 && absX < 200) { swipeDown(); return true; + } else if (absX > 800 || absY > 800) { + return true; } return false; } - - @Override - public void onLongPress(MotionEvent me) { - openPopupIfRequired(me); - } }); + + mGestureDetector.setIsLongpressEnabled(false); } public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { @@ -351,6 +359,9 @@ public class KeyboardView extends View implements View.OnClickListener { * @param keyboard the keyboard to display in this view */ public void setKeyboard(Keyboard keyboard) { + if (mKeyboard != null) { + showPreview(NOT_A_KEY); + } mKeyboard = keyboard; requestLayout(); invalidate(); @@ -518,10 +529,10 @@ public class KeyboardView extends View implements View.OnClickListener { // For characters, use large font. For labels like "Done", use small font. if (label.length() > 1 && key.codes.length < 2) { paint.setTextSize(mLabelTextSize); - paint.setFakeBoldText(true); + paint.setTypeface(Typeface.DEFAULT_BOLD); } else { paint.setTextSize(mKeyTextSize); - paint.setFakeBoldText(false); + paint.setTypeface(Typeface.DEFAULT); } // Draw a drop shadow for the text paint.setShadowLayer(3f, 0, 0, 0xCC000000); @@ -878,6 +889,7 @@ public class KeyboardView extends View implements View.OnClickListener { if (mGestureDetector.onTouchEvent(me)) { showPreview(NOT_A_KEY); mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); return true; } @@ -907,12 +919,17 @@ public class KeyboardView extends View implements View.OnClickListener { Message msg = mHandler.obtainMessage(MSG_REPEAT); mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY); } + if (mCurrentKey != NOT_A_KEY) { + Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me); + mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT); + } showPreview(keyIndex); playKeyClick(); vibrate(); break; case MotionEvent.ACTION_MOVE: + boolean continueLongPress = false; if (keyIndex != NOT_A_KEY) { if (mCurrentKey == NOT_A_KEY) { mCurrentKey = keyIndex; @@ -920,6 +937,7 @@ public class KeyboardView extends View implements View.OnClickListener { } else { if (keyIndex == mCurrentKey) { mCurrentKeyTime += eventTime - mLastMoveTime; + continueLongPress = true; } else { resetMultiTap(); mLastKey = mCurrentKey; @@ -936,11 +954,21 @@ public class KeyboardView extends View implements View.OnClickListener { mRepeatKeyIndex = NOT_A_KEY; } } + if (!continueLongPress) { + // Cancel old longpress + mHandler.removeMessages(MSG_LONGPRESS); + // Start new longpress if key has changed + if (keyIndex != NOT_A_KEY) { + Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me); + mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT); + } + } showPreview(keyIndex); break; case MotionEvent.ACTION_UP: mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); if (keyIndex == mCurrentKey) { mCurrentKeyTime += eventTime - mLastMoveTime; } else { diff --git a/core/java/android/os/HandlerState.java b/core/java/android/os/HandlerState.java new file mode 100644 index 000000000000..0708f7d42cb7 --- /dev/null +++ b/core/java/android/os/HandlerState.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * {@hide} + */ +public abstract class HandlerState { + public HandlerState() { + } + + public void enter(Message message) { + } + + public abstract void processMessage(Message message); + + public void exit(Message message) { + } +} diff --git a/core/java/android/os/HandlerStateMachine.java b/core/java/android/os/HandlerStateMachine.java new file mode 100644 index 000000000000..d004a25b9259 --- /dev/null +++ b/core/java/android/os/HandlerStateMachine.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.util.Log; +import android.util.LogPrinter; + +/** + * {@hide} + * + * Implement a state machine where each state is an object, + * HandlerState. Each HandlerState must implement processMessage + * and optionally enter/exit. When a state machine is created + * the initial state must be set. When messages are sent to + * a state machine the current state's processMessage method is + * invoked. If this is the first message for this state the + * enter method is called prior to processMessage and when + * transtionTo is invoked the state's exit method will be + * called after returning from processMessage. + * + * If a message should be handled in a different state the + * processMessage method may call deferMessage. This causes + * the message to be saved on a list until transitioning + * to a new state, at which time all of the deferred messages + * will be put on the front of the state machines queue and + * processed by the new current state's processMessage + * method. + * + * Below is an example state machine with two state's, S1 and S2. + * The initial state is S1 which defers all messages and only + * transition to S2 when message.what == TEST_WHAT_2. State S2 + * will process each messages until it receives TEST_WHAT_2 + * where it will transition back to S1: + + class StateMachine1 extends HandlerStateMachine { + private static final int TEST_WHAT_1 = 1; + private static final int TEST_WHAT_2 = 2; + + StateMachine1(String name) { + super(name); + setInitialState(mS1); + } + + class S1 extends HandlerState { + @Override public void enter(Message message) { + } + + @Override public void processMessage(Message message) { + deferMessage(message); + if (message.what == TEST_WHAT_2) { + transitionTo(mS2); + } + } + + @Override public void exit(Message message) { + } + } + + class S2 extends HandlerState { + @Override public void processMessage(Message message) { + // Do some processing + if (message.what == TEST_WHAT_2) { + transtionTo(mS1); + } + } + } + + private S1 mS1 = new S1(); + private S2 mS2 = new S2(); + } + + */ +public class HandlerStateMachine { + + private boolean mDbg = false; + private static final String TAG = "HandlerStateMachine"; + private String mName; + private SmHandler mHandler; + private HandlerThread mHandlerThread; + + /** + * Handle messages sent to the state machine by calling + * the current state's processMessage. It also handles + * the enter/exit calls and placing any deferred messages + * back onto the queue when transitioning to a new state. + */ + class SmHandler extends Handler { + + SmHandler(Looper looper) { + super(looper); + } + + /** + * This will dispatch the message to the + * current state's processMessage. + */ + @Override + final public void handleMessage(Message msg) { + if (mDbg) Log.d(TAG, "SmHandler.handleMessage E"); + if (mDestState != null) { + if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destation call enter"); + mCurrentState = mDestState; + mDestState = null; + mCurrentState.enter(msg); + } + if (mCurrentState != null) { + if (mDbg) Log.d(TAG, "SmHandler.handleMessage; call processMessage"); + mCurrentState.processMessage(msg); + } else { + /* Strange no state to execute */ + Log.e(TAG, "handleMessage: no current state, did you call setInitialState"); + } + + if (mDestState != null) { + if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destination call exit"); + mCurrentState.exit(msg); + + /** + * Place the messages from the deferred queue:t + * on to the Handler's message queue in the + * same order that they originally arrived. + * + * We set cur.when = 0 to circumvent the check + * that this message has already been sent. + */ + while (mDeferredMessages != null) { + Message cur = mDeferredMessages; + mDeferredMessages = mDeferredMessages.next; + cur.when = 0; + if (mDbg) Log.d(TAG, "SmHandler.handleMessage; queue deferred message what=" + + cur.what + " target=" + cur.target); + sendMessageAtFrontOfQueue(cur); + } + if (mDbg) Log.d(TAG, "SmHandler.handleMessage X"); + } + } + + public HandlerState mCurrentState; + public HandlerState mDestState; + public Message mDeferredMessages; + } + + /** + * Create an active StateMachine, one that has a + * dedicated thread/looper/queue. + */ + public HandlerStateMachine(String name) { + mName = name; + mHandlerThread = new HandlerThread(name); + mHandlerThread.start(); + mHandler = new SmHandler(mHandlerThread.getLooper()); + } + + /** + * Get a message and set Message.target = this. + */ + public final Message obtainMessage() + { + Message msg = Message.obtain(mHandler); + if (mDbg) Log.d(TAG, "StateMachine.obtainMessage() EX target=" + msg.target); + return msg; + } + + /** + * Get a message and set Message.target = this and + * Message.what = what. + */ + public final Message obtainMessage(int what) { + Message msg = Message.obtain(mHandler, what); + if (mDbg) { + Log.d(TAG, "StateMachine.obtainMessage(what) EX what=" + msg.what + + " target=" + msg.target); + } + return msg; + } + + /** + * Enqueue a message to this state machine. + */ + public final void sendMessage(Message msg) { + if (mDbg) Log.d(TAG, "StateMachine.sendMessage EX msg.what=" + msg.what); + mHandler.sendMessage(msg); + } + + /** + * Enqueue a message to this state machine after a delay. + */ + public final void sendMessageDelayed(Message msg, long delayMillis) { + if (mDbg) { + Log.d(TAG, "StateMachine.sendMessageDelayed EX msg.what=" + + msg.what + " delay=" + delayMillis); + } + mHandler.sendMessageDelayed(msg, delayMillis); + } + + /** + * Set the initial state. This must be invoked before + * and messages are sent to the state machine. + */ + public void setInitialState(HandlerState initialState) { + if (mDbg) { + Log.d(TAG, "StateMachine.setInitialState EX initialState" + + initialState.getClass().getName()); + } + mHandler.mDestState = initialState; + } + + /** + * transition to destination state. Upon returning + * from processMessage the current state's exit will + * be executed and upon the next message arriving + * destState.enter will be invoked. + */ + final public void transitionTo(HandlerState destState) { + if (mDbg) { + Log.d(TAG, "StateMachine.transitionTo EX destState" + + destState.getClass().getName()); + } + mHandler.mDestState = destState; + } + + /** + * Defer this message until next state transition. + * Upon transitioning all deferred messages will be + * placed on the queue and reprocessed in the original + * order. (i.e. The next state the oldest messages will + * be processed first) + */ + final public void deferMessage(Message msg) { + if (mDbg) { + Log.d(TAG, "StateMachine.deferMessage EX mDeferredMessages=" + + mHandler.mDeferredMessages); + } + + /* Copy the "msg" to "newMsg" as "msg" will be recycled */ + Message newMsg = obtainMessage(); + newMsg.copyFrom(msg); + + /* Place on front of queue */ + newMsg.next = mHandler.mDeferredMessages; + mHandler.mDeferredMessages = newMsg; + } + + /** + * @return the name + */ + public String getName() { + return mName; + } + + /** + * @return Handler + */ + public Handler getHandler() { + return mHandler; + } + + /** + * @return if debugging is enabled + */ + public boolean isDbg() { + return mDbg; + } + + /** + * Set debug enable/disabled. + */ + public void setDbg(boolean dbg) { + mDbg = dbg; + if (mDbg) { + mHandlerThread.getLooper().setMessageLogging(new LogPrinter(Log.VERBOSE, TAG)); + } else { + mHandlerThread.getLooper().setMessageLogging(null); + } + } +} diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index abc1e2f3105f..e48f1523a6be 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -28,4 +28,5 @@ interface IPowerManager void setPokeLock(int pokey, IBinder lock, String tag); void setStayOnSetting(int val); long getScreenOnTime(); + void preventScreenOn(boolean prevent); } diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java index 0794e6de08e2..b53e22704d1f 100644 --- a/core/java/android/os/Power.java +++ b/core/java/android/os/Power.java @@ -16,6 +16,8 @@ package android.os; +import java.io.IOException; + /** * Class that provides access to some of the power management functions. * @@ -71,12 +73,12 @@ public class Power * Brightness value for dim backlight */ public static final int BRIGHTNESS_DIM = 20; - + /** * Brightness value for fully on */ public static final int BRIGHTNESS_ON = 255; - + /** * Brightness value to use when battery is low */ @@ -104,11 +106,11 @@ public class Power public static native int setScreenState(boolean on); public static native int setLastUserActivityTimeout(long ms); - + /** * Turn the device off. - * - * This method is considered deprecated in favor of + * + * This method is considered deprecated in favor of * {@link android.policy.ShutdownThread.shutdownAfterDisablingRadio()}. * * @deprecated @@ -120,7 +122,9 @@ public class Power /** * Reboot the device. * @param reason code to pass to the kernel (e.g. "recovery"), or null. + * + * @throws IOException if reboot fails for some reason (eg, lack of + * permission) */ - public static native void reboot(String reason); + public static native void reboot(String reason) throws IOException; } - diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java index e2a315763278..05c295224374 100644 --- a/core/java/android/preference/PreferenceGroupAdapter.java +++ b/core/java/android/preference/PreferenceGroupAdapter.java @@ -133,11 +133,10 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference; if (preferenceAsGroup.isOnSameScreenAsChildren()) { flattenPreferenceGroup(preferences, preferenceAsGroup); - preference.setOnPreferenceChangeInternalListener(this); } - } else { - preference.setOnPreferenceChangeInternalListener(this); } + + preference.setOnPreferenceChangeInternalListener(this); } } diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java index 7aaed494b93a..76aa51d175c8 100644 --- a/core/java/android/provider/Browser.java +++ b/core/java/android/provider/Browser.java @@ -372,15 +372,13 @@ public class Browser { SEARCHES_WHERE_CLAUSE, new String [] { search }, null); + ContentValues map = new ContentValues(); + map.put(SearchColumns.SEARCH, search); + map.put(SearchColumns.DATE, now); /* We should only get one answer that is exactly the same. */ if (c.moveToFirst()) { - ContentValues map = new ContentValues(); - map.put(BookmarkColumns.DATE, now); - cr.update(BOOKMARKS_URI, map, "_id = " + c.getInt(0), null); + cr.update(SEARCHES_URI, map, "_id = " + c.getInt(0), null); } else { - ContentValues map = new ContentValues(); - map.put(SearchColumns.SEARCH, search); - map.put(SearchColumns.DATE, now); cr.insert(SEARCHES_URI, map); } c.deactivate(); diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index 6d24ba87aa53..085c095569da 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -170,12 +170,20 @@ public class Contacts { */ public interface PeopleColumns { /** - * The persons name. + * The person's name. *

Type: TEXT

*/ public static final String NAME = "name"; /** + * Phonetic equivalent of the person's name, in a locale-dependent + * character set (e.g. hiragana for Japanese). + * Used for pronunciation and/or collation in some languages. + *

Type: TEXT

+ */ + public static final String PHONETIC_NAME = "phonetic_name"; + + /** * The display name. If name is not null name, else if number is not null number, * else if email is not null email. *

Type: TEXT

@@ -1509,6 +1517,12 @@ public class Contacts { public static final String NAME = "name"; /** + * The extra field for the contact phonetic name. + *

Type: String

+ */ + public static final String PHONETIC_NAME = "phonetic_name"; + + /** * The extra field for the contact company. *

Type: String

*/ @@ -1535,7 +1549,7 @@ public class Contacts { /** * The extra field for the contact phone number type. *

Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns}, - * or a string specifying a type and label.

+ * or a string specifying a custom label.

*/ public static final String PHONE_TYPE = "phone_type"; @@ -1554,7 +1568,7 @@ public class Contacts { /** * The extra field for an optional second contact phone number type. *

Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns}, - * or a string specifying a type and label.

+ * or a string specifying a custom label.

*/ public static final String SECONDARY_PHONE_TYPE = "secondary_phone_type"; @@ -1567,7 +1581,7 @@ public class Contacts { /** * The extra field for an optional third contact phone number type. *

Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns}, - * or a string specifying a type and label.

+ * or a string specifying a custom label.

*/ public static final String TERTIARY_PHONE_TYPE = "tertiary_phone_type"; @@ -1580,7 +1594,7 @@ public class Contacts { /** * The extra field for the contact email type. *

Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} - * or a string specifying a type and label.

+ * or a string specifying a custom label.

*/ public static final String EMAIL_TYPE = "email_type"; @@ -1599,7 +1613,7 @@ public class Contacts { /** * The extra field for an optional second contact email type. *

Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} - * or a string specifying a type and label.

+ * or a string specifying a custom label.

*/ public static final String SECONDARY_EMAIL_TYPE = "secondary_email_type"; @@ -1612,7 +1626,7 @@ public class Contacts { /** * The extra field for an optional third contact email type. *

Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} - * or a string specifying a type and label.

+ * or a string specifying a custom label.

*/ public static final String TERTIARY_EMAIL_TYPE = "tertiary_email_type"; @@ -1625,7 +1639,7 @@ public class Contacts { /** * The extra field for the contact postal address type. *

Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} - * or a string specifying a type and label.

+ * or a string specifying a custom label.

*/ public static final String POSTAL_TYPE = "postal_type"; diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index d4b728b437ea..7b2f18ca9235 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -84,13 +84,27 @@ public final class MediaStore public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus"; /** - * The name of the Intent-extra used to control the orientation of a MovieView. - * This is an int property that overrides the MovieView activity's requestedOrientation. + * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView. + * This is an int property that overrides the activity's requestedOrientation. * @see android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED */ public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation"; /** + * The name of the Intent-extra used to control the orientation of a ViewImage. + * This is a boolean property that overrides the activity's default fullscreen state. + * @hide + */ + public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen"; + + /** + * The name of the Intent-extra used to control the orientation of a ViewImage. + * This is a boolean property that specifies whether or not to show action icons. + * @hide + */ + public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons"; + + /** * The name of the Intent-extra used to control the onCompletion behavior of a MovieView. * This is a boolean property that specifies whether or not to finish the MovieView activity * when the movie completes playing. The default value is true, which means to automatically @@ -118,6 +132,22 @@ public final class MediaStore public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"; /** + * Standard Intent action that can be sent to have the media application + * capture an video and return it. The caller may pass in an extra EXTRA_VIDEO_QUALITY + * control the video quality. + * @hide + */ + public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE"; + + /** + * The name of the Intent-extra used to control the quality of a recorded video. This is an + * integer property. Currently value 0 means low quality, suitable for MMS messages, and + * value 1 means high quality. In the future other quality levels may be added. + * @hide + */ + public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality"; + + /** * Common fields for most MediaProvider tables */ @@ -1162,13 +1192,21 @@ public final class MediaStore public static final class Video { /** - * The default sort order for this table + * deprecated Replaced by DEFAULT_SORT_ORDER2 + * This variable is a mistake that is retained for backwards compatibility. + * (There is no "name" column in the Video table.) */ public static final String DEFAULT_SORT_ORDER = "name ASC"; + /** + * The default sort order for this table + * @hide + */ + public static final String DEFAULT_SORT_ORDER2 = MediaColumns.DISPLAY_NAME; + public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { - return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); + return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER2); } public interface VideoColumns extends MediaColumns { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e93bbeb95cc0..91624adce91a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1772,9 +1772,19 @@ public final class Settings { /** * The Logging ID (a unique 64-bit value) as a hex string. * Used as a pseudonymous identifier for logging. + * @deprecated This identifier is poorly initialized and has + * many collisions. It should not be used. */ + @Deprecated public static final String LOGGING_ID = "logging_id"; - + + /** + * The Logging ID (a unique 64-bit value) as a hex string. + * Used as a pseudonymous identifier for logging. + * @hide + */ + public static final String LOGGING_ID2 = "logging_id2"; + /** * User preference for which network(s) should be used. Only the * connectivity service should touch this. diff --git a/core/java/android/provider/Sync.java b/core/java/android/provider/Sync.java index 2086a5dd4aa6..94bf80777d0f 100644 --- a/core/java/android/provider/Sync.java +++ b/core/java/android/provider/Sync.java @@ -25,7 +25,6 @@ import android.os.Handler; import java.util.Map; - /** * The Sync provider stores information used in managing the syncing of the device, * including the history and pending syncs. @@ -500,6 +499,9 @@ public final class Sync { /** controls whether or not the individual provider is synced when tickles are received */ public static final String SETTING_SYNC_PROVIDER_PREFIX = "sync_provider_"; + /** query column project */ + private static final String[] PROJECTION = { KEY, VALUE }; + /** * Convenience function for updating a single settings value as a * boolean. This will either create a new entry in the table if the @@ -521,6 +523,32 @@ public final class Sync { } /** + * Convenience function for getting a setting value as a boolean without using the + * QueryMap for light-weight setting querying. + * @param contentResolver The ContentResolver for querying the setting. + * @param name The name of the setting to query + * @param def The default value for the setting. + * @return The value of the setting. + */ + static public boolean getBoolean(ContentResolver contentResolver, + String name, boolean def) { + Cursor cursor = contentResolver.query( + CONTENT_URI, + PROJECTION, + KEY + "=?", + new String[] { name }, + null); + try { + if (cursor != null && cursor.moveToFirst()) { + return Boolean.parseBoolean(cursor.getString(1)); + } + } finally { + if (cursor != null) cursor.close(); + } + return def; + } + + /** * A convenience method to set whether or not the provider is synced when * it receives a network tickle. * diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index 3cbb855d08c9..ded50e457aca 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -45,7 +45,7 @@ import java.util.HashMap; import java.util.Iterator; public class BluetoothA2dpService extends IBluetoothA2dp.Stub { - private static final String TAG = "BluetoothDeviceService"; + private static final String TAG = "BluetoothA2dpService"; private static final boolean DBG = true; public static final String BLUETOOTH_A2DP_SERVICE = "bluetooth_a2dp"; @@ -143,12 +143,27 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (path == null) { return BluetoothError.ERROR; } - if (!connectSinkNative(path)) { + + SinkState sink = mAudioDevices.get(path); + int state = BluetoothA2dp.STATE_DISCONNECTED; + if (sink != null) { + state = sink.state; + } + switch (state) { + case BluetoothA2dp.STATE_CONNECTED: + case BluetoothA2dp.STATE_PLAYING: + case BluetoothA2dp.STATE_DISCONNECTING: return BluetoothError.ERROR; - } else { - updateState(path, BluetoothA2dp.STATE_CONNECTING); + case BluetoothA2dp.STATE_CONNECTING: return BluetoothError.SUCCESS; } + + // State is DISCONNECTED + if (!connectSinkNative(path)) { + return BluetoothError.ERROR; + } + updateState(path, BluetoothA2dp.STATE_CONNECTING); + return BluetoothError.SUCCESS; } public synchronized int disconnectSink(String address) { @@ -165,6 +180,14 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (path == null) { return BluetoothError.ERROR; } + switch (mAudioDevices.get(path).state) { + case BluetoothA2dp.STATE_DISCONNECTED: + return BluetoothError.ERROR; + case BluetoothA2dp.STATE_DISCONNECTING: + return BluetoothError.SUCCESS; + } + + // State is CONNECTING or CONNECTED or PLAYING if (!disconnectSinkNative(path)) { return BluetoothError.ERROR; } else { diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java index 6c8f55487c0e..ac4cdb9d0cfc 100644 --- a/core/java/android/server/search/SearchableInfo.java +++ b/core/java/android/server/search/SearchableInfo.java @@ -31,6 +31,7 @@ import android.content.res.XmlResourceParser; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.text.InputType; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; @@ -75,6 +76,7 @@ public final class SearchableInfo implements Parcelable { public boolean mQueryRewriteFromText = false; private int mIconId = 0; private int mSearchButtonText = 0; + private int mSearchInputType = 0; private String mSuggestAuthority = null; private String mSuggestPath = null; private String mSuggestSelection = null; @@ -415,6 +417,10 @@ public final class SearchableInfo implements Parcelable { mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0); mSearchButtonText = a.getResourceId( com.android.internal.R.styleable.Searchable_searchButtonText, 0); + mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType, + InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_FLAG_SEARCH | + InputType.TYPE_TEXT_VARIATION_SEARCH_STRING); setSearchModeFlags(); if (DBG_INHIBIT_SUGGESTIONS == 0) { @@ -657,6 +663,16 @@ public final class SearchableInfo implements Parcelable { } /** + * Return the input type as specified in the searchable attributes. This will default to + * InputType.TYPE_CLASS_TEXT if not specified (which is appropriate for free text input). + * + * @return the input type + */ + public int getInputType() { + return mSearchInputType; + } + + /** * Return the list of searchable activities, for use in the drop-down. */ public static ArrayList getSearchablesList() { @@ -694,6 +710,7 @@ public final class SearchableInfo implements Parcelable { mSearchMode = in.readInt(); mIconId = in.readInt(); mSearchButtonText = in.readInt(); + mSearchInputType = in.readInt(); setSearchModeFlags(); mSuggestAuthority = in.readString(); @@ -722,6 +739,7 @@ public final class SearchableInfo implements Parcelable { dest.writeInt(mSearchMode); dest.writeInt(mIconId); dest.writeInt(mSearchButtonText); + dest.writeInt(mSearchInputType); dest.writeString(mSuggestAuthority); dest.writeString(mSuggestPath); diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java new file mode 100644 index 000000000000..abbf8a77c31a --- /dev/null +++ b/core/java/android/speech/RecognizerIntent.java @@ -0,0 +1,106 @@ +/* + * 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 android.speech; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; + +/** + * Constants for supporting speech recognition through starting an {@link Intent} + * + * @hide {pending API council review} + */ +public class RecognizerIntent { + private RecognizerIntent() { + // Not for instantiating. + } + + /** + * Starts an activity that will prompt the user for speech and sends it through a + * speech recognizer. + * + *

Required extras: + *

    + *
  • {@link #EXTRA_LANGUAGE_MODEL} + *
+ * + *

Optional extras: + *

    + *
  • {@link Intent#EXTRA_PROMPT} + *
  • {@link #EXTRA_LANGUAGE} + *
  • {@link #EXTRA_MAX_RESULTS} + *
+ * + *

Result extras: + *

    + *
  • {@link #EXTRA_RESULTS} + *
+ * + *

NOTE: There may not be any applications installed to handle this action, so you should + * make sure to catch {@link ActivityNotFoundException}. + */ + public static final String ACTION_RECOGNIZE_SPEECH = "android.speech.action.RECOGNIZE_SPEECH"; + + /** + * Informs the recognizer which speech model to prefer when performing + * {@link #ACTION_RECOGNIZE_SPEECH}. The recognizer uses this + * information to fine tune the results. This extra is required. Activities implementing + * {@link #ACTION_RECOGNIZE_SPEECH} may interpret the values as they see fit. + * + * @see #LANGUAGE_MODEL_FREE_FORM + * @see #LANGUAGE_MODEL_WEB_SEARCH + */ + public static final String EXTRA_LANGUAGE_MODEL = "language_model"; + + /** Free form speech recognition */ + public static final String LANGUAGE_MODEL_FREE_FORM = "free_form"; + /** Use a language model based on web search terms */ + public static final String LANGUAGE_MODEL_WEB_SEARCH = "web_search"; + + /** Optional text prompt to show to the user when asking them to speak. */ + public static final String EXTRA_PROMPT = "prompt"; + + /** + * Optional language override to inform the recognizer that it should expect speech in + * a language different than the one set in the {@link java.util.Locale#getDefault()}. + */ + public static final String EXTRA_LANGUAGE = "lang"; + + /** + * Optional limit on the maximum number of results to return. If omitted the recognizer + * will choose how many results to return. Must be an integer. + */ + public static final String EXTRA_MAX_RESULTS = "max_results"; + + /** Result code returned when no matches are found for the given speech */ + public static final int RESULT_NO_MATCH = Activity.RESULT_FIRST_USER; + /** Result code returned when there is a generic client error */ + public static final int RESULT_CLIENT_ERROR = Activity.RESULT_FIRST_USER + 1; + /** Result code returned when the recognition server returns an error */ + public static final int RESULT_SERVER_ERROR = Activity.RESULT_FIRST_USER + 2; + /** Result code returned when a network error was encountered */ + public static final int RESULT_NETWORK_ERROR = Activity.RESULT_FIRST_USER + 3; + /** Result code returned when an audio error was encountered */ + public static final int RESULT_AUDIO_ERROR = Activity.RESULT_FIRST_USER + 4; + + /** + * An ArrayList of the potential results when performing + * {@link #ACTION_RECOGNIZE_SPEECH}. Only present when {@link Activity#RESULT_OK} is returned. + */ + public static final String EXTRA_RESULTS = "results"; +} diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java index 0ffe4acaffbf..bd868340dcaf 100644 --- a/core/java/android/text/InputType.java +++ b/core/java/android/text/InputType.java @@ -1,3 +1,19 @@ +/* + * 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 android.text; import android.text.TextUtils; @@ -104,6 +120,11 @@ public interface InputType { */ public static final int TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000; + /** + * Flag for {@link #TYPE_CLASS_TEXT}: flags any text being used as a search string + */ + public static final int TYPE_TEXT_FLAG_SEARCH = 0x00040000; + // ---------------------------------------------------------------------- /** @@ -139,8 +160,7 @@ public interface InputType { public static final int TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000050; /** - * Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing - * address. + * Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing address. */ public static final int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000060; @@ -150,14 +170,12 @@ public interface InputType { public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000070; /** - * Variation of {@link #TYPE_CLASS_TEXT}: entering a search string - * for a web search. + * Variation of {@link #TYPE_CLASS_TEXT}: entering a simple text search (e.g. web search) */ - public static final int TYPE_TEXT_VARIATION_WEB_SEARCH = 0x00000080; + public static final int TYPE_TEXT_VARIATION_SEARCH_STRING = 0x00000080; /** - * Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of - * a web form. + * Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form. */ public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x00000090; diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 346db4944b88..95acf9d23617 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1074,7 +1074,9 @@ public abstract class Layout { float h2 = getSecondaryHorizontal(point) - 0.5f; int caps = TextKeyListener.getMetaState(editingBuffer, - KeyEvent.META_SHIFT_ON); + KeyEvent.META_SHIFT_ON) | + TextKeyListener.getMetaState(editingBuffer, + TextKeyListener.META_SELECTING); int fn = TextKeyListener.getMetaState(editingBuffer, KeyEvent.META_ALT_ON); int dist = 0; diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index 48f65c6c3d64..feae6cf52a6f 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -146,26 +146,26 @@ public class DateUtils // The following FORMAT_* symbols are used for specifying the format of // dates and times in the formatDateRange method. - public static final int FORMAT_SHOW_TIME = 0x00001; - public static final int FORMAT_SHOW_WEEKDAY = 0x00002; - public static final int FORMAT_SHOW_YEAR = 0x00004; - public static final int FORMAT_NO_YEAR = 0x00008; - public static final int FORMAT_SHOW_DATE = 0x00010; - public static final int FORMAT_NO_MONTH_DAY = 0x00020; - public static final int FORMAT_12HOUR = 0x00040; - public static final int FORMAT_24HOUR = 0x00080; - public static final int FORMAT_CAP_AMPM = 0x00100; - public static final int FORMAT_NO_NOON = 0x00200; - public static final int FORMAT_CAP_NOON = 0x00400; - public static final int FORMAT_NO_MIDNIGHT = 0x00800; - public static final int FORMAT_CAP_MIDNIGHT = 0x01000; - public static final int FORMAT_UTC = 0x02000; - public static final int FORMAT_ABBREV_TIME = 0x04000; + public static final int FORMAT_SHOW_TIME = 0x00001; + public static final int FORMAT_SHOW_WEEKDAY = 0x00002; + public static final int FORMAT_SHOW_YEAR = 0x00004; + public static final int FORMAT_NO_YEAR = 0x00008; + public static final int FORMAT_SHOW_DATE = 0x00010; + public static final int FORMAT_NO_MONTH_DAY = 0x00020; + public static final int FORMAT_12HOUR = 0x00040; + public static final int FORMAT_24HOUR = 0x00080; + public static final int FORMAT_CAP_AMPM = 0x00100; + public static final int FORMAT_NO_NOON = 0x00200; + public static final int FORMAT_CAP_NOON = 0x00400; + public static final int FORMAT_NO_MIDNIGHT = 0x00800; + public static final int FORMAT_CAP_MIDNIGHT = 0x01000; + public static final int FORMAT_UTC = 0x02000; + public static final int FORMAT_ABBREV_TIME = 0x04000; public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; - public static final int FORMAT_ABBREV_MONTH = 0x10000; - public static final int FORMAT_NUMERIC_DATE = 0x20000; - public static final int FORMAT_ABBREV_ALL = (FORMAT_ABBREV_TIME - | FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_MONTH); + public static final int FORMAT_ABBREV_MONTH = 0x10000; + public static final int FORMAT_NUMERIC_DATE = 0x20000; + public static final int FORMAT_ABBREV_RELATIVE = 0x40000; + public static final int FORMAT_ABBREV_ALL = 0x80000; public static final int FORMAT_CAP_NOON_MIDNIGHT = (FORMAT_CAP_NOON | FORMAT_CAP_MIDNIGHT); public static final int FORMAT_NO_NOON_MIDNIGHT = (FORMAT_NO_NOON | FORMAT_NO_MIDNIGHT); @@ -233,18 +233,20 @@ public class DateUtils }; /** - * Request the full spelled-out name. - * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. - * @more - *

e.g. "Sunday" or "January" + * Request the full spelled-out name. For use with the 'abbrev' parameter of + * {@link #getDayOfWeekString} and {@link #getMonthString}. + * + * @more

+ * e.g. "Sunday" or "January" */ public static final int LENGTH_LONG = 10; /** - * Request an abbreviated version of the name. - * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. - * @more - *

e.g. "Sun" or "Jan" + * Request an abbreviated version of the name. For use with the 'abbrev' + * parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. + * + * @more

+ * e.g. "Sun" or "Jan" */ public static final int LENGTH_MEDIUM = 20; @@ -364,53 +366,162 @@ public class DateUtils * 0, MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, WEEK_IN_MILLIS */ public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) { - Resources r = Resources.getSystem(); + int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH; + return getRelativeTimeSpanString(time, now, minResolution, flags); + } + /** + * Returns a string describing 'time' as a time relative to 'now'. + *

+ * Time spans in the past are formatted like "42 minutes ago". Time spans in + * the future are formatted like "in 42 minutes". + *

+ * Can use {@link #FORMAT_ABBREV_RELATIVE} flag to use abbreviated relative + * times, like "42 mins ago". + * + * @param time the time to describe, in milliseconds + * @param now the current time in milliseconds + * @param minResolution the minimum timespan to report. For example, a time + * 3 seconds in the past will be reported as "0 minutes ago" if + * this is set to MINUTE_IN_MILLIS. Pass one of 0, + * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, + * WEEK_IN_MILLIS + * @param flags a bit mask of formatting options, such as + * {@link #FORMAT_NUMERIC_DATE} or + * {@link #FORMAT_ABBREV_RELATIVE} + */ + public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution, + int flags) { + Resources r = Resources.getSystem(); + boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0; + boolean past = (now >= time); long duration = Math.abs(now - time); - + int resId; long count; if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { count = duration / SECOND_IN_MILLIS; if (past) { - resId = com.android.internal.R.plurals.num_seconds_ago; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_num_seconds_ago; + } else { + resId = com.android.internal.R.plurals.num_seconds_ago; + } } else { - resId = com.android.internal.R.plurals.in_num_seconds; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_in_num_seconds; + } else { + resId = com.android.internal.R.plurals.in_num_seconds; + } } } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { count = duration / MINUTE_IN_MILLIS; if (past) { - resId = com.android.internal.R.plurals.num_minutes_ago; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_num_minutes_ago; + } else { + resId = com.android.internal.R.plurals.num_minutes_ago; + } } else { - resId = com.android.internal.R.plurals.in_num_minutes; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_in_num_minutes; + } else { + resId = com.android.internal.R.plurals.in_num_minutes; + } } } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) { count = duration / HOUR_IN_MILLIS; if (past) { - resId = com.android.internal.R.plurals.num_hours_ago; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_num_hours_ago; + } else { + resId = com.android.internal.R.plurals.num_hours_ago; + } } else { - resId = com.android.internal.R.plurals.in_num_hours; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_in_num_hours; + } else { + resId = com.android.internal.R.plurals.in_num_hours; + } } } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) { count = duration / DAY_IN_MILLIS; if (past) { - resId = com.android.internal.R.plurals.num_days_ago; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_num_days_ago; + } else { + resId = com.android.internal.R.plurals.num_days_ago; + } } else { - resId = com.android.internal.R.plurals.in_num_days; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_in_num_days; + } else { + resId = com.android.internal.R.plurals.in_num_days; + } } } else { - // Longer than a week ago, so just show the date. - int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH; - // We know that we won't be showing the time, so it is safe to pass // in a null context. return formatDateRange(null, time, time, flags); } - + String format = r.getQuantityString(resId, (int) count); return String.format(format, count); } + + /** + * Return string describing the elapsed time since startTime formatted like + * "[relative time/date], [time]". + *

+ * Example output strings for the US date format. + *

    + *
  • 3 mins ago, 10:15 AM
  • + *
  • yesterday, 12:20 PM
  • + *
  • Dec 12, 4:12 AM
  • + *
  • 11/14/2007, 8:20 AM
  • + *
+ * + * @param time some time in the past. + * @param minResolution the minimum elapsed time (in milliseconds) to report + * when showing relative times. For example, a time 3 seconds in + * the past will be reported as "0 minutes ago" if this is set to + * {@link #MINUTE_IN_MILLIS}. + * @param transitionResolution the elapsed time (in milliseconds) at which + * to stop reporting relative measurements. Elapsed times greater + * than this resolution will default to normal date formatting. + * For example, will transition from "6 days ago" to "Dec 12" + * when using {@link #WEEK_IN_MILLIS}. + */ + public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution, + long transitionResolution, int flags) { + Resources r = Resources.getSystem(); + + long now = System.currentTimeMillis(); + long duration = Math.abs(now - time); + + // getRelativeTimeSpanString() doesn't correctly format relative dates + // above a week or exact dates below a day, so clamp + // transitionResolution as needed. + if (transitionResolution > WEEK_IN_MILLIS) { + transitionResolution = WEEK_IN_MILLIS; + } else if (transitionResolution < DAY_IN_MILLIS) { + transitionResolution = DAY_IN_MILLIS; + } + + CharSequence timeClause = formatDateRange(c, time, time, FORMAT_SHOW_TIME); + + String result; + if (duration < transitionResolution) { + CharSequence relativeClause = getRelativeTimeSpanString(time, now, minResolution, flags); + result = r.getString(com.android.internal.R.string.relative_time, relativeClause, timeClause); + } else { + CharSequence dateClause = getRelativeTimeSpanString(c, time, false); + result = r.getString(com.android.internal.R.string.date_time, dateClause, timeClause); + } + + return result; + } /** * Returns a string describing a day relative to the current day. For example if the day is @@ -1005,8 +1116,8 @@ public class DateUtils boolean showYear = (flags & FORMAT_SHOW_YEAR) != 0; boolean noYear = (flags & FORMAT_NO_YEAR) != 0; boolean useUTC = (flags & FORMAT_UTC) != 0; - boolean abbrevWeekDay = (flags & FORMAT_ABBREV_WEEKDAY) != 0; - boolean abbrevMonth = (flags & FORMAT_ABBREV_MONTH) != 0; + boolean abbrevWeekDay = (flags & (FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_ALL)) != 0; + boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0; boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0; boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0; @@ -1087,7 +1198,7 @@ public class DateUtils startTimeFormat = HOUR_MINUTE_24; endTimeFormat = HOUR_MINUTE_24; } else { - boolean abbrevTime = (flags & FORMAT_ABBREV_TIME) != 0; + boolean abbrevTime = (flags & (FORMAT_ABBREV_TIME | FORMAT_ABBREV_ALL)) != 0; boolean capAMPM = (flags & FORMAT_CAP_AMPM) != 0; boolean noNoon = (flags & FORMAT_NO_NOON) != 0; boolean capNoon = (flags & FORMAT_CAP_NOON) != 0; @@ -1419,7 +1530,6 @@ public class DateUtils long now = System.currentTimeMillis(); long span = now - millis; - Resources res = c.getResources(); if (sNowTime == null) { sNowTime = new Time(); sThenTime = new Time(); @@ -1449,6 +1559,7 @@ public class DateUtils prepositionId = R.string.preposition_for_date; } if (withPreposition) { + Resources res = c.getResources(); result = res.getString(prepositionId, result); } return result; diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 652413eb168a..7457439a6a8c 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -31,8 +31,10 @@ ArrowKeyMovementMethod implements MovementMethod { private boolean up(TextView widget, Spannable buffer) { - boolean cap = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); boolean alt = MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; Layout layout = widget.getLayout(); @@ -55,8 +57,10 @@ implements MovementMethod } private boolean down(TextView widget, Spannable buffer) { - boolean cap = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); boolean alt = MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; Layout layout = widget.getLayout(); @@ -79,8 +83,10 @@ implements MovementMethod } private boolean left(TextView widget, Spannable buffer) { - boolean cap = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); boolean alt = MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; Layout layout = widget.getLayout(); @@ -101,8 +107,10 @@ implements MovementMethod } private boolean right(TextView widget, Spannable buffer) { - boolean cap = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); boolean alt = MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; Layout layout = widget.getLayout(); @@ -141,6 +149,13 @@ implements MovementMethod case KeyEvent.KEYCODE_DPAD_RIGHT: handled |= right(widget, buffer); break; + + case KeyEvent.KEYCODE_DPAD_CENTER: + if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { + if (widget.showContextMenu()) { + handled = true; + } + } } if (handled) { @@ -179,7 +194,10 @@ implements MovementMethod int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); - boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); if (cap) { Selection.extendSelection(buffer, off); diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java index f0305d9480e2..d5a473b0d6d9 100644 --- a/core/java/android/text/method/MetaKeyKeyListener.java +++ b/core/java/android/text/method/MetaKeyKeyListener.java @@ -22,7 +22,8 @@ import android.text.*; /** * This base class encapsulates the behavior for handling the meta keys - * (caps, fn, sym). Key listener that care about meta state should + * (shift and alt) and the pseudo-meta state of selecting text. + * Key listeners that care about meta state should * inherit from it; you should not instantiate this class directly in a client. */ @@ -31,13 +32,49 @@ public abstract class MetaKeyKeyListener { public static final int META_ALT_ON = KeyEvent.META_ALT_ON; public static final int META_SYM_ON = KeyEvent.META_SYM_ON; - public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << 8; - public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << 8; - public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << 8; + private static final int LOCKED_SHIFT = 8; + + public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << LOCKED_SHIFT; + public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << LOCKED_SHIFT; + public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << LOCKED_SHIFT; + + /** + * @hide pending API review + */ + public static final int META_SELECTING = 1 << 16; + + private static final int USED_SHIFT = 24; + + private static final long META_CAP_USED = ((long)KeyEvent.META_SHIFT_ON) << USED_SHIFT; + private static final long META_ALT_USED = ((long)KeyEvent.META_ALT_ON) << USED_SHIFT; + private static final long META_SYM_USED = ((long)KeyEvent.META_SYM_ON) << USED_SHIFT; + + private static final int PRESSED_SHIFT = 32; + + private static final long META_CAP_PRESSED = ((long)KeyEvent.META_SHIFT_ON) << PRESSED_SHIFT; + private static final long META_ALT_PRESSED = ((long)KeyEvent.META_ALT_ON) << PRESSED_SHIFT; + private static final long META_SYM_PRESSED = ((long)KeyEvent.META_SYM_ON) << PRESSED_SHIFT; + private static final int RELEASED_SHIFT = 40; + + private static final long META_CAP_RELEASED = ((long)KeyEvent.META_SHIFT_ON) << RELEASED_SHIFT; + private static final long META_ALT_RELEASED = ((long)KeyEvent.META_ALT_ON) << RELEASED_SHIFT; + private static final long META_SYM_RELEASED = ((long)KeyEvent.META_SYM_ON) << RELEASED_SHIFT; + + private static final long META_SHIFT_MASK = META_SHIFT_ON + | META_CAP_LOCKED | META_CAP_USED + | META_CAP_PRESSED | META_CAP_RELEASED; + private static final long META_ALT_MASK = META_ALT_ON + | META_ALT_LOCKED | META_ALT_USED + | META_ALT_PRESSED | META_ALT_RELEASED; + private static final long META_SYM_MASK = META_SYM_ON + | META_SYM_LOCKED | META_SYM_USED + | META_SYM_PRESSED | META_SYM_RELEASED; + private static final Object CAP = new Object(); private static final Object ALT = new Object(); private static final Object SYM = new Object(); + private static final Object SELECTING = new Object(); /** * Resets all meta state to inactive. @@ -46,6 +83,7 @@ public abstract class MetaKeyKeyListener { text.removeSpan(CAP); text.removeSpan(ALT); text.removeSpan(SYM); + text.removeSpan(SELECTING); } /** @@ -59,13 +97,14 @@ public abstract class MetaKeyKeyListener { public static final int getMetaState(CharSequence text) { return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) | getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) | - getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED); + getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) | + getActive(text, SELECTING, META_SELECTING, META_SELECTING); } /** * Gets the state of a particular meta key. * - * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON + * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING * @param text the buffer in which the meta key would have been pressed. * * @return 0 if inactive, 1 if active, 2 if locked. @@ -81,6 +120,9 @@ public abstract class MetaKeyKeyListener { case META_SYM_ON: return getActive(text, SYM, 1, 2); + case META_SELECTING: + return getActive(text, SELECTING, 1, 2); + default: return 0; } @@ -120,7 +162,8 @@ public abstract class MetaKeyKeyListener { * keep track of meta state in the specified text. */ public static boolean isMetaTracker(CharSequence text, Object what) { - return what == CAP || what == ALT || what == SYM; + return what == CAP || what == ALT || what == SYM || + what == SELECTING; } private static void adjust(Spannable content, Object what) { @@ -140,6 +183,7 @@ public abstract class MetaKeyKeyListener { resetLock(content, CAP); resetLock(content, ALT); resetLock(content, SYM); + resetLock(content, SELECTING); } private static void resetLock(Spannable content, Object what) { @@ -189,6 +233,23 @@ public abstract class MetaKeyKeyListener { } /** + * Start selecting text. + * @hide pending API review + */ + public static void startSelecting(View view, Spannable content) { + content.setSpan(SELECTING, 0, 0, PRESSED); + } + + /** + * Stop selecting text. This does not actually collapse the selection; + * call {@link android.text.Selection#setSelection} too. + * @hide pending API review + */ + public static void stopSelecting(View view, Spannable content) { + content.removeSpan(SELECTING); + } + + /** * Handles release of the meta keys. */ public boolean onKeyUp(View view, Editable content, int keyCode, @@ -225,6 +286,170 @@ public abstract class MetaKeyKeyListener { if ((states&META_SHIFT_ON) != 0) resetLock(content, CAP); if ((states&META_ALT_ON) != 0) resetLock(content, ALT); if ((states&META_SYM_ON) != 0) resetLock(content, SYM); + if ((states&META_SELECTING) != 0) resetLock(content, SELECTING); + } + + /** + * Call this if you are a method that ignores the locked meta state + * (arrow keys, for example) and you handle a key. + */ + public static long resetLockedMeta(long state) { + state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK); + state = resetLock(state, META_ALT_ON, META_ALT_MASK); + state = resetLock(state, META_SYM_ON, META_SYM_MASK); + return state; + } + + private static long resetLock(long state, int what, long mask) { + if ((state&(((long)what)<= size()} - * @hide pending API council */ public MenuItem getItem(int index); /** * Closes the menu, if open. - * - * @hide pending API council */ public void close(); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 0d9e2218329c..e928998b6d00 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -33,6 +33,7 @@ import android.util.Log; import java.util.ArrayList; import java.util.concurrent.locks.ReentrantLock; +import java.lang.ref.WeakReference; /** * Provides a dedicated drawing surface embedded inside of a view hierarchy. @@ -308,7 +309,7 @@ public class SurfaceView extends View { mLayout.memoryType = mRequestedType; if (mWindow == null) { - mWindow = new MyWindow(); + mWindow = new MyWindow(this); mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; mLayout.gravity = Gravity.LEFT|Gravity.TOP; mSession.add(mWindow, mLayout, @@ -392,33 +393,45 @@ public class SurfaceView extends View { mNewSurfaceNeeded = true; updateWindow(false); } - - private class MyWindow extends IWindow.Stub { + + private static class MyWindow extends IWindow.Stub { + private WeakReference mSurfaceView; + + public MyWindow(SurfaceView surfaceView) { + mSurfaceView = new WeakReference(surfaceView); + } + public void resized(int w, int h, Rect coveredInsets, Rect visibleInsets, boolean reportDraw) { - if (localLOGV) Log.v( - "SurfaceView", SurfaceView.this + " got resized: w=" + - w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight); - synchronized (this) { - if (mCurWidth != w || mCurHeight != h) { - mCurWidth = w; - mCurHeight = h; - } - if (reportDraw) { - try { - mSession.finishDrawing(mWindow); - } catch (RemoteException e) { + SurfaceView surfaceView = mSurfaceView.get(); + if (surfaceView != null) { + if (localLOGV) Log.v( + "SurfaceView", surfaceView + " got resized: w=" + + w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight); + synchronized (this) { + if (mCurWidth != w || mCurHeight != h) { + mCurWidth = w; + mCurHeight = h; + } + if (reportDraw) { + try { + surfaceView.mSession.finishDrawing(surfaceView.mWindow); + } catch (RemoteException e) { + } } } } } public void dispatchKey(KeyEvent event) { - //Log.w("SurfaceView", "Unexpected key event in surface: " + event); - if (mSession != null && mSurface != null) { - try { - mSession.finishKey(mWindow); - } catch (RemoteException ex) { + SurfaceView surfaceView = mSurfaceView.get(); + if (surfaceView != null) { + //Log.w("SurfaceView", "Unexpected key event in surface: " + event); + if (surfaceView.mSession != null && surfaceView.mSurface != null) { + try { + surfaceView.mSession.finishKey(surfaceView.mWindow); + } catch (RemoteException ex) { + } } } } @@ -446,10 +459,13 @@ public class SurfaceView extends View { public void dispatchAppVisibility(boolean visible) { // The point of SurfaceView is to let the app control the surface. } - + public void dispatchGetNewSurface() { - Message msg = mHandler.obtainMessage(GET_NEW_SURFACE_MSG); - mHandler.sendMessage(msg); + SurfaceView surfaceView = mSurfaceView.get(); + if (surfaceView != null) { + Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG); + surfaceView.mHandler.sendMessage(msg); + } } public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f948b33fb179..1cc7b60ac8ea 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3562,7 +3562,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * * @param outAttrs Fill in with attribute information about the connection. */ - public InputConnection createInputConnection(EditorInfo outAttrs) { + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return null; } @@ -4315,12 +4315,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (mAttachInfo != null) { handler = mAttachInfo.mHandler; } else { - handler = ViewRoot.sUiThreads.get(); - if (handler == null) { - // Assume that post will succeed later - ViewRoot.sRunQueue.post(action); - return true; - } + // Assume that post will succeed later + ViewRoot.getRunQueue().post(action); + return true; } return handler.post(action); @@ -4347,12 +4344,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (mAttachInfo != null) { handler = mAttachInfo.mHandler; } else { - handler = ViewRoot.sUiThreads.get(); - if (handler == null) { - // Assume that post will succeed later - ViewRoot.sRunQueue.postDelayed(action, delayMillis); - return true; - } + // Assume that post will succeed later + ViewRoot.getRunQueue().postDelayed(action, delayMillis); + return true; } return handler.postDelayed(action, delayMillis); @@ -4373,12 +4367,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (mAttachInfo != null) { handler = mAttachInfo.mHandler; } else { - handler = ViewRoot.sUiThreads.get(); - if (handler == null) { - // Assume that post will succeed later - ViewRoot.sRunQueue.removeCallbacks(action); - return true; - } + // Assume that post will succeed later + ViewRoot.getRunQueue().removeCallbacks(action); + return true; } handler.removeCallbacks(action); diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index db0b368ebcf7..a254edb98777 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -34,6 +34,7 @@ import android.util.Log; import android.util.EventLog; import android.util.SparseArray; import android.view.View.MeasureSpec; +import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import android.content.pm.PackageManager; @@ -94,8 +95,7 @@ public final class ViewRoot extends Handler implements ViewParent, static final Object mStaticInit = new Object(); static boolean mInitialized = false; - static final ThreadLocal sUiThreads = new ThreadLocal(); - static final RunQueue sRunQueue = new RunQueue(); + static final ThreadLocal sRunQueues = new ThreadLocal(); long mLastTrackballTime = 0; final TrackballAxis mTrackballAxisX = new TrackballAxis(); @@ -222,13 +222,7 @@ public final class ViewRoot extends Handler implements ViewParent, mFirst = true; // true for the first time the view is added mSurface = new Surface(); mAdded = false; - - Handler handler = sUiThreads.get(); - if (handler == null) { - handler = new RootHandler(); - sUiThreads.set(handler); - } - mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, handler, this); + mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this); } @Override @@ -617,7 +611,7 @@ public final class ViewRoot extends Handler implements ViewParent, attachInfo.mKeepScreenOn = false; viewVisibilityChanged = false; host.dispatchAttachedToWindow(attachInfo, 0); - sRunQueue.executeActions(attachInfo.mHandler); + getRunQueue().executeActions(attachInfo.mHandler); //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); } else { desiredWindowWidth = mWinFrame.width(); @@ -1400,10 +1394,22 @@ public final class ViewRoot extends Handler implements ViewParent, public final static int DISPATCH_APP_VISIBILITY = 1008; public final static int DISPATCH_GET_NEW_SURFACE = 1009; public final static int FINISHED_EVENT = 1010; + public final static int DISPATCH_KEY_FROM_IME = 1011; + public final static int FINISH_INPUT_CONNECTION = 1012; @Override public void handleMessage(Message msg) { switch (msg.what) { + case View.AttachInfo.INVALIDATE_MSG: + ((View) msg.obj).invalidate(); + break; + case View.AttachInfo.INVALIDATE_RECT_MSG: + int left = msg.arg1 >>> 16; + int top = msg.arg1 & 0xFFFF; + int right = msg.arg2 >>> 16; + int bottom = msg.arg2 & 0xFFFF; + ((View) msg.obj).invalidate(left, top, right, bottom); + break; case DO_TRAVERSAL: if (mProfile) { Debug.startMethodTracing("ViewRoot"); @@ -1583,6 +1589,18 @@ public final class ViewRoot extends Handler implements ViewParent, case DIE: dispatchDetachedFromWindow(); break; + case DISPATCH_KEY_FROM_IME: + if (LOCAL_LOGV) Log.v( + "ViewRoot", "Dispatching key " + + msg.obj + " from IME to " + mView); + deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false); + break; + case FINISH_INPUT_CONNECTION: { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.reportFinishInputConnection((InputConnection)msg.obj); + } + } break; } } @@ -2614,24 +2632,6 @@ public final class ViewRoot extends Handler implements ViewParent, } } - private static final class RootHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case View.AttachInfo.INVALIDATE_MSG: - ((View) msg.obj).invalidate(); - break; - case View.AttachInfo.INVALIDATE_RECT_MSG: - int left = msg.arg1 >>> 16; - int top = msg.arg1 & 0xFFFF; - int right = msg.arg2 >>> 16; - int bottom = msg.arg2 & 0xFFFF; - ((View) msg.obj).invalidate(left, top, right, bottom); - break; - } - } - } - private SurfaceHolder mHolder = new SurfaceHolder() { // we only need a SurfaceHolder for opengl. it would be nice // to implement everything else though, especially the callback @@ -2681,6 +2681,16 @@ public final class ViewRoot extends Handler implements ViewParent, } }; + static RunQueue getRunQueue() { + RunQueue rq = sRunQueues.get(); + if (rq != null) { + return rq; + } + rq = new RunQueue(); + sRunQueues.set(rq); + return rq; + } + /** * @hide */ diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index 9f3650b3ce5c..d1d549c76a27 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -118,7 +118,7 @@ public abstract class Animation { * Indicates whether the animation transformation should be applied after the * animation ends. */ - boolean mFillAfter = false; + boolean mFillAfter = true; /** * The time in milliseconds at which the animation must start; @@ -292,12 +292,18 @@ public abstract class Animation { } /** - * How long this animation should last. + * How long this animation should last. The duration cannot be negative. * * @param durationMillis Duration in milliseconds + * + * @throw java.lang.IllegalArgumentException if the duration is < 0 + * * @attr ref android.R.styleable#Animation_duration */ public void setDuration(long durationMillis) { + if (durationMillis < 0) { + throw new IllegalArgumentException("Animation duration cannot be negative"); + } mDuration = durationMillis; } @@ -401,7 +407,7 @@ public abstract class Animation { /** * If fillBefore is true, this animation will apply its transformation - * before the start time of the animation. Defaults to false if not set. + * before the start time of the animation. Defaults to true if not set. * Note that this applies when using an {@link * android.view.animation.AnimationSet AnimationSet} to chain * animations. The transformation is not applied before the AnimationSet @@ -416,7 +422,7 @@ public abstract class Animation { /** * If fillAfter is true, the transformation that this animation performed - * will persist when it is finished. Defaults to false if not set. + * will persist when it is finished. Defaults to true if not set. * Note that this applies when using an {@link * android.view.animation.AnimationSet AnimationSet} to chain * animations. The transformation is not applied before the AnimationSet @@ -607,12 +613,17 @@ public abstract class Animation { } final long startOffset = getStartOffset(); - float normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / - (float) mDuration; + final long duration = mDuration; + float normalizedTime; + if (duration != 0) { + normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / + (float) duration; + } else { + // time is a step-change with a zero duration + normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f; + } boolean expired = normalizedTime >= 1.0f; - // Pin time to 0.0 to 1.0 range - normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); mMore = !expired; if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { @@ -623,6 +634,9 @@ public abstract class Animation { mStarted = true; } + // Pin time to 0.0 to 1.0 range + normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); + if (mCycleFlip) { normalizedTime = 1.0f - normalizedTime; } @@ -788,16 +802,15 @@ public abstract class Animation { void onAnimationStart(Animation animation); /** - *

Notifies the end of the animation. This callback is invoked - * only for animation with repeat mode set to NO_REPEAT.

+ *

Notifies the end of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.

* * @param animation The animation which reached its end. */ void onAnimationEnd(Animation animation); /** - *

Notifies the repetition of the animation. This callback is invoked - * only for animation with repeat mode set to RESTART or REVERSE.

+ *

Notifies the repetition of the animation.

* * @param animation The animation which was repeated. */ diff --git a/core/java/android/view/animation/package.html b/core/java/android/view/animation/package.html index c35804742402..87c99bb2f2de 100755 --- a/core/java/android/view/animation/package.html +++ b/core/java/android/view/animation/package.html @@ -5,240 +5,16 @@ that you can use to create simple animations: tweened animation, in which you tell Android to perform a series of simple transformations (position, size, rotation, and so on) to the content of a - View; and frame - by frame animation, which loads a series of Drawable resources + View; and frame-by-frame animation, which loads a series of Drawable resources one after the other. Both animation types can be used in any View object to provide simple rotating timers, activity icons, and other useful UI elements. - Tweened animation is handled by this package; frame by frame animation is + Tweened animation is handled by this package (android.view.animation); frame-by-frame animation is handled by the {@link android.graphics.drawable.AnimationDrawable} class. - Animations do not have a pause method.

-

Tweened Animation

-

Android can perform simple visual transformations for you, including straight - line motion, size change, and transparency change, on the contents of a {@link - android.view.View View} object. These transformations are represented by the - following classes:

-
    -
  • {@link android.view.animation.AlphaAnimation AlphaAnimation} (transparency - changes)
  • -
  • {@link android.view.animation.RotateAnimation RotateAnimation} (rotations)
  • -
  • {@link android.view.animation.ScaleAnimation ScaleAnimation} (growing - or shrinking)
  • -
  • {@link android.view.animation.TranslateAnimation TranslateAnimation} - (position changes)
  • -
-

Note: tweened animation does not provide tools to help you draw shapes. Tweened - animation is the act of applying one or more of these - transformations applied to the contents of a View object. So, if you have a TextView - with text, you can move, rotate, grow, or shrink the text. If it has a background - image, the background image will also be transformed along with the text.

-

Animations are drawn in the area designated for the View at the start of the animation; - this area does not change to accommodate size or movement, so if your animation - moves or expands outside the original boundaries of your object, it will be clipped - to the size of the original canvas, even if the object's LayoutParams are - set to WRAP_CONTENT (the object will not resize to accommodate moving or expanding/shrinking - animations).

-

Step 1: Define your animation

-

The first step in creating a tweened animation is to define the transformations. - This can be done either in XML or in code. You define an animation by defining - the transformations that you want to occur, when they will occur, and how long - they should take to apply. Transformations - can be sequential or simultaneous—so, for example, you can have the contents - of a TextView move from left to right, and then rotate 180 degrees, or you can - have the text move and rotate simultaneously. Each transformation takes a set - of parameters specific for that transformation (starting size and ending size - for size change, starting angle and ending angle for rotation, and so on), and - also a set of common parameters (for instance, start time and duration). To make - several transformations happen simultaneously, give them the same start time; - to make them sequential, calculate the start time plus the duration of the preceding - transformation.

-

Screen coordinates are (0,0) at the upper left hand corner, and increase as you - go down and to the right.

-

Some values, such as pivotX, can be specified relative to the object itself or - relative to the parent. Be sure to use the proper format for what you want ("50" - for 50% relative to the parent, "50%" for 50% relative to itself).

-

You can determine how a transformation is applied over time by assigning an Interpolator - to it. Android includes several Interpolator subclasses that specify various - speed curves: for instance, AccelerateInterpolator tells a transformation - to start slow and speed up; DecelerateInterpolator tells it to start fast than slow - down, and so on.

-

If - you want a group of transformations to share a set of parameters (for example, - start time and duration), you can bundle them into an AnimationSet, which - defines the common parameters for all its children (and overrides any - values explicitly set by the children). Add your AnimationSet as - a child to the root AnimationSet (which serves to wrap all transformations into - the final animation).

-

Here is the XML that defines a simple animation. The object will first move - to the right, then rotate and double in size, then move up. Note the - transformation start times.

- - - - - - - - - -
XMLEquivalent Java
<set android:shareInterpolator="true" 
-     android:interpolator="@android:anim/accelerate_interpolator">
+    

- <translate android:fromXDelta="0" - android:toXDelta="30" - android:duration="800" - android:fillAfter="true"/> - - <set android:duration="800" - android:pivotX="50%" - android:pivotY="50%" > +

For more information on creating tweened or frame-by-frame animations, read the discussion in the +2D Graphics +Dev Guide.

- <rotate android:fromDegrees="0" - android:toDegrees="-90" - android:fillAfter="true" - android:startOffset="800"/> - - <scale android:fromXScale="1.0" - android:toXScale="2.0" - android:fromYScale="1.0" - android:toYScale="2.0" - android:startOffset="800" /> - </set> - - <translate android:toYDelta="-100" - android:fillAfter="true" - android:duration="800" - android:startOffset="1600"/> -</set>
// Create root AnimationSet.
-AnimationSet rootSet = new AnimationSet(true);
-rootSet.setInterpolator(new AccelerateInterpolator());
-rootSet.setRepeatMode(Animation.NO_REPEAT);
-
-// Create and add first child, a motion animation.
-TranslateAnimation trans1 = new TranslateAnimation(0, 30, 0, 0);
-trans1.setStartOffset(0);
-trans1.setDuration(800);
-trans1.setFillAfter(true);
-rootSet.addAnimation(trans1);
-
-// Create a rotate and a size animation.
-RotateAnimation rotate = new RotateAnimation(
-       0, 
-       -90, 
-       RotateAnimation.RELATIVE_TO_SELF, 0.5f, 
-       RotateAnimation.RELATIVE_TO_SELF, 0.5f);
-       rotate.setFillAfter(true);
-       rotate.setDuration(800);
-
-ScaleAnimation scale = new ScaleAnimation(
-       1, 2, 1, 2, // From x, to x, from y, to y
-       ScaleAnimation.RELATIVE_TO_SELF, 0.5f, 
-       ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
-       scale.setDuration(800);
-       scale.setFillAfter(true);
-
-// Add rotate and size animations to a new set,
-// then add the set to the root set.
-AnimationSet childSet = new AnimationSet(true);
-childSet.setStartOffset(800);
-childSet.addAnimation(rotate);
-childSet.addAnimation(scale);
-rootSet.addAnimation(childSet);
-
-// Add a final motion animation to the root set.
-TranslateAnimation trans2 = new TranslateAnimation(0, 0, 0, -100);
-trans2.setFillAfter(true);
-trans2.setDuration(800);
-trans2.setStartOffset(1600);
-rootSet.addAnimation(trans2);
-
-// Start the animation.
-animWindow.startAnimation(rootSet);
-

 

-

The following diagram shows the animation drawn from this code:

-

A tweened animation: move right, turn and grow, move up.

-

The previous diagram shows a few important things. One is the animation itself, - and the other is that the animation can get cropped if it moves out of its originally - defined area. To avoid this, we could have sized the TextView to fill_parent - for its height.

-

If you define your animation in XML, save it in the res/anim/ folder as described - in Resources. That topic - also describes the XML tags and attributes you can use to specify transformations.

-

Animations - have the following common parameters (from the Animation interface). - If a group of animations share the same values, you can bundle them into an AnimationSet - so you don't have to set these values on each one individually.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PropertyXML AttributeJava Method / Description
Start time android:startOffsetAnimation.setStartOffset() (or setStartTime() for absolute time)The start time (in milliseconds) of a transformation, where 0 is the - start time of the root animation set.
Durationandroid:durationAnimation.setDuration()The duration (in milliseconds) of a transformation.
Fill before android:fillBeforeAnimation.setFillBefore()True if you want this transformation to be applied at time zero, regardless - of your start time value (you will probably never need this).
Fill after android:fillAfterAnimation.SetFillAfter()Whether you want the transform you apply to continue after the duration - of the transformation has expired. If false, the original value will - immediately be applied when the transformation is done. So, for example, - if you want to make a dot move down, then right in an "L" shape, if this - value is not true, at the end of the down motion the text box will immediately - jump back to the top before moving right.
Interpolatorandroid:interpolatorAnimation.SetInterpolator()Which interpolator to use.
Repeat mode Cannot be set in XML Animation.SetRepeatMode()Whether and how the animation should repeat.
-

 

-

Step 2: Load and start your animation

-
    -
  1. If you've created your transformation in XML, you'll need to load it in Java - by calling {@link android.view.animation.AnimationUtils#loadAnimation(android.content.Context, - int) AnimationUtils.loadAnimation()}.
  2. -
  3. Either start the animation immediately by calling {@link android.view.View#startAnimation(android.view.animation.Animation) - View.startAnimation()}, or if you have specified a start time in the animation - parameters, you can call - {@link android.view.View#setAnimation(android.view.animation.Animation) - View.setCurrentAnimation()}.
  4. -
-

The following code demonstrates loading and starting an animation.

-
// Hook into the object to be animated.
-TextView animWindow = (TextView)findViewById(R.id.anim);
-
-// Load the animation from XML (XML file is res/anim/move_animation.xml).
-Animation anim = AnimationUtils.loadAnimation(AnimationSample.this, R.anim.move_animation);
-anim.setRepeatMode(Animation.NO_REPEAT);
-
-// Play the animation.
-animWindow.startAnimation(anim);
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 4416ee5ee7af..a6ce2931f0a2 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -57,8 +57,9 @@ public abstract class BaseInputConnection implements InputConnection { h = mIMM.mServedView.getHandler(); } } - if (h != null && mTargetView != null) { - h.post(new DispatchKey(event, mTargetView.getRootView())); + if (h != null) { + h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME, + event)); } } return false; @@ -81,18 +82,4 @@ public abstract class BaseInputConnection implements InputConnection { mIMM.updateStatusIcon(resId, packageName); return true; } - - static class DispatchKey implements Runnable { - KeyEvent mEvent; - View mView; - - DispatchKey(KeyEvent event, View v) { - mEvent = event; - mView = v; - } - - public void run() { - mView.dispatchKeyEvent(mEvent); - } - } -} \ No newline at end of file +} diff --git a/core/java/android/view/inputmethod/DefaultInputMethod.java b/core/java/android/view/inputmethod/DefaultInputMethod.java index da5cab55ada3..e92cbadf9f91 100644 --- a/core/java/android/view/inputmethod/DefaultInputMethod.java +++ b/core/java/android/view/inputmethod/DefaultInputMethod.java @@ -67,7 +67,7 @@ public class DefaultInputMethod implements InputMethod, InputMethodSession { } public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { + int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { } public void updateCursor(Rect newCursor) { @@ -119,9 +119,9 @@ class SimpleInputMethod extends IInputMethod.Stub { } public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { + int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { mSession.updateSelection(oldSelStart, oldSelEnd, - newSelStart, newSelEnd); + newSelStart, newSelEnd, candidatesStart, candidatesEnd); } public void updateCursor(Rect newCursor) { diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 5f8ba1fb6639..27461ff6a687 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -26,6 +26,9 @@ import android.view.KeyEvent; * {@link InputMethod} back to the application that is receiving its input. It * is used to perform such things as reading text around the cursor, * committing text to the text box, and sending raw key events to the application. + * + *

Implementations of this interface should generally be done by + * subclassing {@link BaseInputConnection}. */ public interface InputConnection { /** @@ -137,6 +140,14 @@ public interface InputConnection { public boolean setComposingText(CharSequence text, int newCursorPosition); /** + * Have the text editor finish whatever composing text is currently + * active. This simple leaves the text as-is, removing any special + * composing styling or other state that was around it. The cursor + * position remains unchanged. + */ + public boolean finishComposingText(); + + /** * Commit text to the text box and set the new cursor position. * Any composing text set previously will be removed * automatically. diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index f150ad61b7ed..a41955c04771 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -65,6 +65,10 @@ public class InputConnectionWrapper implements InputConnection { return mBase.setComposingText(text, newCursorPosition); } + public boolean finishComposingText() { + return mBase.finishComposingText(); + } + public boolean commitText(CharSequence text, int newCursorPosition) { return mBase.commitText(text, newCursorPosition); } diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 259e759d97fc..ad61f945deae 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -16,19 +16,17 @@ package android.view.inputmethod; -import android.graphics.Rect; import android.inputmethodservice.InputMethodService; -import android.os.Bundle; import android.os.IBinder; -import android.view.KeyEvent; -import android.view.MotionEvent; /** * The InputMethod interface represents an input method which can generate key * events and text, such as digital, email addresses, CJK characters, other * language characters, and etc., while handling various input events, and send - * the text back to the application that requests text input. - * + * the text back to the application that requests text input. See + * {@link InputMethodManager} for more general information about the + * architecture. + * *

Applications will not normally use this interface themselves, instead * relying on the standard interaction provided by * {@link android.widget.TextView} and {@link android.widget.EditText}. @@ -42,6 +40,14 @@ import android.view.MotionEvent; * {@link android.Manifest.permission#BIND_INPUT_METHOD} in order to interact * with the service; if this is not required, the system will not use that * input method, because it can not trust that it is not compromised. + * + *

The InputMethod interface is actually split into two parts: the interface + * here is the top-level interface to the input method, providing all + * access to it, which only the system can access (due to the BIND_INPUT_METHOD + * permission requirement). In addition its method + * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)} + * can be called to instantate a secondary {@link InputMethodSession} interface + * which is what clients use to communicate with the input method. */ public interface InputMethod { /** diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index da8259337538..a9c46c388e89 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -17,8 +17,6 @@ package android.view.inputmethod; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; @@ -31,6 +29,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewRoot; import com.android.internal.view.IInputConnectionWrapper; import com.android.internal.view.IInputContext; @@ -43,9 +42,142 @@ import com.android.internal.view.InputBindResult; import java.util.List; /** - * Public interface to the global input method manager. You can retrieve - * an instance of this interface with + * Central system API to the overall input method framework (IMF) architecture, + * which arbitrates interaction between applications and the current input method. + * You can retrieve an instance of this interface with * {@link Context#getSystemService(String) Context.getSystemService()}. + * + *

Topics covered here: + *

    + *
  1. Architecture Overview + *
+ * + * + *

Architecture Overview

+ * + *

There are three primary parties involved in the input method + * framework (IMF) architecture:

+ * + *
    + *
  • The input method manager as expressed by this class + * is the central point of the system that manages interaction between all + * other parts. It is expressed as the client-side API here which exists + * in each application context and communicates with a global system service + * that manages the interaction across all processes. + *
  • An input method (IME) implements a particular + * interaction model allowing the user to generate text. The system binds + * to the current input method that is use, causing it to be created and run, + * and tells it when to hide and show its UI. Only one IME is running at a time. + *
  • Multiple client applications arbitrate with the input + * method manager for input focus and control over the state of the IME. Only + * one such client is ever active (working with the IME) at a time. + *
+ * + * + * + *

Applications

+ * + *

In most cases, applications that are using the standard + * {@link android.widget.TextView} or its subclasses will have little they need + * to do to work well with soft input methods. The main things you need to + * be aware of are:

+ * + *
    + *
  • Properly set the {@link android.R.attr#inputType} if your editable + * text views, so that the input method will have enough context to help the + * user in entering text into them. + *
  • Deal well with losing screen space when the input method is + * displayed. Ideally an application should handle its window being resized + * smaller, but it can rely on the system performing panning of the window + * if needed. You should set the {@link android.R.attr#windowSoftInputMode} + * attribute on your activity or the corresponding values on windows you + * create to help the system determine whether to pan or resize (it will + * try to determine this automatically but may get it wrong). + *
  • You can also control the preferred soft input state (open, closed, etc) + * for your window using the same {@link android.R.attr#windowSoftInputMode} + * attribute. + *
+ * + *

More finer-grained control is available through the APIs here to directly + * interact with the IMF and its IME -- either showing or hiding the input + * area, letting the user pick an input method, etc.

+ * + *

For the rare people amongst us writing their own text editors, you + * will need to implement {@link android.view.View#onCreateInputConnection} + * to return a new instance of your own {@link InputConnection} interface + * allowing the IME to interact with your editor.

+ * + * + * + *

Input Methods

+ * + *

An input method (IME) is implemented + * as a {@link android.app.Service}, typically deriving from + * {@link android.inputmethodservice.InputMethodService}. It must provide + * the core {@link InputMethod} interface, though this is normally handled by + * {@link android.inputmethodservice.InputMethodService} and implementors will + * only need to deal with the higher-level API there.

+ * + * See the {@link android.inputmethodservice.InputMethodService} class for + * more information on implementing IMEs. + * + * + * + *

Security

+ * + *

There are a lot of security issues associated with input methods, + * since they essentially have freedom to completely drive the UI and monitor + * everything the user enters. The Android input method framework also allows + * arbitrary third party IMEs, so care must be taken to restrict their + * selection and interactions.

+ * + *

Here are some key points about the security architecture behind the + * IMF:

+ * + *
    + *
  • Only the system is allowed to directly access an IME's + * {@link InputMethod} interface, via the + * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission. This is + * enforced in the system by not binding to an input method service that does + * not require this permission, so the system can guarantee no other untrusted + * clients are accessing the current input method outside of its control.

    + * + *
  • There may be many client processes of the IMF, but only one may + * be active at a time. The inactive clients can not interact with key + * parts of the IMF through the mechanisms described below.

    + * + *
  • Clients of an input method are only given access to its + * {@link InputMethodSession} interface. One instance of this interface is + * created for each client, and only calls from the session associated with + * the active client will be processed by the current IME. This is enforced + * by {@link android.inputmethodservice.AbstractInputMethodService} for normal + * IMEs, but must be explicitly handled by an IME that is customizing the + * raw {@link InputMethodSession} implementation.

    + * + *
  • Only the active client's {@link InputConnection} will accept + * operations. The IMF tells each client process whether it is active, and + * the framework enforces that in inactive processes calls on to the current + * InputConnection will be ignored. This ensures that the current IME can + * only deliver events and text edits to the UI that the user sees as + * being in focus.

    + * + *
  • An IME can never interact with an {@link InputConnection} while + * the screen is off. This is enforced by making all clients inactive while + * the screen is off, and prevents bad IMEs from driving the UI when the user + * can not be aware of its behavior.

    + * + *
  • A client application can ask that the system let the user pick a + * new IME, but can not programmatically switch to one itself. This avoids + * malicious applications from switching the user to their own IME, which + * remains running when the user navigates away to another application. An + * IME, on the other hand, is allowed to programmatically switch + * the system to another IME, since it already has full control of user + * input.

    + * + *
  • The user must explicitly enable a new IME in settings before + * they can switch to it, to confirm with the system that they know about it + * and want to make it available for use.

    + *
*/ public final class InputMethodManager { static final boolean DEBUG = false; @@ -118,6 +250,8 @@ public final class InputMethodManager { Rect mCursorRect = new Rect(); int mCursorSelStart; int mCursorSelEnd; + int mCursorCandStart; + int mCursorCandEnd; // ----------------------------------------------------------- @@ -203,6 +337,10 @@ public final class InputMethodManager { return false; } + public boolean finishComposingText() { + return false; + } + public boolean showStatusIcon(String packageName, int resId) { return false; } @@ -294,6 +432,9 @@ public final class InputMethodManager { public boolean setComposingText(CharSequence text, int newCursorPosition) { return false; } + public boolean finishComposingText() { + return false; + } }; InputMethodManager(IInputMethodManager service, Looper looper) { @@ -430,23 +571,45 @@ public final class InputMethodManager { * Disconnect any existing input connection, clearing the served view. */ void finishInputLocked() { - synchronized (mH) { - if (mServedView != null) { - if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView); - updateStatusIcon(0, null); - - if (mCurrentTextBoxAttribute != null) { - try { - mService.finishInput(mClient); - } catch (RemoteException e) { - } + if (mServedView != null) { + if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView); + updateStatusIcon(0, null); + + if (mCurrentTextBoxAttribute != null) { + try { + mService.finishInput(mClient); + } catch (RemoteException e) { } - - mServedView = null; - mCompletions = null; - mServedConnecting = false; - clearConnectionLocked(); } + + if (mServedInputConnection != null) { + // We need to tell the previously served view that it is no + // longer the input target, so it can reset its state. Schedule + // this call on its window's Handler so it will be on the correct + // thread and outside of our lock. + Handler vh = mServedView.getHandler(); + if (vh != null) { + // This will result in a call to reportFinishInputConnection() + // below. + vh.sendMessage(vh.obtainMessage(ViewRoot.FINISH_INPUT_CONNECTION, + mServedInputConnection)); + } + } + + mServedView = null; + mCompletions = null; + mServedConnecting = false; + clearConnectionLocked(); + } + } + + /** + * Called from the FINISH_INPUT_CONNECTION message above. + * @hide + */ + public void reportFinishInputConnection(InputConnection ic) { + if (mServedInputConnection != ic) { + ic.finishComposingText(); } } @@ -584,7 +747,7 @@ public final class InputMethodManager { // do its stuff. // Life is good: let's hook everything up! EditorInfo tba = new EditorInfo(); - InputConnection ic = view.createInputConnection(tba); + InputConnection ic = view.onCreateInputConnection(tba); if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); synchronized (mH) { @@ -609,6 +772,8 @@ public final class InputMethodManager { if (ic != null) { mCursorSelStart = tba.initialSelStart; mCursorSelEnd = tba.initialSelEnd; + mCursorCandStart = -1; + mCursorCandEnd = -1; mCursorRect.setEmpty(); setCurrentInputConnection(ic); } else { @@ -678,16 +843,24 @@ public final class InputMethodManager { * @hide */ public void focusOut(View view) { + InputConnection ic = null; synchronized (mH) { if (DEBUG) Log.v(TAG, "focusOut: " + view + " mServedView=" + mServedView + " winFocus=" + view.hasWindowFocus()); - if (mServedView == view && view.hasWindowFocus()) { - mLastServedView = view; - mH.removeMessages(MSG_CHECK_FOCUS); - mH.sendEmptyMessage(MSG_CHECK_FOCUS); + if (mServedView == view) { + ic = mServedInputConnection; + if (view.hasWindowFocus()) { + mLastServedView = view; + mH.removeMessages(MSG_CHECK_FOCUS); + mH.sendEmptyMessage(MSG_CHECK_FOCUS); + } } } + + if (ic != null) { + ic.finishComposingText(); + } } void checkFocus() { @@ -733,22 +906,27 @@ public final class InputMethodManager { /** * Report the current selection range. */ - public void updateSelection(View view, int selStart, int selEnd) { + public void updateSelection(View view, int selStart, int selEnd, + int candidatesStart, int candidatesEnd) { synchronized (mH) { if (mServedView != view || mCurrentTextBoxAttribute == null || mCurMethod == null) { return; } - if (mCursorSelStart != selStart || mCursorSelEnd != selEnd) { + if (mCursorSelStart != selStart || mCursorSelEnd != selEnd + || mCursorCandStart != candidatesStart + || mCursorCandEnd != candidatesEnd) { if (DEBUG) Log.d(TAG, "updateSelection"); try { if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod); mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd, - selStart, selEnd); + selStart, selEnd, candidatesStart, candidatesEnd); mCursorSelStart = selStart; mCursorSelEnd = selEnd; + mCursorCandStart = candidatesStart; + mCursorCandEnd = candidatesEnd; } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java index 603da13902f9..b5bbaffdbf4f 100644 --- a/core/java/android/view/inputmethod/InputMethodSession.java +++ b/core/java/android/view/inputmethod/InputMethodSession.java @@ -53,9 +53,14 @@ public interface InputMethodSession { * start position. * @param newSelEnd The new text offset of the cursor selection * end position. + * @param candidatesStart The text offset of the current candidate + * text start position. + * @param candidatesEnd The text offset of the current candidate + * text end position. */ public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd); + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd); /** * This method is called when cursor location of the target input field diff --git a/core/java/android/view/inputmethod/package.html b/core/java/android/view/inputmethod/package.html index 348aba68204a..328c7b34398d 100644 --- a/core/java/android/view/inputmethod/package.html +++ b/core/java/android/view/inputmethod/package.html @@ -1,7 +1,8 @@ Framework classes for interaction between views and input methods (such -as soft keyboards). In most cases the main classes here are not needed for +as soft keyboards). See {@link android.view.inputmethod.InputMethodManager} for +an overview. In most cases the main classes here are not needed for most applications, since they are dealt with for you by {@link android.widget.TextView}. When implementing a custom text editor, however, you will need to implement the diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 00b17d21f1f3..5a37f040d5ff 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -172,7 +172,7 @@ public final class CookieManager { if (urlPath.startsWith(path)) { int len = path.length(); int urlLen = urlPath.length(); - if (urlLen > len) { + if (path.charAt(len-1) != PATH_DELIM && urlLen > len) { // make sure /wee doesn't match /we return urlPath.charAt(len) == PATH_DELIM; } @@ -440,29 +440,43 @@ public final class CookieManager { /** * Remove all session cookies, which are cookies without expiration date */ - public synchronized void removeSessionCookie() { - Collection> cookieList = mCookieMap.values(); - Iterator> listIter = cookieList.iterator(); - while (listIter.hasNext()) { - ArrayList list = listIter.next(); - Iterator iter = list.iterator(); - while (iter.hasNext()) { - Cookie cookie = iter.next(); - if (cookie.expires == -1) { - iter.remove(); + public void removeSessionCookie() { + final Runnable clearCache = new Runnable() { + public void run() { + synchronized(CookieManager.this) { + Collection> cookieList = mCookieMap.values(); + Iterator> listIter = cookieList.iterator(); + while (listIter.hasNext()) { + ArrayList list = listIter.next(); + Iterator iter = list.iterator(); + while (iter.hasNext()) { + Cookie cookie = iter.next(); + if (cookie.expires == -1) { + iter.remove(); + } + } + } + CookieSyncManager.getInstance().clearSessionCookies(); } } - } - CookieSyncManager.getInstance().clearSessionCookies(); + }; + new Thread(clearCache).start(); } /** * Remove all cookies */ - public synchronized void removeAllCookie() { - mCookieMap = new LinkedHashMap>( - MAX_DOMAIN_COUNT, 0.75f, true); - CookieSyncManager.getInstance().clearAllCookies(); + public void removeAllCookie() { + final Runnable clearCache = new Runnable() { + public void run() { + synchronized(CookieManager.this) { + mCookieMap = new LinkedHashMap>( + MAX_DOMAIN_COUNT, 0.75f, true); + CookieSyncManager.getInstance().clearAllCookies(); + } + } + }; + new Thread(clearCache).start(); } /** @@ -475,23 +489,30 @@ public final class CookieManager { /** * Remove all expired cookies */ - public synchronized void removeExpiredCookie() { - long now = System.currentTimeMillis(); - Collection> cookieList = mCookieMap.values(); - Iterator> listIter = cookieList.iterator(); - while (listIter.hasNext()) { - ArrayList list = listIter.next(); - Iterator iter = list.iterator(); - while (iter.hasNext()) { - Cookie cookie = iter.next(); - // expires == -1 means no expires defined. Otherwise negative - // means far future - if (cookie.expires > 0 && cookie.expires < now) { - iter.remove(); + public void removeExpiredCookie() { + final Runnable clearCache = new Runnable() { + public void run() { + synchronized(CookieManager.this) { + long now = System.currentTimeMillis(); + Collection> cookieList = mCookieMap.values(); + Iterator> listIter = cookieList.iterator(); + while (listIter.hasNext()) { + ArrayList list = listIter.next(); + Iterator iter = list.iterator(); + while (iter.hasNext()) { + Cookie cookie = iter.next(); + // expires == -1 means no expires defined. Otherwise + // negative means far future + if (cookie.expires > 0 && cookie.expires < now) { + iter.remove(); + } + } + } + CookieSyncManager.getInstance().clearExpiredCookies(now); } } - } - CookieSyncManager.getInstance().clearExpiredCookies(now); + }; + new Thread(clearCache).start(); } /** diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java index 4e9370c898c2..30b519a3e977 100644 --- a/core/java/android/webkit/TextDialog.java +++ b/core/java/android/webkit/TextDialog.java @@ -37,6 +37,7 @@ import android.text.method.MetaKeyKeyListener; import android.text.method.MovementMethod; import android.text.method.PasswordTransformationMethod; import android.text.method.TextKeyListener; +import android.view.inputmethod.EditorInfo; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -265,13 +266,20 @@ import android.widget.AutoCompleteTextView; if (mGotEnterDown && !down) { return true; } - // WebView check the trackballtime in onKeyDown to avoid calling native - // from both trackball and key handling. As this is called from - // TextDialog, we always want WebView to check with native. Reset - // trackballtime to ensure it. - mWebView.resetTrackballTime(); - return down ? mWebView.onKeyDown(keyCode, event) : - mWebView.onKeyUp(keyCode, event); + // if it is a navigation key, pass it to WebView + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT + || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT + || keyCode == KeyEvent.KEYCODE_DPAD_UP + || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + // WebView check the trackballtime in onKeyDown to avoid calling + // native from both trackball and key handling. As this is called + // from TextDialog, we always want WebView to check with native. + // Reset trackballtime to ensure it. + mWebView.resetTrackballTime(); + return down ? mWebView.onKeyDown(keyCode, event) : mWebView + .onKeyUp(keyCode, event); + } + return false; } /** @@ -315,31 +323,30 @@ import android.widget.AutoCompleteTextView; updateCachedTextfield(); return; } - // In this case, replace before with all but the last character of the - // new text. - if (count > 1) { - String replace = s.subSequence(start, start + count - 1).toString(); + // Find the last character being replaced. If it can be represented by + // events, we will pass them to native (after replacing the beginning + // of the changed text), so we can see javascript events. + // Otherwise, replace the text being changed (including the last + // character) in the textfield. + TextUtils.getChars(s, start + count - 1, start + count, mCharacter, 0); + KeyCharacterMap kmap = + KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); + KeyEvent[] events = kmap.getEvents(mCharacter); + boolean cannotUseKeyEvents = null == events; + int charactersFromKeyEvents = cannotUseKeyEvents ? 0 : 1; + if (count > 1 || cannotUseKeyEvents) { + String replace = s.subSequence(start, + start + count - charactersFromKeyEvents).toString(); mWebView.replaceTextfieldText(start, start + before, replace, - start + count - 1, start + count - 1); + start + count - charactersFromKeyEvents, + start + count - charactersFromKeyEvents); } else { // This corrects the selection which may have been affected by the // trackball or auto-correct. mWebView.setSelection(start, start + before); } - // Whether the text to be added is only one character, or we already - // added all but the last character, we now figure out the DOM events - // for the last character, and pass them down. - TextUtils.getChars(s, start + count - 1, start + count, mCharacter, 0); - // We only care about the events that translate directly into - // characters. Should we be using KeyCharacterMap.BUILT_IN_KEYBOARD? - // The comment makes it sound like it may not be directly related to - // the keys. However, KeyCharacterMap.ALPHA says it has "maybe some - // numbers." Not sure if that will have the numbers we may need. - KeyCharacterMap kmap = - KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); - KeyEvent[] events = kmap.getEvents(mCharacter); updateCachedTextfield(); - if (null == events) { + if (cannotUseKeyEvents) { return; } int length = events.length; @@ -433,6 +440,8 @@ import android.widget.AutoCompleteTextView; method = null; } setTransformationMethod(method); + setInputType(inPassword ? EditorInfo.TYPE_TEXT_VARIATION_PASSWORD : + EditorInfo.TYPE_CLASS_TEXT); } /* package */ void setMaxLength(int maxLength) { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 7467b832affb..bd910b5a6128 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2841,11 +2841,6 @@ public class WebView extends AbsoluteLayout // a center key. Does not affect long press with the trackball/touch. private boolean mGotEnterDown = false; - // Enable copy/paste with trackball here. - // This should be left disabled until the framework can guarantee - // delivering matching key-up and key-down events for the shift key - private static final boolean ENABLE_COPY_PASTE = true; - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (LOGV_ENABLED) { @@ -2876,7 +2871,7 @@ public class WebView extends AbsoluteLayout return false; } - if (ENABLE_COPY_PASTE && mShiftIsPressed == false + if (mShiftIsPressed == false && nativeFocusNodeWantsKeyEvents() == false && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { mExtendSelection = false; @@ -2911,10 +2906,6 @@ public class WebView extends AbsoluteLayout mPrivateHandler.sendMessageDelayed(mPrivateHandler .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT); nativeRecordButtons(true, true); - // FIXME, currently in webcore keydown it doesn't do anything. - // In keyup, it calls both keydown and keyup, we should fix it. - mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode, - EventHub.KEYEVENT_UNHANDLED_TYPE, event); return true; } // Bubble up the key event as WebView doesn't handle it @@ -2949,15 +2940,10 @@ public class WebView extends AbsoluteLayout } } - if (nativeFocusNodeWantsKeyEvents()) { - mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode, - EventHub.KEYEVENT_FOCUS_NODE_TYPE, event); - // return true as DOM handles the key - return true; - } else if (false) { // reserved to check the meta tag + // TODO: should we pass all the keys to DOM or check the meta tag + if (nativeFocusNodeWantsKeyEvents() || true) { // pass the key to DOM - mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode, - EventHub.KEYEVENT_UNHANDLED_TYPE, event); + mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); // return true as DOM handles the key return true; } @@ -3008,8 +2994,8 @@ public class WebView extends AbsoluteLayout return false; } - if (ENABLE_COPY_PASTE && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT - || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { + if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT + || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { if (commitCopy()) { return true; } @@ -3050,25 +3036,18 @@ public class WebView extends AbsoluteLayout Rect visibleRect = sendOurVisibleRect(); // Note that sendOurVisibleRect calls viewToContent, so the // coordinates should be in content coordinates. - boolean nodeOnScreen = false; - boolean isTextField = false; - boolean isTextArea = false; - FocusNode node = null; if (nativeUpdateFocusNode()) { - node = mFocusNode; - isTextField = node.mIsTextField; - isTextArea = node.mIsTextArea; - nodeOnScreen = Rect.intersects(node.mBounds, visibleRect); - } - if (nodeOnScreen && !isTextField && !isTextArea) { - nativeSetFollowedLink(true); - mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, - EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, - new WebViewCore.FocusData(mFocusData)); - playSoundEffect(SoundEffectConstants.CLICK); - if (!mCallbackProxy.uiOverrideUrlLoading(node.mText)) { - mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode, - EventHub.KEYEVENT_UNHANDLED_TYPE, event); + if (Rect.intersects(mFocusNode.mBounds, visibleRect)) { + nativeSetFollowedLink(true); + mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, + EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, + new WebViewCore.FocusData(mFocusData)); + playSoundEffect(SoundEffectConstants.CLICK); + if (!mCallbackProxy.uiOverrideUrlLoading(mFocusNode.mText)) { + // use CLICK instead of KEY_DOWN/KEY_UP so that we can + // trigger mouse click events + mWebViewCore.sendMessage(EventHub.CLICK); + } } return true; } @@ -3076,15 +3055,10 @@ public class WebView extends AbsoluteLayout return false; } - if (nativeFocusNodeWantsKeyEvents()) { - mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode, - EventHub.KEYEVENT_FOCUS_NODE_TYPE, event); - // return true as DOM handles the key - return true; - } else if (false) { // reserved to check the meta tag + // TODO: should we pass all the keys to DOM or check the meta tag + if (nativeFocusNodeWantsKeyEvents() || true) { // pass the key to DOM - mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode, - EventHub.KEYEVENT_UNHANDLED_TYPE, event); + mWebViewCore.sendMessage(EventHub.KEY_UP, event); // return true as DOM handles the key return true; } @@ -3637,12 +3611,7 @@ public class WebView extends AbsoluteLayout private long mTrackballUpTime = 0; private long mLastFocusTime = 0; private Rect mLastFocusBounds; - - // Used to determine that the trackball is down AND that it has not - // been moved while down, whereas mTrackballDown is true until we - // receive an ACTION_UP - private boolean mTrackTrackball = false; - + // Set by default; BrowserActivity clears to interpret trackball data // directly for movement. Currently, the framework only passes // arrow key events, not trackball events, from one child to the next @@ -3666,7 +3635,6 @@ public class WebView extends AbsoluteLayout } if (ev.getAction() == MotionEvent.ACTION_DOWN) { mPrivateHandler.removeMessages(SWITCH_TO_ENTER); - mTrackTrackball = true; mTrackballDown = true; if (mNativeClass != 0) { nativeRecordButtons(true, true); @@ -3681,12 +3649,10 @@ public class WebView extends AbsoluteLayout + " mLastFocusTime=" + mLastFocusTime); } return false; // let common code in onKeyDown at it - } else if (mTrackTrackball) { - // LONG_PRESS_ENTER is set in common onKeyDown - mPrivateHandler.removeMessages(LONG_PRESS_ENTER); - mTrackTrackball = false; } if (ev.getAction() == MotionEvent.ACTION_UP) { + // LONG_PRESS_ENTER is set in common onKeyDown + mPrivateHandler.removeMessages(LONG_PRESS_ENTER); mTrackballDown = false; mTrackballUpTime = time; if (mShiftIsPressed) { @@ -4511,7 +4477,6 @@ public class WebView extends AbsoluteLayout // as this is shared by keydown and trackballdown, reset all // the states mGotEnterDown = false; - mTrackTrackball = false; mTrackballDown = false; // LONG_PRESS_ENTER is sent as a delayed message. If we // switch to windows overview, the WebView will be diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 9e413f90eda5..323b44d92346 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -25,7 +25,6 @@ import android.graphics.Picture; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -300,14 +299,10 @@ final class WebViewCore { */ private native void nativeSplitContent(); - // these must be kept lock-step with the KeyState enum in WebViewCore.h - static private final int KEY_ACTION_DOWN = 0; - static private final int KEY_ACTION_UP = 1; + private native boolean nativeKey(int keyCode, int unichar, + int repeatCount, boolean isShift, boolean isAlt, boolean isDown); - private native boolean nativeSendKeyToFocusNode(int keyCode, int unichar, - int repeatCount, boolean isShift, boolean isAlt, int keyAction); - - private native boolean nativeKeyUp(int keycode, int keyvalue); + private native boolean nativeClick(); private native void nativeSendListBoxChoices(boolean[] choices, int size); @@ -527,6 +522,7 @@ final class WebViewCore { static final int PASS_TO_JS = 115; static final int SET_GLOBAL_BOUNDS = 116; static final int UPDATE_CACHE_AND_TEXT_ENTRY = 117; + static final int CLICK = 118; static final int DOC_HAS_IMAGES = 120; static final int SET_SNAP_ANCHOR = 121; static final int DELETE_SELECTION = 122; @@ -573,19 +569,6 @@ final class WebViewCore { static final int NO_FOCUS_CHANGE_BLOCK = 0; static final int BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP = 1; - /* The KEY_DOWN and KEY_UP messages pass the keyCode in arg1, and a - "type" in arg2. These are the types, and they describe what the - circumstances were that prompted the UI thread to send the keyevent - to webkit. - - FOCUS_NODE - the currently focused node says it wants key events - (e.g. plugins) - UNHANDLED - the UI side did not handle the key, so we give webkit - a shot at it. - */ - static final int KEYEVENT_FOCUS_NODE_TYPE = 0; - static final int KEYEVENT_UNHANDLED_TYPE = 1; - // Private handler for WebCore messages. private Handler mHandler; // Message queue for containing messages before the WebCore thread is @@ -680,11 +663,15 @@ final class WebViewCore { break; case KEY_DOWN: - keyDown(msg.arg1, msg.arg2, (KeyEvent) msg.obj); + key((KeyEvent) msg.obj, true); break; case KEY_UP: - keyUp(msg.arg1, msg.arg2, (KeyEvent) msg.obj); + key((KeyEvent) msg.obj, false); + break; + + case CLICK: + nativeClick(); break; case VIEW_SIZE_CHANGED: @@ -1130,49 +1117,19 @@ final class WebViewCore { mBrowserFrame.loadUrl(url); } - private void keyDown(int code, int target, KeyEvent event) { + private void key(KeyEvent evt, boolean isDown) { if (LOGV_ENABLED) { - Log.v(LOGTAG, "CORE keyDown at " + System.currentTimeMillis() - + ", " + event); + Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", " + + evt); } - switch (target) { - case EventHub.KEYEVENT_UNHANDLED_TYPE: - break; - case EventHub.KEYEVENT_FOCUS_NODE_TYPE: - if (nativeSendKeyToFocusNode(code, event.getUnicodeChar(), - event.getRepeatCount(), - event.isShiftPressed(), - event.isAltPressed(), - KEY_ACTION_DOWN)) { - return; - } - break; + if (!nativeKey(evt.getKeyCode(), evt.getUnicodeChar(), + evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(), + isDown)) { + // bubble up the event handling + mCallbackProxy.onUnhandledKeyEvent(evt); } - // If we get here, no one handled it, so call our proxy - mCallbackProxy.onUnhandledKeyEvent(event); } - private void keyUp(int code, int target, KeyEvent event) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "CORE keyUp at " + System.currentTimeMillis() - + ", " + event); - } - switch (target) { - case EventHub.KEYEVENT_UNHANDLED_TYPE: - if (!nativeKeyUp(code, event.getUnicodeChar())) { - mCallbackProxy.onUnhandledKeyEvent(event); - } - break; - case EventHub.KEYEVENT_FOCUS_NODE_TYPE: - nativeSendKeyToFocusNode(code, event.getUnicodeChar(), - event.getRepeatCount(), - event.isShiftPressed(), - event.isAltPressed(), - KEY_ACTION_UP); - break; - } - } - // These values are used to avoid requesting a layout based on old values private int mCurrentViewWidth = 0; private int mCurrentViewHeight = 0; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 14405225b484..c22023c98d00 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -896,13 +896,16 @@ public abstract class AbsListView extends AdapterView implements Te public void setFilterText(String filterText) { if (mTextFilterEnabled && filterText != null && filterText.length() > 0) { createTextFilter(false); - // This is going to call our listener onTextChanged, but we are - // not ready to bring up a window yet + // This is going to call our listener onTextChanged, but we might not + // be ready to bring up a window yet mTextFilter.setText(filterText); mTextFilter.setSelection(filterText.length()); if (mAdapter instanceof Filterable) { - Filter f = ((Filterable) mAdapter).getFilter(); - f.filter(filterText); + // if mPopup is non-null, then onTextChanged will do the filtering + if (mPopup == null) { + Filter f = ((Filterable) mAdapter).getFilter(); + f.filter(filterText); + } // Set filtered to true so we will display the filter window when our main // window is ready mFiltered = true; @@ -1361,7 +1364,12 @@ public abstract class AbsListView extends AdapterView implements Te Rect selectorRect = mSelectorRect; if (selector != null && (isFocused() || touchModeDrawsInPressedState()) && selectorRect != null && !selectorRect.isEmpty()) { + + final View v = getChildAt(mSelectedPosition - mFirstPosition); + + if (v != null) v.setPressed(true); setPressed(true); + final boolean longClickable = isLongClickable(); Drawable d = selector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { @@ -1641,9 +1649,10 @@ public abstract class AbsListView extends AdapterView implements Te case KeyEvent.KEYCODE_ENTER: if (isPressed() && mSelectedPosition >= 0 && mAdapter != null && mSelectedPosition < mAdapter.getCount()) { - final int index = mSelectedPosition - mFirstPosition; - performItemClick(getChildAt(index), mSelectedPosition, mSelectedRowId); + final View view = getChildAt(mSelectedPosition - mFirstPosition); + performItemClick(view, mSelectedPosition, mSelectedRowId); setPressed(false); + if (view != null) view.setPressed(false); return true; } } @@ -1763,7 +1772,7 @@ public abstract class AbsListView extends AdapterView implements Te handler.removeCallbacks(mPendingCheckForLongPress); } setPressed(false); - View motionView = this.getChildAt(mMotionPosition - mFirstPosition); + View motionView = getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { motionView.setPressed(false); } diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 65ca8850a48e..b046a6b17ecf 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -22,6 +22,7 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.KeyEvent; import android.view.MotionEvent; public abstract class AbsSeekBar extends ProgressBar { @@ -54,7 +55,6 @@ public abstract class AbsSeekBar extends ProgressBar { public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.SeekBar, defStyle, 0); Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb); @@ -114,10 +114,15 @@ public abstract class AbsSeekBar extends ProgressBar { if (progressDrawable != null) { progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha)); } + + if (mThumb != null && mThumb.isStateful()) { + int[] state = getDrawableState(); + mThumb.setState(state); + } } @Override - void onProgressRefresh(float scale, boolean fromTouch) { + void onProgressRefresh(float scale, boolean fromUser) { Drawable thumb = mThumb; if (thumb != null) { setThumbPos(getWidth(), getHeight(), thumb, scale, Integer.MIN_VALUE); @@ -236,6 +241,7 @@ public abstract class AbsSeekBar extends ProgressBar { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: + setPressed(true); onStartTrackingTouch(); trackTouchEvent(event); break; @@ -248,10 +254,12 @@ public abstract class AbsSeekBar extends ProgressBar { case MotionEvent.ACTION_UP: trackTouchEvent(event); onStopTrackingTouch(); + setPressed(false); break; case MotionEvent.ACTION_CANCEL: onStopTrackingTouch(); + setPressed(false); break; } return true; @@ -306,4 +314,23 @@ public abstract class AbsSeekBar extends ProgressBar { void onStopTrackingTouch() { } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + int progress = getProgress(); + + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (progress <= 0) break; + setProgress(progress - 1, true); + return true; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (progress >= getMax()) break; + setProgress(progress + 1, true); + return true; + } + + return super.onKeyDown(keyCode, event); + } + } diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index d8fa603781c4..1591791c4fdb 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -336,7 +336,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (isPopupShowing()) { + if (isPopupShowing() && mDropDownList.getSelectedItemPosition() >= 0) { boolean consumed = mDropDownList.onKeyUp(keyCode, event); if (consumed) { switch (keyCode) { @@ -359,13 +359,20 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe if (isPopupShowing()) { // the key events are forwarded to the list in the drop down view // note that ListView handles space but we don't want that to happen - if (keyCode != KeyEvent.KEYCODE_SPACE) { + // also if selection is not currently in the drop down, then don't + // let center or enter presses go there since that would cause it + // to select one of its items + if (keyCode != KeyEvent.KEYCODE_SPACE + && (mDropDownList.getSelectedItemPosition() >= 0 + || (keyCode != KeyEvent.KEYCODE_ENTER + && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) { int curIndex = mDropDownList.getSelectedItemPosition(); boolean consumed; if (keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= 0) { // When the selection is at the top, we block the key // event to prevent focus from moving. mDropDownList.hideSelector(); + mDropDownList.requestLayout(); mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); mPopup.update(); return true; @@ -548,17 +555,21 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe private void performCompletion(View selectedView, int position, long id) { if (isPopupShowing()) { Object selectedItem; - if (position == -1) { + if (position < 0) { selectedItem = mDropDownList.getSelectedItem(); } else { selectedItem = mAdapter.getItem(position); } + if (selectedItem == null) { + Log.w(TAG, "performCompletion: no selected item"); + return; + } replaceText(convertSelectionToString(selectedItem)); if (mItemClickListener != null) { final DropDownListView list = mDropDownList; - if (selectedView == null || position == -1) { + if (selectedView == null || position < 0) { selectedView = list.getSelectedView(); position = list.getSelectedItemPosition(); id = list.getSelectedItemId(); @@ -665,8 +676,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe mPopup.setOutsideTouchable(true); mPopup.setTouchInterceptor(new PopupTouchIntercepter()); mPopup.showAsDropDown(this, mDropDownHorizontalOffset, mDropDownVerticalOffset); + mDropDownList.setSelection(ListView.INVALID_POSITION); mDropDownList.hideSelector(); - mDropDownList.setSelection(0); mDropDownList.requestFocus(); post(mHideSelector); } @@ -828,6 +839,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe public void run() { if (mDropDownList != null) { mDropDownList.hideSelector(); + mDropDownList.requestLayout(); } } } diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index d886155f3aef..cabca4024d26 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -56,7 +56,13 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList private static final String TAG = "Gallery"; private static final boolean localLOGV = Config.LOGV; - + + /** + * Duration in milliseconds from the start of a scroll during which we're + * unsure whether the user is scrolling or flinging. + */ + private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250; + /** * Horizontal spacing between items. */ @@ -104,6 +110,17 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList * Executes the delta scrolls from a fling or scroll movement. */ private FlingRunnable mFlingRunnable = new FlingRunnable(); + + /** + * Sets mSuppressSelectionChanged = false. This is used to set it to false + * in the future. It will also trigger a selection changed. + */ + private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() { + public void run() { + mSuppressSelectionChanged = false; + selectionChanged(); + } + }; /** * When fling runnable runs, it resets this to false. Any method along the @@ -142,6 +159,12 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList private boolean mReceivedInvokeKeyDown; private AdapterContextMenuInfo mContextMenuInfo; + + /** + * If true, this onScroll is the first for this user's drag (remember, a + * drag sends many onScrolls). + */ + private boolean mIsFirstScroll; public Gallery(Context context) { this(context, null); @@ -861,8 +884,13 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (!mShouldCallbackDuringFling) { + // We want to suppress selection changes + + // Remove any future code to set mSuppressSelectionChanged = false + removeCallbacks(mDisableSuppressSelectionChangedRunnable); + // This will get reset once we scroll into slots - mSuppressSelectionChanged = true; + if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true; } // Fling the gallery! @@ -891,13 +919,24 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList // As the user scrolls, we want to callback selection changes so related- // info on the screen is up-to-date with the gallery's selection - if (mSuppressSelectionChanged) { - mSuppressSelectionChanged = false; + if (!mShouldCallbackDuringFling) { + if (mIsFirstScroll) { + /* + * We're not notifying the client of selection changes during + * the fling, and this scroll could possibly be a fling. Don't + * do selection changes until we're sure it is not a fling. + */ + if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true; + postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT); + } + } else { + if (mSuppressSelectionChanged) mSuppressSelectionChanged = false; } // Track the motion trackMotionScroll(-1 * (int) distanceX); - + + mIsFirstScroll = false; return true; } @@ -917,6 +956,9 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList mDownTouchView.setPressed(true); } + // Reset the multiple-scroll tracking state + mIsFirstScroll = true; + // Must return true to get matching events for this down event. return true; } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index abba6d02e9e3..2e04b5d38d1b 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -29,6 +29,7 @@ import android.graphics.drawable.ClipDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.StateListDrawable; import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.Shape; import android.util.AttributeSet; @@ -174,7 +175,7 @@ public class ProgressBar extends View { Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); if (drawable != null) { - drawable = tileify(drawable); + drawable = tileify(drawable, false); setProgressDrawable(drawable); } @@ -217,18 +218,12 @@ public class ProgressBar extends View { a.recycle(); } - /* - * TODO: This is almost ready to be removed. This was used to support our - * old style of progress bars with the ticks. Need to check with designers - * on whether they can give us a transparent 'tick' overlay tile for our new - * gradient-based progress bars. (We still need the ticked progress bar for - * media player apps.) I'll remove this and add XML support if they want to - * do the overlay approach. If they want to just have a separate style for - * this legacy stuff, then we can keep it around. + /** + * Converts a drawable to a tiled version of itself. It will recursively + * traverse layer and state list drawables. */ - - // TODO Remove all this once ShapeDrawable + shaders are supported through XML - private Drawable tileify(Drawable drawable) { + private Drawable tileify(Drawable drawable, boolean clip) { + if (drawable instanceof LayerDrawable) { LayerDrawable background = (LayerDrawable) drawable; final int N = background.getNumberOfLayers(); @@ -236,7 +231,7 @@ public class ProgressBar extends View { for (int i = 0; i < N; i++) { int id = background.getId(i); - outDrawables[i] = createDrawableForTile(background.getDrawable(i), + outDrawables[i] = tileify(background.getDrawable(i), (id == R.id.progress || id == R.id.secondaryProgress)); } @@ -246,30 +241,36 @@ public class ProgressBar extends View { newBg.setId(i, background.getId(i)); } - drawable = newBg; - } - return drawable; - } + return newBg; + + } else if (drawable instanceof StateListDrawable) { + StateListDrawable in = (StateListDrawable) drawable; + StateListDrawable out = new StateListDrawable(); + int numStates = in.getStateCount(); + for (int i = 0; i < numStates; i++) { + out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); + } + return out; + + } else if (drawable instanceof BitmapDrawable) { + final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap(); + if (mSampleTile == null) { + mSampleTile = tileBitmap; + } + + final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); - // TODO Remove all this once ShapeDrawable + shaders are supported through XML - private Drawable createDrawableForTile(Drawable tileDrawable, boolean clip) { - if (!(tileDrawable instanceof BitmapDrawable)) return tileDrawable; + final BitmapShader bitmapShader = new BitmapShader(tileBitmap, + Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); + shapeDrawable.getPaint().setShader(bitmapShader); - final Bitmap tileBitmap = ((BitmapDrawable) tileDrawable).getBitmap(); - if (mSampleTile == null) { - mSampleTile = tileBitmap; + return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT, + ClipDrawable.HORIZONTAL) : shapeDrawable; } - final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); - - final BitmapShader bitmapShader = new BitmapShader(tileBitmap, - Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); - shapeDrawable.getPaint().setShader(bitmapShader); - - return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT, - ClipDrawable.HORIZONTAL) : shapeDrawable; + return drawable; } - + Shape getDrawableShape() { final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; return new RoundRectShape(roundedCorners, null, null); @@ -288,7 +289,7 @@ public class ProgressBar extends View { newBg.setOneShot(background.isOneShot()); for (int i = 0; i < N; i++) { - Drawable frame = createDrawableForTile(background.getFrame(i), true); + Drawable frame = tileify(background.getFrame(i), true); frame.setLevel(10000); newBg.addFrame(frame, background.getDuration(i)); } @@ -448,29 +449,29 @@ public class ProgressBar extends View { private int mId; private int mProgress; - private boolean mFromTouch; + private boolean mFromUser; - RefreshProgressRunnable(int id, int progress, boolean fromTouch) { + RefreshProgressRunnable(int id, int progress, boolean fromUser) { mId = id; mProgress = progress; - mFromTouch = fromTouch; + mFromUser = fromUser; } public void run() { - doRefreshProgress(mId, mProgress, mFromTouch); + doRefreshProgress(mId, mProgress, mFromUser); // Put ourselves back in the cache when we are done mRefreshProgressRunnable = this; } - public void setup(int id, int progress, boolean fromTouch) { + public void setup(int id, int progress, boolean fromUser) { mId = id; mProgress = progress; - mFromTouch = fromTouch; + mFromUser = fromUser; } } - private synchronized void doRefreshProgress(int id, int progress, boolean fromTouch) { + private synchronized void doRefreshProgress(int id, int progress, boolean fromUser) { float scale = mMax > 0 ? (float) progress / (float) mMax : 0; final Drawable d = mCurrentDrawable; if (d != null) { @@ -487,16 +488,16 @@ public class ProgressBar extends View { } if (id == R.id.progress) { - onProgressRefresh(scale, fromTouch); + onProgressRefresh(scale, fromUser); } } - void onProgressRefresh(float scale, boolean fromTouch) { + void onProgressRefresh(float scale, boolean fromUser) { } - private synchronized void refreshProgress(int id, int progress, boolean fromTouch) { + private synchronized void refreshProgress(int id, int progress, boolean fromUser) { if (mUiThreadId == Thread.currentThread().getId()) { - doRefreshProgress(id, progress, fromTouch); + doRefreshProgress(id, progress, fromUser); } else { RefreshProgressRunnable r; if (mRefreshProgressRunnable != null) { @@ -504,10 +505,10 @@ public class ProgressBar extends View { r = mRefreshProgressRunnable; // Uncache it mRefreshProgressRunnable = null; - r.setup(id, progress, fromTouch); + r.setup(id, progress, fromUser); } else { // Make a new one - r = new RefreshProgressRunnable(id, progress, fromTouch); + r = new RefreshProgressRunnable(id, progress, fromUser); } post(r); } @@ -528,7 +529,7 @@ public class ProgressBar extends View { setProgress(progress, false); } - synchronized void setProgress(int progress, boolean fromTouch) { + synchronized void setProgress(int progress, boolean fromUser) { if (mIndeterminate) { return; } @@ -543,7 +544,7 @@ public class ProgressBar extends View { if (progress != mProgress) { mProgress = progress; - refreshProgress(R.id.progress, mProgress, fromTouch); + refreshProgress(R.id.progress, mProgress, fromUser); } } @@ -836,6 +837,21 @@ public class ProgressBar extends View { resolveSize(dh, heightMeasureSpec)); } + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + int[] state = getDrawableState(); + + if (mProgressDrawable != null && mProgressDrawable.isStateful()) { + mProgressDrawable.setState(state); + } + + if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { + mIndeterminateDrawable.setState(state); + } + } + static class SavedState extends BaseSavedState { int progress; int secondaryProgress; diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index 845b5420679c..ad5ca07125a4 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -26,10 +26,14 @@ import com.android.internal.R; /** * A RatingBar is an extension of SeekBar and ProgressBar that shows a rating in - * stars. The user can touch and/or drag to set the rating when using the - * default size RatingBar. The smaller RatingBar style ({@link android.R.attr#ratingBarStyleSmall}) - * and the larger indicator-only style ({@link android.R.attr#ratingBarStyleIndicator}) - * do not support user interaction and should only be used as indicators. + * stars. The user can touch/drag or use arrow keys to set the rating when using + * the default size RatingBar. The smaller RatingBar style ( + * {@link android.R.attr#ratingBarStyleSmall}) and the larger indicator-only + * style ({@link android.R.attr#ratingBarStyleIndicator}) do not support user + * interaction and should only be used as indicators. + *

+ * When using a RatingBar that supports user interaction, placing widgets to the + * left or right of the RatingBar is discouraged. *

* The number of stars set (via {@link #setNumStars(int)} or in an XML layout) * will be shown when the layout width is set to wrap content (if another layout @@ -44,17 +48,18 @@ import com.android.internal.R; * @attr ref android.R.styleable#RatingBar_isIndicator */ public class RatingBar extends AbsSeekBar { - + /** - * A callback that notifies clients when the rating has been changed. This - * includes changes that were initiated by the user through a touch gesture as well - * as changes that were initiated programmatically. + * A callback that notifies clients when the rating has been changed. This + * includes changes that were initiated by the user through a touch gesture + * or arrow key/trackball as well as changes that were initiated + * programmatically. */ public interface OnRatingBarChangeListener { /** * Notification that the rating has changed. Clients can use the - * fromTouch parameter to distinguish user-initiated changes from those + * fromUser parameter to distinguish user-initiated changes from those * that occurred programmatically. This will not be called continuously * while the user is dragging, only when the user finalizes a rating by * lifting the touch. @@ -62,10 +67,10 @@ public class RatingBar extends AbsSeekBar { * @param ratingBar The RatingBar whose rating has changed. * @param rating The current rating. This will be in the range * 0..numStars. - * @param fromTouch True if the rating change was initiated by a user's - * touch gesture. + * @param fromUser True if the rating change was initiated by a user's + * touch gesture or arrow key/horizontal trackbell movement. */ - void onRatingChanged(RatingBar ratingBar, float rating, boolean fromTouch); + void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser); } @@ -138,6 +143,7 @@ public class RatingBar extends AbsSeekBar { */ public void setIsIndicator(boolean isIndicator) { mIsUserSeekable = !isIndicator; + setFocusable(!isIndicator); } /** @@ -179,7 +185,7 @@ public class RatingBar extends AbsSeekBar { * @param rating The rating to set. */ public void setRating(float rating) { - setProgress((int) (rating * getProgressPerStar())); + setProgress(Math.round(rating * getProgressPerStar())); } /** @@ -235,14 +241,14 @@ public class RatingBar extends AbsSeekBar { } @Override - void onProgressRefresh(float scale, boolean fromTouch) { - super.onProgressRefresh(scale, fromTouch); + void onProgressRefresh(float scale, boolean fromUser) { + super.onProgressRefresh(scale, fromUser); // Keep secondary progress in sync with primary updateSecondaryProgress(getProgress()); - if (!fromTouch) { - // Callback for non-touch rating changes + if (!fromUser) { + // Callback for non-user rating changes dispatchRatingChange(false); } } @@ -291,10 +297,10 @@ public class RatingBar extends AbsSeekBar { } } - void dispatchRatingChange(boolean fromTouch) { + void dispatchRatingChange(boolean fromUser) { if (mOnRatingBarChangeListener != null) { mOnRatingBarChangeListener.onRatingChanged(this, getRating(), - fromTouch); + fromUser); } } diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java index 456d58d0ae2c..9052ae3b4496 100644 --- a/core/java/android/widget/ResourceCursorAdapter.java +++ b/core/java/android/widget/ResourceCursorAdapter.java @@ -40,7 +40,8 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { * @param context The context where the ListView associated with this * SimpleListItemFactory is running * @param layout resource identifier of a layout file that defines the views - * for this list item. + * for this list item. Unless you override them later, this will + * define both the item views and the drop down views. */ public ResourceCursorAdapter(Context context, int layout, Cursor c) { super(context, c); @@ -65,6 +66,15 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { } /** + *

Sets the layout resource of the item views.

+ * + * @param layout the layout resources used to create item views + */ + public void setViewResource(int layout) { + mLayout = layout; + } + + /** *

Sets the layout resource of the drop down views.

* * @param dropDownLayout the layout resources used to create drop down views diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java index e87dc2dfa3da..dfee29ba78a5 100644 --- a/core/java/android/widget/SeekBar.java +++ b/core/java/android/widget/SeekBar.java @@ -22,33 +22,35 @@ import android.util.AttributeSet; /** - * A Seekbar is an extension of ProgressBar that adds a draggable thumb. The user can touch - * the thumb and drag left or right to set the current progress level. - * + * A SeekBar is an extension of ProgressBar that adds a draggable thumb. The user can touch + * the thumb and drag left or right to set the current progress level or use the arrow keys. + * Placing focusable widgets to the left or right of a SeekBar is discouraged. + *

+ * Clients of the SeekBar can attach a {@link SeekBar.OnSeekBarChangeListener} to * be notified of the user's actions. - * Clients of the Seekbar can attach a {@link SeekBar.OnSeekBarChangeListener} to * * @attr ref android.R.styleable#SeekBar_thumb */ public class SeekBar extends AbsSeekBar { /** - * A callback that notifies clients when the progress level has been changed. This - * includes changes that were initiated by the user through a touch gesture as well - * as changes that were initiated programmatically. + * A callback that notifies clients when the progress level has been + * changed. This includes changes that were initiated by the user through a + * touch gesture or arrow key/trackball as well as changes that were initiated + * programmatically. */ public interface OnSeekBarChangeListener { /** - * Notification that the progress level has changed. Clients can use the fromTouch parameter + * Notification that the progress level has changed. Clients can use the fromUser parameter * to distinguish user-initiated changes from those that occurred programmatically. * * @param seekBar The SeekBar whose progress has changed * @param progress The current progress level. This will be in the range 0..max where max * was set by {@link ProgressBar#setMax(int)}. (The default value for max is 100.) - * @param fromTouch True if the progress change was initiated by a user's touch gesture. + * @param fromUser True if the progress change was initiated by the user. */ - void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch); + void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser); /** * Notification that the user has started a touch gesture. Clients may want to use this @@ -80,11 +82,11 @@ public class SeekBar extends AbsSeekBar { } @Override - void onProgressRefresh(float scale, boolean fromTouch) { - super.onProgressRefresh(scale, fromTouch); + void onProgressRefresh(float scale, boolean fromUser) { + super.onProgressRefresh(scale, fromUser); if (mOnSeekBarChangeListener != null) { - mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromTouch); + mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser); } } @@ -113,4 +115,5 @@ public class SeekBar extends AbsSeekBar { mOnSeekBarChangeListener.onStopTrackingTouch(this); } } + } diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java index 74a996405d1e..c1595eaf8ac6 100644 --- a/core/java/android/widget/SimpleCursorAdapter.java +++ b/core/java/android/widget/SimpleCursorAdapter.java @@ -74,11 +74,12 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { * for this list item. Thelayout file should include at least * those named views defined in "to" * @param c The database cursor. Can be null if the cursor is not available yet. - * @param from A list of column names representing the data to bind to the UI + * @param from A list of column names representing the data to bind to the UI. Can be null + * if the cursor is not available yet. * @param to The views that should display column in the "from" parameter. * These should all be TextViews. The first N views in this list * are given the values of the first N columns in the from - * parameter. + * parameter. Can be null if the cursor is not available yet. */ public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) { super(context, layout, c); @@ -318,20 +319,24 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { return super.convertToString(cursor); } + /** + * Create a map from an array of strings to an array of column-id integers in mCursor. + * If mCursor is null, the array will be discarded. + * + * @param from the Strings naming the columns of interest + */ private void findColumns(String[] from) { - int i; - int count = from.length; - if (mFrom == null) { - mFrom = new int[count]; - } if (mCursor != null) { + int i; + int count = from.length; + if (mFrom == null || mFrom.length != count) { + mFrom = new int[count]; + } for (i = 0; i < count; i++) { mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]); } } else { - for (i = 0; i < count; i++) { - mFrom[i] = -1; - } + mFrom = null; } } @@ -341,6 +346,24 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { // rescan columns in case cursor layout is different findColumns(mOriginalFrom); } + + /** + * Change the cursor and change the column-to-view mappings at the same time. + * + * @param c The database cursor. Can be null if the cursor is not available yet. + * @param from A list of column names representing the data to bind to the UI. Can be null + * if the cursor is not available yet. + * @param to The views that should display column in the "from" parameter. + * These should all be TextViews. The first N views in this list + * are given the values of the first N columns in the from + * parameter. Can be null if the cursor is not available yet. + */ + public void changeCursorAndColumns(Cursor c, String[] from, int[] to) { + mOriginalFrom = from; + mTo = to; + super.changeCursor(c); + findColumns(mOriginalFrom); + } /** * This class can be used by external clients of SimpleCursorAdapter diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 9e5f0195c8c9..73c2b3e5d5d8 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -68,6 +68,7 @@ import android.text.method.TextKeyListener; import android.text.method.TransformationMethod; import android.text.style.ParagraphStyle; import android.text.style.URLSpan; +import android.text.style.UpdateAppearance; import android.text.style.UpdateLayout; import android.text.util.Linkify; import android.util.AttributeSet; @@ -3505,7 +3506,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener InputMethodManager imm = InputMethodManager.peekInstance(); if (highlight != null && mInputMethodState != null && imm != null) { - imm.updateSelection(this, selStart, selEnd); + if (imm.isActive(this)) { + int candStart = -1; + int candEnd = -1; + if (mText instanceof Spannable) { + Spannable sp = (Spannable)mText; + candStart = EditableInputConnection.getComposingSpanStart(sp); + candEnd = EditableInputConnection.getComposingSpanEnd(sp); + } + imm.updateSelection(this, selStart, selEnd, candStart, candEnd); + } if (imm.isWatchingCursor(this)) { final InputMethodState ims = mInputMethodState; @@ -3762,7 +3772,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); } - @Override public InputConnection createInputConnection(EditorInfo outAttrs) { + @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { if (mInputType != EditorInfo.TYPE_NULL) { if (mInputMethodState == null) { mInputMethodState = new InputMethodState(); @@ -5137,7 +5147,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - if (what instanceof UpdateLayout || + if (what instanceof UpdateAppearance || what instanceof ParagraphStyle) { invalidate(); mHighlightPathBogus = true; @@ -5167,6 +5177,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void afterTextChanged(Editable buffer) { TextView.this.sendAfterTextChanged(buffer); + + if (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0) { + MetaKeyKeyListener.stopSelecting(TextView.this, buffer); + } + TextView.this.reportExtractedText(); } @@ -5550,6 +5566,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } + private boolean canSelectText() { + if (mText instanceof Spannable && mText.length() != 0 && + mMovement != null && mMovement.canSelectArbitrarily()) { + return true; + } + + return false; + } + private boolean canCut() { if (mTransformation instanceof PasswordTransformationMethod) { return false; @@ -5623,6 +5648,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean selection = getSelectionStart() != getSelectionEnd(); + if (canSelectText()) { + if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { + menu.add(0, ID_STOP_SELECTING_TEXT, 0, + com.android.internal.R.string.stopSelectingText). + setOnMenuItemClickListener(handler); + added = true; + } else { + menu.add(0, ID_SELECT_TEXT, 0, + com.android.internal.R.string.selectText). + setOnMenuItemClickListener(handler); + added = true; + } + } + if (canCut()) { int name; if (selection) { @@ -5688,6 +5727,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private static final int ID_SELECT_ALL = com.android.internal.R.id.selectAll; + private static final int ID_SELECT_TEXT = com.android.internal.R.id.selectText; + private static final int ID_STOP_SELECTING_TEXT = com.android.internal.R.id.stopSelectingText; private static final int ID_CUT = com.android.internal.R.id.cut; private static final int ID_COPY = com.android.internal.R.id.copy; private static final int ID_PASTE = com.android.internal.R.id.paste; @@ -5728,7 +5769,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mText.length()); return true; + case ID_SELECT_TEXT: + MetaKeyKeyListener.startSelecting(this, (Spannable) mText); + return true; + + case ID_STOP_SELECTING_TEXT: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + Selection.setSelection((Spannable) mText, getSelectionEnd()); + return true; + case ID_CUT: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + if (min == max) { min = 0; max = mText.length(); @@ -5739,6 +5791,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; case ID_COPY: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + if (min == max) { min = 0; max = mText.length(); @@ -5748,6 +5802,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; case ID_PASTE: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + CharSequence paste = clip.getText(); if (paste != null) { @@ -5758,6 +5814,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; case ID_COPY_URL: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class); if (urls.length == 1) { diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java index 9c83aa36f0f4..1f8e6f02f981 100644 --- a/core/java/com/android/internal/app/RingtonePickerActivity.java +++ b/core/java/com/android/internal/app/RingtonePickerActivity.java @@ -169,6 +169,14 @@ public final class RingtonePickerActivity extends AlertActivity implements public void onPrepareListView(ListView listView) { + if (mHasDefaultItem) { + mDefaultRingtonePos = addDefaultRingtoneItem(listView); + + if (RingtoneManager.isDefault(mExistingUri)) { + mClickedPos = mDefaultRingtonePos; + } + } + if (mHasSilentItem) { mSilentPos = addSilentItem(listView); @@ -178,14 +186,6 @@ public final class RingtonePickerActivity extends AlertActivity implements } } - if (mHasDefaultItem) { - mDefaultRingtonePos = addDefaultRingtoneItem(listView); - - if (RingtoneManager.isDefault(mExistingUri)) { - mClickedPos = mDefaultRingtonePos; - } - } - if (mClickedPos == -1) { mClickedPos = getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri)); } diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index fd8fd5ab5c5b..e88a36f4e33f 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -26,6 +26,8 @@ public class HandlerCaller { public int argi2; public int argi3; public int argi4; + public int argi5; + public int argi6; } static final int ARGS_POOL_MAX_SIZE = 10; @@ -153,6 +155,18 @@ public class HandlerCaller { return mH.obtainMessage(what, 0, 0, args); } + public Message obtainMessageIIIIII(int what, int arg1, int arg2, + int arg3, int arg4, int arg5, int arg6) { + SomeArgs args = obtainArgs(); + args.argi1 = arg1; + args.argi2 = arg2; + args.argi3 = arg3; + args.argi4 = arg4; + args.argi5 = arg5; + args.argi6 = arg6; + return mH.obtainMessage(what, 0, 0, args); + } + public Message obtainMessageIIIIO(int what, int arg1, int arg2, int arg3, int arg4, Object arg5) { SomeArgs args = obtainArgs(); diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index c5966ee46350..d6042591dad2 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -23,6 +23,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_COMMIT_TEXT = 50; private static final int DO_COMMIT_COMPLETION = 55; private static final int DO_SET_COMPOSING_TEXT = 60; + private static final int DO_FINISH_COMPOSING_TEXT = 65; private static final int DO_SEND_KEY_EVENT = 70; private static final int DO_DELETE_SURROUNDING_TEXT = 80; private static final int DO_HIDE_STATUS_ICON = 100; @@ -89,6 +90,10 @@ public class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text)); } + public void finishComposingText() { + dispatchMessage(obtainMessage(DO_FINISH_COMPOSING_TEXT)); + } + public void sendKeyEvent(KeyEvent event) { dispatchMessage(obtainMessageO(DO_SEND_KEY_EVENT, event)); } @@ -181,6 +186,10 @@ public class IInputConnectionWrapper extends IInputContext.Stub { mInputConnection.setComposingText((CharSequence)msg.obj, msg.arg1); return; } + case DO_FINISH_COMPOSING_TEXT: { + mInputConnection.finishComposingText(); + return; + } case DO_SEND_KEY_EVENT: { mInputConnection.sendKeyEvent((KeyEvent)msg.obj); return; diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index 7ea65a054209..55a9784b26d9 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -42,6 +42,8 @@ import com.android.internal.view.IInputContextCallback; void setComposingText(CharSequence text, int newCursorPosition); + void finishComposingText(); + void commitText(CharSequence text, int newCursorPosition); void commitCompletion(in CompletionInfo completion); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index b4cfe26330c8..1c9e7976d188 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -47,5 +47,7 @@ interface IInputMethodManager { void setInputMethod(in IBinder token, String id); void hideMySoftInput(in IBinder token); void updateStatusIcon(int iconId, String iconPackage); + + boolean setInputMethodEnabled(String id, boolean enabled); } diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl index 4f285933dec7..8a4497692d6e 100644 --- a/core/java/com/android/internal/view/IInputMethodSession.aidl +++ b/core/java/com/android/internal/view/IInputMethodSession.aidl @@ -34,7 +34,8 @@ oneway interface IInputMethodSession { void updateExtractedText(int token, in ExtractedText text); void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd); + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd); void updateCursor(in Rect newCursor); diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index 5bfcfe9598b2..1cfaf1713e1f 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -250,6 +250,15 @@ public class InputConnectionWrapper implements InputConnection { } } + public boolean finishComposingText() { + try { + mIInputContext.finishComposingText(); + return true; + } catch (RemoteException e) { + return false; + } + } + public boolean sendKeyEvent(KeyEvent event) { try { mIInputContext.sendKeyEvent(event); diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 43dba6f3e465..1543b620018b 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -547,19 +547,7 @@ public final class MenuItemImpl implements MenuItem { boolean setVisibleInt(boolean shown) { final int oldFlags = mFlags; mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN); - if (oldFlags != mFlags) { - final int visibility = (shown) ? View.VISIBLE : View.GONE; - - for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) { - if (hasItemView(i)) { - ((View) mItemViews[i].get()).setVisibility(visibility); - } - } - - return true; - } - - return false; + return oldFlags != mFlags; } public MenuItem setVisible(boolean shown) { diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index efe15f3b5791..263220bcd094 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -44,22 +44,74 @@ public class EditableInputConnection extends BaseInputConnection { public static final Object COMPOSING = new ComposingText(); private final TextView mTextView; - private final Handler mUiHandler; private Object[] mDefaultComposingSpans; public EditableInputConnection(TextView textview) { super(textview); mTextView = textview; - mUiHandler = textview.getHandler(); } + public static final void removeComposingSpans(Spannable text) { + text.removeSpan(COMPOSING); + Object[] sps = text.getSpans(0, text.length(), Object.class); + if (sps != null) { + for (int i=sps.length-1; i>=0; i--) { + Object o = sps[i]; + if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) { + text.removeSpan(o); + } + } + } + } + + public static void setComposingSpans(Spannable text) { + final Object[] sps = text.getSpans(0, text.length(), Object.class); + if (sps != null) { + for (int i=sps.length-1; i>=0; i--) { + final Object o = sps[i]; + if (o == COMPOSING) { + text.removeSpan(o); + continue; + } + final int fl = text.getSpanFlags(o); + if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK)) + != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) { + text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o), + (fl&Spanned.SPAN_POINT_MARK_MASK) + | Spanned.SPAN_COMPOSING + | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + + text.setSpan(COMPOSING, 0, text.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); + } + + public static int getComposingSpanStart(Spannable text) { + return text.getSpanStart(COMPOSING); + } + + public static int getComposingSpanEnd(Spannable text) { + return text.getSpanEnd(COMPOSING); + } + public boolean setComposingText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "setComposingText " + text); replaceText(text, newCursorPosition, true); return true; } + public boolean finishComposingText() { + if (DEBUG) Log.v(TAG, "finishComposingText"); + final Editable content = getEditable(); + if (content != null) { + removeComposingSpans(content); + } + return true; + } + public boolean commitText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "commitText " + text); replaceText(text, newCursorPosition, false); @@ -212,43 +264,6 @@ public class EditableInputConnection extends BaseInputConnection { return null; } - public static void setComposingSpans(Spannable text) { - final Object[] sps = text.getSpans(0, text.length(), Object.class); - if (sps != null) { - for (int i=sps.length-1; i>=0; i--) { - final Object o = sps[i]; - if (o == COMPOSING) { - text.removeSpan(o); - continue; - } - final int fl = text.getSpanFlags(o); - if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK)) - != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) { - text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o), - (fl&Spanned.SPAN_POINT_MARK_MASK) - | Spanned.SPAN_COMPOSING - | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } - - text.setSpan(COMPOSING, 0, text.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); - } - - public static final void removeComposingSpans(Spannable text) { - text.removeSpan(COMPOSING); - Object[] sps = text.getSpans(0, text.length(), Object.class); - if (sps != null) { - for (int i=sps.length-1; i>=0; i--) { - Object o = sps[i]; - if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) { - text.removeSpan(o); - } - } - } - } - private void replaceText(CharSequence text, int newCursorPosition, boolean composing) { final Editable content = getEditable(); diff --git a/core/java/com/google/android/gdata/client/AndroidGDataClient.java b/core/java/com/google/android/gdata/client/AndroidGDataClient.java index 48d023390f43..17f86d6f6c5d 100644 --- a/core/java/com/google/android/gdata/client/AndroidGDataClient.java +++ b/core/java/com/google/android/gdata/client/AndroidGDataClient.java @@ -49,8 +49,7 @@ public class AndroidGDataClient implements GDataClient { private static final String X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override"; - private static final String USER_AGENT_GZIP = - GoogleHttpClient.getGzipCapableUserAgent("Android-GData/1.0"); + private static final String USER_AGENT_APP_VERSION = "Android-GData/1.0"; private static final int MAX_REDIRECTS = 10; @@ -123,7 +122,8 @@ public class AndroidGDataClient implements GDataClient { * through the Android proxy server, using null to indicate not using proxy. */ public AndroidGDataClient(ContentResolver resolver) { - mHttpClient = new GoogleHttpClient(resolver, USER_AGENT_GZIP); + mHttpClient = new GoogleHttpClient(resolver, USER_AGENT_APP_VERSION, + true /* gzip capable */); mResolver = resolver; } diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java index bc6eaee15d39..4656aff15bc1 100644 --- a/core/java/com/google/android/net/GoogleHttpClient.java +++ b/core/java/com/google/android/net/GoogleHttpClient.java @@ -38,13 +38,12 @@ import java.net.URISyntaxException; import android.content.ContentResolver; import android.content.ContentValues; import android.os.SystemClock; +import android.os.Build; import android.net.http.AndroidHttpClient; import android.provider.Checkin; import android.util.Config; import android.util.Log; -import com.android.internal.os.RuntimeInit; - /** * {@link AndroidHttpClient} wrapper that uses {@link UrlRules} to rewrite URLs * and otherwise tweak HTTP requests. @@ -69,6 +68,7 @@ public class GoogleHttpClient implements HttpClient { * Create an HTTP client. Normally one client is shared throughout an app. * @param resolver to use for accessing URL rewriting rules. * @param userAgent to report in your HTTP requests. + * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)} */ public GoogleHttpClient(ContentResolver resolver, String userAgent) { mClient = AndroidHttpClient.newInstance(userAgent); @@ -77,6 +77,35 @@ public class GoogleHttpClient implements HttpClient { } /** + * Create an HTTP client. Normaly this client is shared throughout an app. + * The HTTP client will construct its User-Agent as follows: + * + * ( ) + * or + * ( ); gzip + * (if gzip capable) + * + * @param resolver to use for acccessing URL rewriting rules. + * @param appAndVersion Base app and version to use in the User-Agent. + * e.g., "MyApp/1.0" + * @param gzipCapable Whether or not this client is able to consume gzip'd + * responses. Only used to modify the User-Agent, not other request + * headers. Needed because Google servers require gzip in the User-Agent + * in order to return gzip'd content. + */ + public GoogleHttpClient(ContentResolver resolver, String appAndVersion, + boolean gzipCapable) { + String userAgent = appAndVersion + + " (" + Build.DEVICE + " " + Build.ID + ")"; + if (gzipCapable) { + userAgent = userAgent + "; gzip"; + } + mClient = AndroidHttpClient.newInstance(userAgent); + mResolver = resolver; + mUserAgent = userAgent; + } + + /** * Release resources associated with this client. You must call this, * or significant resources (sockets and memory) may be leaked. */ @@ -181,6 +210,7 @@ public class GoogleHttpClient implements HttpClient { * * @param originalUserAgent to modify (however you identify yourself) * @return user agent with a "yes, I really can handle gzip" token added. + * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)} */ public static String getGzipCapableUserAgent(String originalUserAgent) { return originalUserAgent + "; gzip"; diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 2c74ab78e5a0..c074b69844f8 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -91,6 +91,7 @@ LOCAL_SRC_FILES:= \ android_media_AudioRecord.cpp \ android_media_AudioSystem.cpp \ android_media_AudioTrack.cpp \ + android_media_JetPlayer.cpp \ android_media_ToneGenerator.cpp \ android_hardware_Camera.cpp \ android_hardware_SensorManager.cpp \ @@ -116,10 +117,14 @@ LOCAL_SRC_FILES:= \ LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ $(LOCAL_PATH)/android/graphics \ - $(call include-path-for, bluedroid corecg graphics) \ + $(call include-path-for, bluedroid) \ $(call include-path-for, libhardware)/hardware \ $(LOCAL_PATH)/../../include/ui \ $(LOCAL_PATH)/../../include/utils \ + external/skia/include/core \ + external/skia/include/effects \ + external/skia/include/images \ + external/skia/include/utils \ external/sqlite/dist \ external/sqlite/android \ external/expat/lib \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f85f7d54abe2..097ffac2cf37 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -74,6 +74,7 @@ extern int register_android_hardware_SensorManager(JNIEnv *env); extern int register_android_media_AudioRecord(JNIEnv *env); extern int register_android_media_AudioSystem(JNIEnv *env); extern int register_android_media_AudioTrack(JNIEnv *env); +extern int register_android_media_JetPlayer(JNIEnv *env); extern int register_android_media_ToneGenerator(JNIEnv *env); extern int register_android_message_digest_sha1(JNIEnv *env); @@ -1073,6 +1074,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_media_AudioRecord), REG_JNI(register_android_media_AudioSystem), REG_JNI(register_android_media_AudioTrack), + REG_JNI(register_android_media_JetPlayer), REG_JNI(register_android_media_ToneGenerator), REG_JNI(register_android_opengl_classes), diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 27a63493b660..329a695fee52 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -1,5 +1,5 @@ #include "SkBitmap.h" -#include "SkImageDecoder.h" +#include "SkImageEncoder.h" #include "SkColorPriv.h" #include "GraphicsJNI.h" #include "SkDither.h" diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 841fe492e510..b9e5f67067dc 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -800,7 +800,7 @@ public: jobject bounds) { SkRect r; SkIRect ir; - bool result = canvas->getClipBounds(&r); + bool result = canvas->getClipBounds(&r, SkCanvas::kBW_EdgeType); r.round(&ir); (void)GraphicsJNI::irect_to_jrect(ir, env, bounds); diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp index eef8bb2a6052..b28eb900242c 100644 --- a/core/jni/android/graphics/Shader.cpp +++ b/core/jni/android/graphics/Shader.cpp @@ -4,7 +4,7 @@ #include "SkShader.h" #include "SkGradientShader.h" #include "SkPorterDuff.h" -#include "SkShaderExtras.h" +#include "SkComposeShader.h" #include "SkTemplates.h" #include "SkXfermode.h" diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp index 5cd2ceb4998d..a1059e5a6169 100644 --- a/core/jni/android/opengl/util.cpp +++ b/core/jni/android/opengl/util.cpp @@ -24,7 +24,7 @@ #include -#include +#include #include "android_runtime/AndroidRuntime.h" diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index 4b126a67dc68..e1ef45927190 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -225,6 +225,15 @@ static void android_hardware_Camera_stopPreview(JNIEnv *env, jobject thiz) c->stopPreview(); } +static bool android_hardware_Camera_previewEnabled(JNIEnv *env, jobject thiz) +{ + sp c = get_native_camera(env, thiz); + if (c == 0) + return false; + + return c->previewEnabled(); +} + static void android_hardware_Camera_setHasPreviewCallback(JNIEnv *env, jobject thiz, jboolean installed) { sp c = get_native_camera(env, thiz); @@ -382,6 +391,22 @@ static void android_hardware_Camera_reconnect(JNIEnv *env, jobject thiz) } } +static jint android_hardware_Camera_lock(JNIEnv *env, jobject thiz) +{ + sp c = get_native_camera(env, thiz); + if (c == 0) + return INVALID_OPERATION; + return (jint) c->lock(); +} + +static jint android_hardware_Camera_unlock(JNIEnv *env, jobject thiz) +{ + sp c = get_native_camera(env, thiz); + if (c == 0) + return INVALID_OPERATION; + return (jint) c->lock(); +} + //------------------------------------------------- static JNINativeMethod camMethods[] = { @@ -400,6 +425,9 @@ static JNINativeMethod camMethods[] = { { "stopPreview", "()V", (void *)android_hardware_Camera_stopPreview }, + { "previewEnabled", + "()Z", + (void *)android_hardware_Camera_previewEnabled }, { "setHasPreviewCallback", "(Z)V", (void *)android_hardware_Camera_setHasPreviewCallback }, @@ -418,6 +446,12 @@ static JNINativeMethod camMethods[] = { { "reconnect", "()V", (void*)android_hardware_Camera_reconnect }, + { "lock", + "()I", + (void*)android_hardware_Camera_lock }, + { "unlock", + "()I", + (void*)android_hardware_Camera_unlock }, }; struct field { diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 4a98fcc22489..6bbcaeefca97 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -52,6 +52,7 @@ struct fields_t { int STREAM_RING; //... stream type constants int STREAM_MUSIC; //... stream type constants int STREAM_ALARM; //... stream type constants + int STREAM_NOTIFICATION; //... stream type constants int MODE_STREAM; //... memory mode int MODE_STATIC; //... memory mode jfieldID nativeTrackInJavaObj; // stores in Java the native AudioTrack object @@ -192,6 +193,8 @@ android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_th atStreamType = AudioTrack::MUSIC; } else if (streamType == javaAudioTrackFields.STREAM_ALARM) { atStreamType = AudioTrack::ALARM; + } else if (streamType == javaAudioTrackFields.STREAM_NOTIFICATION) { + atStreamType = AudioTrack::NOTIFICATION; } else { LOGE("Error creating AudioTrack: unknown stream type."); return AUDIOTRACK_ERROR_SETUP_INVALIDSTREAMTYPE; @@ -696,22 +699,23 @@ static JNINativeMethod gMethods[] = { // field names found in android/media/AudioTrack.java -#define JAVA_POSTEVENT_CALLBACK_NAME "postEventFromNative" -#define JAVA_CONST_PCM16_NAME "ENCODING_PCM_16BIT" -#define JAVA_CONST_PCM8_NAME "ENCODING_PCM_8BIT" -#define JAVA_CONST_BUFFER_COUNT_NAME "BUFFER_COUNT" -#define JAVA_CONST_STREAM_VOICE_CALL_NAME "STREAM_VOICE_CALL" -#define JAVA_CONST_STREAM_SYSTEM_NAME "STREAM_SYSTEM" -#define JAVA_CONST_STREAM_RING_NAME "STREAM_RING" -#define JAVA_CONST_STREAM_MUSIC_NAME "STREAM_MUSIC" -#define JAVA_CONST_STREAM_ALARM_NAME "STREAM_ALARM" -#define JAVA_CONST_MODE_STREAM_NAME "MODE_STREAM" -#define JAVA_CONST_MODE_STATIC_NAME "MODE_STATIC" -#define JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME "mNativeTrackInJavaObj" -#define JAVA_JNIDATA_FIELD_NAME "mJniData" - -#define JAVA_AUDIOFORMAT_CLASS_NAME "android/media/AudioFormat" -#define JAVA_AUDIOMANAGER_CLASS_NAME "android/media/AudioManager" +#define JAVA_POSTEVENT_CALLBACK_NAME "postEventFromNative" +#define JAVA_CONST_PCM16_NAME "ENCODING_PCM_16BIT" +#define JAVA_CONST_PCM8_NAME "ENCODING_PCM_8BIT" +#define JAVA_CONST_BUFFER_COUNT_NAME "BUFFER_COUNT" +#define JAVA_CONST_STREAM_VOICE_CALL_NAME "STREAM_VOICE_CALL" +#define JAVA_CONST_STREAM_SYSTEM_NAME "STREAM_SYSTEM" +#define JAVA_CONST_STREAM_RING_NAME "STREAM_RING" +#define JAVA_CONST_STREAM_MUSIC_NAME "STREAM_MUSIC" +#define JAVA_CONST_STREAM_ALARM_NAME "STREAM_ALARM" +#define JAVA_CONST_STREAM_NOTIFICATION_NAME "STREAM_NOTIFICATION" +#define JAVA_CONST_MODE_STREAM_NAME "MODE_STREAM" +#define JAVA_CONST_MODE_STATIC_NAME "MODE_STATIC" +#define JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME "mNativeTrackInJavaObj" +#define JAVA_JNIDATA_FIELD_NAME "mJniData" + +#define JAVA_AUDIOFORMAT_CLASS_NAME "android/media/AudioFormat" +#define JAVA_AUDIOMANAGER_CLASS_NAME "android/media/AudioManager" // ---------------------------------------------------------------------------- // preconditions: @@ -820,7 +824,10 @@ int register_android_media_AudioTrack(JNIEnv *env) JAVA_CONST_STREAM_RING_NAME, &(javaAudioTrackFields.STREAM_RING)) || !android_media_getIntConstantFromClass(env, audioManagerClass, JAVA_AUDIOMANAGER_CLASS_NAME, - JAVA_CONST_STREAM_ALARM_NAME, &(javaAudioTrackFields.STREAM_ALARM)) ) { + JAVA_CONST_STREAM_ALARM_NAME, &(javaAudioTrackFields.STREAM_ALARM)) + || !android_media_getIntConstantFromClass(env, audioManagerClass, + JAVA_AUDIOMANAGER_CLASS_NAME, + JAVA_CONST_STREAM_NOTIFICATION_NAME, &(javaAudioTrackFields.STREAM_NOTIFICATION)) ) { // error log performed in android_media_getIntConstantFromClass() return -1; } diff --git a/core/jni/android_media_JetPlayer.cpp b/core/jni/android_media_JetPlayer.cpp new file mode 100644 index 000000000000..994f16184c8c --- /dev/null +++ b/core/jni/android_media_JetPlayer.cpp @@ -0,0 +1,485 @@ +/* + * 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. + */ + +//FIXME: remove log before release +#define LOG_NDEBUG 0 +#define LOG_TAG "JET_JNI" + + +#include +#include +#include + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" + +#include "utils/Log.h" +#include "media/JetPlayer.h" + + +using namespace android; + +// ---------------------------------------------------------------------------- +static const char* const kClassPathName = "android/media/JetPlayer"; + +// ---------------------------------------------------------------------------- +struct fields_t { + // these fields provide access from C++ to the... + jclass jetClass; // JetPlayer java class global ref + jmethodID postNativeEventInJava; // java method to post events to the Java thread from native + jfieldID nativePlayerInJavaObj; // stores in Java the native JetPlayer object +}; + +static fields_t javaJetPlayerFields; + + +// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- + +/* + * This function is called from JetPlayer instance's render thread + */ +static void +jetPlayerEventCallback(int what, int arg1=0, int arg2=0, void* javaTarget = NULL) +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if(env) { + env->CallStaticVoidMethod( + javaJetPlayerFields.jetClass, javaJetPlayerFields.postNativeEventInJava, + javaTarget, + what, arg1, arg2); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + } else { + LOGE("JET jetPlayerEventCallback(): No JNI env for JET event callback, can't post event."); + return; + } +} + + +// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- + +static jboolean +android_media_JetPlayer_setup(JNIEnv *env, jobject thiz, jobject weak_this, + jint maxTracks, jint trackBufferSize) +{ + //LOGV("android_media_JetPlayer_setup(): entering."); + JetPlayer* lpJet = new JetPlayer(weak_this, maxTracks, trackBufferSize); + + EAS_RESULT result = lpJet->init(); + + if(result==EAS_SUCCESS) { + // save our newly created C++ JetPlayer in the "nativePlayerInJavaObj" field + // of the Java object (in mNativePlayerInJavaObj) + env->SetIntField(thiz, javaJetPlayerFields.nativePlayerInJavaObj, (int)lpJet); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_setup(): initialization failed with EAS error code %d", (int)result); + delete lpJet; + env->SetIntField(weak_this, javaJetPlayerFields.nativePlayerInJavaObj, 0); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static void +android_media_JetPlayer_finalize(JNIEnv *env, jobject thiz) +{ + LOGV("android_media_JetPlayer_finalize(): entering."); + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if(lpJet != NULL) { + lpJet->release(); + delete lpJet; + } + + LOGV("android_media_JetPlayer_finalize(): exiting."); +} + + +// ---------------------------------------------------------------------------- +static void +android_media_JetPlayer_release(JNIEnv *env, jobject thiz) +{ + android_media_JetPlayer_finalize(env, thiz); + env->SetIntField(thiz, javaJetPlayerFields.nativePlayerInJavaObj, 0); + LOGV("android_media_JetPlayer_release() done"); +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_openFile(JNIEnv *env, jobject thiz, jstring path) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + // set up event callback function + lpJet->setEventCallback(jetPlayerEventCallback); + + + const char *pathStr = env->GetStringUTFChars(path, NULL); + if (pathStr == NULL) { // Out of memory + LOGE("android_media_JetPlayer_openFile(): aborting, out of memory"); + jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); + return JNI_FALSE; + } + + LOGV("android_media_JetPlayer_openFile(): trying to open %s", pathStr ); + EAS_RESULT result = lpJet->openFile(pathStr); + env->ReleaseStringUTFChars(path, pathStr); + + if(result==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_openFile(): file successfully opened"); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_openFile(): failed to open file with EAS error %d", + (int)result); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_closeFile(JNIEnv *env, jobject thiz) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + if( lpJet->closeFile()==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_closeFile(): file successfully closed"); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_closeFile(): failed to close file"); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_play(JNIEnv *env, jobject thiz) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + EAS_RESULT result = lpJet->play(); + if( result==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_play(): play successful"); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_play(): failed to play with EAS error code %ld", + result); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_pause(JNIEnv *env, jobject thiz) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + EAS_RESULT result = lpJet->pause(); + if( result==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_pause(): pause successful"); + return JNI_TRUE; + } else { + if(result==EAS_ERROR_QUEUE_IS_EMPTY) { + LOGV("android_media_JetPlayer_pause(): paused with an empty queue"); + return JNI_TRUE; + } else + LOGE("android_media_JetPlayer_pause(): failed to pause with EAS error code %ld", + result); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_queueSegment(JNIEnv *env, jobject thiz, + jint segmentNum, jint libNum, jint repeatCount, jint transpose, jint muteFlags, + jbyte userID) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + EAS_RESULT result + = lpJet->queueSegment(segmentNum, libNum, repeatCount, transpose, muteFlags, userID); + if(result==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_queueSegment(): segment successfully queued"); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_queueSegment(): failed with EAS error code %ld", + result); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_queueSegmentMuteArray(JNIEnv *env, jobject thiz, + jint segmentNum, jint libNum, jint repeatCount, jint transpose, jbooleanArray muteArray, + jbyte userID) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + EAS_RESULT result=EAS_FAILURE; + + jboolean *muteTracks = NULL; + muteTracks = env->GetBooleanArrayElements(muteArray, NULL); + if (muteTracks == NULL) { + LOGE("android_media_JetPlayer_queueSegment(): failed to read track mute mask."); + return JNI_FALSE; + } + + EAS_U32 muteMask=0; + int maxTracks = lpJet->getMaxTracks(); + for (jint trackIndex=0; trackIndexqueueSegment(segmentNum, libNum, repeatCount, transpose, muteMask, userID); + + env->ReleaseBooleanArrayElements(muteArray, muteTracks, 0); + if(result==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_queueSegmentMuteArray(): segment successfully queued"); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_queueSegmentMuteArray(): failed with EAS error code %ld", + result); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_setMuteFlags(JNIEnv *env, jobject thiz, + jint muteFlags /*unsigned?*/, jboolean bSync) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + EAS_RESULT result; + result = lpJet->setMuteFlags(muteFlags, bSync==JNI_TRUE ? true : false); + if(result==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_setMuteFlags(): mute flags successfully updated"); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_setMuteFlags(): failed with EAS error code %ld", result); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_setMuteArray(JNIEnv *env, jobject thiz, + jbooleanArray muteArray, jboolean bSync) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + EAS_RESULT result=EAS_FAILURE; + + jboolean *muteTracks = NULL; + muteTracks = env->GetBooleanArrayElements(muteArray, NULL); + if (muteTracks == NULL) { + LOGE("android_media_JetPlayer_setMuteArray(): failed to read track mute mask."); + return JNI_FALSE; + } + + EAS_U32 muteMask=0; + int maxTracks = lpJet->getMaxTracks(); + for (jint trackIndex=0; trackIndexsetMuteFlags(muteMask, bSync==JNI_TRUE ? true : false); + + env->ReleaseBooleanArrayElements(muteArray, muteTracks, 0); + if(result==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_setMuteArray(): mute flags successfully updated"); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_setMuteArray(): failed to update mute flags with EAS error code %ld", result); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_setMuteFlag(JNIEnv *env, jobject thiz, + jint trackId, jboolean muteFlag, jboolean bSync) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + EAS_RESULT result; + result = lpJet->setMuteFlag(trackId, + muteFlag==JNI_TRUE ? true : false, bSync==JNI_TRUE ? true : false); + if(result==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_setMuteFlag(): mute flag successfully updated for track %d", trackId); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_setMuteFlag(): failed to update mute flag for track %d with EAS error code %ld", + trackId, result); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +static jboolean +android_media_JetPlayer_triggerClip(JNIEnv *env, jobject thiz, jint clipId) +{ + JetPlayer *lpJet = (JetPlayer *)env->GetIntField( + thiz, javaJetPlayerFields.nativePlayerInJavaObj); + if (lpJet == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve JetPlayer pointer for openFile()"); + } + + EAS_RESULT result; + result = lpJet->triggerClip(clipId); + if(result==EAS_SUCCESS) { + //LOGV("android_media_JetPlayer_triggerClip(): triggerClip successful for clip %d", clipId); + return JNI_TRUE; + } else { + LOGE("android_media_JetPlayer_triggerClip(): triggerClip for clip %d failed with EAS error code %ld", + clipId, result); + return JNI_FALSE; + } +} + + +// ---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- +static JNINativeMethod gMethods[] = { + // name, signature, funcPtr + {"native_setup", "(Ljava/lang/Object;II)Z", (void *)android_media_JetPlayer_setup}, + {"native_finalize", "()V", (void *)android_media_JetPlayer_finalize}, + {"native_release", "()V", (void *)android_media_JetPlayer_release}, + {"native_openJetFile", "(Ljava/lang/String;)Z", (void *)android_media_JetPlayer_openFile}, + {"native_closeJetFile","()Z", (void *)android_media_JetPlayer_closeFile}, + {"native_playJet", "()Z", (void *)android_media_JetPlayer_play}, + {"native_pauseJet", "()Z", (void *)android_media_JetPlayer_pause}, + {"native_queueJetSegment", + "(IIIIIB)Z", (void *)android_media_JetPlayer_queueSegment}, + {"native_queueJetSegmentMuteArray", + "(IIII[ZB)Z", (void *)android_media_JetPlayer_queueSegmentMuteArray}, + {"native_setMuteFlags","(IZ)Z", (void *)android_media_JetPlayer_setMuteFlags}, + {"native_setMuteArray","([ZZ)Z", (void *)android_media_JetPlayer_setMuteArray}, + {"native_setMuteFlag", "(IZZ)Z", (void *)android_media_JetPlayer_setMuteFlag}, + {"native_triggerClip", "(I)Z", (void *)android_media_JetPlayer_triggerClip}, +}; + +#define JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME "mNativePlayerInJavaObj" +#define JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME "postEventFromNative" + + +int register_android_media_JetPlayer(JNIEnv *env) +{ + jclass jetPlayerClass = NULL; + javaJetPlayerFields.jetClass = NULL; + javaJetPlayerFields.postNativeEventInJava = NULL; + javaJetPlayerFields.nativePlayerInJavaObj = NULL; + + // Get the JetPlayer java class + jetPlayerClass = env->FindClass(kClassPathName); + if (jetPlayerClass == NULL) { + LOGE("Can't find %s", kClassPathName); + return -1; + } + javaJetPlayerFields.jetClass = (jclass)env->NewGlobalRef(jetPlayerClass); + + // Get the mNativePlayerInJavaObj variable field + javaJetPlayerFields.nativePlayerInJavaObj = env->GetFieldID( + jetPlayerClass, + JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME, "I"); + if (javaJetPlayerFields.nativePlayerInJavaObj == NULL) { + LOGE("Can't find AudioTrack.%s", JAVA_NATIVEJETPLAYERINJAVAOBJ_FIELD_NAME); + return -1; + } + + // Get the callback to post events from this native code to Java + javaJetPlayerFields.postNativeEventInJava = env->GetStaticMethodID(javaJetPlayerFields.jetClass, + JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME, "(Ljava/lang/Object;III)V"); + if (javaJetPlayerFields.postNativeEventInJava == NULL) { + LOGE("Can't find Jet.%s", JAVA_NATIVEJETPOSTEVENT_CALLBACK_NAME); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, + kClassPathName, gMethods, NELEM(gMethods)); +} diff --git a/core/jni/android_os_Power.cpp b/core/jni/android_os_Power.cpp index 02a4083f3dab..4e70f633be83 100644 --- a/core/jni/android_os_Power.cpp +++ b/core/jni/android_os_Power.cpp @@ -2,19 +2,20 @@ ** ** Copyright 2006, The Android Open Source Project ** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at ** -** http://www.apache.org/licenses/LICENSE-2.0 +** http://www.apache.org/licenses/LICENSE-2.0 ** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and ** limitations under the License. */ +#include "JNIHelp.h" #include "jni.h" #include "android_runtime/AndroidRuntime.h" #include @@ -100,6 +101,7 @@ static void android_os_Power_reboot(JNIEnv *env, jobject clazz, jstring reason) LINUX_REBOOT_CMD_RESTART2, (char*) chars); env->ReleaseStringUTFChars(reason, chars); // In case it fails. } + jniThrowIOException(env, errno); #endif } diff --git a/core/jni/android_view_ViewRoot.cpp b/core/jni/android_view_ViewRoot.cpp index 843d293730bc..9d62d895927e 100644 --- a/core/jni/android_view_ViewRoot.cpp +++ b/core/jni/android_view_ViewRoot.cpp @@ -17,10 +17,10 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include "GraphicsJNI.h" #include "jni.h" diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp index a985c246cfa6..8bacc74dc645 100644 --- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp +++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp @@ -23,8 +23,8 @@ #include #include -#include -#include +#include +#include namespace android { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2e04885397b4..29c1f527b426 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -304,7 +304,7 @@ diff --git a/core/res/res/drawable/ic_menu_login.png b/core/res/res/drawable/ic_menu_login.png new file mode 100644 index 0000000000000000000000000000000000000000..2b856bc2e310e0802339a497ca4863b106b52162 GIT binary patch literal 2466 zcmV;T30?MyP)%=N-o%hch`72YiJD zA0Zc zwZer2LP-K%rHUXx*bom#Ns2x2@C{6S4rgcI=gB#kG`z-zOyiJ6*khkh5B#+ZmGiiGERw&>vvc!?aA<5sKn7v-zU_h=9qx5V85 zq=e7$`n=;eZ`%A(OpVQ!#!E!V6Rr!-QPUVmOo@>|M}UMnLvi~|`^wi|d2N51C6f0W zASJxf^AC?y^ZS||F%o-BDw2qpC*CMH{@9s|v0M~r1w=R$b=Vy)*DJ0DT;qxc(R055 zt_y$2GimGgt-FOW!UVh3PH-kX#}h?Wh@vP=>!~9J!yp0-BIJ6(^+U%Kjx$iQC~x5}1ERK^Y@z2b zt2b3I<&FFW?8Y(?)VBgirOF&+nS!ddReu7?Osm~W?BBJ&I`7$edo@kcBiH@8d$}yj z8yJA9VW*vCMJ54nfI#>^0%8Mp!ZK(PK0(Pb(Nyw7?2?&1x-~A_T z$;kV=BXXpJ37x9NX=f9UOocQHNLS7FnmJk;9;PWO;{bGjIM&G3Z>?SftfeA={<)+HF;Kyq<;dOAc zQJc4HUQaD%GMcFHQ3zg+eyBl9ne_O`)9hKO1eJG?1hN%bnK)SCZx+NfWsZFPXRlX7 zuouna%|sA{z6uK;W@!-)bR3#&hD+&TrMZB!PPa`bC~Q&5BK;J?H)_J`($jInp@yem z!HtNgAJ}Ku{aQ9kW*Wd$@kYgd=+`dfUKc)PGiZ+lf&qe{WsyLA-D=Fb24laGH(KUe&+;#byb)=RWIm7_9&r4%g-4XPdt6a(~PDtvWkUzP2b(}|G~ znvtl+AuzGpAp966{L2vju%`K}KxbeoT<{ruURb%M@-H+_SVl6Mx(Ic~3>33r>(;Fs z@Qdm8>HqYf^^bRMmC`ko(L)Fs$=pMy4jx>_I5qa^#qQY0_J~fwio+@fW{*J%y znDb`&o5%5!v$;9B^VM#(z+LZl6idbRqFLrC7mqDI>}~hXfY!&;TQSWu_4S@HlE1g#4#CtAiK2KLeHGi&y&CjU8qK0s|0(qJ%9i*a7R zm$?(QL^vGwae9tH)YZch6Yt%f?gXF@f(9upaWZ~2{uLl*b6K28m#ri6d0DDERHsKn zcZ3!uo6XjU3^tZiIU9`?Lymz44d*=zh&IGTS=n`n%do`3hI@v%Qvjk}l&))q-zoe$ zPt5y!I21PJa=BA@fv1+3i8Oh2!E+0mX*>X;+2`|_5%e}T;A9)R*fT!U2v(x7v+n`&}o`c^v>I?wyl;&EkUi2T|*i^;`hUyRCNmd6?G?A zj%z)O?9{G7AjQ(`6pcm;-22_mDV8aB6%&JbE|hAyv$ggkskH`i?dDlp$12qBtbIEY zjVwcN#YRs$?4u2dJ7+rAl&>joLg9Nd;16u}dcFFf96XLTX1RTu{p+*`V?CL&z(8@K zrU`yc+v`dH9+o)j_p!icmf(hSB%vmlB@pmuTq|5n;R|7(s#i-@nN{pNw!~ln`o~6c z08M=p0`8RPNf3b-m^;xF>u>LckT1wA#i~)Y;M4j~os%t-ZyOVt0JvjIF$mgUWs#{$ zLLvMJo+Oe9E^rzUZGdnALI#9RWiioM-{>s1u)vO`S@ymI^oQ`AJauw3T9c{N5_HN- zWC6k-=yNB`3qDNCrD(fZotP|5t`oV=nkJh9H(+>A=sM`0Qhz zWA9bHQ{`Oz{NkgLE0H~PA1*ZVX0kqrS4^qrO)SOMY_Hh}S9yU>d9hfGU-s(Dx}(>$4_|aY g1o(dk_^$v10LFO@6=Wo9`~Uy|07*qoM6N<$f?V~HZ~y=R literal 0 HcmV?d00001 diff --git a/core/res/res/drawable/ic_menu_notifications.png b/core/res/res/drawable/ic_menu_notifications.png new file mode 100644 index 0000000000000000000000000000000000000000..866d4e0bd1f3446c693e1ebe9b94a0ef80cfc795 GIT binary patch literal 1771 zcmV;P)ppy8K zL_|1@ZLgSOf~4M>#Ox&5-1&X8Gjp4}-OKI~)WB^RW_NFA=KFo$@B6;_CN1T1ne64O zmF)4W37`p}31E+3TG-3Q#l_H%)9G}J0NoO3yK!MGFNwYk>z54o=9s-I2k_Z4yhJv?4)1h@-xqPK}Ha~le61to8E7MLofBeMpFS3th z7vTACXzK_BK$PkYYzfQ6l0rgw3N3Zv;)P=~vomkP{Q(`Z2o125`!PZC=>}m&<2ISdt`KExNyDjx@nlivMd7H;@?rt?{RWUsIsZq zY4UF6af(cH75q18F)o#unoXJFf}#xx$C)HP(H4@Cq#i=_;msM zxuH`-&w?dx!Y^w8U@HtjYn|}Af9$idOyPFnoDv=c=i1kN+eq_o>p=*{*c?3m3GEMP zOZH*=#)7k;0P0$Wf;!dJqWIqEG>DzCajl-KpI&_p{SHTo2pAgx=l;yp%o(6ViqnSr znwP?&pq#g)s>k=9>p%JySVN{Thxytj{_l$WUX>pZZb4$(Hs0oDf1I@h8xjL(XC6Ch z7=|yr;JaKz0jav@LV7h}lDIDTfXCEJIZySye8zQ6(&8SLa$y5$rp?iT zrv@&=+rLnmSlq|2iI)IC7q5WO0Xv{1JP0M>MJPUwVqJ@6-wyf3-~Y3UW>GLKR5+=; z2*Dv&K*C#rTHr09&W9ohFwp)M&XK67M_w9fLxSKZ6XVy$A9oz*MG&AD^qY#>d0~G!1hO1fR$}oOvI7@3uTz0cCDUz=;4wBW_5?JpipyGQDaSmW$RUn`yhRxrKpv$tTrAuPn#?D5=HgpmQX4iTX zSJMe-h?Zh~kM(_9unQ*u$OjN^dpQX36Ma)BG)3q9_(e8?l-b?W>6qKw>QIa4If4?) z!0j7DgF{aP-^aMa>b?a4=WjCGCdt(Q&hfg&eX1nrhmLs%^2<4^+Zu&PM@-Aa9qdi< zds%GW{zm}B4wF;R*D`Xv2r>Q=Sfn2;@osJ~_m#@F32TrYqtI?&m|=}B?iaFJYZ1UU zWxTI*QvTonhXw@<_=PK?+B#}~rz{`a0?q%CH32jMGyyaL#J&CzU;tSVn>{}*r=uIFEyIQKW?LzB2=vrwCnn3Nw7O$QKE(`aIY3rpIw6nMs5qFFtP9LMU7)2uAp z=kv9W962&LV#J7l?A^OJfUmnSUSWh4F-OhoRU@vEc2Zg~!#p4h48zE8XlN+AeEISY z0AJ9~S@M%$YL>2;A%S8pXxM2CQrFGY5flnCAbs z0oNgTc*l71G8&ZmyA?oo$F0I0!zG04(2o4~N6X9W!&S!0HZJ!SxKHQi*d9sn0 zm*;{PtLuRKyTl{5Z5wy)++mp6@Q5{L%ou})AxW(9$@=x{FW~VPsrG?4jhJWH6bksU zW5>E7L-08{Id1`{^~D<;1j4WZIQR1#mcj2d#>|;BjqdJli_g&*oI4tg^0S#llG7$& zDy1nnc<|sa1w5^jpP%o7Bv|)B5TGaMc962Ma4YUX{szzm06zo6&#=MfbP4HXpinLu z&cV4zf*T)WM1rhlM8FN5YuBz#zIpTJL2R~O%X2{q1A2kLK`My-@a2L93j&%n#znav zK72S9Mp*Ie*|SOjD<(h^>9v*uF7whjP_EK}dZ5?(r%#_g0Fds6)xmQ?6-M^>@#FU| zTC}JEU%ymp64eqE=knm04vatHbNz)27pjmiD*M`hT*rJJn*$=?TL19j!#_uCt`f_O zg?K6DX)P@+ttu-k`%WnZEg##xd$%Ks!wWKLDlI3N9?0a6kYEFZQf_>Dh+#UA5HclM zVWG7IK6>=%Uho9QumL!G`t<1p|95Qq#-Q?Ij*9Xs|@c*s{-Ss7H%b}3?E^>-{?8FgrVDv-F9 zmrC_5%B`;T)4c#He)8nWWU=P<6`M9~Is)&mp_i{*xzf82@b`68r7Ki!NsE9RchG(t zQfKM44rLGHX@7B<;YAPRSeEqxnjHiCqu1-mK@8dX$_rWP?g29GLZk5uNM|W5?vWx5 z;3Ft;|E64@1B-gKwoiKA_~y-<(`_;>FPeQ8egvk!+B16PkL)O!riRZi*~fvgDsn zpFX{RJbU(RJv?C1B8*F^;o09ZfpKrZN-cPXwS08mym@DId9xjErf~jndnSJ|i|u7@z!%iqS8>H8wW>1O&>d8fmCg zmWu6yTeogiisDJWkxh!iBAf$r7piMHysB>7e=mX4v|4jzY9X$wC(2ihD8Z zEm?a8lZ$$MnA>e}G>4CU5vA>nOY&@vefz_*5|5+^-Pm$bAy~{g({P@pM z*!`-gsK_;dWPA|~eD{rN=p(6&(iZ_o&%#N~sMG?MuL1b;S}*Vx0qmzNJ!uhqAP}en zd5g^wOBPv=WJqMroH?fyfoP`vfJ*?D4`D(-C)2}7_e3ZZvat^=?VyXUjskEju{@EO z|23}r_wQH2dlq{QCQUI0@ANqG!AYpzEtQ3xfe?seq92)Ur3-s2CePTO+!?Afyd8+IeK-OnnQajzeM@#5dOLID3qRqaOl+EaacfB>af zOBP0SIf2mS=y`djr$w3O$PsPr<0Lcrf4-CrzQwyaT= z>(Rj+?mFmMe`slGaVKZ7GR5cs>Xz-hm?TSGpZ0w^dRSDC;@zE z+O%m^C|?>C_;4!S&xAk}b;y6PG3i!rxayTFS5oJrs2W$He%T4Pe?(7^2(xnHK1ozA zu{b@YfE$?WtGRRM{tg7%RoFz*1>e`%sHu`oUR_BX*e9Vc(srxOP4OK1d)aC#0 - - - + + + diff --git a/core/res/res/drawable/ratingbar_full_empty.xml b/core/res/res/drawable/ratingbar_full_empty.xml new file mode 100644 index 000000000000..527efc3b9fef --- /dev/null +++ b/core/res/res/drawable/ratingbar_full_empty.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/core/res/res/drawable/ratingbar_full_filled.xml b/core/res/res/drawable/ratingbar_full_filled.xml new file mode 100644 index 000000000000..e95ba6d9c107 --- /dev/null +++ b/core/res/res/drawable/ratingbar_full_filled.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/core/res/res/drawable/seek_thumb.xml b/core/res/res/drawable/seek_thumb.xml new file mode 100644 index 000000000000..7fe51b382c08 --- /dev/null +++ b/core/res/res/drawable/seek_thumb.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/core/res/res/drawable/seek_thumb.png b/core/res/res/drawable/seek_thumb_normal.png similarity index 100% rename from core/res/res/drawable/seek_thumb.png rename to core/res/res/drawable/seek_thumb_normal.png diff --git a/core/res/res/drawable/seek_thumb_pressed.png b/core/res/res/drawable/seek_thumb_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..dbaae91817481e18d78df077f036de9d1d1fbd88 GIT binary patch literal 970 zcmV;*12z1KP)!OCVMvMyv9z|b3w99^QdmUPFl%nI>zmp4 zX5W6@2gw}oW@dM1-tYb1n>RB-nx-&KS$wp3Er$3)2q9n%4fXEk`%OK`e8HpuQ1Jtt zc&UH6e=iKfk>Bq9*aJlIa(!d{h3%&!lr#beuyA|+;r!K3yYp?~@dEU&^Z-otd$t}F zw&NIfKJLKom)$oA;+e%jQUTMdQ;elW@6MnoocPJ$_5galo|~K%@~;2{--kG&+wDSh zF?u?(mNcvafL;dl+zyy=gB9F~|Hj_(*q{;=|wTdBx; zl{4c;SXNZVjs>NM`W!xOXK&yLWk`mmbul1W@G)TNWaT=QW}OR>~u0H?x$pHwbAQ|(vUmVp_4asW25IRDE;6qn*62LFw8$2bNQOi|cE z%ZOW&ag^71b{$~XqH?Ne3qdu4OtgGym*R{0V>wHR=dn|$&D2;K$hutKMaR36DDWgxAQ z%D(Txlrp5x8<}|ti3E{00gwuhNsQ-7)o{l&D-p0VgDQ6j`9~0s`833b(>fO<^dawr z2~LzaMJgPdt_y8DrX3#Aag4J=S{nlv5gk$z@gXzt9VfjJwWG$Bz)Q}zL%%6~#sA;L z!ce%Q8iXYfDY0NMxZa_^@4A$%;(T+WyMVkEan7)E@aN#u*2dOn;Z7G1@@`NGLVB4B zSD4k;s~_+vwzmIkKT{Aop26LasOC0bOei0`CF+lekEDDt38uLlE!OCVMvMyv9z|b3w99^QdmUPFl%nI>zmp4 zX5W6@2gw}oW@dM1-tYb1n>RB-nx-&KS$wp3Er$3)2q9n%4fXEk`%OK`e8HpuQ1Jtt zc&UH6e=iKfk>Bq9*aJlIa(!d{h3%&!lr#beuyA|+;r!K3yYp?~@dEU&^Z-otd$t}F zw&NIfKJLKom)$oA;+e%jQUTMdQ;elW@6MnoocPJ$_5galo|~K%@~;2{--kG&+wDSh zF?u?(mNcvafL;dl+zyy=gB9F~|Hj_(*q{;=|wTdBx; zl{4c;SXNZVjs>NM`W!xOXK&yLWk`mmbul1W@G)TNWaT=QW}OR>~u0H?x$pHwbAQ|(vUmVp_4asW25IRDE;6qn*62LFw8$2bNQOi|cE z%ZOW&ag^71b{$~XqH?Ne3qdu4OtgGym*R{0V>wHR=dn|$&D2;K$hutKMaR36DDWgxAQ z%D(Txlrp5x8<}|ti3E{00gwuhNsQ-7)o{l&D-p0VgDQ6j`9~0s`833b(>fO<^dawr z2~LzaMJgPdt_y8DrX3#Aag4J=S{nlv5gk$z@gXzt9VfjJwWG$Bz)Q}zL%%6~#sA;L z!ce%Q8iXYfDY0NMxZa_^@4A$%;(T+WyMVkEan7)E@aN#u*2dOn;Z7G1@@`NGLVB4B zSD4k;s~_+vwzmIkKT{Aop26LasOC0bOei0`CF+lekEDDt38uLlE + + + - +