summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
authorJean-Baptiste Queru <jbq@google.com>2009-03-18 11:33:14 -0700
committerJean-Baptiste Queru <jbq@google.com>2009-03-18 11:33:14 -0700
commit2a73de7b21a89aa2ba4c254d28658b49793425b2 (patch)
treeded5bcd581464b4174d81c373044b6d36eee58d2 /core/java
parent42e48026b21a962e5bf40344d738665ecbd9d74d (diff)
parentba87e3e6c985e7175152993b5efcc7dd2f0e1c93 (diff)
downloadframeworks_base-2a73de7b21a89aa2ba4c254d28658b49793425b2.zip
frameworks_base-2a73de7b21a89aa2ba4c254d28658b49793425b2.tar.gz
frameworks_base-2a73de7b21a89aa2ba4c254d28658b49793425b2.tar.bz2
Merge commit 'remotes/korg/cupcake' into merge
Conflicts: core/java/android/view/animation/TranslateAnimation.java core/jni/Android.mk core/res/res/values-en-rGB/strings.xml libs/audioflinger/AudioFlinger.cpp libs/surfaceflinger/LayerScreenshot.cpp packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/Activity.java105
-rw-r--r--core/java/android/app/ActivityManager.java93
-rw-r--r--core/java/android/app/ActivityManagerNative.java89
-rw-r--r--core/java/android/app/ActivityThread.java202
-rw-r--r--core/java/android/app/ApplicationContext.java60
-rw-r--r--core/java/android/app/ApplicationThreadNative.java18
-rw-r--r--core/java/android/app/Dialog.java4
-rw-r--r--core/java/android/app/ExpandableListActivity.java23
-rw-r--r--core/java/android/app/IActivityManager.java18
-rw-r--r--core/java/android/app/IApplicationThread.java2
-rw-r--r--core/java/android/app/Instrumentation.java2
-rw-r--r--core/java/android/app/IntentService.java74
-rw-r--r--core/java/android/app/LauncherActivity.java278
-rw-r--r--core/java/android/app/ListActivity.java26
-rw-r--r--core/java/android/app/Notification.java9
-rw-r--r--core/java/android/app/NotificationManager.java4
-rw-r--r--core/java/android/app/PendingIntent.java13
-rw-r--r--core/java/android/app/SearchDialog.java193
-rw-r--r--core/java/android/app/SearchManager.java32
-rw-r--r--core/java/android/app/Service.java26
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java248
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java312
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java320
-rwxr-xr-xcore/java/android/appwidget/AppWidgetProvider.java154
-rw-r--r--core/java/android/appwidget/AppWidgetProviderInfo.aidl (renamed from core/java/android/os/HandlerInterface.java)30
-rw-r--r--core/java/android/appwidget/AppWidgetProviderInfo.java168
-rw-r--r--core/java/android/appwidget/package.html136
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java12
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java101
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java29
-rw-r--r--core/java/android/bluetooth/BluetoothIntent.java32
-rw-r--r--core/java/android/bluetooth/HeadsetBase.java33
-rw-r--r--core/java/android/bluetooth/IBluetoothDevice.aidl12
-rw-r--r--core/java/android/bluetooth/ScoSocket.java19
-rw-r--r--core/java/android/content/AsyncQueryHandler.java14
-rw-r--r--core/java/android/content/BroadcastReceiver.java32
-rw-r--r--core/java/android/content/ContentProvider.java139
-rw-r--r--core/java/android/content/ContentProviderNative.java43
-rw-r--r--core/java/android/content/ContentResolver.java320
-rw-r--r--core/java/android/content/ContentServiceNative.java7
-rw-r--r--core/java/android/content/Context.java10
-rw-r--r--core/java/android/content/IContentProvider.java4
-rw-r--r--core/java/android/content/Intent.java155
-rw-r--r--core/java/android/content/SyncManager.java42
-rw-r--r--core/java/android/content/TempProviderSyncAdapter.java24
-rw-r--r--core/java/android/content/package.html7
-rwxr-xr-xcore/java/android/content/pm/ConfigurationInfo.java5
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl7
-rw-r--r--core/java/android/content/pm/PackageInfo.java18
-rw-r--r--core/java/android/content/pm/PackageManager.java35
-rw-r--r--core/java/android/content/pm/PackageParser.java12
-rw-r--r--core/java/android/content/res/AssetFileDescriptor.java271
-rw-r--r--core/java/android/content/res/AssetManager.java4
-rw-r--r--core/java/android/content/res/ColorStateList.java36
-rw-r--r--core/java/android/content/res/Resources.java256
-rw-r--r--core/java/android/content/res/StringBlock.java62
-rw-r--r--core/java/android/content/res/TypedArray.java28
-rw-r--r--core/java/android/database/DatabaseUtils.java1
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java57
-rw-r--r--core/java/android/database/sqlite/SQLiteOpenHelper.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java20
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java56
-rw-r--r--core/java/android/database/sqlite/package.html2
-rw-r--r--core/java/android/emoji/EmojiFactory.java273
-rw-r--r--core/java/android/gadget/GadgetHost.java72
-rw-r--r--core/java/android/gadget/GadgetHostView.java102
-rw-r--r--core/java/android/gadget/GadgetInfo.java126
-rw-r--r--core/java/android/gadget/GadgetManager.java180
-rw-r--r--core/java/android/gadget/package.html4
-rw-r--r--core/java/android/hardware/Camera.java18
-rw-r--r--core/java/android/hardware/GeomagneticField.java409
-rw-r--r--core/java/android/hardware/ISensorService.aidl2
-rw-r--r--core/java/android/hardware/SensorManager.java33
-rw-r--r--core/java/android/inputmethodservice/AbstractInputMethodService.java10
-rw-r--r--core/java/android/inputmethodservice/ExtractButton.java30
-rw-r--r--core/java/android/inputmethodservice/ExtractEditText.java107
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java9
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java98
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java967
-rwxr-xr-xcore/java/android/inputmethodservice/Keyboard.java67
-rwxr-xr-xcore/java/android/inputmethodservice/KeyboardView.java318
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java4
-rw-r--r--core/java/android/net/ConnectivityManager.java51
-rw-r--r--core/java/android/net/IConnectivityManager.aidl4
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java102
-rw-r--r--core/java/android/net/Uri.java9
-rw-r--r--core/java/android/net/UrlQuerySanitizer.java126
-rw-r--r--core/java/android/net/http/AndroidHttpClient.java68
-rw-r--r--core/java/android/net/http/CertificateChainValidator.java154
-rw-r--r--core/java/android/net/http/RequestHandle.java10
-rw-r--r--core/java/android/os/BatteryStats.java425
-rw-r--r--core/java/android/os/Binder.java44
-rw-r--r--core/java/android/os/Build.java3
-rw-r--r--core/java/android/os/Debug.java26
-rw-r--r--core/java/android/os/Environment.java12
-rw-r--r--core/java/android/os/IBinder.java11
-rw-r--r--core/java/android/os/ICheckinService.aidl9
-rw-r--r--core/java/android/os/IMountService.aidl27
-rw-r--r--core/java/android/os/INetStatService.aidl15
-rw-r--r--core/java/android/os/IPowerManager.aidl1
-rw-r--r--core/java/android/os/NetStat.java224
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java18
-rw-r--r--core/java/android/os/ResultReceiver.aidl20
-rw-r--r--core/java/android/os/ResultReceiver.java130
-rw-r--r--core/java/android/package.html2
-rw-r--r--core/java/android/pim/ICalendar.java15
-rw-r--r--core/java/android/pim/RecurrenceSet.java1
-rw-r--r--core/java/android/preference/Preference.java7
-rw-r--r--core/java/android/preference/PreferenceActivity.java7
-rw-r--r--core/java/android/preference/PreferenceGroup.java4
-rw-r--r--core/java/android/preference/PreferenceGroupAdapter.java5
-rw-r--r--core/java/android/preference/PreferenceScreen.java1
-rw-r--r--core/java/android/preference/VolumePreference.java7
-rw-r--r--core/java/android/provider/Browser.java12
-rw-r--r--core/java/android/provider/Checkin.java36
-rw-r--r--core/java/android/provider/Contacts.java75
-rw-r--r--core/java/android/provider/Gmail.java112
-rw-r--r--core/java/android/provider/Im.java137
-rw-r--r--core/java/android/provider/MediaStore.java74
-rw-r--r--core/java/android/provider/Settings.java168
-rw-r--r--core/java/android/provider/Sync.java37
-rw-r--r--core/java/android/provider/Telephony.java5
-rw-r--r--core/java/android/provider/UserDictionary.java23
-rw-r--r--core/java/android/provider/package.html2
-rw-r--r--core/java/android/server/BluetoothA2dpService.java125
-rw-r--r--core/java/android/server/BluetoothDeviceService.java521
-rw-r--r--core/java/android/server/BluetoothEventLoop.java196
-rw-r--r--core/java/android/server/checkin/CheckinProvider.java388
-rw-r--r--core/java/android/server/checkin/FallbackCheckinService.java49
-rw-r--r--core/java/android/server/checkin/package.html5
-rw-r--r--core/java/android/server/search/SearchableInfo.java112
-rw-r--r--core/java/android/speech/RecognizerIntent.java73
-rw-r--r--core/java/android/speech/srec/MicrophoneInputStream.java12
-rw-r--r--core/java/android/speech/srec/package.html1
-rw-r--r--core/java/android/text/Annotation.java26
-rw-r--r--core/java/android/text/Html.java36
-rw-r--r--core/java/android/text/InputType.java46
-rw-r--r--core/java/android/text/NoCopySpan.java31
-rw-r--r--core/java/android/text/ParcelableSpan.java31
-rw-r--r--core/java/android/text/Selection.java6
-rw-r--r--core/java/android/text/SpanWatcher.java2
-rw-r--r--core/java/android/text/SpannableStringBuilder.java4
-rw-r--r--core/java/android/text/Spanned.java8
-rw-r--r--core/java/android/text/StaticLayout.java78
-rw-r--r--core/java/android/text/Styled.java249
-rw-r--r--core/java/android/text/TextUtils.java265
-rw-r--r--core/java/android/text/TextWatcher.java2
-rw-r--r--core/java/android/text/format/DateFormat.java29
-rw-r--r--core/java/android/text/format/DateUtils.java216
-rw-r--r--core/java/android/text/format/Time.java1
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java27
-rw-r--r--core/java/android/text/method/BaseKeyListener.java33
-rw-r--r--core/java/android/text/method/KeyListener.java7
-rw-r--r--core/java/android/text/method/LinkMovementMethod.java2
-rw-r--r--core/java/android/text/method/MetaKeyKeyListener.java30
-rw-r--r--core/java/android/text/method/MovementMethod.java8
-rw-r--r--core/java/android/text/method/NumberKeyListener.java6
-rw-r--r--core/java/android/text/method/PasswordTransformationMethod.java10
-rw-r--r--core/java/android/text/method/QwertyKeyListener.java37
-rw-r--r--core/java/android/text/method/ScrollingMovementMethod.java24
-rw-r--r--core/java/android/text/method/SingleLineTransformationMethod.java12
-rw-r--r--core/java/android/text/method/TextKeyListener.java19
-rw-r--r--core/java/android/text/method/Touch.java5
-rw-r--r--core/java/android/text/style/AbsoluteSizeSpan.java24
-rw-r--r--core/java/android/text/style/AlignmentSpan.java28
-rw-r--r--core/java/android/text/style/BackgroundColorSpan.java24
-rw-r--r--core/java/android/text/style/BulletSpan.java42
-rw-r--r--core/java/android/text/style/ForegroundColorSpan.java24
-rw-r--r--core/java/android/text/style/ImageSpan.java8
-rw-r--r--core/java/android/text/style/LeadingMarginSpan.java29
-rw-r--r--core/java/android/text/style/QuoteSpan.java29
-rw-r--r--core/java/android/text/style/RelativeSizeSpan.java24
-rw-r--r--core/java/android/text/style/ScaleXSpan.java24
-rw-r--r--core/java/android/text/style/StrikethroughSpan.java22
-rw-r--r--core/java/android/text/style/StyleSpan.java23
-rw-r--r--core/java/android/text/style/SubscriptSpan.java22
-rw-r--r--core/java/android/text/style/SuperscriptSpan.java22
-rw-r--r--core/java/android/text/style/TextAppearanceSpan.java70
-rw-r--r--core/java/android/text/style/TypefaceSpan.java23
-rw-r--r--core/java/android/text/style/URLSpan.java31
-rw-r--r--core/java/android/text/style/UnderlineSpan.java22
-rw-r--r--core/java/android/text/util/Regex.java12
-rw-r--r--core/java/android/text/util/Rfc822Validator.java12
-rw-r--r--core/java/android/util/DisplayMetrics.java27
-rw-r--r--core/java/android/util/SparseIntArray.java13
-rw-r--r--core/java/android/util/TypedValue.java23
-rw-r--r--core/java/android/view/FocusFinder.java2
-rw-r--r--core/java/android/view/GestureDetector.java264
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java42
-rw-r--r--core/java/android/view/IWindowManager.aidl3
-rw-r--r--core/java/android/view/IWindowSession.aidl3
-rw-r--r--core/java/android/view/KeyCharacterMap.java24
-rw-r--r--core/java/android/view/KeyEvent.java80
-rwxr-xr-xcore/java/android/view/OrientationEventListener.java174
-rw-r--r--core/java/android/view/OrientationListener.java99
-rw-r--r--core/java/android/view/RemotableViewMethod.java35
-rw-r--r--core/java/android/view/SurfaceHolder.java2
-rw-r--r--core/java/android/view/TouchDelegate.java12
-rw-r--r--core/java/android/view/View.java483
-rw-r--r--core/java/android/view/ViewConfiguration.java231
-rw-r--r--core/java/android/view/ViewDebug.java376
-rw-r--r--core/java/android/view/ViewGroup.java73
-rw-r--r--core/java/android/view/ViewRoot.java252
-rw-r--r--core/java/android/view/ViewTreeObserver.java66
-rw-r--r--core/java/android/view/VolumePanel.java122
-rw-r--r--core/java/android/view/Window.java15
-rw-r--r--core/java/android/view/WindowManager.java55
-rw-r--r--core/java/android/view/WindowManagerPolicy.java49
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java196
-rw-r--r--core/java/android/view/animation/AlphaAnimation.java2
-rw-r--r--core/java/android/view/animation/Animation.java108
-rw-r--r--core/java/android/view/animation/AnimationSet.java100
-rw-r--r--core/java/android/view/animation/LayoutAnimationController.java156
-rw-r--r--core/java/android/view/animation/RotateAnimation.java2
-rw-r--r--core/java/android/view/animation/ScaleAnimation.java2
-rw-r--r--core/java/android/view/animation/TranslateAnimation.java2
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java525
-rw-r--r--core/java/android/view/inputmethod/DefaultInputMethod.java239
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java157
-rw-r--r--core/java/android/view/inputmethod/ExtractedText.java27
-rw-r--r--core/java/android/view/inputmethod/ExtractedTextRequest.java9
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java108
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java74
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java44
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java17
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java757
-rw-r--r--core/java/android/view/inputmethod/InputMethodSession.java12
-rw-r--r--core/java/android/view/inputmethod/MutableInputConnectionWrapper.java38
-rw-r--r--core/java/android/webkit/BrowserFrame.java25
-rw-r--r--core/java/android/webkit/ByteArrayBuilder.java8
-rw-r--r--core/java/android/webkit/CacheManager.java3
-rw-r--r--core/java/android/webkit/CallbackProxy.java14
-rw-r--r--core/java/android/webkit/CookieManager.java16
-rw-r--r--core/java/android/webkit/FileLoader.java8
-rw-r--r--core/java/android/webkit/FrameLoader.java14
-rw-r--r--core/java/android/webkit/LoadListener.java239
-rw-r--r--core/java/android/webkit/MimeTypeMap.java1
-rw-r--r--core/java/android/webkit/PluginContentLoader.java96
-rw-r--r--core/java/android/webkit/PluginData.java116
-rw-r--r--core/java/android/webkit/TextDialog.java181
-rw-r--r--core/java/android/webkit/UrlInterceptHandler.java15
-rw-r--r--core/java/android/webkit/UrlInterceptRegistry.java34
-rw-r--r--core/java/android/webkit/WebSettings.java25
-rw-r--r--core/java/android/webkit/WebView.java746
-rw-r--r--core/java/android/webkit/WebViewCore.java94
-rw-r--r--core/java/android/webkit/WebViewDatabase.java47
-rw-r--r--core/java/android/webkit/gears/DesktopAndroid.java6
-rw-r--r--core/java/android/webkit/gears/GearsPluginSettings.java95
-rw-r--r--core/java/android/webkit/gears/HtmlDialogAndroid.java174
-rw-r--r--core/java/android/webkit/gears/HttpRequestAndroid.java745
-rw-r--r--core/java/android/webkit/gears/IGearsDialogService.java107
-rw-r--r--core/java/android/webkit/gears/UrlInterceptHandlerGears.java164
-rw-r--r--core/java/android/widget/AbsListView.java180
-rw-r--r--core/java/android/widget/AbsSeekBar.java45
-rw-r--r--core/java/android/widget/Adapter.java2
-rw-r--r--core/java/android/widget/AnalogClock.java19
-rw-r--r--core/java/android/widget/ArrayAdapter.java13
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java209
-rw-r--r--core/java/android/widget/BaseAdapter.java4
-rw-r--r--core/java/android/widget/Chronometer.java67
-rw-r--r--core/java/android/widget/CompoundButton.java7
-rw-r--r--core/java/android/widget/CursorAdapter.java20
-rw-r--r--core/java/android/widget/CursorFilter.java5
-rw-r--r--core/java/android/widget/DatePicker.java27
-rw-r--r--core/java/android/widget/ExpandableListView.java16
-rw-r--r--core/java/android/widget/FastScroller.java39
-rw-r--r--core/java/android/widget/Filter.java31
-rw-r--r--core/java/android/widget/FrameLayout.java2
-rw-r--r--core/java/android/widget/Gallery.java8
-rw-r--r--core/java/android/widget/GridView.java33
-rw-r--r--core/java/android/widget/HorizontalScrollView.java1197
-rw-r--r--core/java/android/widget/ImageView.java16
-rw-r--r--core/java/android/widget/LinearLayout.java17
-rw-r--r--core/java/android/widget/ListView.java161
-rw-r--r--core/java/android/widget/MediaController.java1
-rw-r--r--core/java/android/widget/MultiAutoCompleteTextView.java4
-rw-r--r--core/java/android/widget/PopupWindow.java244
-rw-r--r--core/java/android/widget/ProgressBar.java9
-rw-r--r--core/java/android/widget/RadioGroup.java17
-rw-r--r--core/java/android/widget/RelativeLayout.java29
-rw-r--r--core/java/android/widget/RemoteViews.aidl (renamed from core/java/android/gadget/GadgetInfo.aidl)4
-rw-r--r--core/java/android/widget/RemoteViews.java693
-rw-r--r--core/java/android/widget/ResourceCursorAdapter.java22
-rw-r--r--core/java/android/widget/ScrollBarDrawable.java8
-rw-r--r--core/java/android/widget/ScrollView.java44
-rw-r--r--core/java/android/widget/Scroller.java20
-rw-r--r--core/java/android/widget/SimpleAdapter.java27
-rw-r--r--core/java/android/widget/SlidingDrawer.java (renamed from core/java/com/android/internal/widget/SlidingDrawer.java)98
-rw-r--r--core/java/android/widget/TabHost.java6
-rw-r--r--core/java/android/widget/TextView.java1403
-rw-r--r--core/java/android/widget/VideoView.java33
-rw-r--r--core/java/android/widget/ViewAnimator.java53
-rw-r--r--core/java/android/widget/ViewFlipper.java4
-rw-r--r--core/java/android/widget/ZoomButton.java4
-rw-r--r--core/java/android/widget/ZoomButtonsController.java848
-rw-r--r--core/java/android/widget/ZoomControls.java8
-rw-r--r--core/java/com/android/internal/app/ExternalMediaFormatActivity.java114
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl8
-rwxr-xr-x[-rw-r--r--]core/java/com/android/internal/app/IUsageStats.aidl (renamed from core/java/com/android/internal/os/HandlerHelper.java)29
-rw-r--r--core/java/com/android/internal/app/UsbStorageStopActivity.java123
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetHost.aidl (renamed from core/java/com/android/internal/gadget/IGadgetService.aidl)15
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl50
-rw-r--r--core/java/com/android/internal/appwidget/package.html (renamed from core/java/com/android/internal/gadget/package.html)0
-rw-r--r--core/java/com/android/internal/logging/AndroidConfig.java11
-rw-r--r--core/java/com/android/internal/logging/AndroidHandler.java69
-rw-r--r--core/java/com/android/internal/net/DbSSLSessionCache.java289
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java929
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java17
-rw-r--r--core/java/com/android/internal/os/HandlerThread.java91
-rw-r--r--core/java/com/android/internal/os/IResultReceiver.aidl25
-rwxr-xr-xcore/java/com/android/internal/os/PkgUsageStats.aidl20
-rwxr-xr-xcore/java/com/android/internal/os/PkgUsageStats.java60
-rw-r--r--core/java/com/android/internal/os/RecoverySystem.java128
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java83
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java203
-rw-r--r--core/java/com/android/internal/view/IInputContext.aidl16
-rw-r--r--core/java/com/android/internal/view/IInputMethod.aidl9
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl15
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl2
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java48
-rw-r--r--core/java/com/android/internal/view/menu/ExpandedMenuView.java5
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuItemView.java5
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java4
-rw-r--r--core/java/com/android/internal/widget/EditStyledText.java931
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java328
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java42
-rw-r--r--core/java/com/android/internal/widget/NumberPicker.java99
-rw-r--r--core/java/com/android/internal/widget/TextProgressBar.java180
-rw-r--r--core/java/com/google/android/gdata/client/AndroidGDataClient.java21
-rw-r--r--core/java/com/google/android/net/GoogleHttpClient.java102
-rw-r--r--core/java/com/google/android/net/NetStats.java47
-rw-r--r--core/java/com/google/android/net/NetworkStatsEntity.java85
-rw-r--r--core/java/com/google/android/net/SSLClientSessionCacheFactory.java62
-rw-r--r--core/java/com/google/android/util/GoogleWebContentHelper.java2
-rw-r--r--core/java/com/google/android/util/SimplePullParser.java25
336 files changed, 22655 insertions, 8250 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4dc4b6a..849a37d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -53,6 +53,7 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewManager;
import android.view.Window;
import android.view.WindowManager;
import android.view.ContextMenu.ContextMenuInfo;
@@ -93,11 +94,11 @@ import java.util.HashMap;
* {@link android.R.styleable#AndroidManifestActivity &lt;activity&gt;}
* declaration in their package's <code>AndroidManifest.xml</code>.</p>
*
- * <p>The Activity class is an important part of an
- * <a href="{@docRoot}intro/lifecycle.html">application's overall lifecycle</a>,
+ * <p>The Activity class is an important part of an application's overall lifecycle,
* and the way activities are launched and put together is a fundamental
- * part of the platform's
- * <a href="{@docRoot}intro/appmodel.html">application model</a>.</p>
+ * part of the platform's application model. For a detailed perspective on the structure of
+ * Android applications and lifecycles, please read the <em>Dev Guide</em> document on
+ * <a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a>.</p>
*
* <p>Topics covered here:
* <ol>
@@ -527,7 +528,7 @@ import java.util.HashMap;
* {@link android.R.styleable#AndroidManifestUsesPermission &lt;uses-permission&gt;}
* element in their own manifest to be able to start that activity.
*
- * <p>See the <a href="{@docRoot}devel/security.html">Security Model</a>
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
* document for more information on permissions and security in general.
*
* <a name="ProcessLifecycle"></a>
@@ -629,6 +630,9 @@ public class Activity extends ContextThemeWrapper
private WindowManager mWindowManager;
/*package*/ View mDecor = null;
+ /*package*/ boolean mWindowAdded = false;
+ /*package*/ boolean mVisibleFromServer = false;
+ /*package*/ boolean mVisibleFromClient = true;
private CharSequence mTitle;
private int mTitleColor = 0;
@@ -779,6 +783,8 @@ public class Activity extends ContextThemeWrapper
* @see #onPostCreate
*/
protected void onCreate(Bundle savedInstanceState) {
+ mVisibleFromClient = mWindow.getWindowStyle().getBoolean(
+ com.android.internal.R.styleable.Window_windowNoDisplay, true);
mCalled = true;
}
@@ -884,9 +890,9 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Called after {@link #onCreate} or {@link #onStop} when the current
- * activity is now being displayed to the user. It will
- * be followed by {@link #onRestart}.
+ * Called after {@link #onCreate} &mdash; or after {@link #onRestart} when
+ * the activity had been stopped, but is now again being displayed to the
+ * user. It will be followed by {@link #onResume}.
*
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
@@ -901,9 +907,9 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Called after {@link #onStart} when the current activity is being
+ * Called after {@link #onStop} when the current activity is being
* re-displayed to the user (the user has navigated back to it). It will
- * be followed by {@link #onResume}.
+ * be followed by {@link #onStart} and then {@link #onResume}.
*
* <p>For activities that are using raw {@link Cursor} objects (instead of
* creating them through
@@ -917,6 +923,7 @@ public class Activity extends ContextThemeWrapper
* thrown.</em></p>
*
* @see #onStop
+ * @see #onStart
* @see #onResume
*/
protected void onRestart() {
@@ -1134,12 +1141,19 @@ public class Activity extends ContextThemeWrapper
/**
* Called as part of the activity lifecycle when an activity is about to go
* into the background as the result of user choice. For example, when the
- * user presses the Home key, {@link #onUserLeaving} will be called, but
+ * user presses the Home key, {@link #onUserLeaveHint} will be called, but
* when an incoming phone call causes the in-call Activity to be automatically
- * brought to the foreground, {@link #onUserLeaving} will not be called on
- * the activity being interrupted.
+ * brought to the foreground, {@link #onUserLeaveHint} will not be called on
+ * the activity being interrupted. In cases when it is invoked, this method
+ * is called right before the activity's {@link #onPause} callback.
+ *
+ * <p>This callback and {@link #onUserInteraction} are intended to help
+ * activities manage status bar notifications intelligently; specifically,
+ * for helping activities determine the proper time to cancel a notfication.
+ *
+ * @see #onUserInteraction()
*/
- protected void onUserLeaving() {
+ protected void onUserLeaveHint() {
}
/**
@@ -1207,7 +1221,7 @@ public class Activity extends ContextThemeWrapper
/**
* Called when you are no longer visible to the user. You will next
- * receive either {@link #onStart}, {@link #onDestroy}, or nothing,
+ * receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
* depending on later user activity.
*
* <p>Note that this method may never be called, in low memory situations
@@ -1443,7 +1457,6 @@ public class Activity extends ContextThemeWrapper
* @return The Cursor that was returned by query().
*
* @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
- * @see #managedCommitUpdates
* @see #startManagingCursor
* @hide
*/
@@ -1475,7 +1488,6 @@ public class Activity extends ContextThemeWrapper
* @return The Cursor that was returned by query().
*
* @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
- * @see #managedCommitUpdates
* @see #startManagingCursor
*/
public final Cursor managedQuery(Uri uri,
@@ -1863,6 +1875,28 @@ public class Activity extends ContextThemeWrapper
return false;
}
+ /**
+ * Called whenever a key, touch, or trackball event is dispatched to the
+ * activity. Implement this method if you wish to know that the user has
+ * interacted with the device in some way while your activity is running.
+ * This callback and {@link #onUserLeaveHint} are intended to help
+ * activities manage status bar notifications intelligently; specifically,
+ * for helping activities determine the proper time to cancel a notfication.
+ *
+ * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
+ * be accompanied by calls to {@link #onUserInteraction}. This
+ * ensures that your activity will be told of relevant user activity such
+ * as pulling down the notification pane and touching an item there.
+ *
+ * <p>Note that this callback will be invoked for the touch down action
+ * that begins a touch gesture, but may not be invoked for the touch-moved
+ * and touch-up actions that follow.
+ *
+ * @see #onUserLeaveHint()
+ */
+ public void onUserInteraction() {
+ }
+
public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
// Update window manager if: we have a view, that view is
// attached to its parent (which will be a RootView), and
@@ -1935,6 +1969,7 @@ public class Activity extends ContextThemeWrapper
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
+ onUserInteraction();
if (getWindow().superDispatchKeyEvent(event)) {
return true;
}
@@ -1952,6 +1987,9 @@ public class Activity extends ContextThemeWrapper
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ onUserInteraction();
+ }
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
@@ -1969,6 +2007,7 @@ public class Activity extends ContextThemeWrapper
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTrackballEvent(MotionEvent ev) {
+ onUserInteraction();
if (getWindow().superDispatchTrackballEvent(ev)) {
return true;
}
@@ -2865,6 +2904,35 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Control whether this activity's main window is visible. This is intended
+ * only for the special case of an activity that is not going to show a
+ * UI itself, but can't just finish prior to onResume() because it needs
+ * to wait for a service binding or such. Setting this to false allows
+ * you to prevent your UI from being shown during that time.
+ *
+ * <p>The default value for this is taken from the
+ * {@link android.R.attr#windowNoDisplay} attribute of the activity's theme.
+ */
+ public void setVisible(boolean visible) {
+ if (mVisibleFromClient != visible) {
+ mVisibleFromClient = visible;
+ if (mVisibleFromServer) {
+ if (visible) makeVisible();
+ else mDecor.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+
+ void makeVisible() {
+ if (!mWindowAdded) {
+ ViewManager wm = getWindowManager();
+ wm.addView(mDecor, getWindow().getAttributes());
+ mWindowAdded = true;
+ }
+ mDecor.setVisibility(View.VISIBLE);
+ }
+
+ /**
* Check to see whether this activity is in the process of finishing,
* either because you called {@link #finish} on it or someone else
* has requested that it finished. This is often used in
@@ -3482,7 +3550,8 @@ public class Activity extends ContextThemeWrapper
}
final void performUserLeaving() {
- onUserLeaving();
+ onUserInteraction();
+ onUserLeaveHint();
}
final void performStop() {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f9b9221..07520c9d 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -19,16 +19,14 @@ package android.app;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.graphics.Bitmap;
import android.os.RemoteException;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Parcelable.Creator;
import android.text.TextUtils;
-import android.util.Log;
import java.util.List;
/**
@@ -617,7 +615,59 @@ public class ActivityManager {
public String pkgList[];
+ /**
+ * Constant for {@link #importance}: this process is running the
+ * foreground UI.
+ */
+ public static final int IMPORTANCE_FOREGROUND = 100;
+
+ /**
+ * Constant for {@link #importance}: this process is running something
+ * that is considered to be actively visible to the user.
+ */
+ public static final int IMPORTANCE_VISIBLE = 200;
+
+ /**
+ * Constant for {@link #importance}: this process is contains services
+ * that should remain running.
+ */
+ public static final int IMPORTANCE_SERVICE = 300;
+
+ /**
+ * Constant for {@link #importance}: this process process contains
+ * background code that is expendable.
+ */
+ public static final int IMPORTANCE_BACKGROUND = 400;
+
+ /**
+ * Constant for {@link #importance}: this process is empty of any
+ * actively running code.
+ */
+ public static final int IMPORTANCE_EMPTY = 500;
+
+ /**
+ * The relative importance level that the system places on this
+ * process. May be one of {@link #IMPORTANCE_FOREGROUND},
+ * {@link #IMPORTANCE_VISIBLE}, {@link #IMPORTANCE_SERVICE},
+ * {@link #IMPORTANCE_BACKGROUND}, or {@link #IMPORTANCE_EMPTY}. These
+ * constants are numbered so that "more important" values are always
+ * smaller than "less important" values.
+ */
+ public int importance;
+
+ /**
+ * An additional ordering within a particular {@link #importance}
+ * category, providing finer-grained information about the relative
+ * utility of processes within a category. This number means nothing
+ * except that a smaller values are more recently used (and thus
+ * more important). Currently an LRU value is only maintained for
+ * the {@link #IMPORTANCE_BACKGROUND} category, though others may
+ * be maintained in the future.
+ */
+ public int lru;
+
public RunningAppProcessInfo() {
+ importance = IMPORTANCE_FOREGROUND;
}
public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) {
@@ -634,12 +684,16 @@ public class ActivityManager {
dest.writeString(processName);
dest.writeInt(pid);
dest.writeStringArray(pkgList);
+ dest.writeInt(importance);
+ dest.writeInt(lru);
}
public void readFromParcel(Parcel source) {
processName = source.readString();
pid = source.readInt();
pkgList = source.readStringArray();
+ importance = source.readInt();
+ lru = source.readInt();
}
public static final Creator<RunningAppProcessInfo> CREATOR =
@@ -671,4 +725,37 @@ public class ActivityManager {
return null;
}
}
+
+ /**
+ * Have the system perform a force stop of everything associated with
+ * the given application package. All processes that share its uid
+ * will be killed, all services it has running stopped, all activities
+ * removed, etc. In addition, a {@link Intent#ACTION_PACKAGE_RESTARTED}
+ * broadcast will be sent, so that any of its registered alarms can
+ * be stopped, notifications removed, etc.
+ *
+ * <p>You must hold the permission
+ * {@link android.Manifest.permission#RESTART_PACKAGES} to be able to
+ * call this method.
+ *
+ * @param packageName The name of the package to be stopped.
+ */
+ public void restartPackage(String packageName) {
+ try {
+ ActivityManagerNative.getDefault().restartPackage(packageName);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Get the device configuration attributes.
+ */
+ public ConfigurationInfo getDeviceConfigurationInfo() {
+ try {
+ return ActivityManagerNative.getDefault().getDeviceConfigurationInfo();
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index ae9f3bf..53e6f34 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -83,6 +84,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
}
/**
+ * Convenience for checking whether the system is ready. For internal use only.
+ */
+ static public boolean isSystemReady() {
+ if (!sSystemReady) {
+ sSystemReady = getDefault().testIsSystemReady();
+ }
+ return sSystemReady;
+ }
+ static boolean sSystemReady = false;
+
+ /**
* Convenience for sending a sticky broadcast. For internal use only.
* If you don't care about permission, use null.
*/
@@ -959,6 +971,34 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_DEVICE_CONFIGURATION_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ ConfigurationInfo config = getDeviceConfigurationInfo();
+ reply.writeNoException();
+ config.writeToParcel(reply, 0);
+ return true;
+ }
+
+ case PROFILE_CONTROL_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String process = data.readString();
+ boolean start = data.readInt() != 0;
+ String path = data.readString();
+ boolean res = profileControl(process, start, path);
+ reply.writeNoException();
+ reply.writeInt(res ? 1 : 0);
+ return true;
+ }
+
+ case PEEK_SERVICE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ Intent service = Intent.CREATOR.createFromParcel(data);
+ String resolvedType = data.readString();
+ IBinder binder = peekService(service, resolvedType);
+ reply.writeNoException();
+ reply.writeStrongBinder(binder);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -1604,6 +1644,20 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
+
+ public IBinder peekService(Intent service, String resolvedType) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ service.writeToParcel(data, 0);
+ data.writeString(resolvedType);
+ mRemote.transact(PEEK_SERVICE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ IBinder binder = reply.readStrongBinder();
+ reply.recycle();
+ data.recycle();
+ return binder;
+ }
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher)
@@ -2028,6 +2082,11 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
+ public boolean testIsSystemReady()
+ {
+ /* this base class version is never called */
+ return true;
+ }
public int handleApplicationError(IBinder app, int flags,
String tag, String shortMsg, String longMsg,
byte[] crashData) throws RemoteException
@@ -2071,5 +2130,35 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
+ public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_DEVICE_CONFIGURATION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ ConfigurationInfo res = ConfigurationInfo.CREATOR.createFromParcel(reply);
+ reply.recycle();
+ data.recycle();
+ return res;
+ }
+
+ public boolean profileControl(String process, boolean start,
+ String path) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(process);
+ data.writeInt(start ? 1 : 0);
+ data.writeString(path);
+ mRemote.transact(PROFILE_CONTROL_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean res = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return res;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e4c1057..f49005e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -144,13 +144,19 @@ public final class ActivityThread {
return sPackageManager;
}
- DisplayMetrics getDisplayMetricsLocked() {
+ DisplayMetrics getDisplayMetricsLocked(boolean forceUpdate) {
+ if (mDisplayMetrics != null && !forceUpdate) {
+ return mDisplayMetrics;
+ }
if (mDisplay == null) {
WindowManager wm = WindowManagerImpl.getDefault();
mDisplay = wm.getDefaultDisplay();
}
- DisplayMetrics metrics = new DisplayMetrics();
+ DisplayMetrics metrics = mDisplayMetrics = new DisplayMetrics();
mDisplay.getMetrics(metrics);
+ //Log.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
+ // + metrics.heightPixels + " den=" + metrics.density
+ // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi);
return metrics;
}
@@ -173,7 +179,7 @@ public final class ActivityThread {
if (assets.addAssetPath(appDir) == 0) {
return null;
}
- DisplayMetrics metrics = getDisplayMetricsLocked();
+ DisplayMetrics metrics = getDisplayMetricsLocked(false);
r = new Resources(assets, metrics, getConfiguration());
//Log.i(TAG, "Created app resources " + r + ": " + r.getConfiguration());
// XXX need to remove entries when weak references go away
@@ -235,7 +241,7 @@ public final class ActivityThread {
ApplicationContext.createSystemContext(mainThread);
mSystemContext.getResources().updateConfiguration(
mainThread.getConfiguration(),
- mainThread.getDisplayMetricsLocked());
+ mainThread.getDisplayMetricsLocked(false));
//Log.i(TAG, "Created system resources "
// + mSystemContext.getResources() + ": "
// + mSystemContext.getResources().getConfiguration());
@@ -1205,7 +1211,10 @@ public final class ActivityThread {
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
-
+
+ // Formatting for checkin service - update version if row format changes
+ private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
+
public final void schedulePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges) {
queueOrSendMessage(
@@ -1440,6 +1449,10 @@ public final class ActivityThread {
}
}
+ public void profilerControl(boolean start, String path) {
+ queueOrSendMessage(H.PROFILER_CONTROL, path, start ? 1 : 0);
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
long nativeMax = Debug.getNativeHeapSize() / 1024;
@@ -1462,7 +1475,101 @@ public final class ActivityThread {
long dalvikMax = runtime.totalMemory() / 1024;
long dalvikFree = runtime.freeMemory() / 1024;
long dalvikAllocated = dalvikMax - dalvikFree;
-
+ long viewInstanceCount = ViewDebug.getViewInstanceCount();
+ long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
+ long appContextInstanceCount = ApplicationContext.getInstanceCount();
+ long activityInstanceCount = Activity.getInstanceCount();
+ int globalAssetCount = AssetManager.getGlobalAssetCount();
+ int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
+ int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
+ int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
+ int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
+ int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount();
+ long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
+ SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats();
+ SQLiteDebug.getPagerStats(stats);
+
+ // Check to see if we were called by checkin server. If so, print terse format.
+ boolean doCheckinFormat = false;
+ if (args != null) {
+ for (String arg : args) {
+ if ("-c".equals(arg)) doCheckinFormat = true;
+ }
+ }
+
+ // For checkin, we print one long comma-separated list of values
+ if (doCheckinFormat) {
+ // NOTE: if you change anything significant below, also consider changing
+ // ACTIVITY_THREAD_CHECKIN_VERSION.
+ String processName = (mBoundApplication != null)
+ ? mBoundApplication.processName : "unknown";
+
+ // Header
+ pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(',');
+ pw.print(Process.myPid()); pw.print(',');
+ pw.print(processName); pw.print(',');
+
+ // Heap info - max
+ pw.print(nativeMax); pw.print(',');
+ pw.print(dalvikMax); pw.print(',');
+ pw.print("N/A,");
+ pw.print(nativeMax + dalvikMax); pw.print(',');
+
+ // Heap info - allocated
+ pw.print(nativeAllocated); pw.print(',');
+ pw.print(dalvikAllocated); pw.print(',');
+ pw.print("N/A,");
+ pw.print(nativeAllocated + dalvikAllocated); pw.print(',');
+
+ // Heap info - free
+ pw.print(nativeFree); pw.print(',');
+ pw.print(dalvikFree); pw.print(',');
+ pw.print("N/A,");
+ pw.print(nativeFree + dalvikFree); pw.print(',');
+
+ // Heap info - proportional set size
+ pw.print(memInfo.nativePss); pw.print(',');
+ pw.print(memInfo.dalvikPss); pw.print(',');
+ pw.print(memInfo.otherPss); pw.print(',');
+ pw.print(memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); pw.print(',');
+
+ // Heap info - shared
+ pw.print(nativeShared); pw.print(',');
+ pw.print(dalvikShared); pw.print(',');
+ pw.print(otherShared); pw.print(',');
+ pw.print(nativeShared + dalvikShared + otherShared); pw.print(',');
+
+ // Heap info - private
+ pw.print(nativePrivate); pw.print(',');
+ pw.print(dalvikPrivate); pw.print(',');
+ pw.print(otherPrivate); pw.print(',');
+ pw.print(nativePrivate + dalvikPrivate + otherPrivate); pw.print(',');
+
+ // Object counts
+ pw.print(viewInstanceCount); pw.print(',');
+ pw.print(viewRootInstanceCount); pw.print(',');
+ pw.print(appContextInstanceCount); pw.print(',');
+ pw.print(activityInstanceCount); pw.print(',');
+
+ pw.print(globalAssetCount); pw.print(',');
+ pw.print(globalAssetManagerCount); pw.print(',');
+ pw.print(binderLocalObjectCount); pw.print(',');
+ pw.print(binderProxyObjectCount); pw.print(',');
+
+ pw.print(binderDeathObjectCount); pw.print(',');
+ pw.print(openSslSocketCount); pw.print(',');
+
+ // SQL
+ pw.print(sqliteAllocated); pw.print(',');
+ pw.print(stats.databaseBytes / 1024); pw.print(',');
+ pw.print(stats.numPagers); pw.print(',');
+ pw.print((stats.totalBytes - stats.referencedBytes) / 1024); pw.print(',');
+ pw.print(stats.referencedBytes / 1024); pw.print('\n');
+
+ return;
+ }
+
+ // otherwise, show human-readable format
printRow(pw, HEAP_COLUMN, "", "native", "dalvik", "other", "total");
printRow(pw, HEAP_COLUMN, "size:", nativeMax, dalvikMax, "N/A", nativeMax + dalvikMax);
printRow(pw, HEAP_COLUMN, "allocated:", nativeAllocated, dalvikAllocated, "N/A",
@@ -1480,26 +1587,22 @@ public final class ActivityThread {
pw.println(" ");
pw.println(" Objects");
- printRow(pw, TWO_COUNT_COLUMNS, "Views:", ViewDebug.getViewInstanceCount(), "ViewRoots:",
- ViewDebug.getViewRootInstanceCount());
+ printRow(pw, TWO_COUNT_COLUMNS, "Views:", viewInstanceCount, "ViewRoots:",
+ viewRootInstanceCount);
- printRow(pw, TWO_COUNT_COLUMNS, "AppContexts:", ApplicationContext.getInstanceCount(),
- "Activities:", Activity.getInstanceCount());
+ printRow(pw, TWO_COUNT_COLUMNS, "AppContexts:", appContextInstanceCount,
+ "Activities:", activityInstanceCount);
- printRow(pw, TWO_COUNT_COLUMNS, "Assets:", AssetManager.getGlobalAssetCount(),
- "AssetManagers:", AssetManager.getGlobalAssetManagerCount());
+ printRow(pw, TWO_COUNT_COLUMNS, "Assets:", globalAssetCount,
+ "AssetManagers:", globalAssetManagerCount);
- printRow(pw, TWO_COUNT_COLUMNS, "Local Binders:", Debug.getBinderLocalObjectCount(),
- "Proxy Binders:", Debug.getBinderProxyObjectCount());
- printRow(pw, ONE_COUNT_COLUMN, "Death Recipients:", Debug.getBinderDeathObjectCount());
-
- printRow(pw, ONE_COUNT_COLUMN, "OpenSSL Sockets:", OpenSSLSocketImpl.getInstanceCount());
+ printRow(pw, TWO_COUNT_COLUMNS, "Local Binders:", binderLocalObjectCount,
+ "Proxy Binders:", binderProxyObjectCount);
+ printRow(pw, ONE_COUNT_COLUMN, "Death Recipients:", binderDeathObjectCount);
+ printRow(pw, ONE_COUNT_COLUMN, "OpenSSL Sockets:", openSslSocketCount);
+
// SQLite mem info
- long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
- SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats();
- SQLiteDebug.getPagerStats(stats);
-
pw.println(" ");
pw.println(" SQL");
printRow(pw, TWO_COUNT_COLUMNS, "heap:", sqliteAllocated, "dbFiles:",
@@ -1542,6 +1645,7 @@ public final class ActivityThread {
public static final int LOW_MEMORY = 124;
public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
public static final int RELAUNCH_ACTIVITY = 126;
+ public static final int PROFILER_CONTROL = 127;
String codeToString(int code) {
if (localLOGV) {
switch (code) {
@@ -1572,6 +1676,7 @@ public final class ActivityThread {
case LOW_MEMORY: return "LOW_MEMORY";
case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
+ case PROFILER_CONTROL: return "PROFILER_CONTROL";
}
}
return "(unknown)";
@@ -1671,6 +1776,9 @@ public final class ActivityThread {
case ACTIVITY_CONFIGURATION_CHANGED:
handleActivityConfigurationChanged((IBinder)msg.obj);
break;
+ case PROFILER_CONTROL:
+ handleProfilerControl(msg.arg1 != 0, (String)msg.obj);
+ break;
}
}
}
@@ -1751,6 +1859,7 @@ public final class ActivityThread {
final HashMap<String, WeakReference<PackageInfo>> mResourcePackages
= new HashMap<String, WeakReference<PackageInfo>>();
Display mDisplay = null;
+ DisplayMetrics mDisplayMetrics = null;
HashMap<String, WeakReference<Resources> > mActiveResources
= new HashMap<String, WeakReference<Resources> >();
@@ -1918,7 +2027,7 @@ public final class ActivityThread {
PackageInfo info = new PackageInfo(this, "android", context);
context.init(info, null, this);
context.getResources().updateConfiguration(
- getConfiguration(), getDisplayMetricsLocked());
+ getConfiguration(), getDisplayMetricsLocked(false));
mSystemContext = context;
//Log.i(TAG, "Created system resources " + context.getResources()
// + ": " + context.getResources().getConfiguration());
@@ -2557,7 +2666,10 @@ public final class ActivityThread {
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
- wm.addView(decor, l);
+ if (a.mVisibleFromClient) {
+ a.mWindowAdded = true;
+ wm.addView(decor, l);
+ }
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
@@ -2576,7 +2688,8 @@ public final class ActivityThread {
performConfigurationChanged(r.activity, r.newConfig);
r.newConfig = null;
}
- Log.v(TAG, "Resuming " + r + " with isForward=" + isForward);
+ if (localLOGV) Log.v(TAG, "Resuming " + r + " with isForward="
+ + isForward);
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
@@ -2588,8 +2701,11 @@ public final class ActivityThread {
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
}
- r.activity.mDecor.setVisibility(View.VISIBLE);
+ r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
+ if (r.activity.mVisibleFromClient) {
+ r.activity.makeVisible();
+ }
}
r.nextIdle = mNewActivities;
@@ -2800,18 +2916,22 @@ public final class ActivityThread {
View v = r.activity.mDecor;
if (v != null) {
if (show) {
- if (v.getVisibility() != View.VISIBLE) {
- v.setVisibility(View.VISIBLE);
+ if (!r.activity.mVisibleFromServer) {
+ r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
+ if (r.activity.mVisibleFromClient) {
+ r.activity.makeVisible();
+ }
}
if (r.newConfig != null) {
performConfigurationChanged(r.activity, r.newConfig);
r.newConfig = null;
}
} else {
- if (v.getVisibility() == View.VISIBLE) {
- v.setVisibility(View.INVISIBLE);
+ if (r.activity.mVisibleFromServer) {
+ r.activity.mVisibleFromServer = false;
mNumVisibleActivities--;
+ v.setVisibility(View.INVISIBLE);
}
}
}
@@ -3037,11 +3157,13 @@ public final class ActivityThread {
WindowManager wm = r.activity.getWindowManager();
View v = r.activity.mDecor;
if (v != null) {
- if (v.getVisibility() == View.VISIBLE) {
+ if (r.activity.mVisibleFromServer) {
mNumVisibleActivities--;
}
IBinder wtoken = v.getWindowToken();
- wm.removeViewImmediate(v);
+ if (r.activity.mWindowAdded) {
+ wm.removeViewImmediate(v);
+ }
if (wtoken != null) {
WindowManagerImpl.getDefault().closeAll(wtoken,
r.activity.getClass().getName(), "Activity");
@@ -3271,6 +3393,7 @@ public final class ActivityThread {
mConfiguration = new Configuration();
}
mConfiguration.updateFrom(config);
+ DisplayMetrics dm = getDisplayMetricsLocked(true);
// set it for java, this also affects newly created Resources
if (config.locale != null) {
@@ -3290,7 +3413,7 @@ public final class ActivityThread {
WeakReference<Resources> v = it.next();
Resources r = v.get();
if (r != null) {
- r.updateConfiguration(config, null);
+ r.updateConfiguration(config, dm);
//Log.i(TAG, "Updated app resources " + v.getKey()
// + " " + r + ": " + r.getConfiguration());
} else {
@@ -3318,6 +3441,21 @@ public final class ActivityThread {
performConfigurationChanged(r.activity, mConfiguration);
}
+ final void handleProfilerControl(boolean start, String path) {
+ if (start) {
+ File file = new File(path);
+ file.getParentFile().mkdirs();
+ try {
+ Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Profiling failed on path " + path
+ + " -- can the process access this path?");
+ }
+ } else {
+ Debug.stopMethodTracing();
+ }
+ }
+
final void handleLowMemory() {
ArrayList<ComponentCallbacks> callbacks
= new ArrayList<ComponentCallbacks>();
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index 4236a00..3b5ad86 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -1487,7 +1487,7 @@ class ApplicationContext extends Context {
static final class ApplicationPackageManager extends PackageManager {
@Override
public PackageInfo getPackageInfo(String packageName, int flags)
- throws NameNotFoundException {
+ throws NameNotFoundException {
try {
PackageInfo pi = mPM.getPackageInfo(packageName, flags);
if (pi != null) {
@@ -1500,6 +1500,43 @@ class ApplicationContext extends Context {
throw new NameNotFoundException(packageName);
}
+ public Intent getLaunchIntentForPackage(String packageName)
+ throws NameNotFoundException {
+ // First see if the package has an INFO activity; the existence of
+ // such an activity is implied to be the desired front-door for the
+ // overall package (such as if it has multiple launcher entries).
+ Intent intent = getLaunchIntentForPackageCategory(this, packageName,
+ Intent.CATEGORY_INFO);
+ if (intent != null) {
+ return intent;
+ }
+
+ // Otherwise, try to find a main launcher activity.
+ return getLaunchIntentForPackageCategory(this, packageName,
+ Intent.CATEGORY_LAUNCHER);
+ }
+
+ // XXX This should be implemented as a call to the package manager,
+ // to reduce the work needed.
+ static Intent getLaunchIntentForPackageCategory(PackageManager pm,
+ String packageName, String category) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Intent intentToResolve = new Intent(Intent.ACTION_MAIN, null);
+ intentToResolve.addCategory(category);
+ final List<ResolveInfo> apps =
+ pm.queryIntentActivities(intentToResolve, 0);
+ // I wish there were a way to directly get the "main" activity of a
+ // package but ...
+ for (ResolveInfo app : apps) {
+ if (app.activityInfo.packageName.equals(packageName)) {
+ intent.setClassName(packageName, app.activityInfo.name);
+ return intent;
+ }
+ }
+ return null;
+ }
+
@Override
public int[] getPackageGids(String packageName)
throws NameNotFoundException {
@@ -1630,6 +1667,15 @@ class ApplicationContext extends Context {
}
@Override
+ public String[] getSystemSharedLibraryNames() {
+ try {
+ return mPM.getSystemSharedLibraryNames();
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ }
+
+ @Override
public int checkPermission(String permName, String pkgName) {
try {
return mPM.checkPermission(permName, pkgName);
@@ -1974,6 +2020,18 @@ class ApplicationContext extends Context {
getApplicationInfo(appPackageName, 0));
}
+ int mCachedSafeMode = -1;
+ @Override public boolean isSafeMode() {
+ try {
+ if (mCachedSafeMode < 0) {
+ mCachedSafeMode = mPM.isSafeMode() ? 1 : 0;
+ }
+ return mCachedSafeMode != 0;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ }
+
static void configurationChanged() {
synchronized (sSync) {
sIconCache.clear();
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index d2cf55a..bcc9302 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -322,6 +322,15 @@ public abstract class ApplicationThreadNative extends Binder
requestPss();
return true;
}
+
+ case PROFILER_CONTROL_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ boolean start = data.readInt() != 0;
+ String path = data.readString();
+ profilerControl(start, path);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -654,5 +663,14 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
+ public void profilerControl(boolean start, String path) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeInt(start ? 1 : 0);
+ data.writeString(path);
+ mRemote.transact(PROFILER_CONTROL_TRANSACTION, data, null,
+ IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 951b48d..b09a57f 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -122,7 +122,7 @@ public class Dialog implements DialogInterface, Window.Callback,
* uses the window manager and theme from this context to
* present its UI.
* @param theme A style resource describing the theme to use for the
- * window. See <a href="{@docRoot}reference/available-resources.html#stylesandthemes">Style
+ * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style
* and Theme Resources</a> for more information about defining and using
* styles. This theme is applied on top of the current theme in
* <var>context</var>. If 0, the default dialog theme will be used.
@@ -518,7 +518,7 @@ public class Dialog implements DialogInterface, Window.Callback,
private boolean isOutOfBounds(MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
- final int slop = ViewConfiguration.getWindowTouchSlop();
+ final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
final View decorView = getWindow().getDecorView();
return (x < -slop) || (y < -slop)
|| (x > (decorView.getWidth()+slop))
diff --git a/core/java/android/app/ExpandableListActivity.java b/core/java/android/app/ExpandableListActivity.java
index 75dfcae..a2e048f 100644
--- a/core/java/android/app/ExpandableListActivity.java
+++ b/core/java/android/app/ExpandableListActivity.java
@@ -63,21 +63,21 @@ import java.util.Map;
*
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
- * &lt;LinearLayout
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation=&quot;vertical&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
- * android:paddingLeft=&quot;8&quot;
- * android:paddingRight=&quot;8&quot;&gt;
+ * android:paddingLeft=&quot;8dp&quot;
+ * android:paddingRight=&quot;8dp&quot;&gt;
*
- * &lt;ExpandableListView id=&quot;android:list&quot;
+ * &lt;ExpandableListView android:id=&quot;@id/android:list&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
* android:background=&quot;#00FF00&quot;
* android:layout_weight=&quot;1&quot;
* android:drawSelectorOnTop=&quot;false&quot;/&gt;
*
- * &lt;TextView id=&quot;android:empty&quot;
+ * &lt;TextView android:id=&quot;@id/android:empty&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
* android:background=&quot;#FF0000&quot;
@@ -113,19 +113,19 @@ import java.util.Map;
*
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
- * &lt;LinearLayout
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;
* android:orientation=&quot;vertical&quot;&gt;
*
- * &lt;TextView id=&quot;text1&quot;
- * android:textSize=&quot;16&quot;
+ * &lt;TextView android:id=&quot;@+id/text1&quot;
+ * android:textSize=&quot;16sp&quot;
* android:textStyle=&quot;bold&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
*
- * &lt;TextView id=&quot;text2&quot;
- * android:textSize=&quot;16&quot;
+ * &lt;TextView android:id=&quot;@+id/text2&quot;
+ * android:textSize=&quot;16sp&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
* &lt;/LinearLayout&gt;
@@ -162,7 +162,8 @@ public class ExpandableListActivity extends Activity implements
/**
* Override this to populate the context menu when an item is long pressed. menuInfo
- * will contain a {@link AdapterContextMenuInfo} whose position is a packed position
+ * will contain an {@link android.widget.ExpandableListView.ExpandableListContextMenuInfo}
+ * whose packedPosition is a packed position
* that should be used with {@link ExpandableListView#getPackedPositionType(long)} and
* the other similar methods.
* <p>
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 353500e..2ac6160 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -22,6 +22,7 @@ import android.content.ContentProviderNative;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.ProviderInfo;
import android.content.res.Configuration;
@@ -127,7 +128,8 @@ public interface IActivityManager extends IInterface {
boolean doRebind) throws RemoteException;
/* oneway */
public void serviceDoneExecuting(IBinder token) throws RemoteException;
-
+ public IBinder peekService(Intent service, String resolvedType) throws RemoteException;
+
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher)
throws RemoteException;
@@ -216,6 +218,17 @@ public interface IActivityManager extends IInterface {
// Retrieve running application processes in the system
public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses()
throws RemoteException;
+ // Get device configuration
+ public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException;
+
+ // Turn on/off profiling in a particular process.
+ public boolean profileControl(String process, boolean start,
+ String path) throws RemoteException;
+
+ /*
+ * Private non-Binder interfaces
+ */
+ /* package */ boolean testIsSystemReady();
/** Information you can retrieve about a particular application. */
public static class ContentProviderHolder implements Parcelable {
@@ -354,4 +367,7 @@ public interface IActivityManager extends IInterface {
int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80;
int REPORT_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82;
+ int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83;
+ int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84;
+ int PROFILE_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+85;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 47476b5..9f3534b 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -86,6 +86,7 @@ public interface IApplicationThread extends IInterface {
void scheduleLowMemory() throws RemoteException;
void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
void requestPss() throws RemoteException;
+ void profilerControl(boolean start, String path) throws RemoteException;
String descriptor = "android.app.IApplicationThread";
@@ -115,4 +116,5 @@ public interface IApplicationThread extends IInterface {
int SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24;
int SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25;
int REQUEST_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26;
+ int PROFILER_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27;
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index f96d787..f6a28b2 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1267,7 +1267,7 @@ public class Instrumentation {
}
/**
- * Perform calling of an activity's {@link Activity#onUserLeaving} method.
+ * Perform calling of an activity's {@link Activity#onUserLeaveHint} method.
* The default implementation simply calls through to that method.
*
* @param activity The activity being notified that the user has navigated away
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
new file mode 100644
index 0000000..2b12a2a
--- /dev/null
+++ b/core/java/android/app/IntentService.java
@@ -0,0 +1,74 @@
+package android.app;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * An abstract {@link Service} that serializes the handling of the Intents passed upon service
+ * start and handles them on a handler thread.
+ *
+ * <p>To use this class extend it and implement {@link #onHandleIntent}. The {@link Service} will
+ * automatically be stopped when the last enqueued {@link Intent} is handled.
+ */
+public abstract class IntentService extends Service {
+ private volatile Looper mServiceLooper;
+ private volatile ServiceHandler mServiceHandler;
+ private String mName;
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ onHandleIntent((Intent)msg.obj);
+ stopSelf(msg.arg1);
+ }
+ }
+
+ public IntentService(String name) {
+ super();
+ mName = name;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
+ thread.start();
+
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(mServiceLooper);
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ super.onStart(intent, startId);
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.obj = intent;
+ mServiceHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onDestroy() {
+ mServiceLooper.quit();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /**
+ * Invoked on the Handler thread with the {@link Intent} that is passed to {@link #onStart}.
+ * Note that this will be invoked from a different thread than the one that handles the
+ * {@link #onStart} call.
+ */
+ protected abstract void onHandleIntent(Intent intent);
+}
diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java
index 8f0a4f5..d6fcbb1 100644
--- a/core/java/android/app/LauncherActivity.java
+++ b/core/java/android/app/LauncherActivity.java
@@ -18,14 +18,23 @@ package android.app;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
import android.os.Bundle;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.Window;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
@@ -43,33 +52,59 @@ import java.util.List;
*
*/
public abstract class LauncherActivity extends ListActivity {
+
+ Intent mIntent;
+ PackageManager mPackageManager;
/**
+ * An item in the list
+ */
+ public static class ListItem {
+ public CharSequence label;
+ //public CharSequence description;
+ public Drawable icon;
+ public String packageName;
+ public String className;
+
+ ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) {
+ label = resolveInfo.loadLabel(pm);
+ if (label == null && resolveInfo.activityInfo != null) {
+ label = resolveInfo.activityInfo.name;
+ }
+
+ /*
+ if (resolveInfo.activityInfo != null &&
+ resolveInfo.activityInfo.applicationInfo != null) {
+ description = resolveInfo.activityInfo.applicationInfo.loadDescription(pm);
+ }
+ */
+
+ icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm));
+ packageName = resolveInfo.activityInfo.applicationInfo.packageName;
+ className = resolveInfo.activityInfo.name;
+ }
+
+ public ListItem() {
+ }
+ }
+
+ /**
* Adapter which shows the set of activities that can be performed for a given intent.
*/
private class ActivityAdapter extends BaseAdapter implements Filterable {
private final Object lock = new Object();
- private ArrayList<ResolveInfo> mOriginalValues;
+ private ArrayList<ListItem> mOriginalValues;
- protected final Context mContext;
- protected final Intent mIntent;
protected final LayoutInflater mInflater;
- protected List<ResolveInfo> mActivitiesList;
+ protected List<ListItem> mActivitiesList;
private Filter mFilter;
-
- public ActivityAdapter(Context context, Intent intent) {
- mContext = context;
- mIntent = new Intent(intent);
- mIntent.setComponent(null);
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- PackageManager pm = context.getPackageManager();
- mActivitiesList = pm.queryIntentActivities(intent, 0);
- if (mActivitiesList != null) {
- Collections.sort(mActivitiesList, new ResolveInfo.DisplayNameComparator(pm));
- }
+
+ public ActivityAdapter() {
+ mInflater = (LayoutInflater) LauncherActivity.this.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ mActivitiesList = makeListItems();
}
public Intent intentForPosition(int position) {
@@ -78,8 +113,8 @@ public abstract class LauncherActivity extends ListActivity {
}
Intent intent = new Intent(mIntent);
- ActivityInfo ai = mActivitiesList.get(position).activityInfo;
- intent.setClassName(ai.applicationInfo.packageName, ai.name);
+ ListItem item = mActivitiesList.get(position);
+ intent.setClassName(item.packageName, item.className);
return intent;
}
@@ -99,7 +134,7 @@ public abstract class LauncherActivity extends ListActivity {
View view;
if (convertView == null) {
view = mInflater.inflate(
- com.android.internal.R.layout.simple_list_item_1, parent, false);
+ com.android.internal.R.layout.activity_list_item_2, parent, false);
} else {
view = convertView;
}
@@ -107,35 +142,22 @@ public abstract class LauncherActivity extends ListActivity {
return view;
}
- private char getCandidateLetter(ResolveInfo info) {
- PackageManager pm = mContext.getPackageManager();
- CharSequence label = info.loadLabel(pm);
-
- if (label == null) {
- label = info.activityInfo.name;
- }
-
- return Character.toLowerCase(label.charAt(0));
+ private void bindView(View view, ListItem item) {
+ TextView text = (TextView) view;
+ text.setText(item.label);
+ text.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null);
}
-
- private void bindView(View view, ResolveInfo info) {
- TextView text = (TextView) view.findViewById(com.android.internal.R.id.text1);
-
- PackageManager pm = mContext.getPackageManager();
- CharSequence label = info.loadLabel(pm);
- text.setText(label != null ? label : info.activityInfo.name);
- }
-
+
public Filter getFilter() {
if (mFilter == null) {
mFilter = new ArrayFilter();
}
return mFilter;
}
-
+
/**
- * <p>An array filters constrains the content of the array adapter with a prefix. Each item that
- * does not start with the supplied prefix is removed from the list.</p>
+ * An array filters constrains the content of the array adapter with a prefix. Each
+ * item that does not start with the supplied prefix is removed from the list.
*/
private class ArrayFilter extends Filter {
@Override
@@ -144,39 +166,35 @@ public abstract class LauncherActivity extends ListActivity {
if (mOriginalValues == null) {
synchronized (lock) {
- mOriginalValues = new ArrayList<ResolveInfo>(mActivitiesList);
+ mOriginalValues = new ArrayList<ListItem>(mActivitiesList);
}
}
if (prefix == null || prefix.length() == 0) {
synchronized (lock) {
- ArrayList<ResolveInfo> list = new ArrayList<ResolveInfo>(mOriginalValues);
+ ArrayList<ListItem> list = new ArrayList<ListItem>(mOriginalValues);
results.values = list;
results.count = list.size();
}
} else {
- final PackageManager pm = mContext.getPackageManager();
final String prefixString = prefix.toString().toLowerCase();
- ArrayList<ResolveInfo> values = mOriginalValues;
+ ArrayList<ListItem> values = mOriginalValues;
int count = values.size();
- ArrayList<ResolveInfo> newValues = new ArrayList<ResolveInfo>(count);
+ ArrayList<ListItem> newValues = new ArrayList<ListItem>(count);
for (int i = 0; i < count; i++) {
- ResolveInfo value = values.get(i);
+ ListItem item = values.get(i);
- final CharSequence label = value.loadLabel(pm);
- final CharSequence name = label != null ? label : value.activityInfo.name;
-
- String[] words = name.toString().toLowerCase().split(" ");
+ String[] words = item.label.toString().toLowerCase().split(" ");
int wordCount = words.length;
for (int k = 0; k < wordCount; k++) {
final String word = words[k];
if (word.startsWith(prefixString)) {
- newValues.add(value);
+ newValues.add(item);
break;
}
}
@@ -192,7 +210,7 @@ public abstract class LauncherActivity extends ListActivity {
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
//noinspection unchecked
- mActivitiesList = (List<ResolveInfo>) results.values;
+ mActivitiesList = (List<ListItem>) results.values;
if (results.count > 0) {
notifyDataSetChanged();
} else {
@@ -201,19 +219,119 @@ public abstract class LauncherActivity extends ListActivity {
}
}
}
-
-
+
+ /**
+ * Utility class to resize icons to match default icon size.
+ */
+ public class IconResizer {
+ // Code is borrowed from com.android.launcher.Utilities.
+ private int mIconWidth = -1;
+ private int mIconHeight = -1;
+
+ private final Rect mOldBounds = new Rect();
+ private Canvas mCanvas = new Canvas();
+
+ public IconResizer() {
+ mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+
+ final Resources resources = LauncherActivity.this.getResources();
+ mIconWidth = mIconHeight = (int) resources.getDimension(
+ android.R.dimen.app_icon_size);
+ }
+
+ /**
+ * Returns a Drawable representing the thumbnail of the specified Drawable.
+ * The size of the thumbnail is defined by the dimension
+ * android.R.dimen.launcher_application_icon_size.
+ *
+ * This method is not thread-safe and should be invoked on the UI thread only.
+ *
+ * @param icon The icon to get a thumbnail of.
+ *
+ * @return A thumbnail for the specified icon or the icon itself if the
+ * thumbnail could not be created.
+ */
+ public Drawable createIconThumbnail(Drawable icon) {
+ int width = mIconWidth;
+ int height = mIconHeight;
+
+ final int iconWidth = icon.getIntrinsicWidth();
+ final int iconHeight = icon.getIntrinsicHeight();
+
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(width);
+ painter.setIntrinsicHeight(height);
+ }
+
+ if (width > 0 && height > 0) {
+ if (width < iconWidth || height < iconHeight) {
+ final float ratio = (float) iconWidth / iconHeight;
+
+ if (iconWidth > iconHeight) {
+ height = (int) (width / ratio);
+ } else if (iconHeight > iconWidth) {
+ width = (int) (height * ratio);
+ }
+
+ final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ?
+ Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+ final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
+ final Canvas canvas = mCanvas;
+ canvas.setBitmap(thumb);
+ // Copy the old bounds to restore them later
+ // If we were to do oldBounds = icon.getBounds(),
+ // the call to setBounds() that follows would
+ // change the same instance and we would lose the
+ // old bounds
+ mOldBounds.set(icon.getBounds());
+ final int x = (mIconWidth - width) / 2;
+ final int y = (mIconHeight - height) / 2;
+ icon.setBounds(x, y, x + width, y + height);
+ icon.draw(canvas);
+ icon.setBounds(mOldBounds);
+ icon = new BitmapDrawable(thumb);
+ } else if (iconWidth < width && iconHeight < height) {
+ final Bitmap.Config c = Bitmap.Config.ARGB_8888;
+ final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
+ final Canvas canvas = mCanvas;
+ canvas.setBitmap(thumb);
+ mOldBounds.set(icon.getBounds());
+ final int x = (width - iconWidth) / 2;
+ final int y = (height - iconHeight) / 2;
+ icon.setBounds(x, y, x + iconWidth, y + iconHeight);
+ icon.draw(canvas);
+ icon.setBounds(mOldBounds);
+ icon = new BitmapDrawable(thumb);
+ }
+ }
+
+ return icon;
+ }
+ }
+
@Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mPackageManager = getPackageManager();
+
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ setProgressBarIndeterminateVisibility(true);
+ setContentView(com.android.internal.R.layout.activity_list);
- mAdapter = new ActivityAdapter(this, getTargetIntent());
+
+ mIntent = new Intent(getTargetIntent());
+ mIntent.setComponent(null);
+ mAdapter = new ActivityAdapter();
setListAdapter(mAdapter);
getListView().setTextFilterEnabled(true);
+
+ setProgressBarIndeterminateVisibility(false);
}
-
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent intent = ((ActivityAdapter)mAdapter).intentForPosition(position);
@@ -221,6 +339,42 @@ public abstract class LauncherActivity extends ListActivity {
startActivity(intent);
}
- protected abstract Intent getTargetIntent();
-
+ /**
+ * Return the actual Intent for a specific position in our
+ * {@link android.widget.ListView}.
+ * @param position The item whose Intent to return
+ */
+ protected Intent intentForPosition(int position) {
+ ActivityAdapter adapter = (ActivityAdapter) mAdapter;
+ return adapter.intentForPosition(position);
+ }
+
+ /**
+ * Get the base intent to use when running
+ * {@link PackageManager#queryIntentActivities(Intent, int)}.
+ */
+ protected Intent getTargetIntent() {
+ return new Intent();
+ }
+
+ /**
+ * Perform the query to determine which results to show and return a list of them.
+ */
+ public List<ListItem> makeListItems() {
+ // Load all matching activities and sort correctly
+ List<ResolveInfo> list = mPackageManager.queryIntentActivities(mIntent,
+ /* no flags */ 0);
+ Collections.sort(list, new ResolveInfo.DisplayNameComparator(mPackageManager));
+
+ IconResizer resizer = new IconResizer();
+
+ ArrayList<ListItem> result = new ArrayList<ListItem>(list.size());
+ int listSize = list.size();
+ for (int i = 0; i < listSize; i++) {
+ ResolveInfo resolveInfo = list.get(i);
+ result.add(new ListItem(mPackageManager, resolveInfo, resizer));
+ }
+
+ return result;
+ }
}
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java
index 2818937..5523c18 100644
--- a/core/java/android/app/ListActivity.java
+++ b/core/java/android/app/ListActivity.java
@@ -53,22 +53,22 @@ import android.widget.ListView;
* </p>
*
* <pre>
- * &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
- * &lt;LinearLayout
+ * &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation=&quot;vertical&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
- * android:paddingLeft=&quot;8&quot;
- * android:paddingRight=&quot;8&quot;&gt;
+ * android:paddingLeft=&quot;8dp&quot;
+ * android:paddingRight=&quot;8dp&quot;&gt;
*
- * &lt;ListView id=&quot;android:list&quot;
+ * &lt;ListView android:id=&quot;@id/android:list&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
* android:background=&quot;#00FF00&quot;
* android:layout_weight=&quot;1&quot;
* android:drawSelectorOnTop=&quot;false&quot;/&gt;
*
- * &lt;TextView id=&quot;android:empty&quot;
+ * &lt;TextView id=&quot;@id/android:empty&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;fill_parent&quot;
* android:background=&quot;#FF0000&quot;
@@ -99,19 +99,19 @@ import android.widget.ListView;
*
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
- * &lt;LinearLayout
+ * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;
* android:orientation=&quot;vertical&quot;&gt;
*
- * &lt;TextView id=&quot;text1&quot;
- * android:textSize=&quot;16&quot;
+ * &lt;TextView android:id=&quot;@+id/text1&quot;
+ * android:textSize=&quot;16sp&quot;
* android:textStyle=&quot;bold&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
*
- * &lt;TextView id=&quot;text2&quot;
- * android:textSize=&quot;16&quot;
+ * &lt;TextView android:id=&quot;@+id/text2&quot;
+ * android:textSize=&quot;16sp&quot;
* android:layout_width=&quot;fill_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
* &lt;/LinearLayout&gt;
@@ -142,8 +142,8 @@ import android.widget.ListView;
* public class MyListAdapter extends ListActivity {
*
* &#064;Override
- * protected void onCreate(Bundle icicle){
- * super.onCreate(icicle);
+ * protected void onCreate(Bundle savedInstanceState){
+ * super.onCreate(savedInstanceState);
*
* // We'll define a custom screen layout here (the one shown above), but
* // typically, you could just use the standard ListActivity layout.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ea67cdb..51fddb1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -90,8 +90,9 @@ public class Notification implements Parcelable
* The intent to execute when the expanded status entry is clicked. If
* this is an activity, it must include the
* {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
- * that you take care of task management as described in the
- * <a href="{@docRoot}intro/appmodel.html">application model</a> document.
+ * that you take care of task management as described in the <em>Activities and Tasks</em>
+ * section of the <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application
+ * Fundamentals</a> document.
*/
public PendingIntent contentIntent;
@@ -420,8 +421,8 @@ public class Notification implements Parcelable
* @param contentIntent The intent to launch when the user clicks the expanded notification.
* If this is an activity, it must include the
* {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires
- * that you take care of task management as described in the
- * <a href="{@docRoot}intro/appmodel.html">application model</a> document.
+ * that you take care of task management as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">Application Fundamentals: Activities and Tasks</a>.
*/
public void setLatestEventInfo(Context context,
CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index afb3827..39edab7 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -82,9 +82,7 @@ public class NotificationManager
* @param id An identifier for this notification unique within your
* application.
* @param notification A {@link Notification} object describing how to
- * notify the user, other than the view you're providing. If you
- * pass null, there will be no persistent notification and no
- * flashing, vibration, etc.
+ * notify the user, other than the view you're providing. Must not be null.
*/
public void notify(int id, Notification notification)
{
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index b59e9dc..1bed706 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -426,13 +426,9 @@ public final class PendingIntent implements Parcelable {
*/
@Override
public boolean equals(Object otherObj) {
- if (otherObj == null) {
- return false;
- }
- try {
+ if (otherObj instanceof PendingIntent) {
return mTarget.asBinder().equals(((PendingIntent)otherObj)
.mTarget.asBinder());
- } catch (ClassCastException e) {
}
return false;
}
@@ -442,6 +438,13 @@ public final class PendingIntent implements Parcelable {
return mTarget.asBinder().hashCode();
}
+ @Override
+ public String toString() {
+ return "PendingIntent{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " target " + (mTarget != null ? mTarget.asBinder() : null) + "}";
+ }
+
public int describeContents() {
return 0;
}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 2e2a1a1..64288d2 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -16,11 +16,14 @@
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;
@@ -29,11 +32,11 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.server.search.SearchableInfo;
+import android.speech.RecognizerIntent;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
@@ -50,6 +53,7 @@ import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.CursorAdapter;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
@@ -94,6 +98,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private TextView mBadgeLabel;
private AutoCompleteTextView mSearchTextField;
private Button mGoButton;
+ private ImageButton mVoiceButton;
// interaction with searchable application
private ComponentName mLaunchComponent;
@@ -115,10 +120,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private Uri mSuggestionData = null;
private String mSuggestionQuery = null;
+ // For voice searching
+ private Intent mVoiceWebSearchIntent;
+ private Intent mVoiceAppSearchIntent;
+
// support for AutoCompleteTextView suggestions display
private SuggestionsAdapter mSuggestionsAdapter;
-
/**
* Constructor - fires it up and makes it look like the search UI.
*
@@ -153,12 +161,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchTextField = (AutoCompleteTextView)
findViewById(com.android.internal.R.id.search_src_text);
mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
+ mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
// attach listeners
mSearchTextField.addTextChangedListener(mTextWatcher);
mSearchTextField.setOnKeyListener(mTextKeyListener);
mGoButton.setOnClickListener(mGoButtonClickListener);
mGoButton.setOnKeyListener(mButtonsKeyListener);
+ mVoiceButton.setOnClickListener(mVoiceButtonClickListener);
+ mVoiceButton.setOnKeyListener(mButtonsKeyListener);
// pre-hide all the extraneous elements
mBadgeLabel.setVisibility(View.GONE);
@@ -169,13 +180,19 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
setCanceledOnTouchOutside(true);
// Set up broadcast filters
- mCloseDialogsFilter = new
- IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ mCloseDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mPackageFilter = new IntentFilter();
mPackageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
mPackageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
mPackageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
mPackageFilter.addDataScheme("package");
+
+ // Save voice intent for later queries/launching
+ mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+ RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
+
+ mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
}
/**
@@ -236,7 +253,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchTextField.setAdapter(mSuggestionsAdapter);
mSearchTextField.setText(initialQuery);
} else {
- mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable);
+ mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable,
+ mSearchTextField);
mSearchTextField.setAdapter(mSuggestionsAdapter);
// finally, load the user's initial text (which may trigger suggestions)
@@ -261,16 +279,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * Dismiss the search dialog.
+ * The search dialog is being dismissed, so handle all of the local shutdown operations.
*
- * This function is designed to be idempotent so it can be safely called at any time
+ * This function is designed to be idempotent so that dismiss() can be safely called at any time
* (even if already closed) and more likely to really dump any memory. No leaks!
*/
@Override
- public void dismiss() {
- if (isShowing()) {
- super.dismiss();
- }
+ public void onStop() {
+ super.onStop();
+
setOnCancelListener(null);
setOnDismissListener(null);
@@ -281,6 +298,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// This is OK - it just means we didn't have any registered
}
+ // close any leftover cursor
+ if (mSuggestionsAdapter != null) {
+ mSuggestionsAdapter.changeCursor(null);
+ }
+
// dump extra memory we're hanging on to
mLaunchComponent = null;
mAppSearchData = null;
@@ -408,6 +430,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
updateSearchButton();
updateSearchBadge();
updateQueryHint();
+ updateVoiceButton();
// 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
@@ -426,6 +449,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
}
mSearchTextField.setInputType(inputType);
+ mSearchTextField.setImeOptions(mSearchable.getImeOptions());
}
}
@@ -500,6 +524,30 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
+ * Update the visibility of the voice button. There are actually two voice search modes,
+ * either of which will activate the button.
+ */
+ private void updateVoiceButton() {
+ int visibility = View.GONE;
+ if (mSearchable.getVoiceSearchEnabled()) {
+ Intent testIntent = null;
+ if (mSearchable.getVoiceSearchLaunchWebSearch()) {
+ testIntent = mVoiceWebSearchIntent;
+ } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
+ testIntent = mVoiceAppSearchIntent;
+ }
+ if (testIntent != null) {
+ ResolveInfo ri = getContext().getPackageManager().
+ resolveActivity(testIntent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (ri != null) {
+ visibility = View.VISIBLE;
+ }
+ }
+ }
+ mVoiceButton.setVisibility(visibility);
+ }
+
+ /**
* Listeners of various types
*/
@@ -642,11 +690,97 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
};
/**
+ * React to a click in the voice search button.
+ */
+ View.OnClickListener mVoiceButtonClickListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ try {
+ if (mSearchable.getVoiceSearchLaunchWebSearch()) {
+ getContext().startActivity(mVoiceWebSearchIntent);
+ dismiss();
+ } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
+ Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent);
+ getContext().startActivity(appSearchIntent);
+ dismiss();
+ }
+ } catch (ActivityNotFoundException e) {
+ // Should not happen, since we check the availability of
+ // voice search before showing the button. But just in case...
+ Log.w(LOG_TAG, "Could not find voice search activity");
+ }
+ }
+ };
+
+ /**
+ * Create and return an Intent that can launch the voice search activity, perform a specific
+ * voice transcription, and forward the results to the searchable activity.
+ *
+ * @param baseIntent The voice app search intent to start from
+ * @return A completely-configured intent ready to send to the voice search activity
+ */
+ private Intent createVoiceAppSearchIntent(Intent baseIntent) {
+ // create the necessary intent to set up a search-and-forward operation
+ // in the voice search system. We have to keep the bundle separate,
+ // because it becomes immutable once it enters the PendingIntent
+ Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
+ queryIntent.setComponent(mSearchable.mSearchActivity);
+ PendingIntent pending = PendingIntent.getActivity(
+ getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT);
+
+ // Now set up the bundle that will be inserted into the pending intent
+ // when it's time to do the search. We always build it here (even if empty)
+ // because the voice search activity will always need to insert "QUERY" into
+ // it anyway.
+ Bundle queryExtras = new Bundle();
+ if (mAppSearchData != null) {
+ queryExtras.putBundle(SearchManager.APP_DATA, mAppSearchData);
+ }
+
+ // Now build the intent to launch the voice search. Add all necessary
+ // extras to launch the voice recognizer, and then all the necessary extras
+ // to forward the results to the searchable activity
+ Intent voiceIntent = new Intent(baseIntent);
+
+ // Add all of the configuration options supplied by the searchable's metadata
+ String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
+ String prompt = null;
+ String language = null;
+ int maxResults = 1;
+ Resources resources = mActivityContext.getResources();
+ if (mSearchable.getVoiceLanguageModeId() != 0) {
+ languageModel = resources.getString(mSearchable.getVoiceLanguageModeId());
+ }
+ if (mSearchable.getVoicePromptTextId() != 0) {
+ prompt = resources.getString(mSearchable.getVoicePromptTextId());
+ }
+ if (mSearchable.getVoiceLanguageId() != 0) {
+ language = resources.getString(mSearchable.getVoiceLanguageId());
+ }
+ if (mSearchable.getVoiceMaxResults() != 0) {
+ maxResults = mSearchable.getVoiceMaxResults();
+ }
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
+
+ // Add the values that configure forwarding the results
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
+
+ return voiceIntent;
+ }
+
+ /**
* React to the user typing "enter" or other hardwired keys while typing in the search box.
* This handles these special keys while the edit box has focus.
*/
View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ cancel();
+ return true;
+ }
// also guard against possible race conditions (late arrival after dismiss)
if (mSearchable != null &&
TextUtils.getTrimmedLength(mSearchTextField.getText()) > 0) {
@@ -661,7 +795,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// otherwise, dispatch an "edit view" key
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_DPAD_CENTER:
if (event.getAction() == KeyEvent.ACTION_UP) {
v.cancelLongPress();
launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
@@ -700,9 +833,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// also guard against possible race conditions (late arrival after dismiss)
if (mSearchable != null) {
handled = doSuggestionsKey(v, keyCode, event);
- if (!handled) {
- handled = refocusingKeyListener(v, keyCode, event);
- }
}
return handled;
}
@@ -793,24 +923,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
};
/**
- * UI-thread handling of dialog dismiss. Called by mBroadcastReceiver.onReceive().
- *
- * TODO: This is a really heavyweight solution for something that should be so simple.
- * For example, we already have a handler, in our superclass, why aren't we sharing that?
- * I think we need to investigate simplifying this entire methodology, or perhaps boosting
- * it up into the Dialog class.
- */
- private static final int MESSAGE_DISMISS = 0;
- private Handler mDismissHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MESSAGE_DISMISS) {
- dismiss();
- }
- }
- };
-
- /**
* Various ways to launch searches
*/
@@ -907,6 +1019,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
* @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) {
+ // quick check against race conditions
+ if (mSearchable == null) {
+ return;
+ }
+
mSuggestionsAdapter.setNonUserQuery(true); // disables any suggestions processing
if (jamQuery) {
CursorAdapter ca = getSuggestionsAdapter(parent);
@@ -1180,10 +1297,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// These private variables are shared by the filter thread and must be protected
private WeakReference<Cursor> mRecentCursor = new WeakReference<Cursor>(null);
private boolean mNonUserQuery = false;
+ private AutoCompleteTextView mParentView;
- public SuggestionsAdapter(Context context, SearchableInfo searchable) {
+ public SuggestionsAdapter(Context context, SearchableInfo searchable,
+ AutoCompleteTextView actv) {
super(context, -1, null, null, null);
mSearchable = searchable;
+ mParentView = actv;
// set up provider resources (gives us icons, etc.)
Context activityContext = mSearchable.getActivityContext(mContext);
@@ -1296,9 +1416,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
to = ONE_LINE_TO;
}
}
+ // Force the underlying ListView to discard and reload all layouts
+ // (Note, this should be optimized for cases where layout/cursor remain same)
+ mParentView.resetListAndClearViews();
// Now actually set up the cursor, columns, and the list view
changeCursorAndColumns(c, from, to);
- setViewResource(layout);
+ 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.");
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 0a37e81..c1d66f4 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -168,7 +168,8 @@ import android.view.KeyEvent;
* <p><b>Managing focus and knowing if Search is active.</b> The search UI is not a separate
* activity, and when the UI is invoked or dismissed, your activity will not typically be paused,
* resumed, or otherwise notified by the methods defined in
- * <a href="android.app.Activity#ActivityLifecycle">Activity Lifecycle</a>. The search UI is
+ * <a href="{@docRoot}guide/topics/fundamentals.html#actlife">Application Fundamentals:
+ * Activity Lifecycle</a>. The search UI is
* handled in the same way as other system UI elements which may appear from time to time, such as
* notifications, screen locks, or other system alerts:
* <p>When the search UI appears, your activity will lose input focus.
@@ -212,11 +213,11 @@ import android.view.KeyEvent;
* {@link #QUERY getStringExtra(SearchManager.QUERY)}.</li>
* <li>To identify and support your searchable activity, you'll need to
* provide an XML file providing searchability configuration parameters, a reference to that
- * in your searchable activity's <a href="../../../devel/bblocks-manifest.html">manifest</a>
+ * in your searchable activity's <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>
* entry, and an intent-filter declaring that you can
* receive ACTION_SEARCH intents. This is described in more detail in the
* <a href="#SearchabilityMetadata">Searchability Metadata</a> section.</li>
- * <li>Your <a href="../../../devel/bblocks-manifest.html">manifest</a> also needs a metadata entry
+ * <li>Your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> also needs a metadata entry
* providing a global reference to the searchable activity. This is the "glue" directing the search
* UI, when invoked from any of your <i>other</i> activities, to use your application as the
* default search context. This is also described in more detail in the
@@ -359,7 +360,7 @@ import android.view.KeyEvent;
* <li>Implement a Content Provider that provides suggestions. If you already have one, and it
* has access to your suggestions data. If not, you'll have to create one.
* You'll also provide information about your Content Provider in your
- * package's <a href="../../../devel/bblocks-manifest.html">manifest</a>.</li>
+ * package's <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>.</li>
* <li>Update your searchable activity's XML configuration file. There are two categories of
* information used for suggestions:
* <ul><li>The first is (required) data that the search manager will
@@ -634,7 +635,7 @@ import android.view.KeyEvent;
*
* <p><b>Metadata for searchable activity.</b> As with your search implementations described
* above, you must first identify which of your activities is searchable. In the
- * <a href="../../../devel/bblocks-manifest.html">manifest</a> entry for this activity, you must
+ * <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> entry for this activity, you must
* provide two elements:
* <ul><li>An intent-filter specifying that you can receive and process the
* {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} {@link android.content.Intent Intent}.
@@ -643,7 +644,7 @@ import android.view.KeyEvent;
* remaining configuration information for how your application implements search.</li></ul>
*
* <p>Here is a snippet showing the necessary elements in the
- * <a href="../../../devel/bblocks-manifest.html">manifest</a> entry for your searchable activity.
+ * <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> entry for your searchable activity.
* <pre class="prettyprint">
* &lt;!-- Search Activity - searchable --&gt;
* &lt;activity android:name="MySearchActivity"
@@ -746,6 +747,14 @@ import android.view.KeyEvent;
* <a href="../R.attr.html#inputType">inputType</a> attribute.</td>
* <td align="center">No</td>
* </tr>
+ * <tr><th>android:imeOptions</th>
+ * <td>If provided, supplies additional options for the input method.
+ * For most searches, in which free form text is expected, this attribute
+ * need not be provided, and will default to "actionSearch".
+ * Suitable values for this attribute are described in the
+ * <a href="../R.attr.html#imeOptions">imeOptions</a> attribute.</td>
+ * <td align="center">No</td>
+ * </tr>
*
* </tbody>
* </table>
@@ -765,9 +774,8 @@ import android.view.KeyEvent;
* <li>.../res/values/strings.xml</li></ul>
*
* <p>For more complete documentation on this capability, see
- * <a href="../../../devel/resources-i18n.html#AlternateResources">Resources and
- * Internationalization: Supporting Alternate Resources for Alternate Languages and Configurations
- * </a>.
+ * <a href="{@docRoot}guide/topics/resources/resources-i18n.html#AlternateResources">Resources and
+ * Internationalization: Alternate Resources</a>.
*
* <p><b>Metadata for non-searchable activities.</b> Activities which are part of a searchable
* application, but don't implement search itself, require a bit of "glue" in order to cause
@@ -775,7 +783,7 @@ import android.view.KeyEvent;
* provided, then searches from these activities will use the system default search context.
*
* <p>The simplest way to specify this is to add a <i>search reference</i> element to the
- * application entry in the <a href="../../../devel/bblocks-manifest.html">manifest</a> file.
+ * application entry in the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> file.
* The value of this reference can be either of:
* <ul><li>The name of your searchable activity.
* It is typically prefixed by '.' to indicate that it's in the same package.</li>
@@ -803,7 +811,7 @@ import android.view.KeyEvent;
* to generate search suggestions, you'll need to publish it to the system, and you'll need to
* provide a bit of additional XML metadata in order to configure communications with it.
*
- * <p>First, in your <a href="../../../devel/bblocks-manifest.html">manifest</a>, you'll add the
+ * <p>First, in your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>, you'll add the
* following lines.
* <pre class="prettyprint">
* &lt;!-- Content provider for search suggestions --&gt;
@@ -832,7 +840,7 @@ import android.view.KeyEvent;
* <tbody>
* <tr><th>android:searchSuggestAuthority</th>
* <td>This value must match the authority string provided in the <i>provider</i> section
- * of your <a href="../../../devel/bblocks-manifest.html">manifest</a>.</td>
+ * of your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>.</td>
* <td align="center">Yes</td>
* </tr>
*
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 6c08e75..a6a436f 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -42,12 +42,12 @@ import java.io.PrintWriter;
* thread of their hosting process. This means that, if your service is going
* to do any CPU intensive (such as MP3 playback) or blocking (such as
* networking) operations, it should spawn its own thread in which to do that
- * work. More information on this can be found in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
- * Application Model overview</a>.</p>
+ * work. More information on this can be found in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.</p>
*
* <p>The Service class is an important part of an
- * <a href="{@docRoot}intro/lifecycle.html">application's overall lifecycle</a>.</p>
+ * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">application's overall lifecycle</a>.</p>
*
* <p>Topics covered here:
* <ol>
@@ -79,7 +79,7 @@ import java.io.PrintWriter;
* to the service. The service will remain running as long as the connection
* is established (whether or not the client retains a reference on the
* service's IBinder). Usually the IBinder returned is for a complex
- * interface that has been <a href="{@docRoot}reference/aidl.html">written
+ * interface that has been <a href="{@docRoot}guide/developing/tools/aidl.html">written
* in aidl</a>.
*
* <p>A service can be both started and have connections bound to it. In such
@@ -106,7 +106,7 @@ import java.io.PrintWriter;
* {@link #checkCallingPermission}
* method before executing the implementation of that call.
*
- * <p>See the <a href="{@docRoot}devel/security.html">Security Model</a>
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
* document for more information on permissions and security in general.
*
* <a name="ProcessLifecycle"></a>
@@ -201,14 +201,14 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* Return the communication channel to the service. May return null if
* clients can not bind to the service. The returned
* {@link android.os.IBinder} is usually for a complex interface
- * that has been <a href="{@docRoot}reference/aidl.html">described using
+ * that has been <a href="{@docRoot}guide/developing/tools/aidl.html">described using
* aidl</a>.
*
* <p><em>Note that unlike other application components, calls on to the
* IBinder interface returned here may not happen on the main thread
* of the process</em>. More information about this can be found
- * in the <a href="{@docRoot}intro/appmodel.html#Threads">Threading section
- * of the Application Model overview</a>.</p>
+ * in <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.</p>
*
* @param intent The Intent that was used to bind to this service,
* as given to {@link android.content.Context#bindService
@@ -327,11 +327,15 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
}
/**
- * Print the Service's state into the given stream.
+ * Print the Service's state into the given stream. This gets invoked if
+ * you run "adb shell dumpsys activity service <yourservicename>".
+ * This is distinct from "dumpsys <servicename>", which only works for
+ * named system services and which invokes the {@link IBinder#dump} method
+ * on the {@link IBinder} interface registered with ServiceManager.
*
* @param fd The raw file descriptor that the dump is being sent to.
* @param writer The PrintWriter to which you should dump your state. This will be
- * closed for you after you return.
+ * closed for you after you return.
* @param args additional arguments to the dump request.
*/
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
new file mode 100644
index 0000000..10c2b02
--- /dev/null
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -0,0 +1,248 @@
+/*
+ *
+ * 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.appwidget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import com.android.internal.appwidget.IAppWidgetHost;
+import com.android.internal.appwidget.IAppWidgetService;
+
+/**
+ * AppWidgetHost provides the interaction with the AppWidget service for apps,
+ * like the home screen, that want to embed AppWidgets in their UI.
+ */
+public class AppWidgetHost {
+
+ static final int HANDLE_UPDATE = 1;
+ static final int HANDLE_PROVIDER_CHANGED = 2;
+
+ static Object sServiceLock = new Object();
+ static IAppWidgetService sService;
+
+ Context mContext;
+ String mPackageName;
+
+ class Callbacks extends IAppWidgetHost.Stub {
+ public void updateAppWidget(int appWidgetId, RemoteViews views) {
+ Message msg = mHandler.obtainMessage(HANDLE_UPDATE);
+ msg.arg1 = appWidgetId;
+ msg.obj = views;
+ msg.sendToTarget();
+ }
+
+ public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
+ Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);
+ msg.arg1 = appWidgetId;
+ msg.obj = info;
+ msg.sendToTarget();
+ }
+ }
+
+ class UpdateHandler extends Handler {
+ public UpdateHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case HANDLE_UPDATE: {
+ updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
+ break;
+ }
+ case HANDLE_PROVIDER_CHANGED: {
+ onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
+ break;
+ }
+ }
+ }
+ }
+
+ Handler mHandler;
+
+ int mHostId;
+ Callbacks mCallbacks = new Callbacks();
+ HashMap<Integer,AppWidgetHostView> mViews = new HashMap();
+
+ public AppWidgetHost(Context context, int hostId) {
+ mContext = context;
+ mHostId = hostId;
+ mHandler = new UpdateHandler(context.getMainLooper());
+ synchronized (sServiceLock) {
+ if (sService == null) {
+ IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
+ sService = IAppWidgetService.Stub.asInterface(b);
+ }
+ }
+ }
+
+ /**
+ * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity
+ * becomes visible, i.e. from onStart() in your Activity.
+ */
+ public void startListening() {
+ int[] updatedIds = null;
+ ArrayList<RemoteViews> updatedViews = new ArrayList();
+
+ try {
+ if (mPackageName == null) {
+ mPackageName = mContext.getPackageName();
+ }
+ updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+
+ final int N = updatedIds.length;
+ for (int i=0; i<N; i++) {
+ updateAppWidgetView(updatedIds[i], updatedViews.get(i));
+ }
+ }
+
+ /**
+ * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is
+ * no longer visible, i.e. from onStop() in your Activity.
+ */
+ public void stopListening() {
+ try {
+ sService.stopListening(mHostId);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Get a appWidgetId for a host in the calling process.
+ *
+ * @return a appWidgetId
+ */
+ public int allocateAppWidgetId() {
+ try {
+ if (mPackageName == null) {
+ mPackageName = mContext.getPackageName();
+ }
+ return sService.allocateAppWidgetId(mPackageName, mHostId);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Stop listening to changes for this AppWidget.
+ */
+ public void deleteAppWidgetId(int appWidgetId) {
+ synchronized (mViews) {
+ mViews.remove(appWidgetId);
+ try {
+ sService.deleteAppWidgetId(appWidgetId);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+ }
+
+ /**
+ * Remove all records about this host from the AppWidget manager.
+ * <ul>
+ * <li>Call this when initializing your database, as it might be because of a data wipe.</li>
+ * <li>Call this to have the AppWidget manager release all resources associated with your
+ * host. Any future calls about this host will cause the records to be re-allocated.</li>
+ * </ul>
+ */
+ public void deleteHost() {
+ try {
+ sService.deleteHost(mHostId);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Remove all records about all hosts for your package.
+ * <ul>
+ * <li>Call this when initializing your database, as it might be because of a data wipe.</li>
+ * <li>Call this to have the AppWidget manager release all resources associated with your
+ * host. Any future calls about this host will cause the records to be re-allocated.</li>
+ * </ul>
+ */
+ public static void deleteAllHosts() {
+ try {
+ sService.deleteAllHosts();
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ public final AppWidgetHostView createView(Context context, int appWidgetId,
+ AppWidgetProviderInfo appWidget) {
+ AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
+ view.setAppWidget(appWidgetId, appWidget);
+ synchronized (mViews) {
+ mViews.put(appWidgetId, view);
+ }
+ RemoteViews views = null;
+ try {
+ views = sService.getAppWidgetViews(appWidgetId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ view.updateAppWidget(views);
+ return view;
+ }
+
+ /**
+ * Called to create the AppWidgetHostView. Override to return a custom subclass if you
+ * need it. {@more}
+ */
+ protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+ AppWidgetProviderInfo appWidget) {
+ return new AppWidgetHostView(context);
+ }
+
+ /**
+ * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
+ */
+ protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
+ }
+
+ void updateAppWidgetView(int appWidgetId, RemoteViews views) {
+ AppWidgetHostView v;
+ synchronized (mViews) {
+ v = mViews.get(appWidgetId);
+ }
+ if (v != null) {
+ v.updateAppWidget(views);
+ }
+ }
+}
+
+
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
new file mode 100644
index 0000000..be0f96e
--- /dev/null
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -0,0 +1,312 @@
+/*
+ * 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.appwidget;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Config;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+/**
+ * Provides the glue to show AppWidget views. This class offers automatic animation
+ * between updates, and will try recycling old views for each incoming
+ * {@link RemoteViews}.
+ */
+public class AppWidgetHostView extends FrameLayout {
+ static final String TAG = "AppWidgetHostView";
+ static final boolean LOGD = false;
+ static final boolean CROSSFADE = false;
+
+ static final int VIEW_MODE_NOINIT = 0;
+ static final int VIEW_MODE_CONTENT = 1;
+ static final int VIEW_MODE_ERROR = 2;
+ static final int VIEW_MODE_DEFAULT = 3;
+
+ static final int FADE_DURATION = 1000;
+
+ // When we're inflating the initialLayout for a AppWidget, we only allow
+ // views that are allowed in RemoteViews.
+ static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
+ public boolean onLoadClass(Class clazz) {
+ return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
+ }
+ };
+
+ Context mContext;
+
+ int mAppWidgetId;
+ AppWidgetProviderInfo mInfo;
+ View mView;
+ int mViewMode = VIEW_MODE_NOINIT;
+ int mLayoutId = -1;
+ long mFadeStartTime = -1;
+ Bitmap mOld;
+ Paint mOldPaint = new Paint();
+
+ /**
+ * Create a host view. Uses default fade animations.
+ */
+ public AppWidgetHostView(Context context) {
+ this(context, android.R.anim.fade_in, android.R.anim.fade_out);
+ }
+
+ /**
+ * Create a host view. Uses specified animations when pushing
+ * {@link #updateAppWidget(RemoteViews)}.
+ *
+ * @param animationIn Resource ID of in animation to use
+ * @param animationOut Resource ID of out animation to use
+ */
+ public AppWidgetHostView(Context context, int animationIn, int animationOut) {
+ super(context);
+ mContext = context;
+ }
+
+ /**
+ * Set the AppWidget that will be displayed by this view.
+ */
+ public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
+ mAppWidgetId = appWidgetId;
+ mInfo = info;
+ }
+
+ public int getAppWidgetId() {
+ return mAppWidgetId;
+ }
+
+ public AppWidgetProviderInfo getAppWidgetInfo() {
+ return mInfo;
+ }
+
+ /**
+ * Process a set of {@link RemoteViews} coming in as an update from the
+ * AppWidget provider. Will animate into these new views as needed.
+ */
+ public void updateAppWidget(RemoteViews remoteViews) {
+ if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
+
+ boolean recycled = false;
+ View content = null;
+ Exception exception = null;
+
+ // Capture the old view into a bitmap so we can do the crossfade.
+ if (CROSSFADE) {
+ if (mFadeStartTime < 0) {
+ if (mView != null) {
+ final int width = mView.getWidth();
+ final int height = mView.getHeight();
+ try {
+ mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ } catch (OutOfMemoryError e) {
+ // we just won't do the fade
+ mOld = null;
+ }
+ if (mOld != null) {
+ //mView.drawIntoBitmap(mOld);
+ }
+ }
+ }
+ }
+
+ if (remoteViews == null) {
+ if (mViewMode == VIEW_MODE_DEFAULT) {
+ // We've already done this -- nothing to do.
+ return;
+ }
+ content = getDefaultView();
+ mLayoutId = -1;
+ mViewMode = VIEW_MODE_DEFAULT;
+ } else {
+ int layoutId = remoteViews.getLayoutId();
+
+ // If our stale view has been prepared to match active, and the new
+ // layout matches, try recycling it
+ if (content == null && layoutId == mLayoutId) {
+ try {
+ remoteViews.reapply(mContext, mView);
+ content = mView;
+ recycled = true;
+ if (LOGD) Log.d(TAG, "was able to recycled existing layout");
+ } catch (RuntimeException e) {
+ exception = e;
+ }
+ }
+
+ // Try normal RemoteView inflation
+ if (content == null) {
+ try {
+ content = remoteViews.apply(mContext, this);
+ if (LOGD) Log.d(TAG, "had to inflate new layout");
+ } catch (RuntimeException e) {
+ exception = e;
+ }
+ }
+
+ mLayoutId = layoutId;
+ mViewMode = VIEW_MODE_CONTENT;
+ }
+
+ if (content == null) {
+ if (mViewMode == VIEW_MODE_ERROR) {
+ // We've already done this -- nothing to do.
+ return ;
+ }
+ Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);
+ content = getErrorView();
+ mViewMode = VIEW_MODE_ERROR;
+ }
+
+ if (!recycled) {
+ prepareView(content);
+ addView(content);
+ }
+
+ if (mView != content) {
+ removeView(mView);
+ mView = content;
+ }
+
+ if (CROSSFADE) {
+ if (mFadeStartTime < 0) {
+ // if there is already an animation in progress, don't do anything --
+ // the new view will pop in on top of the old one during the cross fade,
+ // and that looks okay.
+ mFadeStartTime = SystemClock.uptimeMillis();
+ invalidate();
+ }
+ }
+ }
+
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (CROSSFADE) {
+ int alpha;
+ int l = child.getLeft();
+ int t = child.getTop();
+ if (mFadeStartTime > 0) {
+ alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION);
+ if (alpha > 255) {
+ alpha = 255;
+ }
+ Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t
+ + " w=" + child.getWidth());
+ if (alpha != 255 && mOld != null) {
+ mOldPaint.setAlpha(255-alpha);
+ //canvas.drawBitmap(mOld, l, t, mOldPaint);
+ }
+ } else {
+ alpha = 255;
+ }
+ int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha,
+ Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+ boolean rv = super.drawChild(canvas, child, drawingTime);
+ canvas.restoreToCount(restoreTo);
+ if (alpha < 255) {
+ invalidate();
+ } else {
+ mFadeStartTime = -1;
+ if (mOld != null) {
+ mOld.recycle();
+ mOld = null;
+ }
+ }
+ return rv;
+ } else {
+ return super.drawChild(canvas, child, drawingTime);
+ }
+ }
+
+ /**
+ * Prepare the given view to be shown. This might include adjusting
+ * {@link FrameLayout.LayoutParams} before inserting.
+ */
+ protected void prepareView(View view) {
+ // Take requested dimensions from parent, but apply default gravity.
+ ViewGroup.LayoutParams requested = view.getLayoutParams();
+ if (requested == null) {
+ requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT,
+ LayoutParams.FILL_PARENT);
+ }
+
+ FrameLayout.LayoutParams params =
+ new FrameLayout.LayoutParams(requested.width, requested.height);
+ params.gravity = Gravity.CENTER;
+ view.setLayoutParams(params);
+ }
+
+ /**
+ * Inflate and return the default layout requested by AppWidget provider.
+ */
+ protected View getDefaultView() {
+ View defaultView = null;
+ Exception exception = null;
+
+ try {
+ if (mInfo != null) {
+ Context theirContext = mContext.createPackageContext(
+ mInfo.provider.getPackageName(), 0 /* no flags */);
+ LayoutInflater inflater = (LayoutInflater)
+ theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater = inflater.cloneInContext(theirContext);
+ inflater.setFilter(sInflaterFilter);
+ defaultView = inflater.inflate(mInfo.initialLayout, this, false);
+ } else {
+ Log.w(TAG, "can't inflate defaultView because mInfo is missing");
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ exception = e;
+ } catch (RuntimeException e) {
+ exception = e;
+ }
+
+ if (exception != null && LOGD) {
+ Log.w(TAG, "Error inflating AppWidget " + mInfo, exception);
+ }
+
+ if (defaultView == null) {
+ if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
+ defaultView = getErrorView();
+ }
+
+ return defaultView;
+ }
+
+ /**
+ * Inflate and return a view that represents an error state.
+ */
+ protected View getErrorView() {
+ TextView tv = new TextView(mContext);
+ tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
+ // TODO: get this color from somewhere.
+ tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
+ return tv;
+ }
+}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
new file mode 100644
index 0000000..3b10ed2
--- /dev/null
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -0,0 +1,320 @@
+/*
+ * 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.appwidget;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.internal.appwidget.IAppWidgetService;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.WeakHashMap;
+
+/**
+ * Updates AppWidget state; gets information about installed AppWidget providers and other
+ * AppWidget related state.
+ */
+public class AppWidgetManager {
+ static final String TAG = "AppWidgetManager";
+
+ /**
+ * Send this from your {@link AppWidgetHost} activity when you want to pick an AppWidget to display.
+ * The AppWidget picker activity will be launched.
+ * <p>
+ * You must supply the following extras:
+ * <table>
+ * <tr>
+ * <td>{@link #EXTRA_APPWIDGET_ID}</td>
+ * <td>A newly allocated appWidgetId, which will be bound to the AppWidget provider
+ * once the user has selected one.</td>
+ * </tr>
+ * </table>
+ *
+ * <p>
+ * The system will respond with an onActivityResult call with the following extras in
+ * the intent:
+ * <table>
+ * <tr>
+ * <td>{@link #EXTRA_APPWIDGET_ID}</td>
+ * <td>The appWidgetId that you supplied in the original intent.</td>
+ * </tr>
+ * </table>
+ * <p>
+ * When you receive the result from the AppWidget pick activity, if the resultCode is
+ * {@link android.app.Activity#RESULT_OK}, an AppWidget has been selected. You should then
+ * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its configuration
+ * activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you should delete
+ * the appWidgetId.
+ *
+ * @see #ACTION_APPWIDGET_CONFIGURE
+ */
+ public static final String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK";
+
+ /**
+ * Sent when it is time to configure your AppWidget while it is being added to a host.
+ * This action is not sent as a broadcast to the AppWidget provider, but as a startActivity
+ * to the activity specified in the {@link AppWidgetProviderInfo AppWidgetProviderInfo meta-data}.
+ *
+ * <p>
+ * The intent will contain the following extras:
+ * <table>
+ * <tr>
+ * <td>{@link #EXTRA_APPWIDGET_ID}</td>
+ * <td>The appWidgetId to configure.</td>
+ * </tr>
+ * </table>
+ *
+ * <p>If you return {@link android.app.Activity#RESULT_OK} using
+ * {@link android.app.Activity#setResult Activity.setResult()}, the AppWidget will be added,
+ * and you will receive an {@link #ACTION_APPWIDGET_UPDATE} broadcast for this AppWidget.
+ * If you return {@link android.app.Activity#RESULT_CANCELED}, the host will cancel the add
+ * and not display this AppWidget, and you will receive a {@link #ACTION_APPWIDGET_DELETED} broadcast.
+ */
+ public static final String ACTION_APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE";
+
+ /**
+ * An intent extra that contains one appWidgetId.
+ * <p>
+ * The value will be an int that can be retrieved like this:
+ * {@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/src/com/android/tests/appwidgethost/AppWidgetHostActivity.java getExtra_EXTRA_APPWIDGET_ID}
+ */
+ public static final String EXTRA_APPWIDGET_ID = "appWidgetId";
+
+ /**
+ * An intent extra that contains multiple appWidgetIds.
+ * <p>
+ * The value will be an int array that can be retrieved like this:
+ * {@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/src/com/android/tests/appwidgethost/TestAppWidgetProvider.java getExtra_EXTRA_APPWIDGET_IDS}
+ */
+ public static final String EXTRA_APPWIDGET_IDS = "appWidgetIds";
+
+ /**
+ * A sentiel value that the AppWidget manager will never return as a appWidgetId.
+ */
+ public static final int INVALID_APPWIDGET_ID = 0;
+
+ /**
+ * Sent when it is time to update your AppWidget.
+ *
+ * <p>This may be sent in response to a new instance for this AppWidget provider having
+ * been instantiated, the requested {@link AppWidgetProviderInfo#updatePeriodMillis update interval}
+ * having lapsed, or the system booting.
+ *
+ * <p>
+ * The intent will contain the following extras:
+ * <table>
+ * <tr>
+ * <td>{@link #EXTRA_APPWIDGET_IDS}</td>
+ * <td>The appWidgetIds to update. This may be all of the AppWidgets created for this
+ * provider, or just a subset. The system tries to send updates for as few AppWidget
+ * instances as possible.</td>
+ * </tr>
+ * </table>
+ *
+ * @see AppWidgetProvider#onUpdate AppWidgetProvider.onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
+ */
+ public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
+
+ /**
+ * Sent when an instance of an AppWidget is deleted from its host.
+ *
+ * @see AppWidgetProvider#onDeleted AppWidgetProvider.onDeleted(Context context, int[] appWidgetIds)
+ */
+ public static final String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED";
+
+ /**
+ * Sent when an instance of an AppWidget is removed from the last host.
+ *
+ * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context)
+ */
+ public static final String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED";
+
+ /**
+ * Sent when an instance of an AppWidget is added to a host for the first time.
+ * This broadcast is sent at boot time if there is a AppWidgetHost installed with
+ * an instance for this provider.
+ *
+ * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context)
+ */
+ public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
+
+ /**
+ * Field for the manifest meta-data tag.
+ *
+ * @see AppWidgetProviderInfo
+ */
+ public static final String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider";
+
+ static WeakHashMap<Context, WeakReference<AppWidgetManager>> sManagerCache = new WeakHashMap();
+ static IAppWidgetService sService;
+
+ Context mContext;
+
+ /**
+ * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
+ * Context} object.
+ */
+ public static AppWidgetManager getInstance(Context context) {
+ synchronized (sManagerCache) {
+ if (sService == null) {
+ IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
+ sService = IAppWidgetService.Stub.asInterface(b);
+ }
+
+ WeakReference<AppWidgetManager> ref = sManagerCache.get(context);
+ AppWidgetManager result = null;
+ if (ref != null) {
+ result = ref.get();
+ }
+ if (result == null) {
+ result = new AppWidgetManager(context);
+ sManagerCache.put(context, new WeakReference(result));
+ }
+ return result;
+ }
+ }
+
+ private AppWidgetManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Set the RemoteViews to use for the specified appWidgetIds.
+ *
+ * <p>
+ * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
+ * and outside of the handler.
+ * This method will only work when called from the uid that owns the AppWidget provider.
+ *
+ * @param appWidgetIds The AppWidget instances for which to set the RemoteViews.
+ * @param views The RemoteViews object to show.
+ */
+ public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
+ try {
+ sService.updateAppWidgetIds(appWidgetIds, views);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Set the RemoteViews to use for the specified appWidgetId.
+ *
+ * <p>
+ * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
+ * and outside of the handler.
+ * This method will only work when called from the uid that owns the AppWidget provider.
+ *
+ * @param appWidgetId The AppWidget instance for which to set the RemoteViews.
+ * @param views The RemoteViews object to show.
+ */
+ public void updateAppWidget(int appWidgetId, RemoteViews views) {
+ updateAppWidget(new int[] { appWidgetId }, views);
+ }
+
+ /**
+ * Set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider.
+ *
+ * <p>
+ * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
+ * and outside of the handler.
+ * This method will only work when called from the uid that owns the AppWidget provider.
+ *
+ * @param provider The {@link ComponentName} for the {@link
+ * android.content.BroadcastReceiver BroadcastReceiver} provider
+ * for your AppWidget.
+ * @param views The RemoteViews object to show.
+ */
+ public void updateAppWidget(ComponentName provider, RemoteViews views) {
+ try {
+ sService.updateAppWidgetProvider(provider, views);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Return a list of the AppWidget providers that are currently installed.
+ */
+ public List<AppWidgetProviderInfo> getInstalledProviders() {
+ try {
+ return sService.getInstalledProviders();
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Get the available info about the AppWidget.
+ *
+ * @return A appWidgetId. If the appWidgetId has not been bound to a provider yet, or
+ * you don't have access to that appWidgetId, null is returned.
+ */
+ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
+ try {
+ return sService.getAppWidgetInfo(appWidgetId);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Set the component for a given appWidgetId.
+ *
+ * <p class="note">You need the APPWIDGET_LIST permission. This method is to be used by the
+ * AppWidget picker.
+ *
+ * @param appWidgetId The AppWidget instance for which to set the RemoteViews.
+ * @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget
+ * provider for this AppWidget.
+ */
+ public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
+ try {
+ sService.bindAppWidgetId(appWidgetId, provider);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Get the list of appWidgetIds that have been bound to the given AppWidget
+ * provider.
+ *
+ * @param provider The {@link android.content.BroadcastReceiver} that is the
+ * AppWidget provider to find appWidgetIds for.
+ */
+ public int[] getAppWidgetIds(ComponentName provider) {
+ try {
+ return sService.getAppWidgetIds(provider);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+}
+
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
new file mode 100755
index 0000000..f70de9c
--- /dev/null
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -0,0 +1,154 @@
+/*
+ * 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.appwidget;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A conveience class to aid in implementing an AppWidget provider.
+ * Everything you can do with AppWidgetProvider, you can do with a regular {@link BroadcastReceiver}.
+ * AppWidgetProvider merely parses the relevant fields out of the Intent that is received in
+ * {@link #onReceive(Context,Intent) onReceive(Context,Intent)}, and calls hook methods
+ * with the received extras.
+ *
+ * <p>Extend this class and override one or more of the {@link #onUpdate}, {@link #onDeleted},
+ * {@link #onEnabled} or {@link #onDisabled} methods to implement your own AppWidget functionality.
+ *
+ * <h3>Sample Code</h3>
+ * For an example of how to write a AppWidget provider, see the
+ * <a href="{@toroot}reference/android/appwidget/package-descr.html#providers">android.appwidget
+ * package overview</a>.
+ */
+public class AppWidgetProvider extends BroadcastReceiver {
+ /**
+ * Constructor to initialize AppWidgetProvider.
+ */
+ public AppWidgetProvider() {
+ }
+
+ /**
+ * Implements {@link BroadcastReceiver#onReceive} to dispatch calls to the various
+ * other methods on AppWidgetProvider.
+ *
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
+ // BEGIN_INCLUDE(onReceive)
+ public void onReceive(Context context, Intent intent) {
+ // Protect against rogue update broadcasts (not really a security issue,
+ // just filter bad broacasts out so subclasses are less likely to crash).
+ String action = intent.getAction();
+ if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
+ if (appWidgetIds != null && appWidgetIds.length > 0) {
+ this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
+ }
+ }
+ }
+ else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
+ if (appWidgetIds != null && appWidgetIds.length > 0) {
+ this.onDeleted(context, appWidgetIds);
+ }
+ }
+ }
+ else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
+ this.onEnabled(context);
+ }
+ else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
+ this.onDisabled(context);
+ }
+ }
+ // END_INCLUDE(onReceive)
+
+ /**
+ * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcast when
+ * this AppWidget provider is being asked to provide {@link android.widget.RemoteViews RemoteViews}
+ * for a set of AppWidgets. Override this method to implement your own AppWidget functionality.
+ *
+ * {@more}
+ *
+ * @param context The {@link android.content.Context Context} in which this receiver is
+ * running.
+ * @param appWidgetManager A {@link AppWidgetManager} object you can call {@link
+ * AppWidgetManager#updateAppWidget} on.
+ * @param appWidgetIds The appWidgetIds for which an update is needed. Note that this
+ * may be all of the AppWidget instances for this provider, or just
+ * a subset of them.
+ *
+ * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+ */
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ }
+
+ /**
+ * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_DELETED} broadcast when
+ * one or more AppWidget instances have been deleted. Override this method to implement
+ * your own AppWidget functionality.
+ *
+ * {@more}
+ *
+ * @param context The {@link android.content.Context Context} in which this receiver is
+ * running.
+ * @param appWidgetIds The appWidgetIds that have been deleted from their host.
+ *
+ * @see AppWidgetManager#ACTION_APPWIDGET_DELETED
+ */
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ }
+
+ /**
+ * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_ENABLED} broadcast when
+ * the a AppWidget for this provider is instantiated. Override this method to implement your
+ * own AppWidget functionality.
+ *
+ * {@more}
+ * When the last AppWidget for this provider is deleted,
+ * {@link AppWidgetManager#ACTION_APPWIDGET_DISABLED} is sent by the AppWidget manager, and
+ * {@link #onDisabled} is called. If after that, an AppWidget for this provider is created
+ * again, onEnabled() will be called again.
+ *
+ * @param context The {@link android.content.Context Context} in which this receiver is
+ * running.
+ *
+ * @see AppWidgetManager#ACTION_APPWIDGET_ENABLED
+ */
+ public void onEnabled(Context context) {
+ }
+
+ /**
+ * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_DISABLED} broadcast, which
+ * is sent when the last AppWidget instance for this provider is deleted. Override this method
+ * to implement your own AppWidget functionality.
+ *
+ * {@more}
+ *
+ * @param context The {@link android.content.Context Context} in which this receiver is
+ * running.
+ *
+ * @see AppWidgetManager#ACTION_APPWIDGET_DISABLED
+ */
+ public void onDisabled(Context context) {
+ }
+}
diff --git a/core/java/android/os/HandlerInterface.java b/core/java/android/appwidget/AppWidgetProviderInfo.aidl
index 62dc273..82b3ada 100644
--- a/core/java/android/os/HandlerInterface.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.aidl
@@ -1,27 +1,19 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * 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
+ * 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.
*/
-package android.os;
-
-/**
- * @hide
- * @deprecated
- */
-public interface HandlerInterface
-{
- void handleMessage(Message msg);
-}
+package android.appwidget;
+parcelable AppWidgetProviderInfo;
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
new file mode 100644
index 0000000..8530c35
--- /dev/null
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -0,0 +1,168 @@
+/*
+ * 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.appwidget;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.content.ComponentName;
+
+/**
+ * Describes the meta data for an installed AppWidget provider. The fields in this class
+ * correspond to the fields in the <code>&lt;appwidget-provider&gt;</code> xml tag.
+ */
+public class AppWidgetProviderInfo implements Parcelable {
+ /**
+ * Identity of this AppWidget component. This component should be a {@link
+ * android.content.BroadcastReceiver}, and it will be sent the AppWidget intents
+ * {@link android.appwidget as described in the AppWidget package documentation}.
+ *
+ * <p>This field corresponds to the <code>android:name</code> attribute in
+ * the <code>&lt;receiver&gt;</code> element in the AndroidManifest.xml file.
+ */
+ public ComponentName provider;
+
+ /**
+ * Minimum width of the AppWidget, in dp.
+ *
+ * <p>This field corresponds to the <code>android:minWidth</code> attribute in
+ * the AppWidget meta-data file.
+ */
+ public int minWidth;
+
+ /**
+ * Minimum height of the AppWidget, in dp.
+ *
+ * <p>This field corresponds to the <code>android:minHeight</code> attribute in
+ * the AppWidget meta-data file.
+ */
+ public int minHeight;
+
+ /**
+ * How often, in milliseconds, that this AppWidget wants to be updated.
+ * The AppWidget manager may place a limit on how often a AppWidget is updated.
+ *
+ * <p>This field corresponds to the <code>android:updatePeriodMillis</code> attribute in
+ * the AppWidget meta-data file.
+ */
+ public int updatePeriodMillis;
+
+ /**
+ * The resource id of the initial layout for this AppWidget. This should be
+ * displayed until the RemoteViews for the AppWidget is available.
+ *
+ * <p>This field corresponds to the <code>android:initialLayout</code> attribute in
+ * the AppWidget meta-data file.
+ */
+ public int initialLayout;
+
+ /**
+ * The activity to launch that will configure the AppWidget.
+ *
+ * <p>This class name of field corresponds to the <code>android:configure</code> attribute in
+ * the AppWidget meta-data file. The package name always corresponds to the package containing
+ * the AppWidget provider.
+ */
+ public ComponentName configure;
+
+ /**
+ * The label to display to the user in the AppWidget picker. If not supplied in the
+ * xml, the application label will be used.
+ *
+ * <p>This field corresponds to the <code>android:label</code> attribute in
+ * the <code>&lt;receiver&gt;</code> element in the AndroidManifest.xml file.
+ */
+ public String label;
+
+ /**
+ * The icon to display for this AppWidget in the AppWidget picker. If not supplied in the
+ * xml, the application icon will be used.
+ *
+ * <p>This field corresponds to the <code>android:icon</code> attribute in
+ * the <code>&lt;receiver&gt;</code> element in the AndroidManifest.xml file.
+ */
+ public int icon;
+
+ public AppWidgetProviderInfo() {
+ }
+
+ /**
+ * Unflatten the AppWidgetProviderInfo from a parcel.
+ */
+ public AppWidgetProviderInfo(Parcel in) {
+ if (0 != in.readInt()) {
+ this.provider = new ComponentName(in);
+ }
+ this.minWidth = in.readInt();
+ this.minHeight = in.readInt();
+ this.updatePeriodMillis = in.readInt();
+ this.initialLayout = in.readInt();
+ if (0 != in.readInt()) {
+ this.configure = new ComponentName(in);
+ }
+ this.label = in.readString();
+ this.icon = in.readInt();
+ }
+
+
+ public void writeToParcel(android.os.Parcel out, int flags) {
+ if (this.provider != null) {
+ out.writeInt(1);
+ this.provider.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(this.minWidth);
+ out.writeInt(this.minHeight);
+ out.writeInt(this.updatePeriodMillis);
+ out.writeInt(this.initialLayout);
+ if (this.configure != null) {
+ out.writeInt(1);
+ this.configure.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeString(this.label);
+ out.writeInt(this.icon);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable.Creator that instantiates AppWidgetProviderInfo objects
+ */
+ public static final Parcelable.Creator<AppWidgetProviderInfo> CREATOR
+ = new Parcelable.Creator<AppWidgetProviderInfo>()
+ {
+ public AppWidgetProviderInfo createFromParcel(Parcel parcel)
+ {
+ return new AppWidgetProviderInfo(parcel);
+ }
+
+ public AppWidgetProviderInfo[] newArray(int size)
+ {
+ return new AppWidgetProviderInfo[size];
+ }
+ };
+
+ public String toString() {
+ return "AppWidgetProviderInfo(provider=" + this.provider + ")";
+ }
+}
+
+
diff --git a/core/java/android/appwidget/package.html b/core/java/android/appwidget/package.html
new file mode 100644
index 0000000..b6cd9c7
--- /dev/null
+++ b/core/java/android/appwidget/package.html
@@ -0,0 +1,136 @@
+<body>
+<p>Android allows applications to publish views to be embedded in other applications. These
+views are called widgets, and are published by "AppWidget providers." The component that can
+contain widgets is called a "AppWidget host."
+</p>
+<h3><a href="package-descr.html#providers">AppWidget Providers</a></h3>
+<ul>
+ <li><a href="package-descr.html#provider_manifest">Declaring a widget in the AndroidManifest</a></li>
+ <li><a href="package-descr.html#provider_meta_data">Adding the AppWidgetProviderInfo meta-data</a></li>
+ <li><a href="package-descr.html#provider_AppWidgetProvider">Using the AppWidgetProvider class</a></li>
+ <li><a href="package-descr.html#provider_configuration">AppWidget Configuration UI</a></li>
+ <li><a href="package-descr.html#provider_broadcasts">AppWidget Broadcast Intents</a></li>
+</ul>
+<h3><a href="package-descr.html#">AppWidget Hosts</a></h3>
+
+
+{@more}
+
+
+<h2><a name="providers"></a>AppWidget Providers</h2>
+<p>
+Any application can publish widgets. All an application needs to do to publish a widget is
+to have a {@link android.content.BroadcastReceiver} that receives the {@link
+android.appwidget.AppWidgetManager#ACTION_APPWIDGET_UPDATE AppWidgetManager.ACTION_APPWIDGET_UPDATE} intent,
+and provide some meta-data about the widget. Android provides the
+{@link android.appwidget.AppWidgetProvider} class, which extends BroadcastReceiver, as a convenience
+class to aid in handling the broadcasts.
+
+<h3><a name="provider_manifest"></a>Declaring a widget in the AndroidManifest</h3>
+
+<p>
+First, declare the {@link android.content.BroadcastReceiver} in your application's
+<code>AndroidManifest.xml</code> file.
+
+{@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/AndroidManifest.xml AppWidgetProvider}
+
+<p>
+The <b><code>&lt;receiver&gt;</b> element has the following attributes:
+<ul>
+ <li><b><code>android:name</code> -</b> which specifies the
+ {@link android.content.BroadcastReceiver} or {@link android.appwidget.AppWidgetProvider}
+ class.</li>
+ <li><b><code>android:label</code> -</b> which specifies the string resource that
+ will be shown by the widget picker as the label.</li>
+ <li><b><code>android:icon</code> -</b> which specifies the drawable resource that
+ will be shown by the widget picker as the icon.</li>
+</ul>
+
+<p>
+The <b><code>&lt;intent-filter&gt;</b> element tells the {@link android.content.pm.PackageManager}
+that this {@link android.content.BroadcastReceiver} receives the {@link
+android.appwidget.AppWidgetManager#ACTION_APPWIDGET_UPDATE AppWidgetManager.ACTION_APPWIDGET_UPDATE} broadcast.
+The widget manager will send other broadcasts directly to your widget provider as required.
+It is only necessary to explicitly declare that you accept the {@link
+android.appwidget.AppWidgetManager#ACTION_APPWIDGET_UPDATE AppWidgetManager.ACTION_APPWIDGET_UPDATE} broadcast.
+
+<p>
+The <b><code>&lt;meta-data&gt;</code></b> element tells the widget manager which xml resource to
+read to find the {@link android.appwidget.AppWidgetProviderInfo} for your widget provider. It has the following
+attributes:
+<ul>
+ <li><b><code>android:name="android.appwidget.provider"</code> -</b> identifies this meta-data
+ as the {@link android.appwidget.AppWidgetProviderInfo} descriptor.</li>
+ <li><b><code>android:resource</code> -</b> is the xml resource to use as that descriptor.</li>
+</ul>
+
+
+<h3><a name="provider_meta_data"></a>Adding the {@link android.appwidget.AppWidgetProviderInfo AppWidgetProviderInfo} meta-data</h3>
+
+<p>
+For a widget, the values in the {@link android.appwidget.AppWidgetProviderInfo} structure are supplied
+in an XML resource. In the example above, the xml resource is referenced with
+<code>android:resource="@xml/appwidget_info"</code>. That XML file would go in your application's
+directory at <code>res/xml/appwidget_info.xml</code>. Here is a simple example.
+
+{@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/res/xml/appwidget_info.xml AppWidgetProviderInfo}
+
+<p>
+The attributes are as documented in the {@link android.appwidget.AppWidgetProviderInfo GagetInfo} class. (86400000 milliseconds means once per day)
+
+
+<h3><a name="provider_AppWidgetProvider"></a>Using the {@link android.appwidget.AppWidgetProvider AppWidgetProvider} class</h3>
+
+<p>The AppWidgetProvider class is the easiest way to handle the widget provider intent broadcasts.
+See the <code>src/com/example/android/apis/appwidget/ExampleAppWidgetProvider.java</code>
+sample class in ApiDemos for an example.
+
+<p class="note">Keep in mind that since the the AppWidgetProvider is a BroadcastReceiver,
+your process is not guaranteed to keep running after the callback methods return. See
+<a href="../../../guide/topics/fundamentals.html#broadlife">Application Fundamentals &gt;
+Broadcast Receiver Lifecycle</a> for more information.
+
+
+
+<h3><a name="provider_configuration"></a>AppWidget Configuration UI</h3>
+
+<p>
+Widget hosts have the ability to start a configuration activity when a widget is instantiated.
+The activity should be declared as normal in AndroidManifest.xml, and it should be listed in
+the AppWidgetProviderInfo XML file in the <code>android:configure</code> attribute.
+
+<p>The activity you specified will be launched with the {@link
+android.appwidget.AppWidgetManager#ACTION_APPWIDGET_CONFIGURE} action. See the documentation for that
+action for more info.
+
+<p>See the <code>src/com/example/android/apis/appwidget/ExampleAppWidgetConfigure.java</code>
+sample class in ApiDemos for an example.
+
+
+
+<h3><a name="providers_broadcasts"></a>AppWidget Broadcast Intents</h3>
+
+<p>{@link android.appwidget.AppWidgetProvider} is just a convenience class. If you would like
+to receive the widget broadcasts directly, you can. The four intents you need to care about are:
+<ul>
+ <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_UPDATE}</li>
+ <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_DELETED}</li>
+ <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_ENABLED}</li>
+ <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_DISABLED}</li>
+</ul>
+
+<p>By way of example, the implementation of
+{@link android.appwidget.AppWidgetProvider#onReceive} is quite simple:</p>
+
+{@sample frameworks/base/core/java/android/appwidget/AppWidgetProvider.java onReceive}
+
+
+<h2>AppWidget Hosts</h3>
+<p>Widget hosts are the containers in which widgets can be placed. Most of the look and feel
+details are left up to the widget hosts. For example, the home screen has one way of viewing
+widgets, but the lock screen could also contain widgets, and it would have a different way of
+adding, removing and otherwise managing widgets.</p>
+<p>For more information on implementing your own widget host, see the
+{@link android.appwidget.AppWidgetHost AppWidgetHost} class.</p>
+</body>
+
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index b0b0154..2ea45d5 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -49,6 +49,7 @@ import java.util.List;
*/
public class BluetoothA2dp {
private static final String TAG = "BluetoothA2dp";
+ private static final boolean DBG = false;
/** int extra for SINK_STATE_CHANGED_ACTION */
public static final String SINK_STATE =
@@ -103,6 +104,7 @@ public class BluetoothA2dp {
* @hide
*/
public int connectSink(String address) {
+ if (DBG) log("connectSink(" + address + ")");
try {
return mService.connectSink(address);
} catch (RemoteException e) {
@@ -119,6 +121,7 @@ public class BluetoothA2dp {
* @hide
*/
public int disconnectSink(String address) {
+ if (DBG) log("disconnectSink(" + address + ")");
try {
return mService.disconnectSink(address);
} catch (RemoteException e) {
@@ -133,6 +136,7 @@ public class BluetoothA2dp {
* @hide
*/
public boolean isSinkConnected(String address) {
+ if (DBG) log("isSinkConnected(" + address + ")");
int state = getSinkState(address);
return state == STATE_CONNECTED || state == STATE_PLAYING;
}
@@ -142,6 +146,7 @@ public class BluetoothA2dp {
* @hide
*/
public List<String> listConnectedSinks() {
+ if (DBG) log("listConnectedSinks()");
try {
return mService.listConnectedSinks();
} catch (RemoteException e) {
@@ -156,6 +161,7 @@ public class BluetoothA2dp {
* @hide
*/
public int getSinkState(String address) {
+ if (DBG) log("getSinkState(" + address + ")");
try {
return mService.getSinkState(address);
} catch (RemoteException e) {
@@ -177,6 +183,7 @@ public class BluetoothA2dp {
* @return Result code, negative indicates an error
*/
public int setSinkPriority(String address, int priority) {
+ if (DBG) log("setSinkPriority(" + address + ", " + priority + ")");
try {
return mService.setSinkPriority(address, priority);
} catch (RemoteException e) {
@@ -191,6 +198,7 @@ public class BluetoothA2dp {
* @return non-negative priority, or negative error code on error.
*/
public int getSinkPriority(String address) {
+ if (DBG) log("getSinkPriority(" + address + ")");
try {
return mService.getSinkPriority(address);
} catch (RemoteException e) {
@@ -244,4 +252,8 @@ public class BluetoothA2dp {
return "<unknown state " + state + ">";
}
}
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index d613e1c..1ba1c1e 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -31,10 +31,15 @@ import java.io.UnsupportedEncodingException;
* @hide
*/
public class BluetoothDevice {
- public static final int MODE_UNKNOWN = -1;
- public static final int MODE_OFF = 0;
- public static final int MODE_CONNECTABLE = 1;
- public static final int MODE_DISCOVERABLE = 2;
+ /** Inquiry scan and page scan are both off.
+ * Device is neither discoverable nor connectable */
+ public static final int SCAN_MODE_NONE = 0;
+ /** Page scan is on, inquiry scan is off.
+ * Device is connectable, but not discoverable */
+ public static final int SCAN_MODE_CONNECTABLE = 1;
+ /** Page scan and inquiry scan are on.
+ * Device is connectable and discoverable */
+ public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 3;
public static final int RESULT_FAILURE = -1;
public static final int RESULT_SUCCESS = 0;
@@ -54,12 +59,14 @@ public class BluetoothDevice {
/** A bond attempt failed because the other side explicilty rejected
* bonding */
public static final int UNBOND_REASON_AUTH_REJECTED = 2;
- /** A bond attempt failed because we cancelled the bonding process */
- public static final int UNBOND_REASON_CANCELLED = 3;
+ /** A bond attempt failed because we canceled the bonding process */
+ public static final int UNBOND_REASON_AUTH_CANCELED = 3;
/** A bond attempt failed because we could not contact the remote device */
- public static final int UNBOND_REASON_AUTH_REMOTE_DEVICE_DOWN = 4;
+ public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
+ /** A bond attempt failed because a discovery is in progress */
+ public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
/** An existing bond was explicitly revoked */
- public static final int UNBOND_REASON_REMOVED = 5;
+ public static final int UNBOND_REASON_REMOVED = 6;
private static final String TAG = "BluetoothDevice";
@@ -174,18 +181,6 @@ public class BluetoothDevice {
return false;
}
- public String getMajorClass() {
- try {
- return mService.getMajorClass();
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- return null;
- }
- public String getMinorClass() {
- try {
- return mService.getMinorClass();
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- return null;
- }
public String getVersion() {
try {
return mService.getVersion();
@@ -211,15 +206,26 @@ public class BluetoothDevice {
return null;
}
- public int getMode() {
+ /**
+ * Get the current scan mode.
+ * Used to determine if the local device is connectable and/or discoverable
+ * @return Scan mode, one of SCAN_MODE_* or an error code
+ */
+ public int getScanMode() {
try {
- return mService.getMode();
+ return mService.getScanMode();
} catch (RemoteException e) {Log.e(TAG, "", e);}
- return MODE_UNKNOWN;
+ return BluetoothError.ERROR_IPC;
}
- public void setMode(int mode) {
+
+ /**
+ * Set the current scan mode.
+ * Used to make the local device connectable and/or discoverable
+ * @param scanMode One of SCAN_MODE_*
+ */
+ public void setScanMode(int scanMode) {
try {
- mService.setMode(mode);
+ mService.setScanMode(scanMode);
} catch (RemoteException e) {Log.e(TAG, "", e);}
}
@@ -435,24 +441,6 @@ public class BluetoothDevice {
return null;
}
- public String getRemoteAlias(String address) {
- try {
- return mService.getRemoteAlias(address);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- return null;
- }
- public boolean setRemoteAlias(String address, String alias) {
- try {
- return mService.setRemoteAlias(address, alias);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- return false;
- }
- public boolean clearRemoteAlias(String address) {
- try {
- return mService.clearRemoteAlias(address);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- return false;
- }
public String getRemoteVersion(String address) {
try {
return mService.getRemoteVersion(address);
@@ -477,24 +465,6 @@ public class BluetoothDevice {
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
- public String getRemoteMajorClass(String address) {
- try {
- return mService.getRemoteMajorClass(address);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- return null;
- }
- public String getRemoteMinorClass(String address) {
- try {
- return mService.getRemoteMinorClass(address);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- return null;
- }
- public String[] getRemoteServiceClasses(String address) {
- try {
- return mService.getRemoteServiceClasses(address);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- return null;
- }
/**
* Returns the RFCOMM channel associated with the 16-byte UUID on
@@ -512,12 +482,19 @@ public class BluetoothDevice {
return false;
}
+ /**
+ * Get the major, minor and servics classes of a remote device.
+ * These classes are encoded as a 32-bit integer. See BluetoothClass.
+ * @param address remote device
+ * @return 32-bit class suitable for use with BluetoothClass.
+ */
public int getRemoteClass(String address) {
try {
return mService.getRemoteClass(address);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return BluetoothClass.ERROR;
}
+
public byte[] getRemoteFeatures(String address) {
try {
return mService.getRemoteFeatures(address);
@@ -576,8 +553,8 @@ public class BluetoothDevice {
}
- /* Sanity check a bluetooth address, such as "00:43:A8:23:10:F0" */
private static final int ADDRESS_LENGTH = 17;
+ /** Sanity check a bluetooth address, such as "00:43:A8:23:10:F0" */
public static boolean checkBluetoothAddress(String address) {
if (address == null || address.length() != ADDRESS_LENGTH) {
return false;
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index c315271..1dbe0cc 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -67,10 +67,15 @@ public class BluetoothHeadset {
/** A headset is currently connected */
public static final int STATE_CONNECTED = 2;
+ /** A SCO audio channel is not established */
+ public static final int AUDIO_STATE_DISCONNECTED = 0;
+ /** A SCO audio channel is established */
+ public static final int AUDIO_STATE_CONNECTED = 1;
+
public static final int RESULT_FAILURE = 0;
public static final int RESULT_SUCCESS = 1;
- /** Connection cancelled before completetion. */
- public static final int RESULT_CANCELLED = 2;
+ /** Connection canceled before completetion. */
+ public static final int RESULT_CANCELED = 2;
/** Default priority for headsets that should be auto-connected */
public static final int PRIORITY_AUTO = 100;
@@ -126,6 +131,7 @@ public class BluetoothHeadset {
* are ok.
*/
public synchronized void close() {
+ if (DBG) log("close()");
if (mConnection != null) {
mContext.unbindService(mConnection);
mConnection = null;
@@ -138,6 +144,7 @@ public class BluetoothHeadset {
* object is currently not connected to the Headset service.
*/
public int getState() {
+ if (DBG) log("getState()");
if (mService != null) {
try {
return mService.getState();
@@ -156,6 +163,7 @@ public class BluetoothHeadset {
* service.
*/
public String getHeadsetAddress() {
+ if (DBG) log("getHeadsetAddress()");
if (mService != null) {
try {
return mService.getHeadsetAddress();
@@ -180,6 +188,7 @@ public class BluetoothHeadset {
* will be expected.
*/
public boolean connectHeadset(String address) {
+ if (DBG) log("connectHeadset(" + address + ")");
if (mService != null) {
try {
if (mService.connectHeadset(address)) {
@@ -199,6 +208,7 @@ public class BluetoothHeadset {
* if not currently connected to the headset service.
*/
public boolean isConnected(String address) {
+ if (DBG) log("isConnected(" + address + ")");
if (mService != null) {
try {
return mService.isConnected(address);
@@ -216,6 +226,7 @@ public class BluetoothHeadset {
* not currently connected to the Headset service.
*/
public boolean disconnectHeadset() {
+ if (DBG) log("disconnectHeadset()");
if (mService != null) {
try {
mService.disconnectHeadset();
@@ -235,6 +246,7 @@ public class BluetoothHeadset {
* error.
*/
public boolean startVoiceRecognition() {
+ if (DBG) log("startVoiceRecognition()");
if (mService != null) {
try {
return mService.startVoiceRecognition();
@@ -252,6 +264,7 @@ public class BluetoothHeadset {
* headset is not in voice recognition mode, or on error.
*/
public boolean stopVoiceRecognition() {
+ if (DBG) log("stopVoiceRecognition()");
if (mService != null) {
try {
return mService.stopVoiceRecognition();
@@ -282,6 +295,7 @@ public class BluetoothHeadset {
* @return True if successful, false if there was some error.
*/
public boolean setPriority(String address, int priority) {
+ if (DBG) log("setPriority(" + address + ", " + priority + ")");
if (mService != null) {
try {
return mService.setPriority(address, priority);
@@ -299,6 +313,7 @@ public class BluetoothHeadset {
* @return non-negative priority, or negative error code on error.
*/
public int getPriority(String address) {
+ if (DBG) log("getPriority(" + address + ")");
if (mService != null) {
try {
return mService.getPriority(address);
@@ -318,6 +333,12 @@ public class BluetoothHeadset {
* @return True if this device might support HSP or HFP.
*/
public static boolean doesClassMatch(int btClass) {
+ // The render service class is required by the spec for HFP, so is a
+ // pretty good signal
+ if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) {
+ return true;
+ }
+ // Just in case they forgot the render service class
switch (BluetoothClass.Device.getDevice(btClass)) {
case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
@@ -344,4 +365,8 @@ public class BluetoothHeadset {
}
}
};
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java
index 57c46f9..9273d0d 100644
--- a/core/java/android/bluetooth/BluetoothIntent.java
+++ b/core/java/android/bluetooth/BluetoothIntent.java
@@ -29,8 +29,8 @@ import android.annotation.SdkConstant.SdkConstantType;
* @hide
*/
public interface BluetoothIntent {
- public static final String MODE =
- "android.bluetooth.intent.MODE";
+ public static final String SCAN_MODE =
+ "android.bluetooth.intent.SCAN_MODE";
public static final String ADDRESS =
"android.bluetooth.intent.ADDRESS";
public static final String NAME =
@@ -45,6 +45,8 @@ public interface BluetoothIntent {
"android.bluetooth.intent.HEADSET_STATE";
public static final String HEADSET_PREVIOUS_STATE =
"android.bluetooth.intent.HEADSET_PREVIOUS_STATE";
+ public static final String HEADSET_AUDIO_STATE =
+ "android.bluetooth.intent.HEADSET_AUDIO_STATE";
public static final String BOND_STATE =
"android.bluetooth.intent.BOND_STATE";
public static final String BOND_PREVIOUS_STATE =
@@ -62,9 +64,14 @@ public interface BluetoothIntent {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String NAME_CHANGED_ACTION =
"android.bluetooth.intent.action.NAME_CHANGED";
+
+ /**
+ * Broadcast when the scan mode changes. Always contains an int extra
+ * named SCAN_MODE that contains the new scan mode.
+ */
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String MODE_CHANGED_ACTION =
- "android.bluetooth.intent.action.MODE_CHANGED";
+ public static final String SCAN_MODE_CHANGED_ACTION =
+ "android.bluetooth.intent.action.SCAN_MODE_CHANGED";
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String DISCOVERY_STARTED_ACTION =
@@ -104,12 +111,6 @@ public interface BluetoothIntent {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String REMOTE_NAME_FAILED_ACTION =
"android.bluetooth.intent.action.REMOTE_NAME_FAILED";
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String REMOTE_ALIAS_CHANGED_ACTION =
- "android.bluetooth.intent.action.REMOTE_ALIAS_CHANGED";
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String REMOTE_ALIAS_CLEARED_ACTION =
- "android.bluetooth.intent.action.REMOTE_ALIAS_CLEARED";
/**
* Broadcast when the bond state of a remote device changes.
@@ -123,7 +124,18 @@ public interface BluetoothIntent {
public static final String BOND_STATE_CHANGED_ACTION =
"android.bluetooth.intent.action.BOND_STATE_CHANGED_ACTION";
+ /**
+ * TODO(API release): Move into BluetoothHeadset
+ */
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String HEADSET_STATE_CHANGED_ACTION =
"android.bluetooth.intent.action.HEADSET_STATE_CHANGED";
+
+ /**
+ * TODO(API release): Consider incorporating as new state in
+ * HEADSET_STATE_CHANGED
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String HEADSET_AUDIO_STATE_CHANGED_ACTION =
+ "android.bluetooth.intent.action.HEADSET_ADUIO_STATE_CHANGED";
}
diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java
index bce3388..fd2d2ab 100644
--- a/core/java/android/bluetooth/HeadsetBase.java
+++ b/core/java/android/bluetooth/HeadsetBase.java
@@ -21,13 +21,10 @@ import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
-import java.io.IOException;
-import java.lang.Thread;
-
/**
* The Android Bluetooth API is not finalized, and *will* change. Use at your
* own risk.
- *
+ *
* The base RFCOMM (service) connection for a headset or handsfree device.
*
* In the future this class will be removed.
@@ -90,7 +87,7 @@ public class HeadsetBase {
/* Create from an already exisiting rfcomm connection */
public HeadsetBase(PowerManager pm, BluetoothDevice bluetooth, String address, int socketFd,
- int rfcommChannel, Handler handler) {
+ int rfcommChannel, Handler handler) {
mDirection = DIRECTION_INCOMING;
mConnectTimestamp = System.currentTimeMillis();
mBluetooth = bluetooth;
@@ -132,30 +129,8 @@ public class HeadsetBase {
*/
protected void initializeAtParser() {
mAtParser = new AtParser();
-
- // Microphone Gain
- mAtParser.register("+VGM", new AtCommandHandler() {
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // AT+VGM=<gain> in range [0,15]
- // Headset/Handsfree is reporting its current gain setting
- //TODO: sync to android UI
- //TODO: Send unsolicited +VGM when volume changed on AG
- return new AtCommandResult(AtCommandResult.OK);
- }
- });
-
- // Speaker Gain
- mAtParser.register("+VGS", new AtCommandHandler() {
- @Override
- public AtCommandResult handleSetCommand(Object[] args) {
- // AT+VGS=<gain> in range [0,15]
- // Headset/Handsfree is reporting its current gain to Android
- //TODO: sync to AG UI
- //TODO: Send unsolicited +VGS when volume changed on AG
- return new AtCommandResult(AtCommandResult.OK);
- }
- });
+ //TODO(): Get rid of this as there are no parsers registered. But because of dependencies,
+ //it needs to be done as part of refactoring HeadsetBase and BluetoothHandsfree
}
public AtParser getAtParser() {
diff --git a/core/java/android/bluetooth/IBluetoothDevice.aidl b/core/java/android/bluetooth/IBluetoothDevice.aidl
index 59f679f..4351d2e 100644
--- a/core/java/android/bluetooth/IBluetoothDevice.aidl
+++ b/core/java/android/bluetooth/IBluetoothDevice.aidl
@@ -32,15 +32,13 @@ interface IBluetoothDevice
String getAddress();
String getName();
boolean setName(in String name);
- String getMajorClass();
- String getMinorClass();
String getVersion();
String getRevision();
String getManufacturer();
String getCompany();
- int getMode();
- boolean setMode(int mode);
+ int getScanMode();
+ boolean setScanMode(int mode);
int getDiscoverableTimeout();
boolean setDiscoverableTimeout(int timeout);
@@ -64,17 +62,11 @@ interface IBluetoothDevice
int getBondState(in String address);
String getRemoteName(in String address);
- String getRemoteAlias(in String address);
- boolean setRemoteAlias(in String address, in String alias);
- boolean clearRemoteAlias(in String address);
String getRemoteVersion(in String address);
String getRemoteRevision(in String address);
int getRemoteClass(in String address);
String getRemoteManufacturer(in String address);
String getRemoteCompany(in String address);
- String getRemoteMajorClass(in String address);
- String getRemoteMinorClass(in String address);
- String[] getRemoteServiceClasses(in String address);
boolean getRemoteServiceChannel(in String address, int uuid16, in IBluetoothDeviceCallback callback);
byte[] getRemoteFeatures(in String adddress);
String lastSeen(in String address);
diff --git a/core/java/android/bluetooth/ScoSocket.java b/core/java/android/bluetooth/ScoSocket.java
index 75b3329..a43a08b 100644
--- a/core/java/android/bluetooth/ScoSocket.java
+++ b/core/java/android/bluetooth/ScoSocket.java
@@ -16,17 +16,12 @@
package android.bluetooth;
-import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
-import java.io.IOException;
-import java.lang.Thread;
-
-
/**
* The Android Bluetooth API is not finalized, and *will* change. Use at your
* own risk.
@@ -56,7 +51,7 @@ public class ScoSocket {
private int mConnectedCode;
private int mClosedCode;
- private WakeLock mWakeLock; // held while STATE_CONNECTING or STATE_CONNECTED
+ private WakeLock mWakeLock; // held while in STATE_CONNECTING
static {
classInitNative();
@@ -130,6 +125,7 @@ public class ScoSocket {
public synchronized void close() {
if (DBG) log(this + " SCO OBJECT close() mState = " + mState);
+ acquireWakeLock();
mState = STATE_CLOSED;
closeNative();
releaseWakeLock();
@@ -152,19 +148,16 @@ public class ScoSocket {
mState = STATE_CLOSED;
}
mHandler.obtainMessage(mConnectedCode, mState, -1, this).sendToTarget();
- if (result < 0) {
- releaseWakeLock();
- }
+ releaseWakeLock();
}
private synchronized void onAccepted(int result) {
if (VDBG) log("onAccepted() " + this);
if (mState != STATE_ACCEPT) {
- if (DBG) log("Strange state" + this);
+ if (DBG) log("Strange state " + this);
return;
}
if (result >= 0) {
- acquireWakeLock();
mState = STATE_CONNECTED;
} else {
mState = STATE_CLOSED;
@@ -184,13 +177,13 @@ public class ScoSocket {
private void acquireWakeLock() {
if (!mWakeLock.isHeld()) {
mWakeLock.acquire();
- if (VDBG) log("mWakeLock.acquire()" + this);
+ if (VDBG) log("mWakeLock.acquire() " + this);
}
}
private void releaseWakeLock() {
if (mWakeLock.isHeld()) {
- if (VDBG) log("mWakeLock.release()" + this);
+ if (VDBG) log("mWakeLock.release() " + this);
mWakeLock.release();
}
}
diff --git a/core/java/android/content/AsyncQueryHandler.java b/core/java/android/content/AsyncQueryHandler.java
index 2d651a7..ac851cc 100644
--- a/core/java/android/content/AsyncQueryHandler.java
+++ b/core/java/android/content/AsyncQueryHandler.java
@@ -146,6 +146,20 @@ public abstract class AsyncQueryHandler extends Handler {
* @param token A token passed into {@link #onQueryComplete} to identify
* the query.
* @param cookie An object that gets passed into {@link #onQueryComplete}
+ * @param uri The URI, using the content:// scheme, for the content to
+ * retrieve.
+ * @param projection A list of which columns to return. Passing null will
+ * return all columns, which is discouraged to prevent reading data
+ * from storage that isn't going to be used.
+ * @param selection A filter declaring which rows to return, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself). Passing null will
+ * return all rows for the given URI.
+ * @param selectionArgs You may include ?s in selection, which will be
+ * replaced by the values from selectionArgs, in the order that they
+ * appear in the selection. The values will be bound as Strings.
+ * @param orderBy How to order the rows, formatted as an SQL ORDER BY
+ * clause (excluding the ORDER BY itself). Passing null will use the
+ * default sort order, which may be unordered.
*/
public void startQuery(int token, Object cookie, Uri uri,
String[] projection, String selection, String[] selectionArgs,
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index cd92002..08f6191 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -16,7 +16,11 @@
package android.content;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
import android.util.Log;
/**
@@ -75,7 +79,7 @@ import android.util.Log;
* <p>The BroadcastReceiver class (when launched as a component through
* a manifest's {@link android.R.styleable#AndroidManifestReceiver &lt;receiver&gt;}
* tag) is an important part of an
- * <a href="{@docRoot}intro/lifecycle.html">application's overall lifecycle</a>.</p>
+ * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">application's overall lifecycle</a>.</p>
*
* <p>Topics covered here:
* <ol>
@@ -131,7 +135,7 @@ import android.util.Log;
* tag in their <code>AndroidManifest.xml</code>) will be able to send an
* Intent to the receiver.
*
- * <p>See the <a href="{@docRoot}devel/security.html">Security Model</a>
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
* document for more information on permissions and security in general.
*
* <a name="ProcessLifecycle"></a>
@@ -175,7 +179,9 @@ public abstract class BroadcastReceiver {
* return a result to you asynchronously -- in particular, for interacting
* with services, you should use
* {@link Context#startService(Intent)} instead of
- * {@link Context#bindService(Intent, ServiceConnection, int)}.
+ * {@link Context#bindService(Intent, ServiceConnection, int)}. If you wish
+ * to interact with a service that is already running, you can use
+ * {@link #peekService}.
*
* @param context The Context in which the receiver is running.
* @param intent The Intent being received.
@@ -183,6 +189,26 @@ public abstract class BroadcastReceiver {
public abstract void onReceive(Context context, Intent intent);
/**
+ * Provide a binder to an already-running service. This method is synchronous
+ * and will not start the target service if it is not present, so it is safe
+ * to call from {@link #onReceive}.
+ *
+ * @param myContext The Context that had been passed to {@link #onReceive(Context, Intent)}
+ * @param service The Intent indicating the service you wish to use. See {@link
+ * Context#startService(Intent)} for more information.
+ */
+ public IBinder peekService(Context myContext, Intent service) {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ IBinder binder = null;
+ try {
+ binder = am.peekService(service, service.resolveTypeIfNeeded(
+ myContext.getContentResolver()));
+ } catch (RemoteException e) {
+ }
+ return binder;
+ }
+
+ /**
* Change the current result code of this broadcast; only works with
* broadcasts sent through
* {@link Context#sendOrderedBroadcast(Intent, String)
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 226c5ab..25544de 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -18,6 +18,7 @@ package android.content;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.CursorToBulkCursorAdaptor;
@@ -41,8 +42,8 @@ import java.io.FileNotFoundException;
* multiple applications you can use a database directly via
* {@link android.database.sqlite.SQLiteDatabase}.
*
- * <p>See <a href="{@docRoot}devel/data/contentproviders.html">this page</a> for more information on
- * content providers.</p>
+ * <p>For more information, read <a href="{@docRoot}guide/topics/providers/content-providers.html">Content
+ * Providers</a>.</p>
*
* <p>When a request is made via
* a {@link ContentResolver} the system inspects the authority of the given URI and passes the
@@ -162,6 +163,13 @@ public abstract class ContentProvider implements ComponentCallbacks {
return ContentProvider.this.openFile(uri, mode);
}
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
+ else checkReadPermission(uri);
+ return ContentProvider.this.openAssetFile(uri, mode);
+ }
+
public ISyncAdapter getSyncAdapter() {
checkWritePermission(null);
return ContentProvider.this.getSyncAdapter().getISyncAdapter();
@@ -226,9 +234,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
/**
* Return the name of the permission required for read-only access to
* this content provider. This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*/
public final String getReadPermission() {
return mReadPermission;
@@ -248,9 +256,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
/**
* Return the name of the permission required for read/write access to
* this content provider. This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*/
public final String getWritePermission() {
return mWritePermission;
@@ -273,9 +281,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* Receives a query request from a client in a local process, and
* returns a Cursor. This is called internally by the {@link ContentResolver}.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
* <p>
* Example client call:<p>
* <pre>// Request a specific record.
@@ -330,9 +338,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* <code>vnd.android.cursor.item</code> for a single record,
* or <code>vnd.android.cursor.dir/</code> for multiple items.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* @param uri the URI to query.
* @return a MIME type string, or null if there is no type.
@@ -344,9 +352,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
* after inserting.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
- * Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
* @param uri The content:// URI of the insertion request.
* @param values A set of column_name/value pairs to add to the database.
* @return The URI for the newly inserted item.
@@ -359,9 +367,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
* after inserting.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* @param uri The content:// URI of the insertion request.
* @param values An array of sets of column_name/value pairs to add to the database.
@@ -382,9 +390,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyDelete()}
* after deleting.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
- * Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* <p>The implementation is responsible for parsing out a row ID at the end
* of the URI, if a specific row is being deleted. That is, the client would
@@ -405,9 +413,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
* after updating.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
- * Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* @param uri The URI to query. This can potentially have a record ID if this
* is an update request for a specific record.
@@ -422,9 +430,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
/**
* Open a file blob associated with a content URI.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
- * Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* <p>Returns a
* ParcelFileDescriptor, from which you can obtain a
@@ -438,8 +446,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* of this method should create a new ParcelFileDescriptor for each call.
*
* @param uri The URI whose file is to be opened.
- * @param mode Access mode for the file. May be "r" for read-only access
- * or "rw" for read and write access.
+ * @param mode Access mode for the file. May be "r" for read-only access,
+ * "rw" for read and write access, or "rwt" for read and write access
+ * that truncates any existing file.
*
* @return Returns a new ParcelFileDescriptor which you can use to access
* the file.
@@ -448,19 +457,66 @@ public abstract class ContentProvider implements ComponentCallbacks {
* no file associated with the given URI or the mode is invalid.
* @throws SecurityException Throws SecurityException if the caller does
* not have permission to access the file.
- */
+ *
+ * @see #openAssetFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ */
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
throw new FileNotFoundException("No files supported by provider at "
+ uri);
}
+
+ /**
+ * This is like {@link #openFile}, but can be implemented by providers
+ * that need to be able to return sub-sections of files, often assets
+ * inside of their .apk. Note that when implementing this your clients
+ * must be able to deal with such files, either directly with
+ * {@link ContentResolver#openAssetFileDescriptor
+ * ContentResolver.openAssetFileDescriptor}, or by using the higher-level
+ * {@link ContentResolver#openInputStream ContentResolver.openInputStream}
+ * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
+ * methods.
+ *
+ * <p><em>Note: if you are implementing this to return a full file, you
+ * should create the AssetFileDescriptor with
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
+ * applications that can not handle sub-sections of files.</em></p>
+ *
+ * @param uri The URI whose file is to be opened.
+ * @param mode Access mode for the file. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
+ *
+ * @return Returns a new AssetFileDescriptor which you can use to access
+ * the file.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the file.
+ *
+ * @see #openFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ */
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ ParcelFileDescriptor fd = openFile(uri, mode);
+ return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+ }
/**
* Convenience for subclasses that wish to implement {@link #openFile}
* by looking up a column named "_data" at the given URI.
*
* @param uri The URI to be opened.
- * @param mode The file mode.
+ * @param mode The file mode. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
*
* @return Returns a new ParcelFileDescriptor that can be used by the
* client to access the file.
@@ -489,16 +545,7 @@ public abstract class ContentProvider implements ComponentCallbacks {
throw new FileNotFoundException("Column _data not found.");
}
- int modeBits;
- if ("r".equals(mode)) {
- modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
- } else if ("rw".equals(mode)) {
- modeBits = ParcelFileDescriptor.MODE_READ_WRITE
- | ParcelFileDescriptor.MODE_CREATE;
- } else {
- throw new FileNotFoundException("Bad mode for " + uri + ": "
- + mode);
- }
+ int modeBits = ContentResolver.modeToMode(uri, mode);
return ParcelFileDescriptor.open(new File(path), modeBits);
}
@@ -507,9 +554,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* This is intended for use by the sync system. If null then this
* content provider is considered not syncable.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* @return the SyncAdapter that is to be used by this ContentProvider, or null
* if this ContentProvider is not syncable
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index ede2c9b..e5e3f74 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.res.AssetFileDescriptor;
import android.database.BulkCursorNative;
import android.database.BulkCursorToCursorAdaptor;
import android.database.Cursor;
@@ -187,6 +188,25 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
return true;
}
+ case OPEN_ASSET_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+
+ AssetFileDescriptor fd;
+ fd = openAssetFile(url, mode);
+ reply.writeNoException();
+ if (fd != null) {
+ reply.writeInt(1);
+ fd.writeToParcel(reply,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
case GET_SYNC_ADAPTER_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
@@ -413,6 +433,29 @@ final class ContentProviderProxy implements IContentProvider
return fd;
}
+ public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
+
+ mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ AssetFileDescriptor fd = has != 0
+ ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+
+ data.recycle();
+ reply.recycle();
+
+ return fd;
+ }
+
public ISyncAdapter getSyncAdapter() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 52f55b6..0d886ee 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -17,6 +17,7 @@
package android.content;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -28,6 +29,7 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.text.TextUtils;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -170,119 +172,100 @@ public abstract class ContentResolver {
* <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
* <li>file ({@link #SCHEME_FILE})</li>
* </ul>
- * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5>
- * <p>
- * A Uri object can be used to reference a resource in an APK file. The
- * Uri should be one of the following formats:
- * <ul>
- * <li><code>android.resource://package_name/id_number</code><br/>
- * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
- * For example <code>com.example.myapp</code><br/>
- * <code>id_number</code> is the int form of the ID.<br/>
- * The easiest way to construct this form is
- * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre>
- * </li>
- * <li><code>android.resource://package_name/type/name</code><br/>
- * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
- * For example <code>com.example.myapp</code><br/>
- * <code>type</code> is the string form of the resource type. For example, <code>raw</code>
- * or <code>drawable</code>.
- * <code>name</code> is the string form of the resource name. That is, whatever the file
- * name was in your res directory, without the type extension.
- * The easiest way to construct this form is
- * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre>
- * </li>
- * </ul>
- * @param uri The desired "content:" URI.
+ *
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ *
+ * @param uri The desired URI.
* @return InputStream
* @throws FileNotFoundException if the provided URI could not be opened.
+ * @see #openAssetFileDescriptor(Uri, String)
*/
public final InputStream openInputStream(Uri uri)
throws FileNotFoundException {
String scheme = uri.getScheme();
- if (SCHEME_CONTENT.equals(scheme)) {
- ParcelFileDescriptor fd = openFileDescriptor(uri, "r");
- return fd != null ? new ParcelFileDescriptor.AutoCloseInputStream(fd) : null;
- } else if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
- String authority = uri.getAuthority();
- Resources r;
- if (TextUtils.isEmpty(authority)) {
- throw new FileNotFoundException("No authority: " + uri);
- } else {
- try {
- r = mContext.getPackageManager().getResourcesForApplication(authority);
- } catch (NameNotFoundException ex) {
- throw new FileNotFoundException("No package found for authority: " + uri);
- }
- }
- List<String> path = uri.getPathSegments();
- if (path == null) {
- throw new FileNotFoundException("No path: " + uri);
- }
- int len = path.size();
- int id;
- if (len == 1) {
- try {
- id = Integer.parseInt(path.get(0));
- } catch (NumberFormatException e) {
- throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
- }
- } else if (len == 2) {
- id = r.getIdentifier(path.get(1), path.get(0), authority);
- } else {
- throw new FileNotFoundException("More than two path segments: " + uri);
- }
- if (id == 0) {
- throw new FileNotFoundException("No resource found for: " + uri);
- }
+ if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ // Note: left here to avoid breaking compatibility. May be removed
+ // with sufficient testing.
+ OpenResourceIdResult r = getResourceId(uri);
try {
- InputStream stream = r.openRawResource(id);
+ InputStream stream = r.r.openRawResource(r.id);
return stream;
} catch (Resources.NotFoundException ex) {
- throw new FileNotFoundException("Resource ID does not exist: " + uri);
+ throw new FileNotFoundException("Resource does not exist: " + uri);
}
} else if (SCHEME_FILE.equals(scheme)) {
+ // Note: left here to avoid breaking compatibility. May be removed
+ // with sufficient testing.
return new FileInputStream(uri.getPath());
} else {
- throw new FileNotFoundException("Unknown scheme: " + uri);
+ AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r");
+ try {
+ return fd != null ? fd.createInputStream() : null;
+ } catch (IOException e) {
+ throw new FileNotFoundException("Unable to create stream");
+ }
}
}
/**
+ * Synonym for {@link #openOutputStream(Uri, String)
+ * openOutputStream(uri, "w")}.
+ * @throws FileNotFoundException if the provided URI could not be opened.
+ */
+ public final OutputStream openOutputStream(Uri uri)
+ throws FileNotFoundException {
+ return openOutputStream(uri, "w");
+ }
+
+ /**
* Open a stream on to the content associated with a content URI. If there
* is no data associated with the URI, FileNotFoundException is thrown.
*
* <h5>Accepts the following URI schemes:</h5>
* <ul>
* <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
* </ul>
*
- * @param uri The desired "content:" URI.
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ *
+ * @param uri The desired URI.
+ * @param mode May be "w", "wa", "rw", or "rwt".
* @return OutputStream
+ * @throws FileNotFoundException if the provided URI could not be opened.
+ * @see #openAssetFileDescriptor(Uri, String)
*/
- public final OutputStream openOutputStream(Uri uri)
+ public final OutputStream openOutputStream(Uri uri, String mode)
throws FileNotFoundException {
- String scheme = uri.getScheme();
- if (SCHEME_CONTENT.equals(scheme)) {
- ParcelFileDescriptor fd = openFileDescriptor(uri, "rw");
- return fd != null
- ? new ParcelFileDescriptor.AutoCloseOutputStream(fd) : null;
- } else {
- throw new FileNotFoundException("Unknown scheme: " + uri);
+ AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode);
+ try {
+ return fd != null ? fd.createOutputStream() : null;
+ } catch (IOException e) {
+ throw new FileNotFoundException("Unable to create stream");
}
}
/**
* Open a raw file descriptor to access data under a "content:" URI. This
- * interacts with the underlying {@link ContentProvider#openFile}
- * ContentProvider.openFile()} method of the provider associated with the
- * given URI, to retrieve any file stored there.
+ * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
+ * underlying {@link ContentProvider#openFile}
+ * ContentProvider.openFile()} method, so will <em>not</em> work with
+ * providers that return sub-sections of files. If at all possible,
+ * you should use {@link #openAssetFileDescriptor(Uri, String)}. You
+ * will receive a FileNotFoundException exception if the provider returns a
+ * sub-section of a file.
*
* <h5>Accepts the following URI schemes:</h5>
* <ul>
* <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
* </ul>
*
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ *
* @param uri The desired URI to open.
* @param mode The file mode to use, as per {@link ContentProvider#openFile
* ContentProvider.openFile}.
@@ -290,32 +273,189 @@ public abstract class ContentResolver {
* own this descriptor and are responsible for closing it when done.
* @throws FileNotFoundException Throws FileNotFoundException of no
* file exists under the URI or the mode is invalid.
+ * @see #openAssetFileDescriptor(Uri, String)
*/
public final ParcelFileDescriptor openFileDescriptor(Uri uri,
String mode) throws FileNotFoundException {
- IContentProvider provider = acquireProvider(uri);
- if (provider == null) {
- throw new FileNotFoundException("No content provider: " + uri);
+ AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode);
+ if (afd == null) {
+ return null;
+ }
+
+ if (afd.getDeclaredLength() < 0) {
+ // This is a full file!
+ return afd.getParcelFileDescriptor();
}
+
+ // Client can't handle a sub-section of a file, so close what
+ // we got and bail with an exception.
try {
- ParcelFileDescriptor fd = provider.openFile(uri, mode);
- if(fd == null) {
+ afd.close();
+ } catch (IOException e) {
+ }
+
+ throw new FileNotFoundException("Not a whole file");
+ }
+
+ /**
+ * Open a raw file descriptor to access data under a "content:" URI. This
+ * interacts with the underlying {@link ContentProvider#openAssetFile}
+ * ContentProvider.openAssetFile()} method of the provider associated with the
+ * given URI, to retrieve any file stored there.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5>
+ * <p>
+ * A Uri object can be used to reference a resource in an APK file. The
+ * Uri should be one of the following formats:
+ * <ul>
+ * <li><code>android.resource://package_name/id_number</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>id_number</code> is the int form of the ID.<br/>
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre>
+ * </li>
+ * <li><code>android.resource://package_name/type/name</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>type</code> is the string form of the resource type. For example, <code>raw</code>
+ * or <code>drawable</code>.
+ * <code>name</code> is the string form of the resource name. That is, whatever the file
+ * name was in your res directory, without the type extension.
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre>
+ * </li>
+ * </ul>
+ *
+ * @param uri The desired URI to open.
+ * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile
+ * ContentProvider.openAssetFile}.
+ * @return Returns a new ParcelFileDescriptor pointing to the file. You
+ * own this descriptor and are responsible for closing it when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * file exists under the URI or the mode is invalid.
+ */
+ public final AssetFileDescriptor openAssetFileDescriptor(Uri uri,
+ String mode) throws FileNotFoundException {
+ String scheme = uri.getScheme();
+ if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ if (!"r".equals(mode)) {
+ throw new FileNotFoundException("Can't write resources: " + uri);
+ }
+ OpenResourceIdResult r = getResourceId(uri);
+ try {
+ return r.r.openRawResourceFd(r.id);
+ } catch (Resources.NotFoundException ex) {
+ throw new FileNotFoundException("Resource does not exist: " + uri);
+ }
+ } else if (SCHEME_FILE.equals(scheme)) {
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+ new File(uri.getPath()), modeToMode(uri, mode));
+ return new AssetFileDescriptor(pfd, 0, -1);
+ } else {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
+ }
+ try {
+ AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
+ if(fd == null) {
+ releaseProvider(provider);
+ return null;
+ }
+ ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+ fd.getParcelFileDescriptor(), provider);
+ return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+ fd.getDeclaredLength());
+ } catch (RemoteException e) {
releaseProvider(provider);
- return null;
+ throw new FileNotFoundException("Dead content provider: " + uri);
+ } catch (FileNotFoundException e) {
+ releaseProvider(provider);
+ throw e;
+ } catch (RuntimeException e) {
+ releaseProvider(provider);
+ throw e;
}
- return new ParcelFileDescriptorInner(fd, provider);
- } catch (RemoteException e) {
- releaseProvider(provider);
- throw new FileNotFoundException("Dead content provider: " + uri);
- } catch (FileNotFoundException e) {
- releaseProvider(provider);
- throw e;
- } catch (RuntimeException e) {
- releaseProvider(provider);
- throw e;
}
}
+ class OpenResourceIdResult {
+ Resources r;
+ int id;
+ }
+
+ OpenResourceIdResult getResourceId(Uri uri) throws FileNotFoundException {
+ String authority = uri.getAuthority();
+ Resources r;
+ if (TextUtils.isEmpty(authority)) {
+ throw new FileNotFoundException("No authority: " + uri);
+ } else {
+ try {
+ r = mContext.getPackageManager().getResourcesForApplication(authority);
+ } catch (NameNotFoundException ex) {
+ throw new FileNotFoundException("No package found for authority: " + uri);
+ }
+ }
+ List<String> path = uri.getPathSegments();
+ if (path == null) {
+ throw new FileNotFoundException("No path: " + uri);
+ }
+ int len = path.size();
+ int id;
+ if (len == 1) {
+ try {
+ id = Integer.parseInt(path.get(0));
+ } catch (NumberFormatException e) {
+ throw new FileNotFoundException("Single path segment is not a resource ID: " + uri);
+ }
+ } else if (len == 2) {
+ id = r.getIdentifier(path.get(1), path.get(0), authority);
+ } else {
+ throw new FileNotFoundException("More than two path segments: " + uri);
+ }
+ if (id == 0) {
+ throw new FileNotFoundException("No resource found for: " + uri);
+ }
+ OpenResourceIdResult res = new OpenResourceIdResult();
+ res.r = r;
+ res.id = id;
+ return res;
+ }
+
+ /** @hide */
+ static public int modeToMode(Uri uri, String mode) throws FileNotFoundException {
+ int modeBits;
+ if ("r".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
+ } else if ("w".equals(mode) || "wt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else if ("wa".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_APPEND;
+ } else if ("rw".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE;
+ } else if ("rwt".equals(mode)) {
+ modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+ | ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE;
+ } else {
+ throw new FileNotFoundException("Bad mode for " + uri + ": "
+ + mode);
+ }
+ return modeBits;
+ }
+
/**
* Inserts a row into a table at the given URL.
*
diff --git a/core/java/android/content/ContentServiceNative.java b/core/java/android/content/ContentServiceNative.java
index f050501..364f9ee 100644
--- a/core/java/android/content/ContentServiceNative.java
+++ b/core/java/android/content/ContentServiceNative.java
@@ -75,6 +75,13 @@ abstract class ContentServiceNative extends Binder implements IContentService
{
try {
switch (code) {
+ case 5038: {
+ data.readString(); // ignore the interface token that service generated
+ Uri uri = Uri.parse(data.readString());
+ notifyChange(uri, null, false, false);
+ return true;
+ }
+
case REGISTER_CONTENT_OBSERVER_TRANSACTION: {
Uri uri = Uri.CREATOR.createFromParcel(data);
boolean notifyForDescendents = data.readInt() != 0;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3908aa1..9a0dc9f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -127,7 +127,7 @@ public abstract class Context {
* current process.
*/
public abstract Context getApplicationContext();
-
+
/**
* Return a localized, styled CharSequence from the application's package's
* default string table.
@@ -428,7 +428,7 @@ public abstract class Context {
* cursor when query is called.
*
* @return The contents of a newly created database with the given name.
- * @throws SQLiteException if the database file could not be opened.
+ * @throws android.database.sqlite.SQLiteException if the database file could not be opened.
*
* @see #MODE_PRIVATE
* @see #MODE_WORLD_READABLE
@@ -1064,7 +1064,7 @@ public abstract class Context {
* @see #AUDIO_SERVICE
* @see android.media.AudioManager
* @see #TELEPHONY_SERVICE
- * @see android.internal.TelephonyManager
+ * @see android.telephony.TelephonyManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
*/
@@ -1250,12 +1250,12 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@blink android.gadget.GadgetManager} for accessing wallpapers.
+ * {@blink android.appwidget.AppWidgetManager} for accessing AppWidgets.
*
* @hide
* @see #getSystemService
*/
- public static final String GADGET_SERVICE = "gadget";
+ public static final String APPWIDGET_SERVICE = "appwidget";
/**
* Determine whether the given permission is allowed for a particular
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index a6ef46f..0606956 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.IBulkCursor;
@@ -52,6 +53,8 @@ public interface IContentProvider extends IInterface {
String[] selectionArgs) throws RemoteException;
public ParcelFileDescriptor openFile(Uri url, String mode)
throws RemoteException, FileNotFoundException;
+ public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException;
public ISyncAdapter getSyncAdapter() throws RemoteException;
/* IPC constants */
@@ -65,4 +68,5 @@ public interface IContentProvider extends IInterface {
static final int GET_SYNC_ADAPTER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
static final int BULK_INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 12;
static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13;
+ static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 23fd171..c47b72f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -504,6 +504,8 @@ import java.util.Set;
* <li> {@link #ACTION_PACKAGE_ADDED}
* <li> {@link #ACTION_PACKAGE_CHANGED}
* <li> {@link #ACTION_PACKAGE_REMOVED}
+ * <li> {@link #ACTION_PACKAGE_RESTARTED}
+ * <li> {@link #ACTION_PACKAGE_DATA_CLEARED}
* <li> {@link #ACTION_UID_REMOVED}
* <li> {@link #ACTION_BATTERY_CHANGED}
* <li> {@link #ACTION_POWER_CONNECTED}
@@ -522,9 +524,9 @@ import java.util.Set;
* <li> {@link #CATEGORY_ALTERNATIVE}
* <li> {@link #CATEGORY_SELECTED_ALTERNATIVE}
* <li> {@link #CATEGORY_LAUNCHER}
+ * <li> {@link #CATEGORY_INFO}
* <li> {@link #CATEGORY_HOME}
* <li> {@link #CATEGORY_PREFERENCE}
- * <li> {@link #CATEGORY_GADGET}
* <li> {@link #CATEGORY_TEST}
* </ul>
*
@@ -1026,6 +1028,15 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
+
+ /**
+ * Activity Action: Start action associated with long pressing on the
+ * search key.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS";
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
@@ -1041,6 +1052,14 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
+
+ /**
+ * Broadcast Action: Sent when the user is present after device wakes up (e.g when the
+ * keyguard is gone).
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_USER_PRESENT= "android.intent.action.USER_PRESENT";
+
/**
* Broadcast Action: The current time has changed. Sent every
* minute. You can <em>not</em> receive this through components declared
@@ -1109,6 +1128,12 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: A new application package has been installed on the
* device. The data contains the name of the package.
+ * <p>My include the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package.
+ * <li> {@link #EXTRA_REPLACING} is set to true if this is following
+ * an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package.
+ * </ul>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -1116,23 +1141,49 @@ public class Intent implements Parcelable {
* Broadcast Action: An existing application package has been removed from
* the device. The data contains the name of the package. The package
* that is being installed does <em>not</em> receive this Intent.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid previously assigned
+ * to the package.
+ * <li> {@link #EXTRA_DATA_REMOVED} is set to true if the entire
+ * application -- data and code -- is being removed.
+ * <li> {@link #EXTRA_REPLACING} is set to true if this will be followed
+ * by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package.
+ * </ul>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
/**
* Broadcast Action: An existing application package has been changed (e.g. a component has been
* enabled or disabled. The data contains the name of the package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * </ul>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
/**
- * Broadcast Action: The user has restarted a package, all runtime state
+ * Broadcast Action: The user has restarted a package, and all of its
+ * processes have been killed. All runtime state
* associated with it (processes, alarms, notifications, etc) should
- * be remove. The data contains the name of the package.
+ * be removed. The data contains the name of the package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * </ul>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
/**
+ * Broadcast Action: The user has cleared the data of a package. This should
+ * be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of
+ * its persistent data is erased and this broadcast sent. The data contains
+ * the name of the package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * </ul>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
+ /**
* Broadcast Action: A user ID has been removed from the system. The user
* ID number is stored in the extra data under {@link #EXTRA_UID}.
*/
@@ -1248,7 +1299,6 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: External media is present, and being disk-checked
* The path to the mount point for the checking media is contained in the Intent.mData field.
- * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MEDIA_CHECKING = "android.intent.action.MEDIA_CHECKING";
@@ -1256,7 +1306,6 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: External media is present, but is using an incompatible fs (or is blank)
* The path to the mount point for the checking media is contained in the Intent.mData field.
- * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MEDIA_NOFS = "android.intent.action.MEDIA_NOFS";
@@ -1524,16 +1573,17 @@ public class Intent implements Parcelable {
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_TAB = "android.intent.category.TAB";
/**
- * This activity can be embedded inside of another activity that is hosting
- * gadgets.
+ * Should be displayed in the top-level launcher.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
- public static final String CATEGORY_GADGET = "android.intent.category.GADGET";
+ public static final String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER";
/**
- * Should be displayed in the top-level launcher.
+ * Provides information about the package it is in; typically used if
+ * a package does not contain a {@link #CATEGORY_LAUNCHER} to provide
+ * a front-door to the user without having to be shown in the all apps list.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
- public static final String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER";
+ public static final String CATEGORY_INFO = "android.intent.category.INFO";
/**
* This is the home activity, that is the first activity that is displayed
* when the device boots.
@@ -1552,9 +1602,6 @@ public class Intent implements Parcelable {
public static final String CATEGORY_DEVELOPMENT_PREFERENCE = "android.intent.category.DEVELOPMENT_PREFERENCE";
/**
* Capable of running inside a parent activity container.
- *
- * <p>Note: being removed in favor of more explicit categories such as
- * CATEGORY_GADGET
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_EMBED = "android.intent.category.EMBED";
@@ -1677,6 +1724,22 @@ public class Intent implements Parcelable {
public static final String EXTRA_UID = "android.intent.extra.UID";
/**
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to indicate whether this represents a full uninstall (removing
+ * both the code and its data) or a partial uninstall (leaving its data,
+ * implying that this is an update).
+ */
+ public static final String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
+
+ /**
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to indicate that this is a replacement of the package, so this
+ * broadcast will immediately be followed by an add broadcast for a
+ * different version of the same package.
+ */
+ public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
+
+ /**
* Used as an int extra field in {@link android.app.AlarmManager} intents
* to tell the application being invoked how many pending alarms are being
* delievered with the intent. For one-shot alarms this will always be 1.
@@ -1739,9 +1802,9 @@ public class Intent implements Parcelable {
* next task activity) defines an atomic group of activities that the
* user can move to. Tasks can be moved to the foreground and background;
* all of the activities inside of a particular task always remain in
- * the same order. See the
- * <a href="{@docRoot}intro/appmodel.html">Application Model</a>
- * documentation for more details on tasks.
+ * the same order. See
+ * <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> for more details on tasks.
*
* <p>This flag is generally used by activities that want
* to present a "launcher" style behavior: they give the user a list of
@@ -1774,9 +1837,8 @@ public class Intent implements Parcelable {
* <p>This flag is ignored if
* {@link #FLAG_ACTIVITY_NEW_TASK} is not set.
*
- * <p>See the
- * <a href="{@docRoot}intro/appmodel.html">Application Model</a>
- * documentation for more details on tasks.
+ * <p>See <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> for more details on tasks.
*/
public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000;
/**
@@ -1791,8 +1853,8 @@ public class Intent implements Parcelable {
* Intent, resulting in the stack now being: A, B.
*
* <p>The currently running instance of task B in the above example will
- * either receiving the new intent you are starting here in its
- * onNewIntent() method, or be itself finished and restarting with the
+ * either receive the new intent you are starting here in its
+ * onNewIntent() method, or be itself finished and restarted with the
* new intent. If it has declared its launch mode to be "multiple" (the
* default) it will be finished and re-created; for all other launch modes
* it will receive the Intent in the current instance.
@@ -1804,9 +1866,8 @@ public class Intent implements Parcelable {
* especially useful, for example, when launching an activity from the
* notification manager.
*
- * <p>See the
- * <a href="{@docRoot}intro/appmodel.html">Application Model</a>
- * documentation for more details on tasks.
+ * <p>See <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> for more details on tasks.
*/
public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x04000000;
/**
@@ -1876,7 +1937,7 @@ public class Intent implements Parcelable {
*/
public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000;
/**
- * If set, this flag will prevent the normal {@link android.app.Activity#onUserLeaving}
+ * If set, this flag will prevent the normal {@link android.app.Activity#onUserLeaveHint}
* callback from occurring on the current frontmost activity before it is
* paused as the newly-started activity is brought to the front.
*
@@ -1892,12 +1953,39 @@ public class Intent implements Parcelable {
* activity does not think the user has acknowledged its notification.
*/
public static final int FLAG_ACTIVITY_NO_USER_ACTION = 0x00040000;
-
+ /**
+ * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
+ * this flag will cause the launched activity to be brought to the front of its
+ * task's history stack if it is already running.
+ *
+ * <p>For example, consider a task consisting of four activities: A, B, C, D.
+ * If D calls startActivity() with an Intent that resolves to the component
+ * of activity B, then B will be brought to the front of the history stack,
+ * with this resulting order: A, C, D, B.
+ *
+ * This flag will be ignored if {@link #FLAG_ACTIVITY_CLEAR_TOP} is also
+ * specified.
+ */
+ public static final int FLAG_ACTIVITY_REORDER_TO_FRONT = 0X00020000;
/**
* If set, when sending a broadcast only registered receivers will be
* called -- no BroadcastReceiver components will be launched.
*/
public static final int FLAG_RECEIVER_REGISTERED_ONLY = 0x40000000;
+ /**
+ * If set, when sending a broadcast <i>before boot has completed</i> only
+ * registered receivers will be called -- no BroadcastReceiver components
+ * will be launched. Sticky intent state will be recorded properly even
+ * if no receivers wind up being called. If {@link #FLAG_RECEIVER_REGISTERED_ONLY}
+ * is specified in the broadcast intent, this flag is unnecessary.
+ *
+ * <p>This flag is only for use by system sevices as a convenience to
+ * avoid having to implement a more complex mechanism around detection
+ * of boot completion.
+ *
+ * @hide
+ */
+ public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x20000000;
// ---------------------------------------------------------------------
@@ -3865,8 +3953,8 @@ public class Intent implements Parcelable {
* FLAG_RECEIVER_* flags are all for use with
* {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}.
*
- * <p>See the <a href="{@docRoot}intro/appmodel.html">Application Model</a>
- * documentation for important information on how some of these options impact
+ * <p>See the <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> documentation for important information on how some of these options impact
* the behavior of your application.
*
* @param flags The desired flags.
@@ -4141,14 +4229,11 @@ public class Intent implements Parcelable {
@Override
public boolean equals(Object obj) {
- Intent other;
- try {
- other = ((FilterComparison)obj).mIntent;
- } catch (ClassCastException e) {
- return false;
+ if (obj instanceof FilterComparison) {
+ Intent other = ((FilterComparison)obj).mIntent;
+ return mIntent.filterEquals(other);
}
-
- return mIntent.filterEquals(other);
+ return false;
}
@Override
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 6bc3774..96470c3 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -123,7 +123,7 @@ class SyncManager {
private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
-
+
private Context mContext;
private ContentResolver mContentResolver;
@@ -249,7 +249,7 @@ class SyncManager {
mSyncQueue = new SyncQueue(mSyncStorageEngine);
mContext = context;
-
+
mSyncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
mSyncThread.start();
mSyncHandler = new SyncHandler(mSyncThread.getLooper());
@@ -489,7 +489,7 @@ class SyncManager {
// Require the precise value "yes" to discourage accidental activation.
return "yes".equals(SystemProperties.get("ro.config.sync"));
}
-
+
/**
* Initiate a sync. This can start a sync for all providers
* (pass null to url, set onlyTicklable to false), only those
@@ -515,7 +515,7 @@ class SyncManager {
* syncs of a specific provider. Can be null. Is ignored
* if the url is null.
* @param delay how many milliseconds in the future to wait before performing this
- * sync. -1 means to make this the next sync to perform.
+ * sync. -1 means to make this the next sync to perform.
*/
public void scheduleSync(Uri url, Bundle extras, long delay) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
@@ -694,7 +694,7 @@ class SyncManager {
class SyncHandlerMessagePayload {
public final ActiveSyncContext activeSyncContext;
public final SyncResult syncResult;
-
+
SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
this.activeSyncContext = syncContext;
this.syncResult = syncResult;
@@ -740,7 +740,7 @@ class SyncManager {
if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
}
-
+
SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
rescheduledSyncOperation.setDelay(newDelayInMs);
scheduleSyncOperation(rescheduledSyncOperation);
@@ -786,7 +786,7 @@ class SyncManager {
// key than the one we are scheduling.
if (!activeIsExpedited && !hasSameKey) {
rescheduleImmediately(activeSyncContext.mSyncOperation);
- sendSyncFinishedOrCanceledMessage(activeSyncContext,
+ sendSyncFinishedOrCanceledMessage(activeSyncContext,
null /* no result since this is a cancel */);
}
}
@@ -1323,7 +1323,7 @@ class SyncManager {
public SyncHandler(Looper looper) {
super(looper);
}
-
+
public void handleMessage(Message msg) {
handleSyncHandlerMessage(msg);
}
@@ -1462,6 +1462,9 @@ class SyncManager {
// start it, otherwise just get out.
SyncOperation syncOperation;
final Sync.Settings.QueryMap syncSettings = getSyncSettings();
+ final ConnectivityManager connManager = (ConnectivityManager)
+ mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final boolean backgroundDataSetting = connManager.getBackgroundDataSetting();
synchronized (mSyncQueue) {
while (true) {
syncOperation = mSyncQueue.head();
@@ -1484,10 +1487,10 @@ class SyncManager {
// skip the sync if it isn't a force and the settings are off for this provider
final boolean force = syncOperation.extras.getBoolean(
ContentResolver.SYNC_EXTRAS_FORCE, false);
- if (!force && (!syncSettings.getBackgroundData()
+ if (!force && (!backgroundDataSetting
|| !syncSettings.getListenForNetworkTickles()
|| !syncSettings.getSyncProviderAutomatically(
- syncOperation.authority))) {
+ syncOperation.authority))) {
if (isLoggable) {
Log.v(TAG, "runStateIdle: sync off, dropping " + syncOperation);
}
@@ -1669,7 +1672,7 @@ class SyncManager {
* @param syncResult the SyncResult from which to read
* @return the most "serious" error set in the SyncResult
* @throws IllegalStateException if the SyncResult does not indicate any errors.
- * If SyncResult.error() is true then it is safe to call this.
+ * If SyncResult.error() is true then it is safe to call this.
*/
private int syncResultToErrorNumber(SyncResult syncResult) {
if (syncResult.syncAlreadyInProgress) return History.ERROR_SYNC_ALREADY_IN_PROGRESS;
@@ -1679,7 +1682,8 @@ class SyncManager {
if (syncResult.stats.numConflictDetectedExceptions > 0) return History.ERROR_CONFLICT;
if (syncResult.tooManyDeletions) return History.ERROR_TOO_MANY_DELETIONS;
if (syncResult.tooManyRetries) return History.ERROR_TOO_MANY_RETRIES;
- throw new IllegalStateException("we are not in an error state, " + toString());
+ if (syncResult.databaseError) return History.ERROR_INTERNAL;
+ throw new IllegalStateException("we are not in an error state, " + syncResult);
}
private void manageSyncNotification() {
@@ -1717,7 +1721,7 @@ class SyncManager {
if (mSyncNotificationInfo.isActive) {
shouldInstall = shouldCancel;
} else {
- final boolean timeToShowNotification =
+ final boolean timeToShowNotification =
now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
final boolean syncIsForced = syncOperation.extras
.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false);
@@ -1769,7 +1773,7 @@ class SyncManager {
if (!mDataConnectionIsConnected) return;
if (mAccounts == null) return;
if (mStorageIsLow) return;
-
+
// Compute the alarm fire time:
// - not syncing: time of the next sync operation
// - syncing, no notification: time from sync start to notification create time
@@ -1850,12 +1854,12 @@ class SyncManager {
clickIntent.putExtra("account", account);
clickIntent.putExtra("provider", authority);
clickIntent.putExtra("numDeletes", numDeletes);
-
+
if (!isActivityAvailable(clickIntent)) {
Log.w(TAG, "No activity found to handle too many deletes.");
return;
}
-
+
final PendingIntent pendingIntent = PendingIntent
.getActivity(mContext, 0, clickIntent, PendingIntent.FLAG_CANCEL_CURRENT);
@@ -1877,7 +1881,7 @@ class SyncManager {
/**
* Checks whether an activity exists on the system image for the given intent.
- *
+ *
* @param intent The intent for an activity.
* @return Whether or not an activity exists.
*/
@@ -1892,10 +1896,10 @@ class SyncManager {
return true;
}
}
-
+
return false;
}
-
+
public long insertStartSyncEvent(SyncOperation syncOperation) {
final int source = syncOperation.syncSource;
final long now = System.currentTimeMillis();
diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java
index 78510aa..eb3a5da 100644
--- a/core/java/android/content/TempProviderSyncAdapter.java
+++ b/core/java/android/content/TempProviderSyncAdapter.java
@@ -1,11 +1,11 @@
package android.content;
-import com.google.android.net.NetStats;
-
import android.database.SQLException;
import android.os.Bundle;
import android.os.Debug;
+import android.os.NetStat;
import android.os.Parcelable;
+import android.os.Process;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Config;
@@ -177,7 +177,8 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter {
private final Bundle mExtras;
private final SyncContext mSyncContext;
private volatile boolean mIsCanceled = false;
- private long[] mNetStats;
+ private long mInitialTxBytes;
+ private long mInitialRxBytes;
private final SyncResult mResult;
SyncThread(SyncContext syncContext, String account, Bundle extras) {
@@ -193,15 +194,18 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter {
if (mAdapterSyncStarted) onSyncCanceled();
if (mProviderSyncStarted) mProvider.onSyncCanceled();
// We may lose the last few sync events when canceling. Oh well.
- long[] newNetStats = NetStats.getStats();
- logSyncDetails(newNetStats[0] - mNetStats[0], newNetStats[1] - mNetStats[1], mResult);
+ int uid = Process.myUid();
+ logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
+ NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
}
@Override
public void run() {
- android.os.Process.setThreadPriority(android.os.Process.myTid(),
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- mNetStats = NetStats.getStats();
+ Process.setThreadPriority(Process.myTid(),
+ Process.THREAD_PRIORITY_BACKGROUND);
+ int uid = Process.myUid();
+ mInitialTxBytes = NetStat.getUidTxBytes(uid);
+ mInitialRxBytes = NetStat.getUidRxBytes(uid);
try {
sync(mSyncContext, mAccount, mExtras);
} catch (SQLException e) {
@@ -210,8 +214,8 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter {
} finally {
mSyncThread = null;
if (!mIsCanceled) {
- long[] newNetStats = NetStats.getStats();
- logSyncDetails(newNetStats[0] - mNetStats[0], newNetStats[1] - mNetStats[1], mResult);
+ logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
+ NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
mSyncContext.onFinished(mResult);
}
}
diff --git a/core/java/android/content/package.html b/core/java/android/content/package.html
index 7b3e8cf..dd5360f 100644
--- a/core/java/android/content/package.html
+++ b/core/java/android/content/package.html
@@ -50,9 +50,9 @@ an application's resources and transfer data between applications.</p>
<p>This topic includes a terminology list associated with resources, and a series
of examples of using resources in code. For a complete guide on creating and
- using resources, see the document on <a href="{@docRoot}devel/resources-i18n.html">Resources
+ using resources, see the document on <a href="{@docRoot}guide/topics/resources/resources-i18n.html">Resources
and Internationalization</a>. For a reference on the supported Android resource types,
- see <a href="{@docRoot}reference/available-resources.html">Available Resource Types</a>.</p>
+ see <a href="{@docRoot}guide/topics/resources/available-resources.html">Available Resource Types</a>.</p>
<p>The Android resource system keeps track of all non-code
assets associated with an application. You use the
{@link android.content.res.Resources Resources} class to access your
@@ -175,7 +175,8 @@ download files with new appearances.</p>
<p>This section gives a few quick examples you can use to make your own resources.
For more details on how to define and use resources, see <a
- href="{@docRoot}devel/resources-i18n.html">Resources</a>. </p>
+ href="{@docRoot}guide/topics/resources/resources-i18n.html">Resources and
+ Internationalization</a>. </p>
<a name="UsingSystemResources"></a>
<h4>Using System Resources</h4>
diff --git a/core/java/android/content/pm/ConfigurationInfo.java b/core/java/android/content/pm/ConfigurationInfo.java
index 9115225..dcc7463 100755
--- a/core/java/android/content/pm/ConfigurationInfo.java
+++ b/core/java/android/content/pm/ConfigurationInfo.java
@@ -16,7 +16,6 @@
package android.content.pm;
-import android.content.res.Configuration;
import android.os.Parcel;
import android.os.Parcelable;
@@ -60,12 +59,12 @@ public class ConfigurationInfo implements Parcelable {
/**
* Value for {@link #reqInputFeatures}: if set, indicates that the application
- * requires a hard keyboard
+ * requires a five way navigation device
*/
public static final int INPUT_FEATURE_FIVE_WAY_NAV = 0x00000002;
/**
- * Flags associated with the application. Any combination of
+ * Flags associated with the input features. Any combination of
* {@link #INPUT_FEATURE_HARD_KEYBOARD},
* {@link #INPUT_FEATURE_FIVE_WAY_NAV}
*/
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index ea86188..d3f6f3c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -255,8 +255,15 @@ interface IPackageManager {
* retrieval of information is complete.
*/
void getPackageSizeInfo(in String packageName, IPackageStatsObserver observer);
+
+ /**
+ * Get a list of shared libraries that are available on the
+ * system.
+ */
+ String[] getSystemSharedLibraryNames();
void enterSafeMode();
+ boolean isSafeMode();
void systemReady();
boolean hasSystemUidErrors();
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 994afc8..d9326f2 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -29,6 +29,20 @@ public class PackageInfo implements Parcelable {
public String versionName;
/**
+ * The shared user ID name of this package, as specified by the &lt;manifest&gt;
+ * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId}
+ * attribute.
+ */
+ public String sharedUserId;
+
+ /**
+ * The shared user ID label of this package, as specified by the &lt;manifest&gt;
+ * tag's {@link android.R.styleable#AndroidManifest_sharedUserLabel sharedUserLabel}
+ * attribute.
+ */
+ public int sharedUserLabel;
+
+ /**
* Information collected from the &lt;application&gt; tag, or null if
* there was none.
*/
@@ -130,6 +144,8 @@ public class PackageInfo implements Parcelable {
dest.writeString(packageName);
dest.writeInt(versionCode);
dest.writeString(versionName);
+ dest.writeString(sharedUserId);
+ dest.writeInt(sharedUserLabel);
if (applicationInfo != null) {
dest.writeInt(1);
applicationInfo.writeToParcel(dest, parcelableFlags);
@@ -163,6 +179,8 @@ public class PackageInfo implements Parcelable {
packageName = source.readString();
versionCode = source.readInt();
versionName = source.readString();
+ sharedUserId = source.readString();
+ sharedUserLabel = source.readInt();
int hasApp = source.readInt();
if (hasApp != 0) {
applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4b902e9..7287d9c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -480,6 +480,26 @@ public abstract class PackageManager {
throws NameNotFoundException;
/**
+ * Return a "good" intent to launch a front-door activity in a package,
+ * for use for example to implement an "open" button when browsing through
+ * packages. The current implementation will look first for a main
+ * activity in the category {@link Intent#CATEGORY_INFO}, next for a
+ * main activity in the category {@link Intent#CATEGORY_LAUNCHER}, or return
+ * null if neither are found.
+ *
+ * <p>Throws {@link NameNotFoundException} if a package with the given
+ * name can not be found on the system.
+ *
+ * @param packageName The name of the package to inspect.
+ *
+ * @return Returns either a fully-qualified Intent that can be used to
+ * launch the main activity in the package, or null if the package does
+ * not contain such an activity.
+ */
+ public abstract Intent getLaunchIntentForPackage(String packageName)
+ throws NameNotFoundException;
+
+ /**
* Return an array of all of the secondary group-ids that have been
* assigned to a package.
*
@@ -851,6 +871,16 @@ public abstract class PackageManager {
* @see #GET_UNINSTALLED_PACKAGES
*/
public abstract List<ApplicationInfo> getInstalledApplications(int flags);
+
+ /**
+ * Get a list of shared libraries that are available on the
+ * system.
+ *
+ * @return An array of shared library names that are
+ * available on the system, or null if none are installed.
+ *
+ */
+ public abstract String[] getSystemSharedLibraryNames();
/**
* Determine the best action to perform for a given Intent. This is how
@@ -1608,4 +1638,9 @@ public abstract class PackageManager {
* the manifest as found in {@link ComponentInfo}.
*/
public abstract int getApplicationEnabledSetting(String packageName);
+
+ /**
+ * Return whether the device has been booted into safe mode.
+ */
+ public abstract boolean isSafeMode();
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e08f1d1..2dcb483 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -101,6 +101,8 @@ public class PackageParser {
pi.packageName = p.packageName;
pi.versionCode = p.mVersionCode;
pi.versionName = p.mVersionName;
+ pi.sharedUserId = p.mSharedUserId;
+ pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = p.applicationInfo;
if ((flags&PackageManager.GET_GIDS) != 0) {
pi.gids = gids;
@@ -258,8 +260,9 @@ public class PackageParser {
boolean assetError = true;
try {
assmgr = new AssetManager();
- if(assmgr.addAssetPath(mArchiveSourcePath) != 0) {
- parser = assmgr.openXmlResourceParser("AndroidManifest.xml");
+ int cookie = assmgr.addAssetPath(mArchiveSourcePath);
+ if(cookie != 0) {
+ parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml");
assetError = false;
} else {
Log.w(TAG, "Failed adding asset path:"+mArchiveSourcePath);
@@ -585,6 +588,8 @@ public class PackageParser {
return null;
}
pkg.mSharedUserId = str.intern();
+ pkg.mSharedUserLabel = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifest_sharedUserLabel, 0);
}
sa.recycle();
@@ -2045,6 +2050,9 @@ public class PackageParser {
// The shared user id that this package wants to use.
public String mSharedUserId;
+ // The shared user label that this package wants to use.
+ public int mSharedUserLabel;
+
// Signatures that were read from the package.
public Signature mSignatures[];
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 4a073f7..231e3e2 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,9 +16,13 @@
package android.content.res;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
/**
@@ -26,16 +30,32 @@ import java.io.IOException;
* opened FileDescriptor that can be used to read the data, as well as the
* offset and length of that entry's data in the file.
*/
-public class AssetFileDescriptor {
+public class AssetFileDescriptor implements Parcelable {
+ /**
+ * Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)}
+ * and {@link #getDeclaredLength} when a length has not been declared. This means
+ * the data extends to the end of the file.
+ */
+ public static final long UNKNOWN_LENGTH = -1;
+
private final ParcelFileDescriptor mFd;
private final long mStartOffset;
private final long mLength;
/**
* Create a new AssetFileDescriptor from the given values.
+ * @param fd The underlying file descriptor.
+ * @param startOffset The location within the file that the asset starts.
+ * This must be 0 if length is UNKNOWN_LENGTH.
+ * @param length The number of bytes of the asset, or
+ * {@link #UNKNOWN_LENGTH if it extends to the end of the file.
*/
public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
long length) {
+ if (length < 0 && startOffset != 0) {
+ throw new IllegalArgumentException(
+ "startOffset must be 0 when using UNKNOWN_LENGTH");
+ }
mFd = fd;
mStartOffset = startOffset;
mLength = length;
@@ -66,9 +86,33 @@ public class AssetFileDescriptor {
}
/**
- * Returns the total number of bytes of this asset entry's data.
+ * Returns the total number of bytes of this asset entry's data. May be
+ * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file.
+ * If the AssetFileDescriptor was constructed with {@link #UNKNOWN_LENGTH},
+ * this will use {@link ParcelFileDescriptor#getStatSize()
+ * ParcelFileDescriptor.getStatSize()} to find the total size of the file,
+ * returning that number if found or {@link #UNKNOWN_LENGTH} if it could
+ * not be determined.
+ *
+ * @see #getDeclaredLength()
*/
public long getLength() {
+ if (mLength >= 0) {
+ return mLength;
+ }
+ long len = mFd.getStatSize();
+ return len >= 0 ? len : UNKNOWN_LENGTH;
+ }
+
+ /**
+ * Return the actual number of bytes that were declared when the
+ * AssetFileDescriptor was constructed. Will be
+ * {@link #UNKNOWN_LENGTH} if the length was not declared, meaning data
+ * should be read to the end of the file.
+ *
+ * @see #getDeclaredLength()
+ */
+ public long getDeclaredLength() {
return mLength;
}
@@ -78,4 +122,227 @@ public class AssetFileDescriptor {
public void close() throws IOException {
mFd.close();
}
+
+ /**
+ * Create and return a new auto-close input stream for this asset. This
+ * will either return a full asset {@link AutoCloseInputStream}, or
+ * an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
+ * ParcelFileDescriptor.AutoCloseInputStream} depending on whether the
+ * the object represents a complete file or sub-section of a file. You
+ * should only call this once for a particular asset.
+ */
+ public FileInputStream createInputStream() throws IOException {
+ if (mLength < 0) {
+ return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
+ }
+ return new AutoCloseInputStream(this);
+ }
+
+ /**
+ * Create and return a new auto-close output stream for this asset. This
+ * will either return a full asset {@link AutoCloseOutputStream}, or
+ * an underlying {@link ParcelFileDescriptor.AutoCloseOutputStream
+ * ParcelFileDescriptor.AutoCloseOutputStream} depending on whether the
+ * the object represents a complete file or sub-section of a file. You
+ * should only call this once for a particular asset.
+ */
+ public FileOutputStream createOutputStream() throws IOException {
+ if (mLength < 0) {
+ return new ParcelFileDescriptor.AutoCloseOutputStream(mFd);
+ }
+ return new AutoCloseOutputStream(this);
+ }
+
+ @Override
+ public String toString() {
+ return "{AssetFileDescriptor: " + mFd
+ + " start=" + mStartOffset + " len=" + mLength + "}";
+ }
+
+ /**
+ * An InputStream you can create on a ParcelFileDescriptor, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescritor.close()} for you when the stream is closed.
+ */
+ public static class AutoCloseInputStream
+ extends ParcelFileDescriptor.AutoCloseInputStream {
+ private long mRemaining;
+
+ public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
+ super(fd.getParcelFileDescriptor());
+ super.skip(fd.getStartOffset());
+ mRemaining = (int)fd.getLength();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mRemaining >= 0
+ ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
+ : super.available();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ int res = super.read();
+ if (res >= 0) mRemaining--;
+ return res;
+ }
+
+ return super.read();
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = (int)mRemaining;
+ int res = super.read(buffer, offset, count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ return super.read(buffer, offset, count);
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ int count = buffer.length;
+ if (count > mRemaining) count = (int)mRemaining;
+ int res = super.read(buffer, 0, count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ return super.read(buffer);
+ }
+
+ @Override
+ public long skip(long count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = mRemaining;
+ long res = super.skip(count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ // TODO Auto-generated method stub
+ return super.skip(count);
+ }
+
+ @Override
+ public void mark(int readlimit) {
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
+ }
+ super.mark(readlimit);
+ }
+
+ @Override
+ public boolean markSupported() {
+ if (mRemaining >= 0) {
+ return false;
+ }
+ return super.markSupported();
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
+ }
+ super.reset();
+ }
+ }
+
+ /**
+ * An OutputStream you can create on a ParcelFileDescriptor, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescritor.close()} for you when the stream is closed.
+ */
+ public static class AutoCloseOutputStream
+ extends ParcelFileDescriptor.AutoCloseOutputStream {
+ private long mRemaining;
+
+ public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
+ super(fd.getParcelFileDescriptor());
+ if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
+ throw new IOException("Unable to seek");
+ }
+ mRemaining = (int)fd.getLength();
+ }
+
+ @Override
+ public void write(byte[] buffer, int offset, int count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ if (count > mRemaining) count = (int)mRemaining;
+ super.write(buffer, offset, count);
+ mRemaining -= count;
+ return;
+ }
+
+ super.write(buffer, offset, count);
+ }
+
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ int count = buffer.length;
+ if (count > mRemaining) count = (int)mRemaining;
+ super.write(buffer);
+ mRemaining -= count;
+ return;
+ }
+
+ super.write(buffer);
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ super.write(oneByte);
+ mRemaining--;
+ return;
+ }
+
+ super.write(oneByte);
+ }
+ }
+
+
+ /* Parcelable interface */
+ public int describeContents() {
+ return mFd.describeContents();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ mFd.writeToParcel(out, flags);
+ out.writeLong(mStartOffset);
+ out.writeLong(mLength);
+ }
+
+ AssetFileDescriptor(Parcel src) {
+ mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
+ mStartOffset = src.readLong();
+ mLength = src.readLong();
+ }
+
+ public static final Parcelable.Creator<AssetFileDescriptor> CREATOR
+ = new Parcelable.Creator<AssetFileDescriptor>() {
+ public AssetFileDescriptor createFromParcel(Parcel in) {
+ return new AssetFileDescriptor(in);
+ }
+ public AssetFileDescriptor[] newArray(int size) {
+ return new AssetFileDescriptor[size];
+ }
+ };
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index fadcb35..1c91736 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -567,8 +567,8 @@ public final class AssetManager {
/**
* Add an additional set of assets to the asset manager. This can be
- * either a directory or ZIP file. Not for use by applications. A
- * zero return value indicates failure.
+ * either a directory or ZIP file. Not for use by applications. Returns
+ * the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public native final int addAssetPath(String path);
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 4b1e678..453a83d 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -16,8 +16,6 @@
package android.content.res;
-import com.google.android.collect.Lists;
-
import com.android.internal.util.ArrayUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -113,7 +111,8 @@ public class ColorStateList implements Parcelable {
* Create a ColorStateList from an XML document, given a set of {@link Resources}.
*/
public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
- throws XmlPullParserException, IOException {
+ throws XmlPullParserException, IOException {
+
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
@@ -125,19 +124,16 @@ public class ColorStateList implements Parcelable {
throw new XmlPullParserException("No start tag found");
}
- final ColorStateList colorStateList = createFromXmlInner(r, parser, attrs);
-
- return colorStateList;
+ return createFromXmlInner(r, parser, attrs);
}
/* Create from inside an XML document. Called on a parser positioned at
* a tag in an XML document, tries to create a ColorStateList from that tag.
* Returns null if the tag is not a valid ColorStateList.
*/
- private static ColorStateList createFromXmlInner(Resources r,
- XmlPullParser parser,
- AttributeSet attrs)
- throws XmlPullParserException, IOException {
+ private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser,
+ AttributeSet attrs) throws XmlPullParserException, IOException {
+
ColorStateList colorStateList;
final String name = parser.getName();
@@ -146,8 +142,7 @@ public class ColorStateList implements Parcelable {
colorStateList = new ColorStateList();
} else {
throw new XmlPullParserException(
- parser.getPositionDescription() + ": invalid drawable tag "
- + name);
+ parser.getPositionDescription() + ": invalid drawable tag " + name);
}
colorStateList.inflate(r, parser, attrs);
@@ -304,7 +299,11 @@ public class ColorStateList implements Parcelable {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeArray(mStateSpecs);
+ final int N = mStateSpecs.length;
+ dest.writeInt(N);
+ for (int i=0; i<N; i++) {
+ dest.writeIntArray(mStateSpecs[i]);
+ }
dest.writeIntArray(mColors);
}
@@ -315,14 +314,11 @@ public class ColorStateList implements Parcelable {
}
public ColorStateList createFromParcel(Parcel source) {
- Object[] o = source.readArray(
- ColorStateList.class.getClassLoader());
- int[][] stateSpecs = new int[o.length][];
-
- for (int i = 0; i < o.length; i++) {
- stateSpecs[i] = (int[]) o[i];
+ final int N = source.readInt();
+ int[][] stateSpecs = new int[N][];
+ for (int i=0; i<N; i++) {
+ stateSpecs[i] = source.createIntArray();
}
-
int[] colors = source.createIntArray();
return new ColorStateList(stateSpecs, colors);
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 10eced6..1a963f6 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -22,7 +22,6 @@ import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.content.Intent;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
@@ -47,6 +46,7 @@ public class Resources {
static final String TAG = "Resources";
private static final boolean DEBUG_LOAD = false;
private static final boolean DEBUG_CONFIG = false;
+ private static final boolean TRACE_FOR_PRELOAD = false;
private static final int sSdkVersion = SystemProperties.getInt(
"ro.build.version.sdk", 0);
@@ -58,6 +58,8 @@ public class Resources {
// single-threaded, and after that these are immutable.
private static final SparseArray<Drawable.ConstantState> mPreloadedDrawables
= new SparseArray<Drawable.ConstantState>();
+ private static final SparseArray<ColorStateList> mPreloadedColorStateLists
+ = new SparseArray<ColorStateList>();
private static boolean mPreloaded;
/*package*/ final TypedValue mTmpValue = new TypedValue();
@@ -79,7 +81,7 @@ public class Resources {
private final Configuration mConfiguration = new Configuration();
/*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
PluralRules mPluralRule;
-
+
/**
* This exception is thrown by the resource APIs when a requested resource
* can not be found.
@@ -91,7 +93,7 @@ public class Resources {
public NotFoundException(String name) {
super(name);
}
- };
+ }
/**
* Create a new Resources object on top of an existing set of assets in an
@@ -399,7 +401,6 @@ public class Resources {
*
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
- * @return CharSequence The string data associated with the resource, plus
* @see #getDimensionPixelOffset
* @see #getDimensionPixelSize
*/
@@ -432,7 +433,6 @@ public class Resources {
*
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
- * @return CharSequence The string data associated with the resource, plus
* @see #getDimension
* @see #getDimensionPixelSize
*/
@@ -467,7 +467,6 @@ public class Resources {
*
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
- * @return CharSequence The string data associated with the resource, plus
* @see #getDimension
* @see #getDimensionPixelOffset
*/
@@ -486,6 +485,36 @@ public class Resources {
}
/**
+ * Retrieve a fractional unit for a particular resource ID.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @param base The base value of this fraction. In other words, a
+ * standard fraction is multiplied by this value.
+ * @param pbase The parent base value of this fraction. In other
+ * words, a parent fraction (nn%p) is multiplied by this
+ * value.
+ *
+ * @return Attribute fractional value multiplied by the appropriate
+ * base value.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ */
+ public float getFraction(int id, int base, int pbase) {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_FRACTION) {
+ return TypedValue.complexToFraction(value.data, base, pbase);
+ }
+ throw new NotFoundException(
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
+ }
+ }
+
+ /**
* Return a drawable object associated with a particular resource ID.
* Various types of objects will be returned depending on the underlying
* resource -- for example, a solid color, PNG image, scalable image, etc.
@@ -721,22 +750,36 @@ public class Resources {
*/
public InputStream openRawResource(int id) throws NotFoundException {
synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
- getValue(id, value, true);
+ return openRawResource(id, mTmpValue);
+ }
+ }
- try {
- return mAssets.openNonAsset(
- value.assetCookie, value.string.toString(),
- AssetManager.ACCESS_STREAMING);
- } catch (Exception e) {
- NotFoundException rnf = new NotFoundException(
- "File " + value.string.toString()
- + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
- }
+ /**
+ * Open a data stream for reading a raw resource. This can only be used
+ * with resources whose value is the name of an asset files -- that is, it can be
+ * used to open drawable, sound, and raw resources; it will fail on string
+ * and color resources.
+ *
+ * @param id The resource identifier to open, as generated by the appt tool.
+ * @param value The TypedValue object to hold the resource information.
+ *
+ * @return InputStream Access to the resource data.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @hide Pending API council approval
+ */
+ public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
+ getValue(id, value, true);
+ try {
+ return mAssets.openNonAsset(value.assetCookie, value.string.toString(),
+ AssetManager.ACCESS_STREAMING);
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException("File " + value.string.toString() +
+ " from drawable resource ID #0x" + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
}
}
@@ -1189,7 +1232,9 @@ public class Resources {
width = mMetrics.widthPixels;
height = mMetrics.heightPixels;
} else {
+ //noinspection SuspiciousNameCombination
width = mMetrics.heightPixels;
+ //noinspection SuspiciousNameCombination
height = mMetrics.widthPixels;
}
int keyboardHidden = mConfiguration.keyboardHidden;
@@ -1302,6 +1347,7 @@ public class Resources {
try {
return Integer.parseInt(name);
} catch (Exception e) {
+ // Ignore
}
return mAssets.getResourceIdentifier(name, defType, defPackage);
}
@@ -1535,21 +1581,18 @@ public class Resources {
/*package*/ Drawable loadDrawable(TypedValue value, int id)
throws NotFoundException {
- if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
- && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- // Should we be caching these? If we use constant colors much
- // at all, most likely...
- //System.out.println("Creating drawable for color: #" +
- // Integer.toHexString(value.data));
- Drawable dr = new ColorDrawable(value.data);
- dr.setChangingConfigurations(value.changingConfigurations);
- return dr;
+
+ if (TRACE_FOR_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) android.util.Log.d("PreloadDrawable", name);
+ }
}
- final int key = (value.assetCookie<<24)|value.data;
+ final int key = (value.assetCookie << 24) | value.data;
Drawable dr = getCachedDrawable(key);
- //System.out.println("Cached drawable @ #" +
- // Integer.toHexString(key.intValue()) + ": " + dr);
+
if (dr != null) {
return dr;
}
@@ -1557,46 +1600,52 @@ public class Resources {
Drawable.ConstantState cs = mPreloadedDrawables.get(key);
if (cs != null) {
dr = cs.newDrawable();
-
} else {
- if (value.string == null) {
- throw new NotFoundException(
- "Resource is not a Drawable (color or path): " + value);
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+ value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ dr = new ColorDrawable(value.data);
}
-
- String file = value.string.toString();
-
- if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
- + value.assetCookie + ": " + file);
-
- if (file.endsWith(".xml")) {
- try {
- XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "drawable");
- dr = Drawable.createFromXml(this, rp);
- rp.close();
- } catch (Exception e) {
- NotFoundException rnf = new NotFoundException(
- "File " + file + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
+
+ if (dr == null) {
+ if (value.string == null) {
+ throw new NotFoundException(
+ "Resource is not a Drawable (color or path): " + value);
}
-
- } else {
- try {
- InputStream is = mAssets.openNonAsset(
- value.assetCookie, file, AssetManager.ACCESS_BUFFER);
- // System.out.println("Opened file " + file + ": " + is);
- dr = Drawable.createFromStream(is, file);
- is.close();
- // System.out.println("Created stream: " + dr);
- } catch (Exception e) {
- NotFoundException rnf = new NotFoundException(
- "File " + file + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
+
+ String file = value.string.toString();
+
+ if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
+ + value.assetCookie + ": " + file);
+
+ if (file.endsWith(".xml")) {
+ try {
+ XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable");
+ dr = Drawable.createFromXml(this, rp);
+ rp.close();
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + file + " from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+
+ } else {
+ try {
+ InputStream is = mAssets.openNonAsset(
+ value.assetCookie, file, AssetManager.ACCESS_BUFFER);
+ // System.out.println("Opened file " + file + ": " + is);
+ dr = Drawable.createFromResourceStream(this, value, is, file);
+ is.close();
+ // System.out.println("Created stream: " + dr);
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + file + " from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
}
}
}
@@ -1607,13 +1656,13 @@ public class Resources {
if (cs != null) {
if (mPreloading) {
mPreloadedDrawables.put(key, cs);
- }
- synchronized (mTmpValue) {
- //Log.i(TAG, "Saving cached drawable @ #" +
- // Integer.toHexString(key.intValue())
- // + " in " + this + ": " + cs);
- mDrawableCache.put(
- key, new WeakReference<Drawable.ConstantState>(cs));
+ } else {
+ synchronized (mTmpValue) {
+ //Log.i(TAG, "Saving cached drawable @ #" +
+ // Integer.toHexString(key.intValue())
+ // + " in " + this + ": " + cs);
+ mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
+ }
}
}
}
@@ -1621,7 +1670,7 @@ public class Resources {
return dr;
}
- private final Drawable getCachedDrawable(int key) {
+ private Drawable getCachedDrawable(int key) {
synchronized (mTmpValue) {
WeakReference<Drawable.ConstantState> wr = mDrawableCache.get(key);
if (wr != null) { // we have the key
@@ -1642,13 +1691,40 @@ public class Resources {
/*package*/ ColorStateList loadColorStateList(TypedValue value, int id)
throws NotFoundException {
- if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
- && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- return ColorStateList.valueOf(value.data);
+ if (TRACE_FOR_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) android.util.Log.d("PreloadColorStateList", name);
+ }
}
- final int key = (value.assetCookie<<24)|value.data;
- ColorStateList csl = getCachedColorStateList(key);
+ final int key = (value.assetCookie << 24) | value.data;
+
+ ColorStateList csl;
+
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
+ value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+
+ csl = mPreloadedColorStateLists.get(key);
+ if (csl != null) {
+ return csl;
+ }
+
+ csl = ColorStateList.valueOf(value.data);
+ if (mPreloading) {
+ mPreloadedColorStateLists.put(key, csl);
+ }
+
+ return csl;
+ }
+
+ csl = getCachedColorStateList(key);
+ if (csl != null) {
+ return csl;
+ }
+
+ csl = mPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
@@ -1680,12 +1756,16 @@ public class Resources {
}
if (csl != null) {
- synchronized (mTmpValue) {
- //Log.i(TAG, "Saving cached color state list @ #" +
- // Integer.toHexString(key.intValue())
- // + " in " + this + ": " + csl);
- mColorStateListCache.put(
- key, new WeakReference<ColorStateList>(csl));
+ if (mPreloading) {
+ mPreloadedColorStateLists.put(key, csl);
+ } else {
+ synchronized (mTmpValue) {
+ //Log.i(TAG, "Saving cached color state list @ #" +
+ // Integer.toHexString(key.intValue())
+ // + " in " + this + ": " + csl);
+ mColorStateListCache.put(
+ key, new WeakReference<ColorStateList>(csl));
+ }
}
}
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 3df7708..e684cb8 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -141,6 +141,8 @@ final class StringBlock {
int type = style[i];
if (localLOGV) Log.v(TAG, "Applying style span id=" + type
+ ", start=" + style[i+1] + ", end=" + style[i+2]);
+
+
if (type == ids.boldId) {
buffer.setSpan(new StyleSpan(Typeface.BOLD),
style[i+1], style[i+2]+1,
@@ -178,9 +180,8 @@ final class StringBlock {
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.listItemId) {
- buffer.setSpan(new BulletSpan(10),
- style[i+1], style[i+2]+1,
- Spannable.SPAN_PARAGRAPH);
+ addParagraphSpan(buffer, new BulletSpan(10),
+ style[i+1], style[i+2]+1);
} else if (type == ids.marqueeId) {
buffer.setSpan(TextUtils.TruncateAt.MARQUEE,
style[i+1], style[i+2]+1,
@@ -194,9 +195,8 @@ final class StringBlock {
sub = subtag(tag, ";height=");
if (sub != null) {
int size = Integer.parseInt(sub);
- buffer.setSpan(new Height(size),
- style[i+1], style[i+2]+1,
- Spannable.SPAN_PARAGRAPH);
+ addParagraphSpan(buffer, new Height(size),
+ style[i+1], style[i+2]+1);
}
sub = subtag(tag, ";size=");
@@ -231,6 +231,28 @@ final class StringBlock {
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+ } else if (tag.startsWith("annotation;")) {
+ int len = tag.length();
+ int next;
+
+ for (int t = tag.indexOf(';'); t < len; t = next) {
+ int eq = tag.indexOf('=', t);
+ if (eq < 0) {
+ break;
+ }
+
+ next = tag.indexOf(';', eq);
+ if (next < 0) {
+ next = len;
+ }
+
+ String key = tag.substring(t + 1, eq);
+ String value = tag.substring(eq + 1, next);
+
+ buffer.setSpan(new Annotation(key, value),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
}
}
@@ -239,6 +261,34 @@ final class StringBlock {
return new SpannedString(buffer);
}
+ /**
+ * If a translator has messed up the edges of paragraph-level markup,
+ * fix it to actually cover the entire paragraph that it is attached to
+ * instead of just whatever range they put it on.
+ */
+ private static void addParagraphSpan(Spannable buffer, Object what,
+ int start, int end) {
+ int len = buffer.length();
+
+ if (start != 0 && start != len && buffer.charAt(start - 1) != '\n') {
+ for (start--; start > 0; start--) {
+ if (buffer.charAt(start - 1) == '\n') {
+ break;
+ }
+ }
+ }
+
+ if (end != 0 && end != len && buffer.charAt(end - 1) != '\n') {
+ for (end++; end < len; end++) {
+ if (buffer.charAt(end - 1) == '\n') {
+ break;
+ }
+ }
+ }
+
+ buffer.setSpan(what, start, end, Spannable.SPAN_PARAGRAPH);
+ }
+
private static String subtag(String full, String attribute) {
int start = full.indexOf(attribute);
if (start < 0) {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 82a57dd..3a32c03 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -438,6 +438,34 @@ public class TypedArray {
throw new RuntimeException(getPositionDescription()
+ ": You must supply a " + name + " attribute.");
}
+
+ /**
+ * Special version of {@link #getDimensionPixelSize} for retrieving
+ * {@link android.view.ViewGroup}'s layout_width and layout_height
+ * attributes. This is only here for performance reasons; applications
+ * should use {@link #getDimensionPixelSize}.
+ *
+ * @param index Index of the attribute to retrieve.
+ * @param defValue The default value to return if this attribute is not
+ * default or contains the wrong type of data.
+ *
+ * @return Attribute dimension value multiplied by the appropriate
+ * metric and truncated to integer pixels.
+ */
+ public int getLayoutDimension(int index, int defValue) {
+ index *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if (type >= TypedValue.TYPE_FIRST_INT
+ && type <= TypedValue.TYPE_LAST_INT) {
+ return data[index+AssetManager.STYLE_DATA];
+ } else if (type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(
+ data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+ }
+
+ return defValue;
+ }
/**
* Retrieve a fractional unit attribute at <var>index</var>.
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 2ff7294..10f3806 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -84,6 +84,7 @@ public class DatabaseUtils {
code = 9;
} else {
reply.writeException(e);
+ Log.e(TAG, "Writing exception to parcel", e);
return;
}
reply.writeInt(code);
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index fa062c8..2af080a 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -25,6 +25,7 @@ import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
+import android.util.EventLog;
import java.io.File;
import java.util.HashMap;
@@ -51,7 +52,8 @@ import java.util.concurrent.locks.ReentrantLock;
* is the Unicode Collation Algorithm and not tailored to the current locale.
*/
public class SQLiteDatabase extends SQLiteClosable {
- private final static String TAG = "Database";
+ private static final String TAG = "Database";
+ private static final int DB_OPERATION_EVENT = 52000;
/**
* Algorithms used in ON CONFLICT clause
@@ -207,6 +209,11 @@ public class SQLiteDatabase extends SQLiteClosable {
private WeakHashMap<SQLiteClosable, Object> mPrograms;
private final RuntimeException mLeakedException;
+
+ // package visible, since callers will access directly to minimize overhead in the case
+ // that logging is not enabled.
+ /* package */ final boolean mLogStats;
+
/**
* @param closable
*/
@@ -436,7 +443,14 @@ public class SQLiteDatabase extends SQLiteClosable {
if (mTransactionIsSuccessful) {
execSQL("COMMIT;");
} else {
- execSQL("ROLLBACK;");
+ try {
+ execSQL("ROLLBACK;");
+ } catch (SQLException e) {
+ if (Config.LOGD) {
+ Log.d(TAG, "exception during rollback, maybe the DB previously "
+ + "performed an auto-rollback");
+ }
+ }
}
} finally {
unlockForced();
@@ -764,9 +778,9 @@ public class SQLiteDatabase extends SQLiteClosable {
}
/**
- * Returns the maximum size the database may grow to.
+ * Returns the current database page size, in bytes.
*
- * @return the new maximum database size
+ * @return the database page size, in bytes
*/
public long getPageSize() {
SQLiteStatement prog = null;
@@ -1472,10 +1486,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException If the SQL string is invalid for some reason
*/
public void execSQL(String sql) throws SQLException {
- long timeStart = 0;
- if (Config.LOGV) {
- timeStart = System.currentTimeMillis();
- }
+ boolean logStats = mLogStats;
+ long timeStart = logStats ? SystemClock.elapsedRealtime() : 0;
lock();
try {
native_execSQL(sql);
@@ -1485,9 +1497,8 @@ public class SQLiteDatabase extends SQLiteClosable {
} finally {
unlock();
}
- if (Config.LOGV) {
- long timeEnd = System.currentTimeMillis();
- Log.v(TAG, "Executed (" + (timeEnd - timeStart) + " ms):" + sql);
+ if (logStats) {
+ logTimeStat(false /* not a read */, timeStart, SystemClock.elapsedRealtime());
}
}
@@ -1504,10 +1515,9 @@ public class SQLiteDatabase extends SQLiteClosable {
if (bindArgs == null) {
throw new IllegalArgumentException("Empty bindArgs");
}
- long timeStart = 0;
- if (Config.LOGV) {
- timeStart = System.currentTimeMillis();
- }
+
+ boolean logStats = mLogStats;
+ long timeStart = logStats ? SystemClock.elapsedRealtime() : 0;
lock();
SQLiteStatement statement = null;
try {
@@ -1528,9 +1538,8 @@ public class SQLiteDatabase extends SQLiteClosable {
}
unlock();
}
- if (Config.LOGV) {
- long timeEnd = System.currentTimeMillis();
- Log.v(TAG, "Executed (" + (timeEnd - timeStart) + " ms):" + sql);
+ if (logStats) {
+ logTimeStat(false /* not a read */, timeStart, SystemClock.elapsedRealtime());
}
}
@@ -1550,7 +1559,7 @@ public class SQLiteDatabase extends SQLiteClosable {
}
/**
- * Private constructor. See {@link createDatabase} and {@link openDatabase}.
+ * Private constructor. See {@link #create} and {@link #openDatabase}.
*
* @param path The full path to the database
* @param factory The factory to use when creating cursors, may be NULL.
@@ -1563,6 +1572,8 @@ public class SQLiteDatabase extends SQLiteClosable {
}
mFlags = flags;
mPath = path;
+ mLogStats = "1".equals(android.os.SystemProperties.get("db.logstats"));
+
mLeakedException = new IllegalStateException(path +
" SQLiteDatabase created and never closed");
mFactory = factory;
@@ -1605,6 +1616,10 @@ public class SQLiteDatabase extends SQLiteClosable {
return mPath;
}
+ /* package */ void logTimeStat(boolean read, long begin, long end) {
+ EventLog.writeEvent(DB_OPERATION_EVENT, mPath, read ? 0 : 1, end - begin);
+ }
+
/**
* Sets the locale for this database. Does nothing if this database has
* the NO_LOCALIZED_COLLATORS flag set or was opened read only.
@@ -1629,7 +1644,7 @@ public class SQLiteDatabase extends SQLiteClosable {
private native void dbopen(String path, int flags);
/**
- * Native call to execute a raw SQL statement. {@link mLock} must be held
+ * Native call to execute a raw SQL statement. {@link #lock} must be held
* when calling this method.
*
* @param sql The raw SQL string
@@ -1638,7 +1653,7 @@ public class SQLiteDatabase extends SQLiteClosable {
/* package */ native void native_execSQL(String sql) throws SQLException;
/**
- * Native call to set the locale. {@link mLock} must be held when calling
+ * Native call to set the locale. {@link #lock} must be held when calling
* this method.
* @throws SQLException
*/
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 35bf645..52aac3a 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -26,6 +26,8 @@ import android.util.Log;
* optionally {@link #onOpen}, and this class takes care of opening the database
* if it exists, creating it if it does not, and upgrading it as necessary.
* Transactions are used to make sure the database is always in a sensible state.
+ * <p>For an example, see the NotePadProvider class in the NotePad sample application,
+ * in the <em>samples/</em> directory of the SDK.</p>
*/
public abstract class SQLiteOpenHelper {
private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index f89c87d..9e85452 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -22,7 +22,7 @@ import android.util.Log;
* A base class for compiled SQLite programs.
*/
public abstract class SQLiteProgram extends SQLiteClosable {
- static final String TAG = "SQLiteProgram";
+ private static final String TAG = "SQLiteProgram";
/** The database this program is compiled against. */
protected SQLiteDatabase mDatabase;
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 22c53ab..1386a0d 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -17,13 +17,15 @@
package android.database.sqlite;
import android.database.CursorWindow;
+import android.os.SystemClock;
+import android.util.Log;
/**
* A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
* This class is used by SQLiteCursor and isn't useful itself.
*/
public class SQLiteQuery extends SQLiteProgram {
- //private static final String TAG = "Cursor";
+ private static final String TAG = "Cursor";
/** The index of the unbound OFFSET parameter */
private int mOffsetIndex;
@@ -55,12 +57,14 @@ public class SQLiteQuery extends SQLiteProgram {
* Reads rows into a buffer. This method acquires the database lock.
*
* @param window The window to fill into
- * @param startPos The position to start reading rows from
* @return number of total rows in the query
*/
/* package */ int fillWindow(CursorWindow window,
int maxRead, int lastPos) {
mDatabase.lock();
+
+ boolean logStats = mDatabase.mLogStats;
+ long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
try {
acquireReference();
try {
@@ -68,8 +72,18 @@ public class SQLiteQuery extends SQLiteProgram {
// if the start pos is not equal to 0, then most likely window is
// too small for the data set, loading by another thread
// is not safe in this situation. the native code will ignore maxRead
- return native_fill_window(window, window.getStartPosition(), mOffsetIndex,
+ int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
maxRead, lastPos);
+
+ // Logging
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.d(TAG, "fillWindow(): " + mQuery);
+ }
+ if (logStats) {
+ mDatabase.logTimeStat(true /* read */, startTime,
+ SystemClock.elapsedRealtime());
+ }
+ return numRows;
} catch (IllegalStateException e){
// simply ignore it
return 0;
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index bf9361d..5889ad9 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -16,6 +16,9 @@
package android.database.sqlite;
+import android.os.SystemClock;
+import android.util.Log;
+
/**
* A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
* The statement cannot return multiple rows, but 1x1 result sets are allowed.
@@ -24,6 +27,10 @@ package android.database.sqlite;
*/
public class SQLiteStatement extends SQLiteProgram
{
+ private static final String TAG = "SQLiteStatement";
+
+ private final String mSql;
+
/**
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
@@ -32,6 +39,11 @@ public class SQLiteStatement extends SQLiteProgram
*/
/* package */ SQLiteStatement(SQLiteDatabase db, String sql) {
super(db, sql);
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ mSql = sql;
+ } else {
+ mSql = null;
+ }
}
/**
@@ -43,10 +55,19 @@ public class SQLiteStatement extends SQLiteProgram
*/
public void execute() {
mDatabase.lock();
+ boolean logStats = mDatabase.mLogStats;
+ long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+
acquireReference();
try {
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.v(TAG, "execute() for [" + mSql + "]");
+ }
native_execute();
- } finally {
+ if (logStats) {
+ mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
+ }
+ } finally {
releaseReference();
mDatabase.unlock();
}
@@ -64,9 +85,18 @@ public class SQLiteStatement extends SQLiteProgram
*/
public long executeInsert() {
mDatabase.lock();
+ boolean logStats = mDatabase.mLogStats;
+ long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+
acquireReference();
try {
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.v(TAG, "executeInsert() for [" + mSql + "]");
+ }
native_execute();
+ if (logStats) {
+ mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
+ }
return mDatabase.lastInsertRow();
} finally {
releaseReference();
@@ -84,9 +114,19 @@ public class SQLiteStatement extends SQLiteProgram
*/
public long simpleQueryForLong() {
mDatabase.lock();
+ boolean logStats = mDatabase.mLogStats;
+ long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+
acquireReference();
try {
- return native_1x1_long();
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.v(TAG, "simpleQueryForLong() for [" + mSql + "]");
+ }
+ long retValue = native_1x1_long();
+ if (logStats) {
+ mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
+ }
+ return retValue;
} finally {
releaseReference();
mDatabase.unlock();
@@ -103,9 +143,19 @@ public class SQLiteStatement extends SQLiteProgram
*/
public String simpleQueryForString() {
mDatabase.lock();
+ boolean logStats = mDatabase.mLogStats;
+ long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+
acquireReference();
try {
- return native_1x1_string();
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.v(TAG, "simpleQueryForString() for [" + mSql + "]");
+ }
+ String retValue = native_1x1_string();
+ if (logStats) {
+ mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
+ }
+ return retValue;
} finally {
releaseReference();
mDatabase.unlock();
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
index c03a8dc..ff0f9f5 100644
--- a/core/java/android/database/sqlite/package.html
+++ b/core/java/android/database/sqlite/package.html
@@ -6,7 +6,7 @@ classes that an application would use to manage its own private database.
Applications use these classes to maange private databases. If creating a
content provider, you will probably have to use these classes to create and
manage your own database to store content. See <a
-href="{@docRoot}devel/data.html">Storing, Retrieving and Exposing Data</a> to learn
+href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> to learn
the conventions for implementing a content provider. See the
NotePadProvider class in the NotePad sample application in the SDK for an
example of a content provider. Android ships with SQLite version 3.4.0
diff --git a/core/java/android/emoji/EmojiFactory.java b/core/java/android/emoji/EmojiFactory.java
new file mode 100644
index 0000000..389bd07
--- /dev/null
+++ b/core/java/android/emoji/EmojiFactory.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.emoji;
+
+import android.graphics.Bitmap;
+
+import java.lang.ref.WeakReference;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A class for the factories which produce Emoji (pictgram) images.
+ * This is intended to be used by IME, Email app, etc.
+ * There's no plan to make this public for now.
+ * @hide
+ */
+public final class EmojiFactory {
+ // private static final String LOG_TAG = "EmojiFactory";
+
+ private int sCacheSize = 100;
+
+ // HashMap for caching Bitmap object. In order not to make an cache object
+ // blow up, we use LinkedHashMap with size limit.
+ private class CustomLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
+ public CustomLinkedHashMap() {
+ // These magic numbers are gotten from the source code of
+ // LinkedHashMap.java and HashMap.java.
+ super(16, 0.75f, true);
+ }
+
+ /*
+ * If size() becomes more than sCacheSize, least recently used cache
+ * is erased.
+ * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
+ */
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+ return size() > sCacheSize;
+ }
+ }
+
+ // A pointer to native EmojiFactory object.
+ private int mNativeEmojiFactory;
+ private String mName;
+ // Cache.
+ private Map<Integer, WeakReference<Bitmap>> mCache;
+
+ /**
+ * @noinspection UnusedDeclaration
+ */
+ /*
+ * Private constructor that must received an already allocated native
+ * EmojiFactory int (pointer).
+ *
+ * This can be called from JNI code.
+ */
+ private EmojiFactory(int nativeEmojiFactory, String name) {
+ mNativeEmojiFactory = nativeEmojiFactory;
+ mName = name;
+ mCache = new CustomLinkedHashMap<Integer, WeakReference<Bitmap>>();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nativeDestructor(mNativeEmojiFactory);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public String name() {
+ return mName;
+ }
+
+ /**
+ * Returns Bitmap object corresponding to the AndroidPua.
+ *
+ * Note that each Bitmap is cached by this class, which means that, if you modify a
+ * Bitmap object (using setPos() method), all same emoji Bitmap will be modified.
+ * If it is unacceptable, please copy the object before modifying it.
+ *
+ * @param pua A unicode codepoint.
+ * @return Bitmap object when this factory knows the Bitmap relevant to the codepoint.
+ * Otherwise null is returned.
+ */
+ public synchronized Bitmap getBitmapFromAndroidPua(int pua) {
+ WeakReference<Bitmap> cache = mCache.get(pua);
+ if (cache == null) {
+ Bitmap ret = nativeGetBitmapFromAndroidPua(mNativeEmojiFactory, pua);
+ // There is no need to cache returned null, since in most cases it means there
+ // is no map from the AndroidPua to a specific image. In other words, it usually does
+ // not include the cost of creating Bitmap object.
+ if (ret != null) {
+ mCache.put(pua, new WeakReference<Bitmap>(ret));
+ }
+ return ret;
+ } else {
+ Bitmap tmp = cache.get();
+ if (tmp == null) {
+ Bitmap ret = nativeGetBitmapFromAndroidPua(mNativeEmojiFactory, pua);
+ mCache.put(pua, new WeakReference<Bitmap>(ret));
+ return ret;
+ } else {
+ return tmp;
+ }
+ }
+ }
+
+ /**
+ * Returns Bitmap object corresponding to the vendor specified sjis.
+ *
+ * See comments in getBitmapFromAndroidPua().
+ *
+ * @param sjis sjis code specific to each career(vendor)
+ * @return Bitmap object when this factory knows the Bitmap relevant to the code. Otherwise
+ * null is returned.
+ */
+ public synchronized Bitmap getBitmapFromVendorSpecificSjis(char sjis) {
+ return getBitmapFromAndroidPua(getAndroidPuaFromVendorSpecificSjis(sjis));
+ }
+
+ /**
+ * Returns Bitmap object corresponding to the vendor specific Unicode.
+ *
+ * See comments in getBitmapFromAndroidPua().
+ *
+ * @param vsp vendor specific PUA.
+ * @return Bitmap object when this factory knows the Bitmap relevant to the code. Otherwise
+ * null is returned.
+ */
+ public synchronized Bitmap getBitmapFromVendorSpecificPua(int vsp) {
+ return getBitmapFromAndroidPua(getAndroidPuaFromVendorSpecificPua(vsp));
+ }
+
+ /**
+ * Returns Unicode PUA for Android corresponding to the vendor specific sjis.
+ *
+ * @param sjis vendor specific sjis
+ * @return Unicode PUA for Android, or -1 if there's no map for the sjis.
+ */
+ public int getAndroidPuaFromVendorSpecificSjis(char sjis) {
+ return nativeGetAndroidPuaFromVendorSpecificSjis(mNativeEmojiFactory, sjis);
+ }
+
+ /**
+ * Returns vendor specific sjis corresponding to the Unicode AndroidPua.
+ *
+ * @param pua Unicode PUA for Android,
+ * @return vendor specific sjis, or -1 if there's no map for the AndroidPua.
+ */
+ public int getVendorSpecificSjisFromAndroidPua(int pua) {
+ return nativeGetVendorSpecificSjisFromAndroidPua(mNativeEmojiFactory, pua);
+ }
+
+ /**
+ * Returns Unicode PUA for Android corresponding to the vendor specific Unicode.
+ *
+ * @param vsp vendor specific PUA.
+ * @return Unicode PUA for Android, or -1 if there's no map for the
+ * Unicode.
+ */
+ public int getAndroidPuaFromVendorSpecificPua(int vsp) {
+ return nativeGetAndroidPuaFromVendorSpecificPua(mNativeEmojiFactory, vsp);
+ }
+
+ public String getAndroidPuaFromVendorSpecificPua(String vspString) {
+ if (vspString == null) {
+ return null;
+ }
+ int minVsp = nativeGetMinimumVendorSpecificPua(mNativeEmojiFactory);
+ int maxVsp = nativeGetMaximumVendorSpecificPua(mNativeEmojiFactory);
+ int len = vspString.length();
+ int[] codePoints = new int[vspString.codePointCount(0, len)];
+
+ int new_len = 0;
+ for (int i = 0; i < len; i = vspString.offsetByCodePoints(i, 1), new_len++) {
+ int codePoint = vspString.codePointAt(i);
+ if (minVsp <= codePoint && codePoint <= maxVsp) {
+ int newCodePoint = getAndroidPuaFromVendorSpecificPua(codePoint);
+ if (newCodePoint > 0) {
+ codePoints[new_len] = newCodePoint;
+ continue;
+ }
+ }
+ codePoints[new_len] = codePoint;
+ }
+ return new String(codePoints, 0, new_len);
+ }
+
+ /**
+ * Returns vendor specific Unicode corresponding to the Unicode AndroidPua.
+ *
+ * @param pua Unicode PUA for Android,
+ * @return vendor specific sjis, or -1 if there's no map for the AndroidPua.
+ */
+ public int getVendorSpecificPuaFromAndroidPua(int pua) {
+ return nativeGetVendorSpecificPuaFromAndroidPua(mNativeEmojiFactory, pua);
+ }
+
+ public String getVendorSpecificPuaFromAndroidPua(String puaString) {
+ if (puaString == null) {
+ return null;
+ }
+ int minVsp = nativeGetMinimumAndroidPua(mNativeEmojiFactory);
+ int maxVsp = nativeGetMaximumAndroidPua(mNativeEmojiFactory);
+ int len = puaString.length();
+ int[] codePoints = new int[puaString.codePointCount(0, len)];
+
+ int new_len = 0;
+ for (int i = 0; i < len; i = puaString.offsetByCodePoints(i, 1), new_len++) {
+ int codePoint = puaString.codePointAt(i);
+ if (minVsp <= codePoint && codePoint <= maxVsp) {
+ int newCodePoint = getVendorSpecificPuaFromAndroidPua(codePoint);
+ if (newCodePoint > 0) {
+ codePoints[new_len] = newCodePoint;
+ continue;
+ }
+ }
+ codePoints[new_len] = codePoint;
+ }
+ return new String(codePoints, 0, new_len);
+ }
+
+ /**
+ * Constructs an instance of EmojiFactory corresponding to the name.
+ *
+ * @param class_name Name of the factory. This must include complete package name.
+ * @return A concrete EmojiFactory instance corresponding to factory_name.
+ * If factory_name is invalid, null is returned.
+ */
+ public static native EmojiFactory newInstance(String class_name);
+
+ /**
+ * Constructs an instance of available EmojiFactory.
+ *
+ * @return A concrete EmojiFactory instance. If there are several available
+ * EmojiFactory class, preferred one is chosen by the system. If there isn't, null
+ * is returned.
+ */
+ public static native EmojiFactory newAvailableInstance();
+
+ // native methods
+
+ private native void nativeDestructor(int factory);
+ private native Bitmap nativeGetBitmapFromAndroidPua(int nativeEmojiFactory, int AndroidPua);
+ private native int nativeGetAndroidPuaFromVendorSpecificSjis(int nativeEmojiFactory,
+ char sjis);
+ private native int nativeGetVendorSpecificSjisFromAndroidPua(int nativeEmojiFactory,
+ int pua);
+ private native int nativeGetAndroidPuaFromVendorSpecificPua(int nativeEmojiFactory,
+ int vsp);
+ private native int nativeGetVendorSpecificPuaFromAndroidPua(int nativeEmojiFactory,
+ int pua);
+ private native int nativeGetMaximumVendorSpecificPua(int nativeEmojiFactory);
+ private native int nativeGetMinimumVendorSpecificPua(int nativeEmojiFactory);
+ private native int nativeGetMaximumAndroidPua(int nativeEmojiFactory);
+ private native int nativeGetMinimumAndroidPua(int nativeEmojiFactory);
+}
diff --git a/core/java/android/gadget/GadgetHost.java b/core/java/android/gadget/GadgetHost.java
deleted file mode 100644
index 418f2aa..0000000
--- a/core/java/android/gadget/GadgetHost.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.gadget;
-
-import android.content.Context;
-import android.widget.RemoteViews;
-
-/**
- * GadgetHost provides the interaction with the Gadget Service for apps,
- * like the home screen, that want to embed gadgets in their UI.
- */
-public class GadgetHost {
- public GadgetHost(Context context, int hostId) {
- }
-
- /**
- * Start receiving onGadgetChanged calls for your gadgets. Call this when your activity
- * becomes visible, i.e. from onStart() in your Activity.
- */
- public void startListening() {
- }
-
- /**
- * Stop receiving onGadgetChanged calls for your gadgets. Call this when your activity is
- * no longer visible, i.e. from onStop() in your Activity.
- */
- public void stopListening() {
- }
-
- /**
- * Stop listening to changes for this gadget.
- */
- public void gadgetRemoved(int gadgetId) {
- }
-
- /**
- * Remove all records about gadget instances from the gadget manager. Call this when
- * initializing your database, as it might be because of a data wipe.
- */
- public void clearGadgets() {
- }
-
- public final GadgetHostView createView(Context context, int gadgetId, GadgetInfo gadget) {
- GadgetHostView view = onCreateView(context, gadgetId, gadget);
- view.setGadget(gadgetId, gadget);
- view.updateGadget(null);
- return view;
- }
-
- /**
- * Called to create the GadgetHostView. Override to return a custom subclass if you
- * need it. {@more}
- */
- protected GadgetHostView onCreateView(Context context, int gadgetId, GadgetInfo gadget) {
- return new GadgetHostView(context);
- }
-}
-
diff --git a/core/java/android/gadget/GadgetHostView.java b/core/java/android/gadget/GadgetHostView.java
deleted file mode 100644
index e2bef8c..0000000
--- a/core/java/android/gadget/GadgetHostView.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.gadget;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.gadget.GadgetInfo;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.RemoteViews;
-import android.widget.TextView;
-
-public class GadgetHostView extends FrameLayout {
- static final String TAG = "GadgetHostView";
-
- // When we're inflating the initialLayout for a gadget, we only allow
- // views that are allowed in RemoteViews.
- static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
- public boolean onLoadClass(Class clazz) {
- return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
- }
- };
-
- int mGadgetId;
- GadgetInfo mInfo;
- View mContentView;
-
- public GadgetHostView(Context context) {
- super(context);
- }
-
- public void setGadget(int gadgetId, GadgetInfo info) {
- if (mInfo != null) {
- // TODO: remove the old view, or whatever
- }
- mGadgetId = gadgetId;
- mInfo = info;
- }
-
- public void updateGadget(RemoteViews remoteViews) {
- Context context = getContext();
-
- View contentView = null;
- Exception exception = null;
- try {
- if (remoteViews == null) {
- // there is no remoteViews (yet), so use the initial layout
- Context theirContext = context.createPackageContext(mInfo.provider.getPackageName(),
- 0);
- LayoutInflater inflater = (LayoutInflater)theirContext.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- inflater = inflater.cloneInContext(theirContext);
- inflater.setFilter(sInflaterFilter);
- contentView = inflater.inflate(mInfo.initialLayout, this, false);
- } else {
- // use the RemoteViews
- contentView = remoteViews.apply(mContext, this);
- }
- }
- catch (PackageManager.NameNotFoundException e) {
- exception = e;
- }
- catch (RuntimeException e) {
- exception = e;
- }
- if (contentView == null) {
- Log.w(TAG, "Error inflating gadget " + mInfo, exception);
- // TODO: Should we throw an exception here for the host activity to catch?
- // Maybe we should show a generic error widget.
- TextView tv = new TextView(context);
- tv.setText("Error inflating gadget");
- contentView = tv;
- }
-
- FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT,
- FrameLayout.LayoutParams.WRAP_CONTENT);
-
- mContentView = contentView;
- this.addView(contentView, lp);
-
- // TODO: do an animation (maybe even one provided by the gadget).
- }
-}
-
diff --git a/core/java/android/gadget/GadgetInfo.java b/core/java/android/gadget/GadgetInfo.java
deleted file mode 100644
index 1a7a9a0..0000000
--- a/core/java/android/gadget/GadgetInfo.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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.gadget;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.content.ComponentName;
-
-/**
- * Describes the meta data for an installed gadget.
- */
-public class GadgetInfo implements Parcelable {
- /**
- * Identity of this gadget component. This component should be a {@link
- * android.content.BroadcastReceiver}, and it will be sent the Gadget intents
- * {@link android.gadget as described in the gadget package documentation}.
- */
- public ComponentName provider;
-
- /**
- * Minimum width of the gadget, in dp.
- */
- public int minWidth;
-
- /**
- * Minimum height of the gadget, in dp.
- */
- public int minHeight;
-
- /**
- * How often, in milliseconds, that this gadget wants to be updated.
- * The gadget manager may place a limit on how often a gadget is updated.
- */
- public int updatePeriodMillis;
-
- /**
- * The resource id of the initial layout for this gadget. This should be
- * displayed until the RemoteViews for the gadget is available.
- */
- public int initialLayout;
-
- /**
- * The activity to launch that will configure the gadget.
- */
- public ComponentName configure;
-
- public GadgetInfo() {
- }
-
- /**
- * Unflatten the GadgetInfo from a parcel.
- */
- public GadgetInfo(Parcel in) {
- if (0 != in.readInt()) {
- this.provider = new ComponentName(in);
- }
- this.minWidth = in.readInt();
- this.minHeight = in.readInt();
- this.updatePeriodMillis = in.readInt();
- this.initialLayout = in.readInt();
- if (0 != in.readInt()) {
- this.configure = new ComponentName(in);
- }
- }
-
-
- public void writeToParcel(android.os.Parcel out, int flags) {
- if (this.provider != null) {
- out.writeInt(1);
- this.provider.writeToParcel(out, flags);
- } else {
- out.writeInt(0);
- }
- out.writeInt(this.minWidth);
- out.writeInt(this.minHeight);
- out.writeInt(this.updatePeriodMillis);
- out.writeInt(this.initialLayout);
- if (this.configure != null) {
- out.writeInt(1);
- this.configure.writeToParcel(out, flags);
- } else {
- out.writeInt(0);
- }
- }
-
- public int describeContents() {
- return 0;
- }
-
- /**
- * Parcelable.Creator that instantiates GadgetInfo objects
- */
- public static final Parcelable.Creator<GadgetInfo> CREATOR
- = new Parcelable.Creator<GadgetInfo>()
- {
- public GadgetInfo createFromParcel(Parcel parcel)
- {
- return new GadgetInfo(parcel);
- }
-
- public GadgetInfo[] newArray(int size)
- {
- return new GadgetInfo[size];
- }
- };
-
- public String toString() {
- return "GadgetInfo(provider=" + this.provider + ")";
- }
-}
-
-
diff --git a/core/java/android/gadget/GadgetManager.java b/core/java/android/gadget/GadgetManager.java
deleted file mode 100644
index 088dc86..0000000
--- a/core/java/android/gadget/GadgetManager.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.gadget;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-import android.widget.RemoteViews;
-
-import com.android.internal.gadget.IGadgetService;
-
-import java.lang.ref.WeakReference;
-import java.util.List;
-import java.util.WeakHashMap;
-
-public class GadgetManager {
- static final String TAG = "GadgetManager";
-
- /**
- * Send this when you want to pick a gadget to display.
- *
- * <p>
- * The system will respond with an onActivityResult call with the following extras in
- * the intent:
- * <ul>
- * <li><b>gadgetId</b></li>
- * <li><b>gadgetId</b></li>
- * <li><b>gadgetId</b></li>
- * </ul>
- * TODO: Add constants for these.
- * TODO: Where does this go?
- */
- public static final String GADGET_PICK_ACTION = "android.gadget.action.PICK_GADGET";
-
- public static final String EXTRA_GADGET_ID = "gadgetId";
-
- /**
- * Sent when it is time to update your gadget.
- */
- public static final String GADGET_UPDATE_ACTION = "android.gadget.action.GADGET_UPDATE";
-
- /**
- * Sent when the gadget is added to a host for the first time. TODO: Maybe we don't want this.
- */
- public static final String GADGET_ENABLE_ACTION = "android.gadget.action.GADGET_ENABLE";
-
- /**
- * Sent when the gadget is removed from the last host. TODO: Maybe we don't want this.
- */
- public static final String GADGET_DISABLE_ACTION = "android.gadget.action.GADGET_DISABLE";
-
- /**
- * Field for the manifest meta-data tag.
- */
- public static final String GADGET_PROVIDER_META_DATA = "android.gadget.provider";
-
- static WeakHashMap<Context, WeakReference<GadgetManager>> sManagerCache = new WeakHashMap();
- static IGadgetService sService;
-
- Context mContext;
-
- public static GadgetManager getInstance(Context context) {
- synchronized (sManagerCache) {
- if (sService == null) {
- IBinder b = ServiceManager.getService(Context.GADGET_SERVICE);
- sService = IGadgetService.Stub.asInterface(b);
- }
-
- WeakReference<GadgetManager> ref = sManagerCache.get(context);
- GadgetManager result = null;
- if (ref != null) {
- result = ref.get();
- }
- if (result == null) {
- result = new GadgetManager(context);
- sManagerCache.put(context, new WeakReference(result));
- }
- return result;
- }
- }
-
- private GadgetManager(Context context) {
- mContext = context;
- }
-
- /**
- * Call this with the new RemoteViews for your gadget whenever you need to.
- *
- * <p>
- * This method will only work when called from the uid that owns the gadget provider.
- *
- * @param gadgetId The gadget instance for which to set the RemoteViews.
- * @param views The RemoteViews object to show.
- */
- public void updateGadget(int gadgetId, RemoteViews views) {
- }
-
- /**
- * Return a list of the gadget providers that are currently installed.
- */
- public List<GadgetInfo> getInstalledProviders() {
- try {
- return sService.getInstalledProviders();
- }
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- }
-
- /**
- * Get the available info about the gadget. If the gadgetId has not been bound yet,
- * this method will return null.
- *
- * TODO: throws GadgetNotFoundException ??? if not valid
- */
- public GadgetInfo getGadgetInfo(int gadgetId) {
- try {
- return sService.getGadgetInfo(gadgetId);
- }
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- }
-
- /**
- * Get a gadgetId for a host in the calling process.
- *
- * @return a gadgetId
- */
- public int allocateGadgetId(String hostPackage) {
- try {
- return sService.allocateGadgetId(hostPackage);
- }
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- }
-
- /**
- * Delete the gadgetId. Same as removeGadget on GadgetHost.
- */
- public void deleteGadgetId(int gadgetId) {
- try {
- sService.deleteGadgetId(gadgetId);
- }
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- }
-
- /**
- * Set the component for a given gadgetId. You need the GADGET_LIST permission.
- */
- public void bindGadgetId(int gadgetId, ComponentName provider) {
- try {
- sService.bindGadgetId(gadgetId, provider);
- }
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- }
-}
-
diff --git a/core/java/android/gadget/package.html b/core/java/android/gadget/package.html
deleted file mode 100644
index 280ccfb..0000000
--- a/core/java/android/gadget/package.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<body>
-@hide
-</body>
-
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index c09567c..106c920 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -18,6 +18,7 @@ package android.hardware;
import java.lang.ref.WeakReference;
import java.util.HashMap;
+import java.util.StringTokenizer;
import java.io.IOException;
import android.util.Log;
@@ -47,7 +48,6 @@ public class Camera {
private static final int ERROR_CALLBACK = 5;
private int mNativeContext; // accessed by native methods
- private int mListenerContext;
private EventHandler mEventHandler;
private ShutterCallback mShutterCallback;
private PictureCallback mRawImageCallback;
@@ -494,11 +494,17 @@ public class Camera {
*/
public void unflatten(String flattened) {
mMap.clear();
- String[] pairs = flattened.split(";");
- for (String p : pairs) {
- String[] kv = p.split("=");
- if (kv.length == 2)
- mMap.put(kv[0], kv[1]);
+
+ StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
+ while (tokenizer.hasMoreElements()) {
+ String kv = tokenizer.nextToken();
+ int pos = kv.indexOf('=');
+ if (pos == -1) {
+ continue;
+ }
+ String k = kv.substring(0, pos);
+ String v = kv.substring(pos + 1);
+ mMap.put(k, v);
}
}
diff --git a/core/java/android/hardware/GeomagneticField.java b/core/java/android/hardware/GeomagneticField.java
new file mode 100644
index 0000000..b4c04b1
--- /dev/null
+++ b/core/java/android/hardware/GeomagneticField.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import java.util.GregorianCalendar;
+
+/**
+ * This class is used to estimated estimate magnetic field at a given point on
+ * Earth, and in particular, to compute the magnetic declination from true
+ * north.
+ *
+ * <p>This uses the World Magnetic Model produced by the United States National
+ * Geospatial-Intelligence Agency. More details about the model can be found at
+ * <a href="http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml">http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml</a>.
+ * This class currently uses WMM-2005 which is valid until 2010, but should
+ * produce acceptable results for several years after that.
+ */
+public class GeomagneticField {
+ // The magnetic field at a given point, in nonoteslas in geodetic
+ // coordinates.
+ private float mX;
+ private float mY;
+ private float mZ;
+
+ // Geocentric coordinates -- set by computeGeocentricCoordinates.
+ private float mGcLatitudeRad;
+ private float mGcLongitudeRad;
+ private float mGcRadiusKm;
+
+ // Constants from WGS84 (the coordinate system used by GPS)
+ static private final float EARTH_SEMI_MAJOR_AXIS_KM = 6378.137f;
+ static private final float EARTH_SEMI_MINOR_AXIS_KM = 6356.7523f;
+ static private final float EARTH_REFERENCE_RADIUS_KM = 6371.2f;
+
+ // These coefficients and the formulae used below are from:
+ // NOAA Technical Report: The US/UK World Magnetic Model for 2005-2010
+ static private final float[][] G_COEFF = new float[][] {
+ { 0f },
+ { -29556.8f, -1671.7f },
+ { -2340.6f, 3046.9f, 1657.0f },
+ { 1335.4f, -2305.1f, 1246.7f, 674.0f },
+ { 919.8f, 798.1f, 211.3f, -379.4f, 100.0f },
+ { -227.4f, 354.6f, 208.7f, -136.5f, -168.3f, -14.1f },
+ { 73.2f, 69.7f, 76.7f, -151.2f, -14.9f, 14.6f, -86.3f },
+ { 80.1f, -74.5f, -1.4f, 38.5f, 12.4f, 9.5f, 5.7f, 1.8f },
+ { 24.9f, 7.7f, -11.6f, -6.9f, -18.2f, 10.0f, 9.2f, -11.6f, -5.2f },
+ { 5.6f, 9.9f, 3.5f, -7.0f, 5.1f, -10.8f, -1.3f, 8.8f, -6.7f, -9.1f },
+ { -2.3f, -6.3f, 1.6f, -2.6f, 0.0f, 3.1f, 0.4f, 2.1f, 3.9f, -0.1f, -2.3f },
+ { 2.8f, -1.6f, -1.7f, 1.7f, -0.1f, 0.1f, -0.7f, 0.7f, 1.8f, 0.0f, 1.1f, 4.1f },
+ { -2.4f, -0.4f, 0.2f, 0.8f, -0.3f, 1.1f, -0.5f, 0.4f, -0.3f, -0.3f, -0.1f,
+ -0.3f, -0.1f } };
+
+ static private final float[][] H_COEFF = new float[][] {
+ { 0f },
+ { 0.0f, 5079.8f },
+ { 0.0f, -2594.7f, -516.7f },
+ { 0.0f, -199.9f, 269.3f, -524.2f },
+ { 0.0f, 281.5f, -226.0f, 145.8f, -304.7f },
+ { 0.0f, 42.4f, 179.8f, -123.0f, -19.5f, 103.6f },
+ { 0.0f, -20.3f, 54.7f, 63.6f, -63.4f, -0.1f, 50.4f },
+ { 0.0f, -61.5f, -22.4f, 7.2f, 25.4f, 11.0f, -26.4f, -5.1f },
+ { 0.0f, 11.2f, -21.0f, 9.6f, -19.8f, 16.1f, 7.7f, -12.9f, -0.2f },
+ { 0.0f, -20.1f, 12.9f, 12.6f, -6.7f, -8.1f, 8.0f, 2.9f, -7.9f, 6.0f },
+ { 0.0f, 2.4f, 0.2f, 4.4f, 4.8f, -6.5f, -1.1f, -3.4f, -0.8f, -2.3f, -7.9f },
+ { 0.0f, 0.3f, 1.2f, -0.8f, -2.5f, 0.9f, -0.6f, -2.7f, -0.9f, -1.3f, -2.0f, -1.2f },
+ { 0.0f, -0.4f, 0.3f, 2.4f, -2.6f, 0.6f, 0.3f, 0.0f, 0.0f, 0.3f, -0.9f, -0.4f,
+ 0.8f } };
+
+ static private final float[][] DELTA_G = new float[][] {
+ { 0f },
+ { 8.0f, 10.6f },
+ { -15.1f, -7.8f, -0.8f },
+ { 0.4f, -2.6f, -1.2f, -6.5f },
+ { -2.5f, 2.8f, -7.0f, 6.2f, -3.8f },
+ { -2.8f, 0.7f, -3.2f, -1.1f, 0.1f, -0.8f },
+ { -0.7f, 0.4f, -0.3f, 2.3f, -2.1f, -0.6f, 1.4f },
+ { 0.2f, -0.1f, -0.3f, 1.1f, 0.6f, 0.5f, -0.4f, 0.6f },
+ { 0.1f, 0.3f, -0.4f, 0.3f, -0.3f, 0.2f, 0.4f, -0.7f, 0.4f },
+ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f } };
+
+ static private final float[][] DELTA_H = new float[][] {
+ { 0f },
+ { 0.0f, -20.9f },
+ { 0.0f, -23.2f, -14.6f },
+ { 0.0f, 5.0f, -7.0f, -0.6f },
+ { 0.0f, 2.2f, 1.6f, 5.8f, 0.1f },
+ { 0.0f, 0.0f, 1.7f, 2.1f, 4.8f, -1.1f },
+ { 0.0f, -0.6f, -1.9f, -0.4f, -0.5f, -0.3f, 0.7f },
+ { 0.0f, 0.6f, 0.4f, 0.2f, 0.3f, -0.8f, -0.2f, 0.1f },
+ { 0.0f, -0.2f, 0.1f, 0.3f, 0.4f, 0.1f, -0.2f, 0.4f, 0.4f },
+ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },
+ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f } };
+
+ static private final long BASE_TIME =
+ new GregorianCalendar(2005, 1, 1).getTimeInMillis();
+
+ // The ratio between the Gauss-normalized associated Legendre functions and
+ // the Schmid quasi-normalized ones. Compute these once staticly since they
+ // don't depend on input variables at all.
+ static private final float[][] SCHMIDT_QUASI_NORM_FACTORS =
+ computeSchmidtQuasiNormFactors(G_COEFF.length);
+
+ /**
+ * Estimate the magnetic field at a given point and time.
+ *
+ * @param gdLatitudeDeg
+ * Latitude in WGS84 geodetic coordinates -- positive is east.
+ * @param gdLongitudeDeg
+ * Longitude in WGS84 geodetic coordinates -- positive is north.
+ * @param altitudeMeters
+ * Altitude in WGS84 geodetic coordinates, in meters.
+ * @param timeMillis
+ * Time at which to evaluate the declination, in milliseconds
+ * since January 1, 1970. (approximate is fine -- the declination
+ * changes very slowly).
+ */
+ public GeomagneticField(float gdLatitudeDeg,
+ float gdLongitudeDeg,
+ float altitudeMeters,
+ long timeMillis) {
+ final int MAX_N = G_COEFF.length; // Maximum degree of the coefficients.
+
+ // We don't handle the north and south poles correctly -- pretend that
+ // we're not quite at them to avoid crashing.
+ gdLatitudeDeg = Math.min(90.0f - 1e-5f,
+ Math.max(-90.0f + 1e-5f, gdLatitudeDeg));
+ computeGeocentricCoordinates(gdLatitudeDeg,
+ gdLongitudeDeg,
+ altitudeMeters);
+
+ assert G_COEFF.length == H_COEFF.length;
+
+ // Note: LegendreTable computes associated Legendre functions for
+ // cos(theta). We want the associated Legendre functions for
+ // sin(latitude), which is the same as cos(PI/2 - latitude), except the
+ // derivate will be negated.
+ LegendreTable legendre =
+ new LegendreTable(MAX_N - 1,
+ (float) (Math.PI / 2.0 - mGcLatitudeRad));
+
+ // Compute a table of (EARTH_REFERENCE_RADIUS_KM / radius)^n for i in
+ // 0..MAX_N-2 (this is much faster than calling Math.pow MAX_N+1 times).
+ float[] relativeRadiusPower = new float[MAX_N + 2];
+ relativeRadiusPower[0] = 1.0f;
+ relativeRadiusPower[1] = EARTH_REFERENCE_RADIUS_KM / mGcRadiusKm;
+ for (int i = 2; i < relativeRadiusPower.length; ++i) {
+ relativeRadiusPower[i] = relativeRadiusPower[i - 1] *
+ relativeRadiusPower[1];
+ }
+
+ // Compute tables of sin(lon * m) and cos(lon * m) for m = 0..MAX_N --
+ // this is much faster than calling Math.sin and Math.com MAX_N+1 times.
+ float[] sinMLon = new float[MAX_N];
+ float[] cosMLon = new float[MAX_N];
+ sinMLon[0] = 0.0f;
+ cosMLon[0] = 1.0f;
+ sinMLon[1] = (float) Math.sin(mGcLongitudeRad);
+ cosMLon[1] = (float) Math.cos(mGcLongitudeRad);
+
+ for (int m = 2; m < MAX_N; ++m) {
+ // Standard expansions for sin((m-x)*theta + x*theta) and
+ // cos((m-x)*theta + x*theta).
+ int x = m >> 1;
+ sinMLon[m] = sinMLon[m-x] * cosMLon[x] + cosMLon[m-x] * sinMLon[x];
+ cosMLon[m] = cosMLon[m-x] * cosMLon[x] - sinMLon[m-x] * sinMLon[x];
+ }
+
+ float inverseCosLatitude = 1.0f / (float) Math.cos(mGcLatitudeRad);
+ float yearsSinceBase =
+ (timeMillis - BASE_TIME) / (365f * 24f * 60f * 60f * 1000f);
+
+ // We now compute the magnetic field strength given the geocentric
+ // location. The magnetic field is the derivative of the potential
+ // function defined by the model. See NOAA Technical Report: The US/UK
+ // World Magnetic Model for 2005-2010 for the derivation.
+ float gcX = 0.0f; // Geocentric northwards component.
+ float gcY = 0.0f; // Geocentric eastwards component.
+ float gcZ = 0.0f; // Geocentric downwards component.
+
+ for (int n = 1; n < MAX_N; n++) {
+ for (int m = 0; m <= n; m++) {
+ // Adjust the coefficients for the current date.
+ float g = G_COEFF[n][m] + yearsSinceBase * DELTA_G[n][m];
+ float h = H_COEFF[n][m] + yearsSinceBase * DELTA_H[n][m];
+
+ // Negative derivative with respect to latitude, divided by
+ // radius. This looks like the negation of the version in the
+ // NOAA Techincal report because that report used
+ // P_n^m(sin(theta)) and we use P_n^m(cos(90 - theta)), so the
+ // derivative with respect to theta is negated.
+ gcX += relativeRadiusPower[n+2]
+ * (g * cosMLon[m] + h * sinMLon[m])
+ * legendre.mPDeriv[n][m]
+ * SCHMIDT_QUASI_NORM_FACTORS[n][m];
+
+ // Negative derivative with respect to longitude, divided by
+ // radius.
+ gcY += relativeRadiusPower[n+2] * m
+ * (g * sinMLon[m] - h * cosMLon[m])
+ * legendre.mP[n][m]
+ * SCHMIDT_QUASI_NORM_FACTORS[n][m]
+ * inverseCosLatitude;
+
+ // Negative derivative with respect to radius.
+ gcZ -= (n + 1) * relativeRadiusPower[n+2]
+ * (g * cosMLon[m] + h * sinMLon[m])
+ * legendre.mP[n][m]
+ * SCHMIDT_QUASI_NORM_FACTORS[n][m];
+ }
+ }
+
+ // Convert back to geodetic coordinates. This is basically just a
+ // rotation around the Y-axis by the difference in latitudes between the
+ // geocentric frame and the geodetic frame.
+ double latDiffRad = Math.toRadians(gdLatitudeDeg) - mGcLatitudeRad;
+ mX = (float) (gcX * Math.cos(latDiffRad)
+ + gcZ * Math.sin(latDiffRad));
+ mY = gcY;
+ mZ = (float) (- gcX * Math.sin(latDiffRad)
+ + gcZ * Math.cos(latDiffRad));
+ }
+
+ /**
+ * @return The X (northward) component of the magnetic field in nanoteslas.
+ */
+ public float getX() {
+ return mX;
+ }
+
+ /**
+ * @return The Y (eastward) component of the magnetic field in nanoteslas.
+ */
+ public float getY() {
+ return mY;
+ }
+
+ /**
+ * @return The Z (downward) component of the magnetic field in nanoteslas.
+ */
+ public float getZ() {
+ return mZ;
+ }
+
+ /**
+ * @return The declination of the horizontal component of the magnetic
+ * field from true north, in degrees (i.e. positive means the
+ * magnetic field is rotated east that much from true north).
+ */
+ public float getDeclination() {
+ return (float) Math.toDegrees(Math.atan2(mY, mX));
+ }
+
+ /**
+ * @return The inclination of the magnetic field in degrees -- positive
+ * means the magnetic field is rotated downwards.
+ */
+ public float getInclination() {
+ return (float) Math.toDegrees(Math.atan2(mZ,
+ getHorizontalStrength()));
+ }
+
+ /**
+ * @return Horizontal component of the field strength in nonoteslas.
+ */
+ public float getHorizontalStrength() {
+ return (float) Math.sqrt(mX * mX + mY * mY);
+ }
+
+ /**
+ * @return Total field strength in nanoteslas.
+ */
+ public float getFieldStrength() {
+ return (float) Math.sqrt(mX * mX + mY * mY + mZ * mZ);
+ }
+
+ /**
+ * @param gdLatitudeDeg
+ * Latitude in WGS84 geodetic coordinates.
+ * @param gdLongitudeDeg
+ * Longitude in WGS84 geodetic coordinates.
+ * @param altitudeMeters
+ * Altitude above sea level in WGS84 geodetic coordinates.
+ * @return Geocentric latitude (i.e. angle between closest point on the
+ * equator and this point, at the center of the earth.
+ */
+ private void computeGeocentricCoordinates(float gdLatitudeDeg,
+ float gdLongitudeDeg,
+ float altitudeMeters) {
+ float altitudeKm = altitudeMeters / 1000.0f;
+ float a2 = EARTH_SEMI_MAJOR_AXIS_KM * EARTH_SEMI_MAJOR_AXIS_KM;
+ float b2 = EARTH_SEMI_MINOR_AXIS_KM * EARTH_SEMI_MINOR_AXIS_KM;
+ double gdLatRad = Math.toRadians(gdLatitudeDeg);
+ float clat = (float) Math.cos(gdLatRad);
+ float slat = (float) Math.sin(gdLatRad);
+ float tlat = slat / clat;
+ float latRad =
+ (float) Math.sqrt(a2 * clat * clat + b2 * slat * slat);
+
+ mGcLatitudeRad = (float) Math.atan(tlat * (latRad * altitudeKm + b2)
+ / (latRad * altitudeKm + a2));
+
+ mGcLongitudeRad = (float) Math.toRadians(gdLongitudeDeg);
+
+ float radSq = altitudeKm * altitudeKm
+ + 2 * altitudeKm * (float) Math.sqrt(a2 * clat * clat +
+ b2 * slat * slat)
+ + (a2 * a2 * clat * clat + b2 * b2 * slat * slat)
+ / (a2 * clat * clat + b2 * slat * slat);
+ mGcRadiusKm = (float) Math.sqrt(radSq);
+ }
+
+
+ /**
+ * Utility class to compute a table of Gauss-normalized associated Legendre
+ * functions P_n^m(cos(theta))
+ */
+ static private class LegendreTable {
+ // These are the Gauss-normalized associated Legendre functions -- that
+ // is, they are normal Legendre functions multiplied by
+ // (n-m)!/(2n-1)!! (where (2n-1)!! = 1*3*5*...*2n-1)
+ public final float[][] mP;
+
+ // Derivative of mP, with respect to theta.
+ public final float[][] mPDeriv;
+
+ /**
+ * @param maxN
+ * The maximum n- and m-values to support
+ * @param thetaRad
+ * Returned functions will be Gauss-normalized
+ * P_n^m(cos(thetaRad)), with thetaRad in radians.
+ */
+ public LegendreTable(int maxN, float thetaRad) {
+ // Compute the table of Gauss-normalized associated Legendre
+ // functions using standard recursion relations. Also compute the
+ // table of derivatives using the derivative of the recursion
+ // relations.
+ float cos = (float) Math.cos(thetaRad);
+ float sin = (float) Math.sin(thetaRad);
+
+ mP = new float[maxN + 1][];
+ mPDeriv = new float[maxN + 1][];
+ mP[0] = new float[] { 1.0f };
+ mPDeriv[0] = new float[] { 0.0f };
+ for (int n = 1; n <= maxN; n++) {
+ mP[n] = new float[n + 1];
+ mPDeriv[n] = new float[n + 1];
+ for (int m = 0; m <= n; m++) {
+ if (n == m) {
+ mP[n][m] = sin * mP[n - 1][m - 1];
+ mPDeriv[n][m] = cos * mP[n - 1][m - 1]
+ + sin * mPDeriv[n - 1][m - 1];
+ } else if (n == 1 || m == n - 1) {
+ mP[n][m] = cos * mP[n - 1][m];
+ mPDeriv[n][m] = -sin * mP[n - 1][m]
+ + cos * mPDeriv[n - 1][m];
+ } else {
+ assert n > 1 && m < n - 1;
+ float k = ((n - 1) * (n - 1) - m * m)
+ / (float) ((2 * n - 1) * (2 * n - 3));
+ mP[n][m] = cos * mP[n - 1][m] - k * mP[n - 2][m];
+ mPDeriv[n][m] = -sin * mP[n - 1][m]
+ + cos * mPDeriv[n - 1][m] - k * mPDeriv[n - 2][m];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Compute the ration between the Gauss-normalized associated Legendre
+ * functions and the Schmidt quasi-normalized version. This is equivalent to
+ * sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)!
+ */
+ private static float[][] computeSchmidtQuasiNormFactors(int maxN) {
+ float[][] schmidtQuasiNorm = new float[maxN + 1][];
+ schmidtQuasiNorm[0] = new float[] { 1.0f };
+ for (int n = 1; n <= maxN; n++) {
+ schmidtQuasiNorm[n] = new float[n + 1];
+ schmidtQuasiNorm[n][0] =
+ schmidtQuasiNorm[n - 1][0] * (2 * n - 1) / (float) n;
+ for (int m = 1; m <= n; m++) {
+ schmidtQuasiNorm[n][m] = schmidtQuasiNorm[n][m - 1]
+ * (float) Math.sqrt((n - m + 1) * (m == 1 ? 2 : 1)
+ / (float) (n + m));
+ }
+ }
+ return schmidtQuasiNorm;
+ }
+} \ No newline at end of file
diff --git a/core/java/android/hardware/ISensorService.aidl b/core/java/android/hardware/ISensorService.aidl
index 8aad9b4..04af2ae 100644
--- a/core/java/android/hardware/ISensorService.aidl
+++ b/core/java/android/hardware/ISensorService.aidl
@@ -25,5 +25,5 @@ import android.os.ParcelFileDescriptor;
interface ISensorService
{
ParcelFileDescriptor getDataChanel();
- boolean enableSensor(IBinder listener, int sensor, int enable);
+ boolean enableSensor(IBinder listener, String name, int sensor, int enable);
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index f02094e..e232c2c 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -620,6 +620,9 @@ public class SensorManager extends IRotationWatcher.Stub
*/
@Deprecated
public boolean registerListener(SensorListener listener, int sensors, int rate) {
+ if (listener == null) {
+ return false;
+ }
boolean result = false;
result = registerLegacyListener(SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER,
listener, sensors, rate) || result;
@@ -638,6 +641,9 @@ public class SensorManager extends IRotationWatcher.Stub
private boolean registerLegacyListener(int legacyType, int type,
SensorListener listener, int sensors, int rate)
{
+ if (listener == null) {
+ return false;
+ }
boolean result = false;
// Are we activating this legacy sensor?
if ((sensors & legacyType) != 0) {
@@ -693,6 +699,9 @@ public class SensorManager extends IRotationWatcher.Stub
private void unregisterLegacyListener(int legacyType, int type,
SensorListener listener, int sensors)
{
+ if (listener == null) {
+ return;
+ }
// do we know about this listener?
LegacyListener legacyListener = null;
synchronized (mLegacyListenersMap) {
@@ -741,7 +750,7 @@ public class SensorManager extends IRotationWatcher.Stub
*/
@Deprecated
public void unregisterListener(SensorListener listener) {
- unregisterListener(listener, SENSOR_ALL);
+ unregisterListener(listener, SENSOR_ALL | SENSOR_ORIENTATION_RAW);
}
/**
@@ -800,6 +809,9 @@ public class SensorManager extends IRotationWatcher.Stub
*/
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,
Handler handler) {
+ if (listener == null || sensor == null) {
+ return false;
+ }
boolean result;
int delay = -1;
switch (rate) {
@@ -829,9 +841,11 @@ public class SensorManager extends IRotationWatcher.Stub
}
}
+ String name = sensor.getName();
+ int handle = sensor.getHandle();
if (l == null) {
l = new ListenerDelegate(listener, sensor, handler);
- result = mSensorService.enableSensor(l, sensor.getHandle(), delay);
+ result = mSensorService.enableSensor(l, name, handle, delay);
if (result) {
sListeners.add(l);
sListeners.notify();
@@ -840,7 +854,7 @@ public class SensorManager extends IRotationWatcher.Stub
sSensorThread.startLocked(mSensorService);
}
} else {
- result = mSensorService.enableSensor(l, sensor.getHandle(), delay);
+ result = mSensorService.enableSensor(l, name, handle, delay);
if (result) {
l.addSensor(sensor);
}
@@ -854,6 +868,9 @@ public class SensorManager extends IRotationWatcher.Stub
}
private void unregisterListener(Object listener, Sensor sensor) {
+ if (listener == null || sensor == null) {
+ return;
+ }
try {
synchronized (sListeners) {
final int size = sListeners.size();
@@ -861,8 +878,9 @@ public class SensorManager extends IRotationWatcher.Stub
ListenerDelegate l = sListeners.get(i);
if (l.getListener() == listener) {
// disable these sensors
+ String name = sensor.getName();
int handle = sensor.getHandle();
- mSensorService.enableSensor(l, handle, SENSOR_DISABLE);
+ mSensorService.enableSensor(l, name, handle, SENSOR_DISABLE);
// if we have no more sensors enabled on this listener,
// take it off the list.
if (l.removeSensor(sensor) == 0) {
@@ -878,6 +896,9 @@ public class SensorManager extends IRotationWatcher.Stub
}
private void unregisterListener(Object listener) {
+ if (listener == null) {
+ return;
+ }
try {
synchronized (sListeners) {
final int size = sListeners.size();
@@ -886,7 +907,9 @@ public class SensorManager extends IRotationWatcher.Stub
if (l.getListener() == listener) {
// disable all sensors for this listener
for (Sensor sensor : l.getSensors()) {
- mSensorService.enableSensor(l, sensor.getHandle(), SENSOR_DISABLE);
+ String name = sensor.getName();
+ int handle = sensor.getHandle();
+ mSensorService.enableSensor(l, name, handle, SENSOR_DISABLE);
}
sListeners.remove(i);
break;
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 7d02f65..eedcc35 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -24,6 +24,9 @@ import android.view.MotionEvent;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
/**
* AbstractInputMethodService provides a abstract base class for input methods.
* Normal input method implementations will not derive from this directly,
@@ -156,6 +159,13 @@ public abstract class AbstractInputMethodService extends Service
*/
public abstract AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
+ /**
+ * Implement this to handle {@link android.os.Binder#dump Binder.dump()}
+ * calls on your input method.
+ */
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ }
+
@Override
final public IBinder onBind(Intent intent) {
if (mInputMethod == null) {
diff --git a/core/java/android/inputmethodservice/ExtractButton.java b/core/java/android/inputmethodservice/ExtractButton.java
new file mode 100644
index 0000000..d6fe38d
--- /dev/null
+++ b/core/java/android/inputmethodservice/ExtractButton.java
@@ -0,0 +1,30 @@
+package android.inputmethodservice;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/***
+ * Specialization of {@link Button} that ignores the window not being focused.
+ */
+class ExtractButton extends Button {
+ public ExtractButton(Context context) {
+ super(context, null);
+ }
+
+ public ExtractButton(Context context, AttributeSet attrs) {
+ super(context, attrs, com.android.internal.R.attr.buttonStyle);
+ }
+
+ public ExtractButton(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Pretend like the window this view is in always has focus, so it will
+ * highlight when selected.
+ */
+ @Override public boolean hasWindowFocus() {
+ return this.isEnabled() ? true : false;
+ }
+}
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index e59f38b..0295f69 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -2,6 +2,7 @@ package android.inputmethodservice;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.inputmethod.ExtractedText;
import android.widget.EditText;
/***
@@ -9,6 +10,9 @@ import android.widget.EditText;
* extracted text in a full-screen input method.
*/
public class ExtractEditText extends EditText {
+ private InputMethodService mIME;
+ private int mSettingExtractedText;
+
public ExtractEditText(Context context) {
super(context, null);
}
@@ -20,4 +24,107 @@ public class ExtractEditText extends EditText {
public ExtractEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
+
+ void setIME(InputMethodService ime) {
+ mIME = ime;
+ }
+
+ /**
+ * Start making changes that will not be reported to the client. That
+ * is, {@link #onSelectionChanged(int, int)} will not result in sending
+ * the new selection to the client
+ */
+ public void startInternalChanges() {
+ mSettingExtractedText += 1;
+ }
+
+ /**
+ * Finish making changes that will not be reported to the client. That
+ * is, {@link #onSelectionChanged(int, int)} will not result in sending
+ * the new selection to the client
+ */
+ public void finishInternalChanges() {
+ mSettingExtractedText -= 1;
+ }
+
+ /**
+ * Implement just to keep track of when we are setting text from the
+ * client (vs. seeing changes in ourself from the user).
+ */
+ @Override public void setExtractedText(ExtractedText text) {
+ try {
+ mSettingExtractedText++;
+ super.setExtractedText(text);
+ } finally {
+ mSettingExtractedText--;
+ }
+ }
+
+ /**
+ * Report to the underlying text editor about selection changes.
+ */
+ @Override protected void onSelectionChanged(int selStart, int selEnd) {
+ if (mSettingExtractedText == 0 && mIME != null && selStart >= 0 && selEnd >= 0) {
+ mIME.onExtractedSelectionChanged(selStart, selEnd);
+ }
+ }
+
+ /**
+ * Redirect clicks to the IME for handling there. First allows any
+ * on click handler to run, though.
+ */
+ @Override public boolean performClick() {
+ if (!super.performClick() && mIME != null) {
+ mIME.onExtractedTextClicked();
+ return true;
+ }
+ return false;
+ }
+
+ @Override public boolean onTextContextMenuItem(int id) {
+ if (mIME != null) {
+ if (mIME.onExtractTextContextMenuItem(id)) {
+ return true;
+ }
+ }
+ return super.onTextContextMenuItem(id);
+ }
+
+ /**
+ * We are always considered to be an input method target.
+ */
+ public boolean isInputMethodTarget() {
+ return true;
+ }
+
+ /**
+ * Return true if the edit text is currently showing a scroll bar.
+ */
+ public boolean hasVerticalScrollBar() {
+ return computeVerticalScrollRange() > computeVerticalScrollExtent();
+ }
+
+ /**
+ * Pretend like the window this view is in always has focus, so its
+ * highlight and cursor will be displayed.
+ */
+ @Override public boolean hasWindowFocus() {
+ return this.isEnabled() ? true : false;
+ }
+
+ /**
+ * Pretend like this view always has focus, so its
+ * highlight and cursor will be displayed.
+ */
+ @Override public boolean isFocused() {
+ return this.isEnabled() ? true : false;
+ }
+
+ /**
+ * Pretend like this view always has focus, so its
+ * highlight and cursor will be displayed.
+ */
+ @Override public boolean hasFocus() {
+ return this.isEnabled() ? true : false;
+ }
}
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 5a85c66..6cf90d6 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -30,6 +30,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
private static final int DO_UPDATE_SELECTION = 90;
private static final int DO_UPDATE_CURSOR = 95;
private static final int DO_APP_PRIVATE_COMMAND = 100;
+ private static final int DO_TOGGLE_SOFT_INPUT = 105;
final HandlerCaller mCaller;
final InputMethodSession mInputMethodSession;
@@ -106,6 +107,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
mCaller.recycleArgs(args);
return;
}
+ case DO_TOGGLE_SOFT_INPUT: {
+ mInputMethodSession.toggleSoftInput(msg.arg1, msg.arg2);
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -149,4 +154,8 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
public void appPrivateCommand(String action, Bundle data) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
}
+
+ public void toggleSoftInput(int showFlags, int hideFlags) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
+ }
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 9abc23b..20a05a5 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -8,9 +8,12 @@ import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.InputConnectionWrapper;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
@@ -18,6 +21,11 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
/**
* Implements the internal IInputMethod interface to convert incoming calls
* on to it back to calls on the public InputMethod interface, scheduling
@@ -28,6 +36,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
private static final String TAG = "InputMethodWrapper";
private static final boolean DEBUG = false;
+ private static final int DO_DUMP = 1;
private static final int DO_ATTACH_TOKEN = 10;
private static final int DO_SET_INPUT_CONTEXT = 20;
private static final int DO_UNSET_INPUT_CONTEXT = 30;
@@ -39,9 +48,14 @@ class IInputMethodWrapper extends IInputMethod.Stub
private static final int DO_SHOW_SOFT_INPUT = 60;
private static final int DO_HIDE_SOFT_INPUT = 70;
+ final AbstractInputMethodService mTarget;
final HandlerCaller mCaller;
final InputMethod mInputMethod;
+ static class Notifier {
+ boolean notified;
+ }
+
// NOTE: we should have a cache of these.
static class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
final Context mContext;
@@ -64,7 +78,9 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
- public IInputMethodWrapper(Context context, InputMethod inputMethod) {
+ public IInputMethodWrapper(AbstractInputMethodService context,
+ InputMethod inputMethod) {
+ mTarget = context;
mCaller = new HandlerCaller(context, this);
mInputMethod = inputMethod;
}
@@ -75,6 +91,20 @@ class IInputMethodWrapper extends IInputMethod.Stub
public void executeMessage(Message msg) {
switch (msg.what) {
+ case DO_DUMP: {
+ HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ try {
+ mTarget.dump((FileDescriptor)args.arg1,
+ (PrintWriter)args.arg2, (String[])args.arg3);
+ } catch (RuntimeException e) {
+ ((PrintWriter)args.arg2).println("Exception: " + e);
+ }
+ synchronized (args.arg4) {
+ ((CountDownLatch)args.arg4).countDown();
+ }
+ return;
+ }
+
case DO_ATTACH_TOKEN: {
mInputMethod.attachToken((IBinder)msg.obj);
return;
@@ -86,12 +116,22 @@ class IInputMethodWrapper extends IInputMethod.Stub
case DO_UNSET_INPUT_CONTEXT:
mInputMethod.unbindInput();
return;
- case DO_START_INPUT:
- mInputMethod.startInput((EditorInfo)msg.obj);
+ case DO_START_INPUT: {
+ HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ IInputContext inputContext = (IInputContext)args.arg1;
+ InputConnection ic = inputContext != null
+ ? new InputConnectionWrapper(inputContext) : null;
+ mInputMethod.startInput(ic, (EditorInfo)args.arg2);
return;
- case DO_RESTART_INPUT:
- mInputMethod.restartInput((EditorInfo)msg.obj);
+ }
+ case DO_RESTART_INPUT: {
+ HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ IInputContext inputContext = (IInputContext)args.arg1;
+ InputConnection ic = inputContext != null
+ ? new InputConnectionWrapper(inputContext) : null;
+ mInputMethod.restartInput(ic, (EditorInfo)args.arg2);
return;
+ }
case DO_CREATE_SESSION: {
mInputMethod.createSession(new InputMethodSessionCallbackWrapper(
mCaller.mContext, (IInputMethodCallback)msg.obj));
@@ -105,16 +145,37 @@ class IInputMethodWrapper extends IInputMethod.Stub
mInputMethod.revokeSession((InputMethodSession)msg.obj);
return;
case DO_SHOW_SOFT_INPUT:
- mInputMethod.showSoftInput(
- msg.arg1 != 0 ? InputMethod.SHOW_EXPLICIT : 0);
+ mInputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
return;
case DO_HIDE_SOFT_INPUT:
- mInputMethod.hideSoftInput();
+ mInputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
return;
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
+ @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ if (mTarget.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+
+ fout.println("Permission Denial: can't dump InputMethodManager from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ CountDownLatch latch = new CountDownLatch(1);
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOOOO(DO_DUMP,
+ fd, fout, args, latch));
+ try {
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ fout.println("Timeout waiting for dump");
+ }
+ } catch (InterruptedException e) {
+ fout.println("Interrupted waiting for dump");
+ }
+ }
+
public void attachToken(IBinder token) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
}
@@ -130,12 +191,14 @@ class IInputMethodWrapper extends IInputMethod.Stub
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
}
- public void startInput(EditorInfo attribute) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_INPUT, attribute));
+ public void startInput(IInputContext inputContext, EditorInfo attribute) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
+ inputContext, attribute));
}
- public void restartInput(EditorInfo attribute) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RESTART_INPUT, attribute));
+ public void restartInput(IInputContext inputContext, EditorInfo attribute) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_RESTART_INPUT,
+ inputContext, attribute));
}
public void createSession(IInputMethodCallback callback) {
@@ -163,12 +226,13 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
- public void showSoftInput(boolean explicit) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_SHOW_SOFT_INPUT,
- explicit ? 1 : 0));
+ public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
+ flags, resultReceiver));
}
- public void hideSoftInput() {
- mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_HIDE_SOFT_INPUT));
+ public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
+ flags, resultReceiver));
}
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 3a9b26a..32270c4 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -26,7 +26,16 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.InputType;
+import android.text.Layout;
+import android.text.Spannable;
+import android.text.method.MovementMethod;
import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -43,7 +52,10 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
import android.widget.FrameLayout;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
/**
* InputMethodService provides a standard implementation of an InputMethod,
@@ -51,6 +63,22 @@ import android.widget.FrameLayout;
* base class {@link AbstractInputMethodService} and the {@link InputMethod}
* interface for more information on the basics of writing input methods.
*
+ * <p>In addition to the normal Service lifecycle methods, this class
+ * introduces some new specific callbacks that most subclasses will want
+ * to make use of:</p>
+ * <ul>
+ * <li> {@link #onInitializeInterface()} for user-interface initialization,
+ * in particular to deal with configuration changes while the service is
+ * running.
+ * <li> {@link #onBindInput} to find out about switching to a new client.
+ * <li> {@link #onStartInput} to deal with an input session starting with
+ * the client.
+ * <li> {@link #onCreateInputView()}, {@link #onCreateCandidatesView()},
+ * and {@link #onCreateExtractTextView()} for non-demand generation of the UI.
+ * <li> {@link #onStartInputView(EditorInfo, boolean)} to deal with input
+ * starting within the input area of the IME.
+ * </ul>
+ *
* <p>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,
@@ -181,9 +209,12 @@ public class InputMethodService extends AbstractInputMethodService {
static final String TAG = "InputMethodService";
static final boolean DEBUG = false;
+ InputMethodManager mImm;
+
LayoutInflater mInflater;
View mRootView;
SoftInputWindow mWindow;
+ boolean mInitialized;
boolean mWindowCreated;
boolean mWindowAdded;
boolean mWindowVisible;
@@ -196,16 +227,25 @@ public class InputMethodService extends AbstractInputMethodService {
InputBinding mInputBinding;
InputConnection mInputConnection;
boolean mInputStarted;
+ boolean mInputViewStarted;
+ boolean mCandidatesViewStarted;
+ InputConnection mStartedInputConnection;
EditorInfo mInputEditorInfo;
+ int mShowInputFlags;
boolean mShowInputRequested;
boolean mLastShowInputRequested;
- boolean mShowCandidatesRequested;
+ int mCandidatesVisibility;
+ CompletionInfo[] mCurCompletions;
+
+ boolean mShowInputForced;
boolean mFullscreenApplied;
boolean mIsFullscreen;
View mExtractView;
ExtractEditText mExtractEditText;
+ ViewGroup mExtractAccessories;
+ Button mExtractAction;
ExtractedText mExtractedText;
int mExtractedToken;
@@ -236,6 +276,21 @@ public class InputMethodService extends AbstractInputMethodService {
}
};
+ final View.OnClickListener mActionClickListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ final EditorInfo ei = getCurrentInputEditorInfo();
+ final InputConnection ic = getCurrentInputConnection();
+ if (ei != null && ic != null) {
+ if (ei.actionId != 0) {
+ ic.performEditorAction(ei.actionId);
+ } else if ((ei.imeOptions&EditorInfo.IME_MASK_ACTION)
+ != EditorInfo.IME_ACTION_NONE) {
+ ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION);
+ }
+ }
+ }
+ };
+
/**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides
@@ -262,6 +317,9 @@ public class InputMethodService extends AbstractInputMethodService {
mInputConnection = binding.getConnection();
if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding
+ " ic=" + mInputConnection);
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) ic.reportFullscreenMode(mIsFullscreen);
+ initialize();
onBindInput();
}
@@ -277,31 +335,50 @@ public class InputMethodService extends AbstractInputMethodService {
mInputConnection = null;
}
- public void startInput(EditorInfo attribute) {
+ public void startInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
- doStartInput(attribute, false);
+ doStartInput(ic, attribute, false);
}
- public void restartInput(EditorInfo attribute) {
+ public void restartInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute);
- doStartInput(attribute, true);
+ doStartInput(ic, attribute, true);
}
/**
* Handle a request by the system to hide the soft input area.
*/
- public void hideSoftInput() {
+ public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "hideSoftInput()");
+ boolean wasVis = isInputViewShown();
+ mShowInputFlags = 0;
mShowInputRequested = false;
+ mShowInputForced = false;
hideWindow();
+ if (resultReceiver != null) {
+ resultReceiver.send(wasVis != isInputViewShown()
+ ? InputMethodManager.RESULT_HIDDEN
+ : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
+ }
}
/**
* Handle a request by the system to show the soft input area.
*/
- public void showSoftInput(int flags) {
+ public void showSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "showSoftInput()");
- onShowRequested(flags);
+ boolean wasVis = isInputViewShown();
+ mShowInputFlags = 0;
+ if (onShowInputRequested(flags, false)) {
+ showWindow(true);
+ }
+ if (resultReceiver != null) {
+ resultReceiver.send(wasVis != isInputViewShown()
+ ? InputMethodManager.RESULT_SHOWN
+ : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
+ }
}
}
@@ -316,8 +393,7 @@ public class InputMethodService extends AbstractInputMethodService {
return;
}
if (DEBUG) Log.v(TAG, "finishInput() in " + this);
- onFinishInput();
- mInputStarted = false;
+ doFinishInput();
}
/**
@@ -328,6 +404,7 @@ public class InputMethodService extends AbstractInputMethodService {
if (!isEnabled()) {
return;
}
+ mCurCompletions = completions;
onDisplayCompletions(completions);
}
@@ -377,6 +454,13 @@ public class InputMethodService extends AbstractInputMethodService {
}
InputMethodService.this.onAppPrivateCommand(action, data);
}
+
+ /**
+ *
+ */
+ public void toggleSoftInput(int showFlags, int hideFlags) {
+ InputMethodService.this.onToggleSoftInput(showFlags, hideFlags);
+ }
}
/**
@@ -391,7 +475,7 @@ public class InputMethodService extends AbstractInputMethodService {
* of the application behind. This value is relative to the top edge
* of the input method window.
*/
- int contentTopInsets;
+ public int contentTopInsets;
/**
* This is the top part of the UI that is visibly covering the
@@ -404,7 +488,7 @@ public class InputMethodService extends AbstractInputMethodService {
* needed to make the focus visible. This value is relative to the top edge
* of the input method window.
*/
- int visibleTopInsets;
+ public int visibleTopInsets;
/**
* Option for {@link #touchableInsets}: the entire window frame
@@ -437,6 +521,7 @@ public class InputMethodService extends AbstractInputMethodService {
@Override public void onCreate() {
super.onCreate();
+ mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
mInflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this);
@@ -444,20 +529,42 @@ public class InputMethodService extends AbstractInputMethodService {
mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT);
}
+ /**
+ * This is a hook that subclasses can use to perform initialization of
+ * their interface. It is called for you prior to any of your UI objects
+ * being created, both after the service is first created and after a
+ * configuration change happens.
+ */
+ public void onInitializeInterface() {
+ }
+
+ void initialize() {
+ if (!mInitialized) {
+ mInitialized = true;
+ onInitializeInterface();
+ }
+ }
+
void initViews() {
- mWindowVisible = false;
+ mInitialized = false;
mWindowCreated = false;
mShowInputRequested = false;
- mShowCandidatesRequested = false;
+ mShowInputForced = false;
mRootView = mInflater.inflate(
com.android.internal.R.layout.input_method, null);
mWindow.setContentView(mRootView);
mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
-
+ if (Settings.System.getInt(getContentResolver(),
+ Settings.System.FANCY_IME_ANIMATIONS, 0) != 0) {
+ mWindow.getWindow().setWindowAnimations(
+ com.android.internal.R.style.Animation_InputMethodFancy);
+ }
mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea);
mExtractView = null;
mExtractEditText = null;
+ mExtractAccessories = null;
+ mExtractAction = null;
mFullscreenApplied = false;
mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea);
@@ -466,7 +573,8 @@ public class InputMethodService extends AbstractInputMethodService {
mIsInputViewShown = false;
mExtractFrame.setVisibility(View.GONE);
- mCandidatesFrame.setVisibility(View.INVISIBLE);
+ mCandidatesVisibility = getCandidatesHiddenVisibility();
+ mCandidatesFrame.setVisibility(mCandidatesVisibility);
mInputFrame.setVisibility(View.GONE);
}
@@ -486,26 +594,48 @@ public class InputMethodService extends AbstractInputMethodService {
* regenerating the input method UI as a result of the configuration
* change, so you can rely on your {@link #onCreateInputView} and
* other methods being called as appropriate due to a configuration change.
+ *
+ * <p>When a configuration change does happen,
+ * {@link #onInitializeInterface()} is guaranteed to be called the next
+ * time prior to any of the other input or UI creation callbacks. The
+ * following will be called immediately depending if appropriate for current
+ * state: {@link #onStartInput} if input is active, and
+ * {@link #onCreateInputView} and {@link #onStartInputView} and related
+ * appropriate functions if the UI is displayed.
*/
@Override public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
boolean visible = mWindowVisible;
+ int showFlags = mShowInputFlags;
boolean showingInput = mShowInputRequested;
- boolean showingCandidates = mShowCandidatesRequested;
+ CompletionInfo[] completions = mCurCompletions;
initViews();
+ mInputViewStarted = false;
+ mCandidatesViewStarted = false;
+ if (mInputStarted) {
+ doStartInput(getCurrentInputConnection(),
+ getCurrentInputEditorInfo(), true);
+ }
if (visible) {
- if (showingCandidates) {
- setCandidatesViewShown(true);
- }
if (showingInput) {
- // If we are showing the full soft keyboard, then go through
- // this path to take care of current decisions about fullscreen
- // etc.
- onShowRequested(InputMethod.SHOW_EXPLICIT);
- } else {
- // Otherwise just put it back for its candidates.
+ // If we were last showing the soft keyboard, try to do so again.
+ if (onShowInputRequested(showFlags, true)) {
+ showWindow(true);
+ if (completions != null) {
+ mCurCompletions = completions;
+ onDisplayCompletions(completions);
+ }
+ } else {
+ hideWindow();
+ }
+ } else if (mCandidatesVisibility == View.VISIBLE) {
+ // If the candidates are currently visible, make sure the
+ // window is shown for them.
showWindow(false);
+ } else {
+ // Otherwise hide the window.
+ hideWindow();
}
}
}
@@ -568,6 +698,10 @@ public class InputMethodService extends AbstractInputMethodService {
* the input method, or null if there is none.
*/
public InputConnection getCurrentInputConnection() {
+ InputConnection ic = mStartedInputConnection;
+ if (ic != null) {
+ return ic;
+ }
return mInputConnection;
}
@@ -593,7 +727,10 @@ public class InputMethodService extends AbstractInputMethodService {
if (mIsFullscreen != isFullscreen || !mFullscreenApplied) {
changed = true;
mIsFullscreen = isFullscreen;
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) ic.reportFullscreenMode(isFullscreen);
mFullscreenApplied = true;
+ initialize();
Drawable bg = onCreateBackgroundDrawable();
if (bg == null) {
// We need to give the window a real drawable, so that it
@@ -609,7 +746,7 @@ public class InputMethodService extends AbstractInputMethodService {
setExtractView(v);
}
}
- startExtractingText();
+ startExtractingText(false);
}
}
@@ -708,6 +845,7 @@ public class InputMethodService extends AbstractInputMethodService {
mIsInputViewShown = isShown;
mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);
if (mInputView == null) {
+ initialize();
View v = onCreateInputView();
if (v != null) {
setInputView(v);
@@ -717,12 +855,19 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * Returns true if we have been asked to show our input view.
+ */
+ public boolean isShowInputRequested() {
+ return mShowInputRequested;
+ }
+
+ /**
* Return whether the soft input view is <em>currently</em> shown to the
* user. This is the state that was last determined and
* applied by {@link #updateInputViewShown()}.
*/
public boolean isInputViewShown() {
- return mIsInputViewShown;
+ return mIsInputViewShown && mWindowVisible;
}
/**
@@ -744,9 +889,10 @@ public class InputMethodService extends AbstractInputMethodService {
* it is hidden.
*/
public void setCandidatesViewShown(boolean shown) {
- if (mShowCandidatesRequested != shown) {
- mCandidatesFrame.setVisibility(shown ? View.VISIBLE : View.INVISIBLE);
- mShowCandidatesRequested = shown;
+ int vis = shown ? View.VISIBLE : getCandidatesHiddenVisibility();
+ if (mCandidatesVisibility != vis) {
+ mCandidatesFrame.setVisibility(vis);
+ mCandidatesVisibility = vis;
}
if (!mShowInputRequested && mWindowVisible != shown) {
// If we are being asked to show the candidates view while the app
@@ -760,11 +906,27 @@ public class InputMethodService extends AbstractInputMethodService {
}
}
- public void setStatusIcon(int iconResId) {
+ /**
+ * Returns the visibility mode (either {@link View#INVISIBLE View.INVISIBLE}
+ * or {@link View#GONE View.GONE}) of the candidates view when it is not
+ * shown. The default implementation returns GONE when in fullscreen mode,
+ * otherwise VISIBLE. Be careful if you change this to return GONE in
+ * other situations -- if showing or hiding the candidates view causes
+ * your window to resize, this can cause temporary drawing artifacts as
+ * the resize takes place.
+ */
+ public int getCandidatesHiddenVisibility() {
+ return isFullscreenMode() ? View.GONE : View.INVISIBLE;
+ }
+
+ public void showStatusIcon(int iconResId) {
mStatusIcon = iconResId;
- if (mInputConnection != null && mWindowVisible) {
- mInputConnection.showStatusIcon(getPackageName(), iconResId);
- }
+ mImm.showStatusIcon(mToken, getPackageName(), iconResId);
+ }
+
+ public void hideStatusIcon() {
+ mStatusIcon = 0;
+ mImm.hideStatusIcon(mToken);
}
/**
@@ -775,22 +937,30 @@ public class InputMethodService extends AbstractInputMethodService {
* @param id Unique identifier of the new input method ot start.
*/
public void switchInputMethod(String id) {
- ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE))
- .setInputMethod(mToken, id);
+ mImm.setInputMethod(mToken, id);
}
public void setExtractView(View view) {
mExtractFrame.removeAllViews();
mExtractFrame.addView(view, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
+ ViewGroup.LayoutParams.FILL_PARENT));
mExtractView = view;
if (view != null) {
mExtractEditText = (ExtractEditText)view.findViewById(
com.android.internal.R.id.inputExtractEditText);
- startExtractingText();
+ mExtractEditText.setIME(this);
+ mExtractAction = (Button)view.findViewById(
+ com.android.internal.R.id.inputExtractAction);
+ if (mExtractAction != null) {
+ mExtractAccessories = (ViewGroup)view.findViewById(
+ com.android.internal.R.id.inputExtractAccessories);
+ }
+ startExtractingText(false);
} else {
mExtractEditText = null;
+ mExtractAccessories = null;
+ mExtractAction = null;
}
}
@@ -890,27 +1060,111 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * Called when the input view is being hidden from the user. This will
+ * be called either prior to hiding the window, or prior to switching to
+ * another target for editing.
+ *
+ * <p>The default
+ * implementation uses the InputConnection to clear any active composing
+ * text; you can override this (not calling the base class implementation)
+ * to perform whatever behavior you would like.
+ *
+ * @param finishingInput If true, {@link #onFinishInput} will be
+ * called immediately after.
+ */
+ public void onFinishInputView(boolean finishingInput) {
+ if (!finishingInput) {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.finishComposingText();
+ }
+ }
+ }
+
+ /**
+ * Called when only the candidates view has been shown for showing
+ * processing as the user enters text through a hard keyboard.
+ * This will always be called after {@link #onStartInput},
+ * allowing you to do your general setup there and just view-specific
+ * setup here. You are guaranteed that {@link #onCreateCandidatesView()}
+ * will have been called some time before this function is called.
+ *
+ * <p>Note that this will <em>not</em> be called when the input method
+ * is running in full editing mode, and thus receiving
+ * {@link #onStartInputView} to initiate that operation. This is only
+ * for the case when candidates are being shown while the input method
+ * editor is hidden but wants to show its candidates UI as text is
+ * entered through some other mechanism.
+ *
+ * @param info Description of the type of text being edited.
+ * @param restarting Set to true if we are restarting input on the
+ * same text field as before.
+ */
+ public void onStartCandidatesView(EditorInfo info, boolean restarting) {
+ }
+
+ /**
+ * Called when the candidates view is being hidden from the user. This will
+ * be called either prior to hiding the window, or prior to switching to
+ * another target for editing.
+ *
+ * <p>The default
+ * implementation uses the InputConnection to clear any active composing
+ * text; you can override this (not calling the base class implementation)
+ * to perform whatever behavior you would like.
+ *
+ * @param finishingInput If true, {@link #onFinishInput} will be
+ * called immediately after.
+ */
+ public void onFinishCandidatesView(boolean finishingInput) {
+ if (!finishingInput) {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.finishComposingText();
+ }
+ }
+ }
+
+ /**
* The system has decided that it may be time to show your input method.
* This is called due to a corresponding call to your
- * {@link InputMethod#showSoftInput(int) InputMethod.showSoftInput(int)}
- * method. The default implementation simply calls
- * {@link #showWindow(boolean)}, except if the
- * {@link InputMethod#SHOW_EXPLICIT InputMethod.SHOW_EXPLICIT} flag is
- * not set and the input method is running in fullscreen mode.
+ * {@link InputMethod#showSoftInput InputMethod.showSoftInput()}
+ * method. The default implementation uses
+ * {@link #onEvaluateInputViewShown()}, {@link #onEvaluateFullscreenMode()},
+ * and the current configuration to decide whether the input view should
+ * be shown at this point.
*
* @param flags Provides additional information about the show request,
- * as per {@link InputMethod#showSoftInput(int) InputMethod.showSoftInput(int)}.
+ * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}.
+ * @param configChange This is true if we are re-showing due to a
+ * configuration change.
+ * @return Returns true to indicate that the window should be shown.
*/
- public void onShowRequested(int flags) {
+ public boolean onShowInputRequested(int flags, boolean configChange) {
if (!onEvaluateInputViewShown()) {
- return;
+ return false;
}
- if ((flags&InputMethod.SHOW_EXPLICIT) == 0 && onEvaluateFullscreenMode()) {
- // Don't show if this is not explicit requested by the user and
- // the input method is fullscreen. That would be too disruptive.
- return;
+ if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
+ if (!configChange && onEvaluateFullscreenMode()) {
+ // Don't show if this is not explicitly requested by the user and
+ // the input method is fullscreen. That would be too disruptive.
+ // However, we skip this change for a config change, since if
+ // the IME is already shown we do want to go into fullscreen
+ // mode at this point.
+ return false;
+ }
+ Configuration config = getResources().getConfiguration();
+ if (config.keyboard != Configuration.KEYBOARD_NOKEYS) {
+ // And if the device has a hard keyboard, even if it is
+ // currently hidden, don't show the input method implicitly.
+ // These kinds of devices don't need it that much.
+ return false;
+ }
}
- showWindow(true);
+ if ((flags&InputMethod.SHOW_FORCED) != 0) {
+ mShowInputForced = true;
+ }
+ return true;
}
public void showWindow(boolean showInput) {
@@ -935,46 +1189,77 @@ public class InputMethodService extends AbstractInputMethodService {
}
if (DEBUG) Log.v(TAG, "showWindow: updating UI");
+ initialize();
updateFullscreenMode();
updateInputViewShown();
if (!mWindowAdded || !mWindowCreated) {
mWindowAdded = true;
mWindowCreated = true;
+ initialize();
+ if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");
View v = onCreateCandidatesView();
if (DEBUG) Log.v(TAG, "showWindow: candidates=" + v);
if (v != null) {
setCandidatesView(v);
}
}
- if (doShowInput) {
- if (mInputStarted) {
- if (DEBUG) Log.v(TAG, "showWindow: starting input view");
+ if (mShowInputRequested) {
+ if (!mInputViewStarted) {
+ if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
+ mInputViewStarted = true;
onStartInputView(mInputEditorInfo, false);
}
- startExtractingText();
+ } else if (!mCandidatesViewStarted) {
+ if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
+ mCandidatesViewStarted = true;
+ onStartCandidatesView(mInputEditorInfo, false);
+ }
+
+ if (doShowInput) {
+ startExtractingText(false);
}
if (!wasVisible) {
if (DEBUG) Log.v(TAG, "showWindow: showing!");
+ onWindowShown();
mWindow.show();
- if (mInputConnection != null) {
- mInputConnection.showStatusIcon(getPackageName(), mStatusIcon);
- }
}
}
public void hideWindow() {
+ if (mInputViewStarted) {
+ if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
+ onFinishInputView(false);
+ } else if (mCandidatesViewStarted) {
+ if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
+ onFinishCandidatesView(false);
+ }
+ mInputViewStarted = false;
+ mCandidatesViewStarted = false;
if (mWindowVisible) {
mWindow.hide();
mWindowVisible = false;
- if (mInputConnection != null) {
- mInputConnection.hideStatusIcon();
- }
+ onWindowHidden();
}
}
/**
+ * Called when the input method window has been shown to the user, after
+ * previously not being visible. This is done after all of the UI setup
+ * for the window has occurred (creating its views etc).
+ */
+ public void onWindowShown() {
+ }
+
+ /**
+ * Called when the input method window has been hidden from the user,
+ * after previously being visible.
+ */
+ public void onWindowHidden() {
+ }
+
+ /**
* Called when a new client has bound to the input method. This
* may be followed by a series of {@link #onStartInput(EditorInfo, boolean)}
* and {@link #onFinishInput()} calls as the user navigates through its
@@ -1008,18 +1293,46 @@ public class InputMethodService extends AbstractInputMethodService {
public void onStartInput(EditorInfo attribute, boolean restarting) {
}
- void doStartInput(EditorInfo attribute, boolean restarting) {
- if (mInputStarted && !restarting) {
+ void doFinishInput() {
+ if (mInputViewStarted) {
+ if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
+ onFinishInputView(true);
+ } else if (mCandidatesViewStarted) {
+ if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
+ onFinishCandidatesView(true);
+ }
+ mInputViewStarted = false;
+ mCandidatesViewStarted = false;
+ if (mInputStarted) {
+ if (DEBUG) Log.v(TAG, "CALL: onFinishInput");
onFinishInput();
}
+ mInputStarted = false;
+ mStartedInputConnection = null;
+ mCurCompletions = null;
+ }
+
+ void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
+ if (!restarting) {
+ doFinishInput();
+ }
mInputStarted = true;
+ mStartedInputConnection = ic;
mInputEditorInfo = attribute;
+ initialize();
+ if (DEBUG) Log.v(TAG, "CALL: onStartInput");
onStartInput(attribute, restarting);
if (mWindowVisible) {
if (mShowInputRequested) {
+ if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
+ mInputViewStarted = true;
onStartInputView(mInputEditorInfo, restarting);
+ startExtractingText(true);
+ } else if (mCandidatesVisibility == View.VISIBLE) {
+ if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
+ mCandidatesViewStarted = true;
+ onStartCandidatesView(mInputEditorInfo, restarting);
}
- startExtractingText();
}
}
@@ -1029,8 +1342,17 @@ public class InputMethodService extends AbstractInputMethodService {
* {@link #onStartInput(EditorInfo, boolean)} to perform input in a
* new editor, or the input method may be left idle. This method is
* <em>not</em> called when input restarts in the same editor.
+ *
+ * <p>The default
+ * implementation uses the InputConnection to clear any active composing
+ * text; you can override this (not calling the base class implementation)
+ * to perform whatever behavior you would like.
*/
public void onFinishInput() {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.finishComposingText();
+ }
}
/**
@@ -1055,9 +1377,11 @@ public class InputMethodService extends AbstractInputMethodService {
if (mExtractedToken != token) {
return;
}
- if (mExtractEditText != null && text != null) {
- mExtractedText = text;
- mExtractEditText.setExtractedText(text);
+ if (text != null) {
+ if (mExtractEditText != null) {
+ mExtractedText = text;
+ mExtractEditText.setExtractedText(text);
+ }
}
}
@@ -1073,9 +1397,19 @@ public class InputMethodService extends AbstractInputMethodService {
public void onUpdateSelection(int oldSelStart, int oldSelEnd,
int newSelStart, int newSelEnd,
int candidatesStart, int candidatesEnd) {
- if (mExtractEditText != null && mExtractedText != null) {
+ final ExtractEditText eet = mExtractEditText;
+ if (eet != null && isFullscreenMode() && mExtractedText != null) {
final int off = mExtractedText.startOffset;
- mExtractEditText.setSelection(newSelStart-off, newSelEnd-off);
+ eet.startInternalChanges();
+ newSelStart -= off;
+ newSelEnd -= off;
+ final int len = eet.getText().length();
+ if (newSelStart < 0) newSelStart = 0;
+ else if (newSelStart > len) newSelStart = len;
+ if (newSelEnd < 0) newSelEnd = 0;
+ else if (newSelEnd > len) newSelEnd = len;
+ eet.setSelection(newSelStart, newSelEnd);
+ eet.finishInternalChanges();
}
}
@@ -1095,38 +1429,91 @@ public class InputMethodService extends AbstractInputMethodService {
* 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY
* InputMethodManager.HIDE_IMPLICIT_ONLY} bit set.
*/
- public void dismissSoftInput(int flags) {
- ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE))
- .hideSoftInputFromInputMethod(mToken, flags);
+ public void requestHideSelf(int flags) {
+ mImm.hideSoftInputFromInputMethod(mToken, flags);
+ }
+
+ /**
+ * Show the input method. This is a call back to the
+ * IMF to handle showing the input method.
+ * Close this input method's soft input area, removing it from the display.
+ * The input method will continue running, but the user can no longer use
+ * it to generate input by touching the screen.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link InputMethodManager#SHOW_FORCED
+ * InputMethodManager.} bit set.
+ */
+ private void requestShowSelf(int flags) {
+ mImm.showSoftInputFromInputMethod(mToken, flags);
}
+ /**
+ * Override this to intercept key down events before they are processed by the
+ * application. If you return true, the application will not itself
+ * process the event. If you return true, the normal application processing
+ * will occur as if the IME had not seen the event at all.
+ *
+ * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
+ * KeyEvent.KEYCODE_BACK} to hide the current IME UI if it is shown. In
+ * additional, in fullscreen mode only, it will consume DPAD movement
+ * events to move the cursor in the extracted text view, not allowing
+ * them to perform navigation in the underlying application.
+ */
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
&& event.getRepeatCount() == 0) {
if (mShowInputRequested) {
// If the soft input area is shown, back closes it and we
// consume the back key.
- dismissSoftInput(0);
+ requestHideSelf(0);
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);
+ } else if (mWindowVisible) {
+ if (mCandidatesVisibility == View.VISIBLE) {
+ // If we are showing candidates even if no input area, then
+ // hide them.
+ setCandidatesViewShown(false);
+ return true;
+ } else {
+ // If we have the window visible for some other reason --
+ // most likely to show candidates -- then just get rid
+ // of it. This really shouldn't happen, but just in case...
+ hideWindow();
+ return true;
+ }
}
}
- return false;
+ return doMovementKey(keyCode, event, MOVEMENT_DOWN);
}
+ /**
+ * Override this to intercept special key multiple events before they are
+ * processed by the
+ * application. If you return true, the application will not itself
+ * process the event. If you return true, the normal application processing
+ * will occur as if the IME had not seen the event at all.
+ *
+ * <p>The default implementation always returns false, except when
+ * in fullscreen mode, where it will consume DPAD movement
+ * events to move the cursor in the extracted text view, not allowing
+ * them to perform navigation in the underlying application.
+ */
public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
- return false;
+ return doMovementKey(keyCode, event, count);
}
+ /**
+ * Override this to intercept key up events before they are processed by the
+ * application. If you return true, the application will not itself
+ * process the event. If you return true, the normal application processing
+ * will occur as if the IME had not seen the event at all.
+ *
+ * <p>The default implementation always returns false, except when
+ * in fullscreen mode, where it will consume DPAD movement
+ * events to move the cursor in the extracted text view, not allowing
+ * them to perform navigation in the underlying application.
+ */
public boolean onKeyUp(int keyCode, KeyEvent event) {
- return false;
+ return doMovementKey(keyCode, event, MOVEMENT_UP);
}
public boolean onTrackballEvent(MotionEvent event) {
@@ -1136,21 +1523,415 @@ public class InputMethodService extends AbstractInputMethodService {
public void onAppPrivateCommand(String action, Bundle data) {
}
- void startExtractingText() {
- if (mExtractEditText != null && getCurrentInputStarted()
+ /**
+ * Handle a request by the system to toggle the soft input area.
+ */
+ private void onToggleSoftInput(int showFlags, int hideFlags) {
+ if (DEBUG) Log.v(TAG, "toggleSoftInput()");
+ if (isInputViewShown()) {
+ requestHideSelf(hideFlags);
+ } else {
+ requestShowSelf(showFlags);
+ }
+ }
+
+ static final int MOVEMENT_DOWN = -1;
+ static final int MOVEMENT_UP = -2;
+
+ void reportExtractedMovement(int keyCode, int count) {
+ int dx = 0, dy = 0;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ dx = -count;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ dx = count;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ dy = -count;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ dy = count;
+ break;
+ }
+ onExtractedCursorMovement(dx, dy);
+ }
+
+ boolean doMovementKey(int keyCode, KeyEvent event, int count) {
+ final ExtractEditText eet = mExtractEditText;
+ if (isFullscreenMode() && isInputViewShown() && eet != null) {
+ // If we are in fullscreen mode, the cursor will move around
+ // the extract edit text, but should NOT cause focus to move
+ // to other fields.
+ MovementMethod movement = eet.getMovementMethod();
+ Layout layout = eet.getLayout();
+ if (movement != null && layout != null) {
+ // We want our own movement method to handle the key, so the
+ // cursor will properly move in our own word wrapping.
+ if (count == MOVEMENT_DOWN) {
+ if (movement.onKeyDown(eet,
+ (Spannable)eet.getText(), keyCode, event)) {
+ reportExtractedMovement(keyCode, 1);
+ return true;
+ }
+ } else if (count == MOVEMENT_UP) {
+ if (movement.onKeyUp(eet,
+ (Spannable)eet.getText(), keyCode, event)) {
+ return true;
+ }
+ } else {
+ if (movement.onKeyOther(eet, (Spannable)eet.getText(), event)) {
+ reportExtractedMovement(keyCode, count);
+ } else {
+ KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN);
+ if (movement.onKeyDown(eet,
+ (Spannable)eet.getText(), keyCode, down)) {
+ KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP);
+ movement.onKeyUp(eet,
+ (Spannable)eet.getText(), keyCode, up);
+ while (--count > 0) {
+ movement.onKeyDown(eet,
+ (Spannable)eet.getText(), keyCode, down);
+ movement.onKeyUp(eet,
+ (Spannable)eet.getText(), keyCode, up);
+ }
+ reportExtractedMovement(keyCode, count);
+ }
+ }
+ }
+ }
+ // Regardless of whether the movement method handled the key,
+ // we never allow DPAD navigation to the application.
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Send the given key event code (as defined by {@link KeyEvent}) to the
+ * current input connection is a key down + key up event pair. The sent
+ * events have {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD}
+ * set, so that the recipient can identify them as coming from a software
+ * input method, and
+ * {@link KeyEvent#FLAG_KEEP_TOUCH_MODE KeyEvent.FLAG_KEEP_TOUCH_MODE}, so
+ * that they don't impact the current touch mode of the UI.
+ *
+ * @param keyEventCode The raw key code to send, as defined by
+ * {@link KeyEvent}.
+ */
+ public void sendDownUpKeyEvents(int keyEventCode) {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+ long eventTime = SystemClock.uptimeMillis();
+ ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
+ ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, keyEventCode, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
+ }
+
+ /**
+ * Ask the input target to execute its default action via
+ * {@link InputConnection#performEditorAction
+ * InputConnection.performEditorAction()}.
+ *
+ * @param fromEnterKey If true, this will be executed as if the user had
+ * pressed an enter key on the keyboard, that is it will <em>not</em>
+ * be done if the editor has set {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION
+ * EditorInfo.IME_FLAG_NO_ENTER_ACTION}. If false, the action will be
+ * sent regardless of how the editor has set that flag.
+ *
+ * @return Returns a boolean indicating whether an action has been sent.
+ * If false, either the editor did not specify a default action or it
+ * does not want an action from the enter key. If true, the action was
+ * sent (or there was no input connection at all).
+ */
+ public boolean sendDefaultEditorAction(boolean fromEnterKey) {
+ EditorInfo ei = getCurrentInputEditorInfo();
+ if (ei != null &&
+ (!fromEnterKey || (ei.imeOptions &
+ EditorInfo.IME_FLAG_NO_ENTER_ACTION) == 0) &&
+ (ei.imeOptions & EditorInfo.IME_MASK_ACTION) !=
+ EditorInfo.IME_ACTION_NONE) {
+ // If the enter key was pressed, and the editor has a default
+ // action associated with pressing enter, then send it that
+ // explicit action instead of the key event.
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Send the given UTF-16 character to the current input connection. Most
+ * characters will be delivered simply by calling
+ * {@link InputConnection#commitText InputConnection.commitText()} with
+ * the character; some, however, may be handled different. In particular,
+ * the enter character ('\n') will either be delivered as an action code
+ * or a raw key event, as appropriate.
+ *
+ * @param charCode The UTF-16 character code to send.
+ */
+ public void sendKeyChar(char charCode) {
+ switch (charCode) {
+ case '\n': // Apps may be listening to an enter key to perform an action
+ if (!sendDefaultEditorAction(true)) {
+ sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
+ }
+ break;
+ default:
+ // Make sure that digits go through any text watcher on the client side.
+ if (charCode >= '0' && charCode <= '9') {
+ sendDownUpKeyEvents(charCode - '0' + KeyEvent.KEYCODE_0);
+ } else {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.commitText(String.valueOf((char) charCode), 1);
+ }
+ }
+ break;
+ }
+ }
+
+ /**
+ * This is called when the user has moved the cursor in the extracted
+ * text view, when running in fullsreen mode. The default implementation
+ * performs the corresponding selection change on the underlying text
+ * editor.
+ */
+ public void onExtractedSelectionChanged(int start, int end) {
+ InputConnection conn = getCurrentInputConnection();
+ if (conn != null) {
+ conn.setSelection(start, end);
+ }
+ }
+
+ /**
+ * This is called when the user has clicked on the extracted text view,
+ * when running in fullscreen mode. The default implementation hides
+ * the candidates view when this happens, but only if the extracted text
+ * editor has a vertical scroll bar because its text doesn't fit.
+ * Re-implement this to provide whatever behavior you want.
+ */
+ public void onExtractedTextClicked() {
+ if (mExtractEditText == null) {
+ return;
+ }
+ if (mExtractEditText.hasVerticalScrollBar()) {
+ setCandidatesViewShown(false);
+ }
+ }
+
+ /**
+ * This is called when the user has performed a cursor movement in the
+ * extracted text view, when it is running in fullscreen mode. The default
+ * implementation hides the candidates view when a vertical movement
+ * happens, but only if the extracted text editor has a vertical scroll bar
+ * because its text doesn't fit.
+ * Re-implement this to provide whatever behavior you want.
+ * @param dx The amount of cursor movement in the x dimension.
+ * @param dy The amount of cursor movement in the y dimension.
+ */
+ public void onExtractedCursorMovement(int dx, int dy) {
+ if (mExtractEditText == null || dy == 0) {
+ return;
+ }
+ if (mExtractEditText.hasVerticalScrollBar()) {
+ setCandidatesViewShown(false);
+ }
+ }
+
+ /**
+ * This is called when the user has selected a context menu item from the
+ * extracted text view, when running in fullscreen mode. The default
+ * implementation sends this action to the current InputConnection's
+ * {@link InputConnection#performContextMenuAction(int)}, for it
+ * to be processed in underlying "real" editor. Re-implement this to
+ * provide whatever behavior you want.
+ */
+ public boolean onExtractTextContextMenuItem(int id) {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.performContextMenuAction(id);
+ }
+ return true;
+ }
+
+ /**
+ * Return text that can be used as a button label for the given
+ * {@link EditorInfo#imeOptions EditorInfo.imeOptions}. Returns null
+ * if there is no action requested. Note that there is no guarantee that
+ * the returned text will be relatively short, so you probably do not
+ * want to use it as text on a soft keyboard key label.
+ *
+ * @param imeOptions The value from @link EditorInfo#imeOptions EditorInfo.imeOptions}.
+ *
+ * @return Returns a label to use, or null if there is no action.
+ */
+ public CharSequence getTextForImeAction(int imeOptions) {
+ switch (imeOptions&EditorInfo.IME_MASK_ACTION) {
+ case EditorInfo.IME_ACTION_NONE:
+ return null;
+ case EditorInfo.IME_ACTION_GO:
+ return getText(com.android.internal.R.string.ime_action_go);
+ case EditorInfo.IME_ACTION_SEARCH:
+ return getText(com.android.internal.R.string.ime_action_search);
+ case EditorInfo.IME_ACTION_SEND:
+ return getText(com.android.internal.R.string.ime_action_send);
+ case EditorInfo.IME_ACTION_NEXT:
+ return getText(com.android.internal.R.string.ime_action_next);
+ case EditorInfo.IME_ACTION_DONE:
+ return getText(com.android.internal.R.string.ime_action_done);
+ default:
+ return getText(com.android.internal.R.string.ime_action_default);
+ }
+ }
+
+ /**
+ * Called when it is time to update the actions available from a full-screen
+ * IME. You do not need to deal with this if you are using the standard
+ * full screen extract UI. If replacing it, you will need to re-implement
+ * this to put the action in your own UI and handle it.
+ */
+ public void onUpdateExtractingAccessories(EditorInfo ei) {
+ if (mExtractAccessories == null) {
+ return;
+ }
+ final boolean hasAction = ei.actionLabel != null || (
+ (ei.imeOptions&EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE &&
+ (ei.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0);
+ if (hasAction) {
+ mExtractAccessories.setVisibility(View.VISIBLE);
+ if (ei.actionLabel != null) {
+ mExtractAction.setText(ei.actionLabel);
+ } else {
+ mExtractAction.setText(getTextForImeAction(ei.imeOptions));
+ }
+ mExtractAction.setOnClickListener(mActionClickListener);
+ } else {
+ mExtractAccessories.setVisibility(View.GONE);
+ mExtractAction.setOnClickListener(null);
+ }
+ }
+
+ /**
+ * This is called when, while currently displayed in extract mode, the
+ * current input target changes. The default implementation will
+ * auto-hide the IME if the new target is not a full editor, since this
+ * can be an confusing experience for the user.
+ */
+ public void onExtractingInputChanged(EditorInfo ei) {
+ if (ei.inputType == InputType.TYPE_NULL) {
+ requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+ }
+
+ void startExtractingText(boolean inputChanged) {
+ final ExtractEditText eet = mExtractEditText;
+ if (eet != null && getCurrentInputStarted()
&& isFullscreenMode()) {
mExtractedToken++;
ExtractedTextRequest req = new ExtractedTextRequest();
req.token = mExtractedToken;
+ req.flags = InputConnection.GET_TEXT_WITH_STYLES;
req.hintMaxLines = 10;
req.hintMaxChars = 10000;
- mExtractedText = mInputConnection.getExtractedText(req,
- InputConnection.EXTRACTED_TEXT_MONITOR);
- if (mExtractedText != null) {
- mExtractEditText.setExtractedText(mExtractedText);
+ mExtractedText = getCurrentInputConnection().getExtractedText(req,
+ InputConnection.GET_EXTRACTED_TEXT_MONITOR);
+
+ final EditorInfo ei = getCurrentInputEditorInfo();
+
+ try {
+ eet.startInternalChanges();
+ onUpdateExtractingAccessories(ei);
+ int inputType = ei.inputType;
+ if ((inputType&EditorInfo.TYPE_MASK_CLASS)
+ == EditorInfo.TYPE_CLASS_TEXT) {
+ if ((inputType&EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE) != 0) {
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ }
+ }
+ eet.setInputType(inputType);
+ eet.setHint(ei.hintText);
+ if (mExtractedText != null) {
+ eet.setEnabled(true);
+ eet.setExtractedText(mExtractedText);
+ } else {
+ eet.setEnabled(false);
+ eet.setText("");
+ }
+ } finally {
+ eet.finishInternalChanges();
+ }
+
+ if (inputChanged) {
+ onExtractingInputChanged(ei);
}
- mExtractEditText.setInputType(getCurrentInputEditorInfo().inputType);
- mExtractEditText.setHint(mInputEditorInfo.hintText);
}
}
+
+ /**
+ * Performs a dump of the InputMethodService's internal state. Override
+ * to add your own information to the dump.
+ */
+ @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ final Printer p = new PrintWriterPrinter(fout);
+ p.println("Input method service state for " + this + ":");
+ p.println(" mWindowCreated=" + mWindowCreated
+ + " mWindowAdded=" + mWindowAdded
+ + " mWindowVisible=" + mWindowVisible);
+ p.println(" Configuration=" + getResources().getConfiguration());
+ p.println(" mToken=" + mToken);
+ p.println(" mInputBinding=" + mInputBinding);
+ p.println(" mInputConnection=" + mInputConnection);
+ p.println(" mStartedInputConnection=" + mStartedInputConnection);
+ p.println(" mInputStarted=" + mInputStarted
+ + " mInputViewStarted=" + mInputViewStarted
+ + " mCandidatesViewStarted=" + mCandidatesViewStarted);
+
+ if (mInputEditorInfo != null) {
+ p.println(" mInputEditorInfo:");
+ mInputEditorInfo.dump(p, " ");
+ } else {
+ p.println(" mInputEditorInfo: null");
+ }
+
+ p.println(" mShowInputRequested=" + mShowInputRequested
+ + " mLastShowInputRequested=" + mLastShowInputRequested
+ + " mShowInputForced=" + mShowInputForced
+ + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags));
+ p.println(" mCandidatesVisibility=" + mCandidatesVisibility
+ + " mFullscreenApplied=" + mFullscreenApplied
+ + " mIsFullscreen=" + mIsFullscreen);
+
+ if (mExtractedText != null) {
+ p.println(" mExtractedText:");
+ p.println(" text=" + mExtractedText.text.length() + " chars"
+ + " startOffset=" + mExtractedText.startOffset);
+ p.println(" selectionStart=" + mExtractedText.selectionStart
+ + " selectionEnd=" + mExtractedText.selectionEnd
+ + " flags=0x" + Integer.toHexString(mExtractedText.flags));
+ } else {
+ p.println(" mExtractedText: null");
+ }
+ p.println(" mExtractedToken=" + mExtractedToken);
+ p.println(" mIsInputViewShown=" + mIsInputViewShown
+ + " mStatusIcon=" + mStatusIcon);
+ p.println("Last computed insets:");
+ p.println(" contentTopInsets=" + mTmpInsets.contentTopInsets
+ + " visibleTopInsets=" + mTmpInsets.visibleTopInsets
+ + " touchableInsets=" + mTmpInsets.touchableInsets);
+ }
}
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index cfd3188..6a560ce 100755
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -132,7 +132,19 @@ public class Keyboard {
/** Keyboard mode, or zero, if none. */
private int mKeyboardMode;
+
+ // Variables for pre-computing nearest keys.
+ private static final int GRID_WIDTH = 10;
+ private static final int GRID_HEIGHT = 5;
+ private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
+ private int mCellWidth;
+ private int mCellHeight;
+ private int[][] mGridNeighbors;
+ private int mProximityThreshold;
+ /** Number of key widths from current touch point to search for nearest keys. */
+ private static float SEARCH_DISTANCE = 1.4f;
+
/**
* Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
* Some of the key size defaults can be overridden per row from what the {@link Keyboard}
@@ -177,13 +189,13 @@ public class Keyboard {
parent.mDisplayWidth, parent.mDefaultWidth);
defaultHeight = getDimensionOrFraction(a,
com.android.internal.R.styleable.Keyboard_keyHeight,
- parent.mDisplayWidth, parent.mDefaultHeight);
- defaultHorizontalGap = getDimensionOrFraction(a,
+ parent.mDisplayHeight, parent.mDefaultHeight);
+ defaultHorizontalGap = getDimensionOrFraction(a,
com.android.internal.R.styleable.Keyboard_horizontalGap,
parent.mDisplayWidth, parent.mDefaultHorizontalGap);
verticalGap = getDimensionOrFraction(a,
com.android.internal.R.styleable.Keyboard_verticalGap,
- parent.mDisplayWidth, parent.mDefaultVerticalGap);
+ parent.mDisplayHeight, parent.mDefaultVerticalGap);
a.recycle();
a = res.obtainAttributes(Xml.asAttributeSet(parser),
com.android.internal.R.styleable.Keyboard_Row);
@@ -540,7 +552,6 @@ public class Keyboard {
row.defaultHorizontalGap = mDefaultHorizontalGap;
row.verticalGap = mDefaultVerticalGap;
row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM;
-
final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
for (int i = 0; i < characters.length(); i++) {
char c = characters.charAt(i);
@@ -638,6 +649,52 @@ public class Keyboard {
public int getShiftKeyIndex() {
return mShiftKeyIndex;
}
+
+ private void computeNearestNeighbors() {
+ // Round-up so we don't have any pixels outside the grid
+ mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
+ mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
+ mGridNeighbors = new int[GRID_SIZE][];
+ int[] indices = new int[mKeys.size()];
+ final int gridWidth = GRID_WIDTH * mCellWidth;
+ final int gridHeight = GRID_HEIGHT * mCellHeight;
+ for (int x = 0; x < gridWidth; x += mCellWidth) {
+ for (int y = 0; y < gridHeight; y += mCellHeight) {
+ int count = 0;
+ for (int i = 0; i < mKeys.size(); i++) {
+ final Key key = mKeys.get(i);
+ if (key.squaredDistanceFrom(x, y) < mProximityThreshold ||
+ key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold ||
+ key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1)
+ < mProximityThreshold ||
+ key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) {
+ indices[count++] = i;
+ }
+ }
+ int [] cell = new int[count];
+ System.arraycopy(indices, 0, cell, 0, count);
+ mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
+ }
+ }
+ }
+
+ /**
+ * Returns the indices of the keys that are closest to the given point.
+ * @param x the x-coordinate of the point
+ * @param y the y-coordinate of the point
+ * @return the array of integer indices for the nearest keys to the given point. If the given
+ * point is out of range, then an array of size zero is returned.
+ */
+ public int[] getNearestKeys(int x, int y) {
+ if (mGridNeighbors == null) computeNearestNeighbors();
+ if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
+ int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
+ if (index < GRID_SIZE) {
+ return mGridNeighbors[index];
+ }
+ }
+ return new int[0];
+ }
protected Row createRowFromXml(Resources res, XmlResourceParser parser) {
return new Row(res, this, parser);
@@ -739,6 +796,8 @@ public class Keyboard {
mDefaultVerticalGap = getDimensionOrFraction(a,
com.android.internal.R.styleable.Keyboard_verticalGap,
mDisplayHeight, 0);
+ mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
+ mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison
a.recycle();
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 2f3b54b..7a63c0c 100755
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -16,34 +16,33 @@
package android.inputmethodservice;
-import com.android.internal.R;
-
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
+import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard.Key;
import android.os.Handler;
import android.os.Message;
-import android.os.Vibrator;
-import android.preference.PreferenceManager;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.GestureDetector;
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;
import android.widget.TextView;
+import com.android.internal.R;
+
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -74,7 +73,6 @@ public class KeyboardView extends View implements View.OnClickListener {
* For keys that repeat, this is only called once.
* @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
* key, the value will be zero.
- * @hide Pending API Council approval
*/
void onPress(int primaryCode);
@@ -82,7 +80,6 @@ public class KeyboardView extends View implements View.OnClickListener {
* Called when the user releases a key. This is sent after the {@link #onKey} is called.
* For keys that repeat, this is only called once.
* @param primaryCode the code of the key that was released
- * @hide Pending API Council approval
*/
void onRelease(int primaryCode);
@@ -99,6 +96,12 @@ public class KeyboardView extends View implements View.OnClickListener {
void onKey(int primaryCode, int[] keyCodes);
/**
+ * Sends a sequence of characters to the listener.
+ * @param text the sequence of characters to be displayed.
+ */
+ void onText(CharSequence text);
+
+ /**
* Called when the user quickly moves the finger from right to left.
*/
void swipeLeft();
@@ -149,6 +152,7 @@ public class KeyboardView extends View implements View.OnClickListener {
private int mMiniKeyboardOffsetY;
private Map<Key,View> mMiniKeyboardCache;
private int[] mWindowOffset;
+ private Key[] mKeys;
/** Listener for {@link OnKeyboardActionListener}. */
private OnKeyboardActionListener mKeyboardActionListener;
@@ -158,12 +162,15 @@ public class KeyboardView extends View implements View.OnClickListener {
private static final int MSG_REPEAT = 3;
private static final int MSG_LONGPRESS = 4;
+ private static final int DELAY_BEFORE_PREVIEW = 70;
+ private static final int DELAY_AFTER_PREVIEW = 60;
+
private int mVerticalCorrection;
private int mProximityThreshold;
private boolean mPreviewCentered = false;
private boolean mShowPreview = true;
- private boolean mShowTouchPoints = false;
+ private boolean mShowTouchPoints = true;
private int mPopupPreviewX;
private int mPopupPreviewY;
@@ -213,12 +220,21 @@ public class KeyboardView extends View implements View.OnClickListener {
private static final int MULTITAP_INTERVAL = 800; // milliseconds
private StringBuilder mPreviewLabel = new StringBuilder(1);
+ /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
+ private boolean mDrawPending;
+ /** The dirty region in the keyboard bitmap */
+ private Rect mDirtyRect = new Rect();
+ /** The keyboard bitmap for faster updates */
+ private Bitmap mBuffer;
+ /** The canvas for the above mutable keyboard bitmap */
+ private Canvas mCanvas;
+
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SHOW_PREVIEW:
- mPreviewText.setVisibility(VISIBLE);
+ showKey(msg.arg1);
break;
case MSG_REMOVE_PREVIEW:
mPreviewText.setVisibility(INVISIBLE);
@@ -233,7 +249,6 @@ public class KeyboardView extends View implements View.OnClickListener {
openPopupIfRequired((MotionEvent) msg.obj);
break;
}
-
}
};
@@ -334,7 +349,7 @@ public class KeyboardView extends View implements View.OnClickListener {
}
private void initGestureDetector() {
- mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
+ mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent me1, MotionEvent me2,
float velocityX, float velocityY) {
@@ -386,9 +401,15 @@ public class KeyboardView extends View implements View.OnClickListener {
showPreview(NOT_A_KEY);
}
mKeyboard = keyboard;
+ List<Key> keys = mKeyboard.getKeys();
+ mKeys = keys.toArray(new Key[keys.size()]);
requestLayout();
- invalidate();
+ // Release buffer, just in case the new keyboard has a different size.
+ // It will be reallocated on the next draw.
+ mBuffer = null;
+ invalidateAll();
computeProximityThreshold(keyboard);
+ mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
}
/**
@@ -410,7 +431,7 @@ public class KeyboardView extends View implements View.OnClickListener {
if (mKeyboard != null) {
if (mKeyboard.setShifted(shifted)) {
// The whole keyboard probably needs to be redrawn
- invalidate();
+ invalidateAll();
return true;
}
}
@@ -492,7 +513,7 @@ public class KeyboardView extends View implements View.OnClickListener {
}
private CharSequence adjustCase(CharSequence label) {
- if (mKeyboard.isShifted() && label != null && label.length() == 1
+ if (mKeyboard.isShifted() && label != null && label.length() < 3
&& Character.isLowerCase(label.charAt(0))) {
label = label.toString().toUpperCase();
}
@@ -521,22 +542,44 @@ public class KeyboardView extends View implements View.OnClickListener {
*/
private void computeProximityThreshold(Keyboard keyboard) {
if (keyboard == null) return;
- List<Key> keys = keyboard.getKeys();
+ final Key[] keys = mKeys;
if (keys == null) return;
- int length = keys.size();
+ int length = keys.length;
int dimensionSum = 0;
for (int i = 0; i < length; i++) {
- Key key = keys.get(i);
- dimensionSum += key.width + key.gap + key.height;
+ Key key = keys[i];
+ dimensionSum += Math.min(key.width, key.height) + key.gap;
}
if (dimensionSum < 0 || length == 0) return;
- mProximityThreshold = dimensionSum / (length * 2);
+ mProximityThreshold = (int) (dimensionSum * 1.4f / length);
mProximityThreshold *= mProximityThreshold; // Square it
}
-
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ // Release the buffer, if any and it will be reallocated on the next draw
+ mBuffer = null;
+ }
+
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
+ if (mDrawPending || mBuffer == null) {
+ onBufferDraw();
+ }
+ canvas.drawBitmap(mBuffer, 0, 0, null);
+ }
+
+ private void onBufferDraw() {
+ if (mBuffer == null) {
+ mBuffer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBuffer);
+ invalidateAll();
+ }
+ final Canvas canvas = mCanvas;
+ canvas.clipRect(mDirtyRect, Op.REPLACE);
+
if (mKeyboard == null) return;
final Paint paint = mPaint;
@@ -545,29 +588,25 @@ public class KeyboardView extends View implements View.OnClickListener {
final Rect padding = mPadding;
final int kbdPaddingLeft = mPaddingLeft;
final int kbdPaddingTop = mPaddingTop;
- final List<Key> keys = mKeyboard.getKeys();
+ final Key[] keys = mKeys;
final Key invalidKey = mInvalidatedKey;
- //canvas.translate(0, mKeyboardPaddingTop);
+
paint.setAlpha(255);
paint.setColor(mKeyTextColor);
boolean drawSingleKey = false;
if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
-// System.out.println("Key bounds = " + (invalidKey.x + mPaddingLeft) + ","
-// + (invalidKey.y + mPaddingTop) + ","
-// + (invalidKey.x + invalidKey.width + mPaddingLeft) + ","
-// + (invalidKey.y + invalidKey.height + mPaddingTop));
-// System.out.println("Clip bounds =" + clipRegion.toShortString());
- // Is clipRegion completely contained within the invalidated key?
- if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
- invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
- invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
- invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
- drawSingleKey = true;
- }
+ // Is clipRegion completely contained within the invalidated key?
+ if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
+ invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
+ invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
+ invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
+ drawSingleKey = true;
+ }
}
- final int keyCount = keys.size();
+ canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+ final int keyCount = keys.length;
for (int i = 0; i < keyCount; i++) {
- final Key key = keys.get(i);
+ final Key key = keys[i];
if (drawSingleKey && invalidKey != key) {
continue;
}
@@ -635,18 +674,22 @@ public class KeyboardView extends View implements View.OnClickListener {
paint.setColor(0xFF00FF00);
canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
}
+
+ mDrawPending = false;
+ mDirtyRect.setEmpty();
}
private int getKeyIndices(int x, int y, int[] allKeys) {
- final List<Key> keys = mKeyboard.getKeys();
+ final Key[] keys = mKeys;
final boolean shifted = mKeyboard.isShifted();
int primaryIndex = NOT_A_KEY;
int closestKey = NOT_A_KEY;
int closestKeyDist = mProximityThreshold + 1;
java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
- final int keyCount = keys.size();
+ int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
+ final int keyCount = nearestKeyIndices.length;
for (int i = 0; i < keyCount; i++) {
- final Key key = keys.get(i);
+ final Key key = keys[nearestKeyIndices[i]];
int dist = 0;
boolean isInside = key.isInside(x,y);
if (((mProximityCorrectOn
@@ -657,7 +700,7 @@ public class KeyboardView extends View implements View.OnClickListener {
final int nCodes = key.codes.length;
if (dist < closestKeyDist) {
closestKeyDist = dist;
- closestKey = i;
+ closestKey = nearestKeyIndices[i];
}
if (allKeys == null) continue;
@@ -671,9 +714,6 @@ public class KeyboardView extends View implements View.OnClickListener {
allKeys.length - j - nCodes);
for (int c = 0; c < nCodes; c++) {
allKeys[j + c] = key.codes[c];
- if (shifted) {
- //allKeys[j + c] = Character.toUpperCase(key.codes[c]);
- }
mDistances[j + c] = dist;
}
break;
@@ -682,7 +722,7 @@ public class KeyboardView extends View implements View.OnClickListener {
}
if (isInside) {
- primaryIndex = i;
+ primaryIndex = nearestKeyIndices[i];
}
}
if (primaryIndex == NOT_A_KEY) {
@@ -693,12 +733,10 @@ public class KeyboardView extends View implements View.OnClickListener {
private void detectAndSendKey(int x, int y, long eventTime) {
int index = mCurrentKey;
- if (index != NOT_A_KEY) {
- final Key key = mKeyboard.getKeys().get(index);
+ if (index != NOT_A_KEY && index < mKeys.length) {
+ final Key key = mKeys[index];
if (key.text != null) {
- for (int i = 0; i < key.text.length(); i++) {
- mKeyboardActionListener.onKey(key.text.charAt(i), key.codes);
- }
+ mKeyboardActionListener.onText(key.text);
mKeyboardActionListener.onRelease(NOT_A_KEY);
} else {
int code = key.codes[0];
@@ -743,14 +781,14 @@ public class KeyboardView extends View implements View.OnClickListener {
mCurrentKeyIndex = keyIndex;
// Release the old key and press the new key
- final List<Key> keys = mKeyboard.getKeys();
+ final Key[] keys = mKeys;
if (oldKeyIndex != mCurrentKeyIndex) {
- if (oldKeyIndex != NOT_A_KEY && keys.size() > oldKeyIndex) {
- keys.get(oldKeyIndex).onReleased(mCurrentKeyIndex == NOT_A_KEY);
+ if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
+ keys[oldKeyIndex].onReleased(mCurrentKeyIndex == NOT_A_KEY);
invalidateKey(oldKeyIndex);
}
- if (mCurrentKeyIndex != NOT_A_KEY && keys.size() > mCurrentKeyIndex) {
- keys.get(mCurrentKeyIndex).onPressed();
+ if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
+ keys[mCurrentKeyIndex].onPressed();
invalidateKey(mCurrentKeyIndex);
}
}
@@ -760,75 +798,99 @@ public class KeyboardView extends View implements View.OnClickListener {
if (previewPopup.isShowing()) {
if (keyIndex == NOT_A_KEY) {
mHandler.sendMessageDelayed(mHandler
- .obtainMessage(MSG_REMOVE_PREVIEW), 60);
+ .obtainMessage(MSG_REMOVE_PREVIEW),
+ DELAY_AFTER_PREVIEW);
}
}
if (keyIndex != NOT_A_KEY) {
- Key key = keys.get(keyIndex);
- if (key.icon != null) {
- mPreviewText.setCompoundDrawables(null, null, null,
- key.iconPreview != null ? key.iconPreview : key.icon);
- mPreviewText.setText(null);
- } else {
- mPreviewText.setCompoundDrawables(null, null, null, null);
- mPreviewText.setText(getPreviewText(key));
- if (key.label.length() > 1 && key.codes.length < 2) {
- mPreviewText.setTextSize(mLabelTextSize);
- } else {
- mPreviewText.setTextSize(mPreviewTextSizeLarge);
- }
- }
- mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
- + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
- final int popupHeight = mPreviewHeight;
- LayoutParams lp = mPreviewText.getLayoutParams();
- if (lp != null) {
- lp.width = popupWidth;
- lp.height = popupHeight;
- }
- previewPopup.setWidth(popupWidth);
- previewPopup.setHeight(popupHeight);
- if (!mPreviewCentered) {
- mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
- mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
- } else {
- // TODO: Fix this if centering is brought back
- mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
- mPopupPreviewY = - mPreviewText.getMeasuredHeight();
- }
- mHandler.removeMessages(MSG_REMOVE_PREVIEW);
- if (mOffsetInWindow == null) {
- mOffsetInWindow = new int[2];
- getLocationInWindow(mOffsetInWindow);
- mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
- mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
- }
- // Set the preview background state
- mPreviewText.getBackground().setState(
- key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
- if (previewPopup.isShowing()) {
- previewPopup.update(mPopupParent, mPopupPreviewX + mOffsetInWindow[0],
- mPopupPreviewY + mOffsetInWindow[1],
- popupWidth, popupHeight);
+ if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+ // Show right away, if it's already visible and finger is moving around
+ showKey(keyIndex);
} else {
- previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
- mPopupPreviewX + mOffsetInWindow[0],
- mPopupPreviewY + mOffsetInWindow[1]);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
+ DELAY_BEFORE_PREVIEW);
}
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
- ViewConfiguration.getTapTimeout());
}
}
}
+
+ private void showKey(final int keyIndex) {
+ final PopupWindow previewPopup = mPreviewPopup;
+ final Key[] keys = mKeys;
+ Key key = keys[keyIndex];
+ if (key.icon != null) {
+ mPreviewText.setCompoundDrawables(null, null, null,
+ key.iconPreview != null ? key.iconPreview : key.icon);
+ mPreviewText.setText(null);
+ } else {
+ mPreviewText.setCompoundDrawables(null, null, null, null);
+ mPreviewText.setText(getPreviewText(key));
+ if (key.label.length() > 1 && key.codes.length < 2) {
+ mPreviewText.setTextSize(mKeyTextSize);
+ mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
+ } else {
+ mPreviewText.setTextSize(mPreviewTextSizeLarge);
+ mPreviewText.setTypeface(Typeface.DEFAULT);
+ }
+ }
+ mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
+ + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
+ final int popupHeight = mPreviewHeight;
+ LayoutParams lp = mPreviewText.getLayoutParams();
+ if (lp != null) {
+ lp.width = popupWidth;
+ lp.height = popupHeight;
+ }
+ if (!mPreviewCentered) {
+ mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
+ mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
+ } else {
+ // TODO: Fix this if centering is brought back
+ mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
+ mPopupPreviewY = - mPreviewText.getMeasuredHeight();
+ }
+ mHandler.removeMessages(MSG_REMOVE_PREVIEW);
+ if (mOffsetInWindow == null) {
+ mOffsetInWindow = new int[2];
+ getLocationInWindow(mOffsetInWindow);
+ mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
+ mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
+ }
+ // Set the preview background state
+ mPreviewText.getBackground().setState(
+ key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
+ if (previewPopup.isShowing()) {
+ previewPopup.update(mPopupPreviewX + mOffsetInWindow[0],
+ mPopupPreviewY + mOffsetInWindow[1],
+ popupWidth, popupHeight);
+ } else {
+ previewPopup.setWidth(popupWidth);
+ previewPopup.setHeight(popupHeight);
+ previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
+ mPopupPreviewX + mOffsetInWindow[0],
+ mPopupPreviewY + mOffsetInWindow[1]);
+ }
+ mPreviewText.setVisibility(VISIBLE);
+ }
+ private void invalidateAll() {
+ mDirtyRect.union(0, 0, getWidth(), getHeight());
+ mDrawPending = true;
+ invalidate();
+ }
+
private void invalidateKey(int keyIndex) {
- if (keyIndex < 0 || keyIndex >= mKeyboard.getKeys().size()) {
+ if (keyIndex < 0 || keyIndex >= mKeys.length) {
return;
}
- final Key key = mKeyboard.getKeys().get(keyIndex);
+ final Key key = mKeys[keyIndex];
mInvalidatedKey = key;
+ mDirtyRect.union(key.x + mPaddingLeft, key.y + mPaddingTop,
+ key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
+ onBufferDraw();
invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,
key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
}
@@ -838,11 +900,11 @@ public class KeyboardView extends View implements View.OnClickListener {
if (mPopupLayout == 0) {
return false;
}
- if (mCurrentKey < 0 || mCurrentKey >= mKeyboard.getKeys().size()) {
+ if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
return false;
}
- Key popupKey = mKeyboard.getKeys().get(mCurrentKey);
+ Key popupKey = mKeys[mCurrentKey];
boolean result = onLongPress(popupKey);
if (result) {
mAbortKey = true;
@@ -878,6 +940,11 @@ public class KeyboardView extends View implements View.OnClickListener {
dismissPopupKeyboard();
}
+ public void onText(CharSequence text) {
+ mKeyboardActionListener.onText(text);
+ dismissPopupKeyboard();
+ }
+
public void swipeLeft() { }
public void swipeRight() { }
public void swipeUp() { }
@@ -926,7 +993,7 @@ public class KeyboardView extends View implements View.OnClickListener {
mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
mMiniKeyboardOnScreen = true;
//mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
- invalidate();
+ invalidateAll();
return true;
}
return false;
@@ -968,8 +1035,8 @@ public class KeyboardView extends View implements View.OnClickListener {
mLastMoveTime = mDownTime;
checkMultiTap(eventTime, keyIndex);
mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
- mKeyboard.getKeys().get(keyIndex).codes[0] : 0);
- if (mCurrentKey >= 0 && mKeyboard.getKeys().get(mCurrentKey).repeatable) {
+ mKeys[keyIndex].codes[0] : 0);
+ if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
mRepeatKeyIndex = mCurrentKey;
repeatKey();
Message msg = mHandler.obtainMessage(MSG_REPEAT);
@@ -1040,11 +1107,11 @@ public class KeyboardView extends View implements View.OnClickListener {
}
showPreview(NOT_A_KEY);
Arrays.fill(mKeyIndices, NOT_A_KEY);
- invalidateKey(keyIndex);
// If we're not on a repeating key (which sends on a DOWN event)
if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
detectAndSendKey(touchX, touchY, eventTime);
}
+ invalidateKey(keyIndex);
mRepeatKeyIndex = NOT_A_KEY;
break;
}
@@ -1054,7 +1121,7 @@ public class KeyboardView extends View implements View.OnClickListener {
}
private boolean repeatKey() {
- Key key = mKeyboard.getKeys().get(mRepeatKeyIndex);
+ Key key = mKeys[mRepeatKeyIndex];
detectAndSendKey(key.x, key.y, mLastTapTime);
return true;
}
@@ -1079,7 +1146,14 @@ public class KeyboardView extends View implements View.OnClickListener {
if (mPreviewPopup.isShowing()) {
mPreviewPopup.dismiss();
}
+ mHandler.removeMessages(MSG_REPEAT);
+ mHandler.removeMessages(MSG_LONGPRESS);
+ mHandler.removeMessages(MSG_SHOW_PREVIEW);
+
dismissPopupKeyboard();
+ mBuffer = null;
+ mCanvas = null;
+ mMiniKeyboardCache.clear();
}
@Override
@@ -1092,10 +1166,10 @@ public class KeyboardView extends View implements View.OnClickListener {
if (mPopupKeyboard.isShowing()) {
mPopupKeyboard.dismiss();
mMiniKeyboardOnScreen = false;
- invalidate();
+ invalidateAll();
}
}
-
+
public boolean handleBack() {
if (mPopupKeyboard.isShowing()) {
dismissPopupKeyboard();
@@ -1113,7 +1187,7 @@ public class KeyboardView extends View implements View.OnClickListener {
private void checkMultiTap(long eventTime, int keyIndex) {
if (keyIndex == NOT_A_KEY) return;
- Key key = mKeyboard.getKeys().get(keyIndex);
+ Key key = mKeys[keyIndex];
if (key.codes.length > 1) {
mInMultiTap = true;
if (eventTime < mLastTapTime + MULTITAP_INTERVAL
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 9ff1665..c37845f 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -18,6 +18,7 @@ package android.inputmethodservice;
import android.app.Dialog;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.os.IBinder;
import android.view.Gravity;
import android.view.WindowManager;
@@ -139,6 +140,9 @@ class SoftInputWindow extends Dialog {
lp.gravity = Gravity.BOTTOM;
lp.width = -1;
+ // Let the input method window's orientation follow sensor based rotation
+ // Turn this off for now, it is very problematic.
+ //lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
getWindow().setAttributes(lp);
getWindow().setFlags(
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 213813a..1429bc1 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
import android.os.RemoteException;
/**
@@ -100,6 +102,18 @@ public class ConnectivityManager
*/
public static final String EXTRA_EXTRA_INFO = "extraInfo";
+ /**
+ * Broadcast Action: The setting for background data usage has changed
+ * values. Use {@link #getBackgroundDataSetting()} to get the current value.
+ * <p>
+ * If an application uses the network in the background, it should listen
+ * for this broadcast and stop using the background data if the value is
+ * false.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
+ "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
+
public static final int TYPE_MOBILE = 0;
public static final int TYPE_WIFI = 1;
@@ -224,6 +238,43 @@ public class ConnectivityManager
}
/**
+ * Returns the value of the setting for background data usage. If false,
+ * applications should not use the network if the application is not in the
+ * foreground. Developers should respect this setting, and check the value
+ * of this before performing any background data operations.
+ * <p>
+ * All applications that have background services that use the network
+ * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
+ *
+ * @return Whether background data usage is allowed.
+ */
+ public boolean getBackgroundDataSetting() {
+ try {
+ return mService.getBackgroundDataSetting();
+ } catch (RemoteException e) {
+ // Err on the side of safety
+ return false;
+ }
+ }
+
+ /**
+ * Sets the value of the setting for background data usage.
+ *
+ * @param allowBackgroundData Whether an application should use data while
+ * it is in the background.
+ *
+ * @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING
+ * @see #getBackgroundDataSetting()
+ * @hide
+ */
+ public void setBackgroundDataSetting(boolean allowBackgroundData) {
+ try {
+ mService.setBackgroundDataSetting(allowBackgroundData);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Don't allow use of default constructor.
*/
@SuppressWarnings({"UnusedDeclaration"})
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e1d921f..de68598 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -44,4 +44,8 @@ interface IConnectivityManager
int stopUsingNetworkFeature(int networkType, in String feature);
boolean requestRouteToHost(int networkType, int hostAddress);
+
+ boolean getBackgroundDataSetting();
+
+ void setBackgroundDataSetting(boolean allowBackgroundData);
}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index f816caa..deaa3c3 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -16,38 +16,45 @@
package android.net;
-import android.util.Log;
-import android.util.Config;
import android.net.http.DomainNameChecker;
import android.os.SystemProperties;
-
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
+import android.util.Config;
+import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
-import java.security.NoSuchAlgorithmException;
+import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
-import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
+
+/**
+ * SSLSocketFactory that provides optional (on debug devices, only) skipping of ssl certificfate
+ * chain validation and custom read timeouts used just when connecting to the server/negotiating
+ * an ssl session.
+ *
+ * You can skip the ssl certificate checking at runtime by setting socket.relaxsslcheck=yes on
+ * devices that do not have have ro.secure set.
+ */
public class SSLCertificateSocketFactory extends SSLSocketFactory {
- private static final boolean DBG = true;
private static final String LOG_TAG = "SSLCertificateSocketFactory";
-
- private static X509TrustManager sDefaultTrustManager;
- private final int socketReadTimeoutForSslHandshake;
+ private static X509TrustManager sDefaultTrustManager;
static {
try {
@@ -83,27 +90,56 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
}
};
- private SSLSocketFactory factory;
+ private final SSLSocketFactory mFactory;
+
+ private final int mSocketReadTimeoutForSslHandshake;
+ /**
+ * Do not use this constructor (will be deprecated). Use {@link #getDefault(int)} instead.
+ */
public SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake)
- throws NoSuchAlgorithmException, KeyManagementException {
- SSLContext context = SSLContext.getInstance("TLS");
- context.init(null, TRUST_MANAGER, new java.security.SecureRandom());
- factory = (SSLSocketFactory) context.getSocketFactory();
- this.socketReadTimeoutForSslHandshake = socketReadTimeoutForSslHandshake;
+ throws NoSuchAlgorithmException, KeyManagementException {
+ this(socketReadTimeoutForSslHandshake, null /* cache */);
+ }
+
+ private SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake,
+ SSLClientSessionCache cache) throws NoSuchAlgorithmException, KeyManagementException {
+ SSLContextImpl sslContext = new SSLContextImpl();
+ sslContext.engineInit(null /* kms */,
+ TRUST_MANAGER, new java.security.SecureRandom(),
+ cache /* client cache */, null /* server cache */);
+ this.mFactory = sslContext.engineGetSocketFactory();
+ this.mSocketReadTimeoutForSslHandshake = socketReadTimeoutForSslHandshake;
}
/**
- * Returns a default instantiation of a new socket factory which
- * only allows SSL connections with valid certificates.
+ * Returns a new instance of a socket factory using the specified socket read
+ * timeout while connecting with the server/negotiating an ssl session.
*
* @param socketReadTimeoutForSslHandshake the socket read timeout used for performing
* ssl handshake. The socket read timeout is set back to 0 after the handshake.
* @return a new SocketFactory, or null on error
*/
public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake) {
+ return getDefault(socketReadTimeoutForSslHandshake, null /* cache */);
+ }
+
+ /**
+ * Returns a new instance of a socket factory using the specified socket read
+ * timeout while connecting with the server/negotiating an ssl session.
+ * Persists ssl sessions using the provided {@link SSLClientSessionCache}.
+ *
+ * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing
+ * ssl handshake. The socket read timeout is set back to 0 after the handshake.
+ * @param cache The {@link SSLClientSessionCache} to use, if any.
+ * @return a new SocketFactory, or null on error
+ *
+ * @hide
+ */
+ public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake,
+ SSLClientSessionCache cache) {
try {
- return new SSLCertificateSocketFactory(socketReadTimeoutForSslHandshake);
+ return new SSLCertificateSocketFactory(socketReadTimeoutForSslHandshake, cache);
} catch (NoSuchAlgorithmException e) {
Log.e(LOG_TAG,
"SSLCertifcateSocketFactory.getDefault" +
@@ -217,10 +253,10 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
}
public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
- SSLSocket sslSock = (SSLSocket) factory.createSocket(s, i, inaddr, j);
+ SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i, inaddr, j);
- if (socketReadTimeoutForSslHandshake >= 0) {
- sslSock.setSoTimeout(socketReadTimeoutForSslHandshake);
+ if (mSocketReadTimeoutForSslHandshake >= 0) {
+ sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake);
}
validateSocket(sslSock,s);
@@ -230,10 +266,10 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
}
public Socket createSocket(String s, int i) throws IOException {
- SSLSocket sslSock = (SSLSocket) factory.createSocket(s, i);
+ SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i);
- if (socketReadTimeoutForSslHandshake >= 0) {
- sslSock.setSoTimeout(socketReadTimeoutForSslHandshake);
+ if (mSocketReadTimeoutForSslHandshake >= 0) {
+ sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake);
}
validateSocket(sslSock,s);
@@ -243,11 +279,11 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
}
public String[] getDefaultCipherSuites() {
- return factory.getSupportedCipherSuites();
+ return mFactory.getSupportedCipherSuites();
}
public String[] getSupportedCipherSuites() {
- return factory.getSupportedCipherSuites();
+ return mFactory.getSupportedCipherSuites();
}
}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 32a26e4..c23df21 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -2235,12 +2235,13 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
}
/**
- * Creates a new Uri by encoding and appending a path segment to a base Uri.
+ * Creates a new Uri by appending an already-encoded path segment to a
+ * base Uri.
*
* @param baseUri Uri to append path segment to
- * @param pathSegment to encode and append
- * @return a new Uri based on baseUri with the given segment encoded and
- * appended to the path
+ * @param pathSegment encoded path segment to append
+ * @return a new Uri based on baseUri with the given segment appended to
+ * the path
* @throws NullPointerException if baseUri is null
*/
public static Uri withAppendedPath(Uri baseUri, String pathSegment) {
diff --git a/core/java/android/net/UrlQuerySanitizer.java b/core/java/android/net/UrlQuerySanitizer.java
index 70e50b7..a6efcdd 100644
--- a/core/java/android/net/UrlQuerySanitizer.java
+++ b/core/java/android/net/UrlQuerySanitizer.java
@@ -23,7 +23,7 @@ import java.util.Set;
import java.util.StringTokenizer;
/**
- *
+ *
* Sanitizes the Query portion of a URL. Simple example:
* <code>
* UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
@@ -32,7 +32,7 @@ import java.util.StringTokenizer;
* String name = sanitizer.getValue("name"));
* // name now contains "Joe_User"
* </code>
- *
+ *
* Register ValueSanitizers to customize the way individual
* parameters are sanitized:
* <code>
@@ -46,7 +46,7 @@ import java.util.StringTokenizer;
* unregistered parameter sanitizer does not allow any special characters,
* and ' ' is a special character.)
* </code>
- *
+ *
* There are several ways to create ValueSanitizers. In order of increasing
* sophistication:
* <ol>
@@ -56,7 +56,7 @@ import java.util.StringTokenizer;
* <li>Subclass UrlQuerySanitizer.ValueSanitizer to define your own value
* sanitizer.
* </ol>
- *
+ *
*/
public class UrlQuerySanitizer {
@@ -84,7 +84,7 @@ public class UrlQuerySanitizer {
*/
public String mValue;
}
-
+
final private HashMap<String, ValueSanitizer> mSanitizers =
new HashMap<String, ValueSanitizer>();
final private HashMap<String, String> mEntries =
@@ -95,9 +95,9 @@ public class UrlQuerySanitizer {
private boolean mPreferFirstRepeatedParameter;
private ValueSanitizer mUnregisteredParameterValueSanitizer =
getAllIllegal();
-
+
/**
- * A functor used to sanitize a single query value.
+ * A functor used to sanitize a single query value.
*
*/
public static interface ValueSanitizer {
@@ -108,7 +108,7 @@ public class UrlQuerySanitizer {
*/
public String sanitize(String value);
}
-
+
/**
* Sanitize values based on which characters they contain. Illegal
* characters are replaced with either space or '_', depending upon
@@ -117,7 +117,7 @@ public class UrlQuerySanitizer {
public static class IllegalCharacterValueSanitizer implements
ValueSanitizer {
private int mFlags;
-
+
/**
* Allow space (' ') characters.
*/
@@ -165,21 +165,21 @@ public class UrlQuerySanitizer {
* such as "javascript:" or "vbscript:"
*/
public final static int SCRIPT_URL_OK = 1 << 10;
-
+
/**
* Mask with all fields set to OK
*/
public final static int ALL_OK = 0x7ff;
-
+
/**
* Mask with both regular space and other whitespace OK
*/
public final static int ALL_WHITESPACE_OK =
SPACE_OK | OTHER_WHITESPACE_OK;
-
+
// Common flag combinations:
-
+
/**
* <ul>
* <li>Deny all special characters.
@@ -262,18 +262,18 @@ public class UrlQuerySanitizer {
*/
public final static int ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL =
ALL_OK & ~(NUL_OK | LT_OK | GT_OK);
-
+
/**
* Script URL definitions
*/
-
+
private final static String JAVASCRIPT_PREFIX = "javascript:";
-
+
private final static String VBSCRIPT_PREFIX = "vbscript:";
-
+
private final static int MIN_SCRIPT_PREFIX_LENGTH = Math.min(
JAVASCRIPT_PREFIX.length(), VBSCRIPT_PREFIX.length());
-
+
/**
* Construct a sanitizer. The parameters set the behavior of the
* sanitizer.
@@ -312,7 +312,7 @@ public class UrlQuerySanitizer {
}
}
}
-
+
// If whitespace isn't OK, get rid of whitespace at beginning
// and end of value.
if ( (mFlags & ALL_WHITESPACE_OK) == 0) {
@@ -337,7 +337,7 @@ public class UrlQuerySanitizer {
}
return stringBuilder.toString();
}
-
+
/**
* Trim whitespace from the beginning and end of a string.
* <p>
@@ -361,7 +361,7 @@ public class UrlQuerySanitizer {
}
return value.substring(start, end + 1);
}
-
+
/**
* Check if c is whitespace.
* @param c character to test
@@ -380,7 +380,7 @@ public class UrlQuerySanitizer {
return false;
}
}
-
+
/**
* Check whether an individual character is legal. Uses the
* flag bit-set passed into the constructor.
@@ -400,11 +400,11 @@ public class UrlQuerySanitizer {
case '%' : return (mFlags & PCT_OK) != 0;
case '\0': return (mFlags & NUL_OK) != 0;
default : return (c >= 32 && c < 127) ||
- (c >= 128 && c <= 255 && ((mFlags & NON_7_BIT_ASCII_OK) != 0));
- }
+ ((c >= 128) && ((mFlags & NON_7_BIT_ASCII_OK) != 0));
+ }
}
}
-
+
/**
* Get the current value sanitizer used when processing
* unregistered parameter values.
@@ -412,14 +412,14 @@ public class UrlQuerySanitizer {
* <b>Note:</b> The default unregistered parameter value sanitizer is
* one that doesn't allow any special characters, similar to what
* is returned by calling createAllIllegal.
- *
+ *
* @return the current ValueSanitizer used to sanitize unregistered
* parameter values.
*/
public ValueSanitizer getUnregisteredParameterValueSanitizer() {
return mUnregisteredParameterValueSanitizer;
}
-
+
/**
* Set the value sanitizer used when processing unregistered
* parameter values.
@@ -430,46 +430,46 @@ public class UrlQuerySanitizer {
ValueSanitizer sanitizer) {
mUnregisteredParameterValueSanitizer = sanitizer;
}
-
-
+
+
// Private fields for singleton sanitizers:
-
+
private static final ValueSanitizer sAllIllegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.ALL_ILLEGAL);
-
+
private static final ValueSanitizer sAllButNulLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.ALL_BUT_NUL_LEGAL);
-
+
private static final ValueSanitizer sAllButWhitespaceLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.ALL_BUT_WHITESPACE_LEGAL);
-
+
private static final ValueSanitizer sURLLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.URL_LEGAL);
-
+
private static final ValueSanitizer sUrlAndSpaceLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.URL_AND_SPACE_LEGAL);
-
+
private static final ValueSanitizer sAmpLegal =
new IllegalCharacterValueSanitizer(
- IllegalCharacterValueSanitizer.AMP_LEGAL);
-
+ IllegalCharacterValueSanitizer.AMP_LEGAL);
+
private static final ValueSanitizer sAmpAndSpaceLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.AMP_AND_SPACE_LEGAL);
-
+
private static final ValueSanitizer sSpaceLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.SPACE_LEGAL);
-
+
private static final ValueSanitizer sAllButNulAndAngleBracketsLegal =
new IllegalCharacterValueSanitizer(
IllegalCharacterValueSanitizer.ALL_BUT_NUL_AND_ANGLE_BRACKETS_LEGAL);
-
+
/**
* Return a value sanitizer that does not allow any special characters,
* and also does not allow script URLs.
@@ -478,7 +478,7 @@ public class UrlQuerySanitizer {
public static final ValueSanitizer getAllIllegal() {
return sAllIllegal;
}
-
+
/**
* Return a value sanitizer that allows everything except Nul ('\0')
* characters. Script URLs are allowed.
@@ -547,7 +547,7 @@ public class UrlQuerySanitizer {
public static final ValueSanitizer getAllButNulAndAngleBracketsLegal() {
return sAllButNulAndAngleBracketsLegal;
}
-
+
/**
* Constructs a UrlQuerySanitizer.
* <p>
@@ -560,7 +560,7 @@ public class UrlQuerySanitizer {
*/
public UrlQuerySanitizer() {
}
-
+
/**
* Constructs a UrlQuerySanitizer and parse a URL.
* This constructor is provided for convenience when the
@@ -585,7 +585,7 @@ public class UrlQuerySanitizer {
setAllowUnregisteredParamaters(true);
parseUrl(url);
}
-
+
/**
* Parse the query parameters out of an encoded URL.
* Works by extracting the query portion from the URL and then
@@ -604,7 +604,7 @@ public class UrlQuerySanitizer {
}
parseQuery(query);
}
-
+
/**
* Parse a query. A query string is any number of parameter-value clauses
* separated by any non-zero number of ampersands. A parameter-value clause
@@ -631,7 +631,7 @@ public class UrlQuerySanitizer {
}
}
}
-
+
/**
* Get a set of all of the parameters found in the sanitized query.
* <p>
@@ -641,7 +641,7 @@ public class UrlQuerySanitizer {
public Set<String> getParameterSet() {
return mEntries.keySet();
}
-
+
/**
* An array list of all of the parameter value pairs in the sanitized
* query, in the order they appeared in the query. May contain duplicate
@@ -691,7 +691,7 @@ public class UrlQuerySanitizer {
}
mSanitizers.put(parameter, valueSanitizer);
}
-
+
/**
* Register a value sanitizer for an array of parameters.
* @param parameters An array of unencoded parameter names.
@@ -705,7 +705,7 @@ public class UrlQuerySanitizer {
mSanitizers.put(parameters[i], valueSanitizer);
}
}
-
+
/**
* Set whether or not unregistered parameters are allowed. If they
* are not allowed, then they will be dropped when a query is sanitized.
@@ -718,7 +718,7 @@ public class UrlQuerySanitizer {
boolean allowUnregisteredParamaters) {
mAllowUnregisteredParamaters = allowUnregisteredParamaters;
}
-
+
/**
* Get whether or not unregistered parameters are allowed. If not
* allowed, they will be dropped when a query is parsed.
@@ -728,10 +728,10 @@ public class UrlQuerySanitizer {
public boolean getAllowUnregisteredParamaters() {
return mAllowUnregisteredParamaters;
}
-
+
/**
* Set whether or not the first occurrence of a repeated parameter is
- * preferred. True means the first repeated parameter is preferred.
+ * preferred. True means the first repeated parameter is preferred.
* False means that the last repeated parameter is preferred.
* <p>
* The preferred parameter is the one that is returned when getParameter
@@ -746,7 +746,7 @@ public class UrlQuerySanitizer {
boolean preferFirstRepeatedParameter) {
mPreferFirstRepeatedParameter = preferFirstRepeatedParameter;
}
-
+
/**
* Get whether or not the first occurrence of a repeated parameter is
* preferred.
@@ -757,10 +757,10 @@ public class UrlQuerySanitizer {
public boolean getPreferFirstRepeatedParameter() {
return mPreferFirstRepeatedParameter;
}
-
+
/**
* Parse an escaped parameter-value pair. The default implementation
- * unescapes both the parameter and the value, then looks up the
+ * unescapes both the parameter and the value, then looks up the
* effective value sanitizer for the parameter and uses it to sanitize
* the value. If all goes well then addSanitizedValue is called with
* the unescaped parameter and the sanitized unescaped value.
@@ -779,7 +779,7 @@ public class UrlQuerySanitizer {
String sanitizedValue = valueSanitizer.sanitize(unescapedValue);
addSanitizedEntry(unescapedParameter, sanitizedValue);
}
-
+
/**
* Record a sanitized parameter-value pair. Override if you want to
* do additional filtering or validation.
@@ -796,7 +796,7 @@ public class UrlQuerySanitizer {
}
mEntries.put(parameter, value);
}
-
+
/**
* Get the value sanitizer for a parameter. Returns null if there
* is no value sanitizer registered for the parameter.
@@ -807,7 +807,7 @@ public class UrlQuerySanitizer {
public ValueSanitizer getValueSanitizer(String parameter) {
return mSanitizers.get(parameter);
}
-
+
/**
* Get the effective value sanitizer for a parameter. Like getValueSanitizer,
* except if there is no value sanitizer registered for a parameter, and
@@ -823,7 +823,7 @@ public class UrlQuerySanitizer {
}
return sanitizer;
}
-
+
/**
* Unescape an escaped string.
* <ul>
@@ -867,7 +867,7 @@ public class UrlQuerySanitizer {
}
return stringBuilder.toString();
}
-
+
/**
* Test if a character is a hexidecimal digit. Both upper case and lower
* case hex digits are allowed.
@@ -877,7 +877,7 @@ public class UrlQuerySanitizer {
protected boolean isHexDigit(char c) {
return decodeHexDigit(c) >= 0;
}
-
+
/**
* Convert a character that represents a hexidecimal digit into an integer.
* If the character is not a hexidecimal digit, then -1 is returned.
@@ -885,7 +885,7 @@ public class UrlQuerySanitizer {
* @param c the hexidecimal digit.
* @return the integer value of the hexidecimal digit.
*/
-
+
protected int decodeHexDigit(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
@@ -900,7 +900,7 @@ public class UrlQuerySanitizer {
return -1;
}
}
-
+
/**
* Clear the existing entries. Called to get ready to parse a new
* query string.
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
index 01442ae..c2013d5 100644
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -26,7 +26,6 @@ import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.ClientProtocolException;
@@ -48,6 +47,8 @@ import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.BasicHttpContext;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
import java.io.IOException;
import java.io.InputStream;
@@ -56,12 +57,13 @@ import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.net.URI;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.security.KeyManagementException;
import android.util.Log;
import android.content.ContentResolver;
import android.provider.Settings;
import android.text.TextUtils;
+import android.os.SystemProperties;
/**
* Subclass of the Apache {@link DefaultHttpClient} that is configured with
@@ -100,10 +102,13 @@ public final class AndroidHttpClient implements HttpClient {
/**
* Create a new HttpClient with reasonable defaults (which you can update).
+ *
* @param userAgent to report in your HTTP requests.
+ * @param sessionCache persistent session cache
* @return AndroidHttpClient for you to use for all your requests.
*/
- public static AndroidHttpClient newInstance(String userAgent) {
+ public static AndroidHttpClient newInstance(String userAgent,
+ SSLClientSessionCache sessionCache) {
HttpParams params = new BasicHttpParams();
// Turn off stale checking. Our connections break all the time anyway,
@@ -125,7 +130,8 @@ public final class AndroidHttpClient implements HttpClient {
schemeRegistry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https",
- SSLSocketFactory.getSocketFactory(), 443));
+ socketFactoryWithCache(sessionCache), 443));
+
ClientConnectionManager manager =
new ThreadSafeClientConnManager(params, schemeRegistry);
@@ -134,6 +140,41 @@ public final class AndroidHttpClient implements HttpClient {
return new AndroidHttpClient(manager, params);
}
+ /**
+ * Returns a socket factory backed by the given persistent session cache.
+ *
+ * @param sessionCache to retrieve sessions from, null for no cache
+ */
+ private static SSLSocketFactory socketFactoryWithCache(
+ SSLClientSessionCache sessionCache) {
+ if (sessionCache == null) {
+ // Use the default factory which doesn't support persistent
+ // caching.
+ return SSLSocketFactory.getSocketFactory();
+ }
+
+ // Create a new SSL context backed by the cache.
+ // TODO: Keep a weak *identity* hash map of caches to engines. In the
+ // mean time, if we have two engines for the same cache, they'll still
+ // share sessions but will have to do so through the persistent cache.
+ SSLContextImpl sslContext = new SSLContextImpl();
+ try {
+ sslContext.engineInit(null, null, null, sessionCache, null);
+ } catch (KeyManagementException e) {
+ throw new AssertionError(e);
+ }
+ return new SSLSocketFactory(sslContext.engineGetSocketFactory());
+ }
+
+ /**
+ * Create a new HttpClient with reasonable defaults (which you can update).
+ * @param userAgent to report in your HTTP requests.
+ * @return AndroidHttpClient for you to use for all your requests.
+ */
+ public static AndroidHttpClient newInstance(String userAgent) {
+ return newInstance(userAgent, null /* session cache */);
+ }
+
private final HttpClient delegate;
private RuntimeException mLeakedException = new IllegalStateException(
@@ -347,6 +388,15 @@ public final class AndroidHttpClient implements HttpClient {
}
/**
+ * Returns true if auth logging is turned on for this configuration. Can only be set on
+ * insecure devices.
+ */
+ private boolean isAuthLoggable() {
+ String secure = SystemProperties.get("ro.secure");
+ return "0".equals(secure) && Log.isLoggable(tag + "-auth", level);
+ }
+
+ /**
* Prints a message using this configuration.
*/
private void println(String message) {
@@ -392,7 +442,8 @@ public final class AndroidHttpClient implements HttpClient {
if (configuration != null
&& configuration.isLoggable()
&& request instanceof HttpUriRequest) {
- configuration.println(toCurl((HttpUriRequest) request));
+ configuration.println(toCurl((HttpUriRequest) request,
+ configuration.isAuthLoggable()));
}
}
}
@@ -400,12 +451,17 @@ public final class AndroidHttpClient implements HttpClient {
/**
* Generates a cURL command equivalent to the given request.
*/
- private static String toCurl(HttpUriRequest request) throws IOException {
+ private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
StringBuilder builder = new StringBuilder();
builder.append("curl ");
for (Header header: request.getAllHeaders()) {
+ if (!logAuthToken
+ && (header.getName().equals("Authorization") ||
+ header.getName().equals("Cookie"))) {
+ continue;
+ }
builder.append("--header \"");
builder.append(header.toString().trim());
builder.append("\" ");
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index b7f7368..0edbe5b 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -16,8 +16,6 @@
package android.net.http;
-import android.os.SystemClock;
-
import java.io.IOException;
import java.security.cert.Certificate;
@@ -28,23 +26,13 @@ import java.security.cert.X509Certificate;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.Enumeration;
-
-import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
-import org.apache.http.HttpHost;
-
-import org.bouncycastle.asn1.x509.X509Name;
-
/**
* Class responsible for all server certificate validation functionality
*
@@ -52,9 +40,6 @@ import org.bouncycastle.asn1.x509.X509Name;
*/
class CertificateChainValidator {
- private static long sTotal = 0;
- private static long sTotalReused = 0;
-
/**
* The singleton instance of the certificate chain validator
*/
@@ -110,91 +95,42 @@ class CertificateChainValidator {
* @return An SSL error object if there is an error and null otherwise
*/
public SslError doHandshakeAndValidateServerCertificates(
- HttpsConnection connection, SSLSocket sslSocket, String domain)
- throws SSLHandshakeException, IOException {
-
- ++sTotal;
-
- SSLContext sslContext = HttpsConnection.getContext();
- if (sslContext == null) {
- closeSocketThrowException(sslSocket, "SSL context is null");
- }
-
+ HttpsConnection connection, SSLSocket sslSocket, String domain)
+ throws IOException {
X509Certificate[] serverCertificates = null;
- long sessionBeforeHandshakeLastAccessedTime = 0;
- byte[] sessionBeforeHandshakeId = null;
-
- SSLSession sessionAfterHandshake = null;
-
- synchronized(sslContext) {
- // get SSL session before the handshake
- SSLSession sessionBeforeHandshake =
- getSSLSession(sslContext, connection.getHost());
- if (sessionBeforeHandshake != null) {
- sessionBeforeHandshakeLastAccessedTime =
- sessionBeforeHandshake.getLastAccessedTime();
+ // start handshake, close the socket if we fail
+ try {
+ sslSocket.setUseClientMode(true);
+ sslSocket.startHandshake();
+ } catch (IOException e) {
+ closeSocketThrowException(
+ sslSocket, e.getMessage(),
+ "failed to perform SSL handshake");
+ }
- sessionBeforeHandshakeId =
- sessionBeforeHandshake.getId();
- }
+ // retrieve the chain of the server peer certificates
+ Certificate[] peerCertificates =
+ sslSocket.getSession().getPeerCertificates();
- // start handshake, close the socket if we fail
- try {
- sslSocket.setUseClientMode(true);
- sslSocket.startHandshake();
- } catch (IOException e) {
- closeSocketThrowException(
- sslSocket, e.getMessage(),
- "failed to perform SSL handshake");
+ if (peerCertificates == null || peerCertificates.length <= 0) {
+ closeSocketThrowException(
+ sslSocket, "failed to retrieve peer certificates");
+ } else {
+ serverCertificates =
+ new X509Certificate[peerCertificates.length];
+ for (int i = 0; i < peerCertificates.length; ++i) {
+ serverCertificates[i] =
+ (X509Certificate)(peerCertificates[i]);
}
- // retrieve the chain of the server peer certificates
- Certificate[] peerCertificates =
- sslSocket.getSession().getPeerCertificates();
-
- if (peerCertificates == null || peerCertificates.length <= 0) {
- closeSocketThrowException(
- sslSocket, "failed to retrieve peer certificates");
- } else {
- serverCertificates =
- new X509Certificate[peerCertificates.length];
- for (int i = 0; i < peerCertificates.length; ++i) {
- serverCertificates[i] =
- (X509Certificate)(peerCertificates[i]);
- }
-
- // update the SSL certificate associated with the connection
- if (connection != null) {
- if (serverCertificates[0] != null) {
- connection.setCertificate(
- new SslCertificate(serverCertificates[0]));
- }
+ // update the SSL certificate associated with the connection
+ if (connection != null) {
+ if (serverCertificates[0] != null) {
+ connection.setCertificate(
+ new SslCertificate(serverCertificates[0]));
}
}
-
- // get SSL session after the handshake
- sessionAfterHandshake =
- getSSLSession(sslContext, connection.getHost());
- }
-
- if (sessionBeforeHandshakeLastAccessedTime != 0 &&
- sessionAfterHandshake != null &&
- Arrays.equals(
- sessionBeforeHandshakeId, sessionAfterHandshake.getId()) &&
- sessionBeforeHandshakeLastAccessedTime <
- sessionAfterHandshake.getLastAccessedTime()) {
-
- if (HttpLog.LOGV) {
- HttpLog.v("SSL session was reused: total reused: "
- + sTotalReused
- + " out of total of: " + sTotal);
-
- ++sTotalReused;
- }
-
- // no errors!!!
- return null;
}
// check if the first certificate in the chain is for this site
@@ -216,7 +152,6 @@ class CertificateChainValidator {
}
}
- //
// first, we validate the chain using the standard validation
// solution; if we do not find any errors, we are done; if we
// fail the standard validation, we re-validate again below,
@@ -393,14 +328,14 @@ class CertificateChainValidator {
}
private void closeSocketThrowException(
- SSLSocket socket, String errorMessage, String defaultErrorMessage)
- throws SSLHandshakeException, IOException {
+ SSLSocket socket, String errorMessage, String defaultErrorMessage)
+ throws IOException {
closeSocketThrowException(
socket, errorMessage != null ? errorMessage : defaultErrorMessage);
}
- private void closeSocketThrowException(SSLSocket socket, String errorMessage)
- throws SSLHandshakeException, IOException {
+ private void closeSocketThrowException(SSLSocket socket,
+ String errorMessage) throws IOException {
if (HttpLog.LOGV) {
HttpLog.v("validation error: " + errorMessage);
}
@@ -416,29 +351,4 @@ class CertificateChainValidator {
throw new SSLHandshakeException(errorMessage);
}
-
- /**
- * @param sslContext The SSL context shared accross all the SSL sessions
- * @param host The host associated with the session
- * @return A suitable SSL session from the SSL context
- */
- private SSLSession getSSLSession(SSLContext sslContext, HttpHost host) {
- if (sslContext != null && host != null) {
- Enumeration en = sslContext.getClientSessionContext().getIds();
- while (en.hasMoreElements()) {
- byte[] id = (byte[]) en.nextElement();
- if (id != null) {
- SSLSession session =
- sslContext.getClientSessionContext().getSession(id);
- if (session.isValid() &&
- host.getHostName().equals(session.getPeerHost()) &&
- host.getPort() == session.getPeerPort()) {
- return session;
- }
- }
- }
- }
-
- return null;
- }
}
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
index 65e6117..c4ee5b0 100644
--- a/core/java/android/net/http/RequestHandle.java
+++ b/core/java/android/net/http/RequestHandle.java
@@ -55,7 +55,7 @@ public class RequestHandle {
private final static String AUTHORIZATION_HEADER = "Authorization";
private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
- private final static int MAX_REDIRECT_COUNT = 16;
+ public final static int MAX_REDIRECT_COUNT = 16;
/**
* Creates a new request session.
@@ -106,6 +106,14 @@ public class RequestHandle {
return mRedirectCount >= MAX_REDIRECT_COUNT;
}
+ public int getRedirectCount() {
+ return mRedirectCount;
+ }
+
+ public void setRedirectCount(int count) {
+ mRedirectCount = count;
+ }
+
/**
* Create and queue a redirect request.
*
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index ed7c366..7590bfe 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1,33 +1,44 @@
package android.os;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Formatter;
import java.util.Map;
+import android.util.Log;
+import android.util.Printer;
import android.util.SparseArray;
/**
* A class providing access to battery usage statistics, including information on
* wakelocks, processes, packages, and services. All times are represented in microseconds
* except where indicated otherwise.
+ * @hide
*/
-public abstract class BatteryStats {
+public abstract class BatteryStats implements Parcelable {
+ private static final boolean LOCAL_LOGV = false;
+
/**
- * A constant indicating a partial wake lock.
+ * A constant indicating a partial wake lock timer.
*/
public static final int WAKE_TYPE_PARTIAL = 0;
/**
- * A constant indicating a full wake lock.
+ * A constant indicating a full wake lock timer.
*/
public static final int WAKE_TYPE_FULL = 1;
/**
- * A constant indicating a window wake lock.
+ * A constant indicating a window wake lock timer.
*/
public static final int WAKE_TYPE_WINDOW = 2;
+
+ /**
+ * A constant indicating a sensor timer.
+ *
+ * {@hide}
+ */
+ public static final int SENSOR = 3;
/**
* Include all of the data in the stats, including previously saved data.
@@ -48,6 +59,22 @@ public abstract class BatteryStats {
* Include only the run since the last time the device was unplugged in the stats.
*/
public static final int STATS_UNPLUGGED = 3;
+
+ /**
+ * Bump the version on this if the checkin format changes.
+ */
+ private static final int BATTERY_STATS_CHECKIN_VERSION = 1;
+
+ // TODO: Update this list if you add/change any stats above.
+ private static final String[] STAT_NAMES = { "total", "last", "current", "unplugged" };
+
+ private static final String APK_DATA = "apk";
+ private static final String PROCESS_DATA = "process";
+ private static final String SENSOR_DATA = "sensor";
+ private static final String WAKELOCK_DATA = "wakelock";
+ private static final String NETWORK_DATA = "network";
+ private static final String BATTERY_DATA = "battery";
+ private static final String MISC_DATA = "misc";
private final StringBuilder mFormatBuilder = new StringBuilder(8);
private final Formatter mFormatter = new Formatter(mFormatBuilder);
@@ -69,11 +96,16 @@ public abstract class BatteryStats {
* Returns the total time in microseconds associated with this Timer for the
* selected type of statistics.
*
- * @param now system uptime time in microseconds
+ * @param batteryRealtime system realtime on battery in microseconds
* @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
* @return a time in microseconds
*/
- public abstract long getTotalTime(long now, int which);
+ public abstract long getTotalTime(long batteryRealtime, int which);
+
+ /**
+ * Temporary for debugging.
+ */
+ public abstract void logState();
}
/**
@@ -115,8 +147,28 @@ public abstract class BatteryStats {
* @return a Map from Strings to Uid.Pkg objects.
*/
public abstract Map<String, ? extends Pkg> getPackageStats();
+
+ /**
+ * {@hide}
+ */
+ public abstract int getUid();
+
+ /**
+ * {@hide}
+ */
+ public abstract long getTcpBytesReceived(int which);
+
+ /**
+ * {@hide}
+ */
+ public abstract long getTcpBytesSent(int which);
public static abstract class Sensor {
+ // Magic sensor number for the GPS.
+ public static final int GPS = -10000;
+
+ public abstract int getHandle();
+
public abstract Timer getSensorTime();
}
@@ -173,11 +225,11 @@ public abstract class BatteryStats {
/**
* Returns the amount of time spent started.
*
- * @param now elapsed realtime in microseconds.
+ * @param batteryUptime elapsed uptime on battery in microseconds.
* @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
* @return
*/
- public abstract long getStartTime(long now, int which);
+ public abstract long getStartTime(long batteryUptime, int which);
/**
* Returns the total number of times startService() has been called.
@@ -200,7 +252,28 @@ public abstract class BatteryStats {
* Returns the number of times the device has been started.
*/
public abstract int getStartCount();
-
+
+ /**
+ * Returns the time in milliseconds that the screen has been on while the device was
+ * running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getScreenOnTime(long batteryRealtime, int which);
+
+ /**
+ * Returns the time in milliseconds that the phone has been on while the device was
+ * running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getPhoneOnTime(long batteryRealtime, int which);
+
+ /**
+ * Return whether we are currently running on battery.
+ */
+ public abstract boolean getIsOnBattery();
+
/**
* Returns a SparseArray containing the statistics for each uid.
*/
@@ -312,17 +385,20 @@ public abstract class BatteryStats {
*
* @param sb a StringBuilder object.
* @param timer a Timer object contining the wakelock times.
- * @param now the current time in microseconds.
+ * @param batteryRealtime the current on-battery time in microseconds.
* @param name the name of the wakelock.
* @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
* @param linePrefix a String to be prepended to each line of output.
* @return the line prefix
*/
- private final String printWakeLock(StringBuilder sb, Timer timer, long now,
- String name, int which, String linePrefix) {
+ private static final String printWakeLock(StringBuilder sb, Timer timer,
+ long batteryRealtime, String name, int which, String linePrefix) {
+
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTimeMillis = (timer.getTotalTime(now, which) + 500) / 1000;
+ long totalTimeMicros = timer.getTotalTime(batteryRealtime, which);
+ long totalTimeMillis = (totalTimeMicros + 500) / 1000;
+
int count = timer.getCount(which);
if (totalTimeMillis != 0) {
sb.append(linePrefix);
@@ -337,40 +413,225 @@ public abstract class BatteryStats {
}
return linePrefix;
}
+
+ /**
+ * Checkin version of wakelock printer. Prints simple comma-separated list.
+ *
+ * @param sb a StringBuilder object.
+ * @param timer a Timer object contining the wakelock times.
+ * @param now the current time in microseconds.
+ * @param name the name of the wakelock.
+ * @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @param linePrefix a String to be prepended to each line of output.
+ * @return the line prefix
+ */
+ private static final String printWakeLockCheckin(StringBuilder sb, Timer timer, long now,
+ String name, int which, String linePrefix) {
+ long totalTimeMicros = 0;
+ int count = 0;
+ if (timer != null) {
+ totalTimeMicros = timer.getTotalTime(now, which);
+ count = timer.getCount(which);
+ }
+ sb.append(linePrefix);
+ sb.append((totalTimeMicros + 500) / 1000); // microseconds to milliseconds with rounding
+ sb.append(',');
+ sb.append(name);
+ sb.append(',');
+ sb.append(count);
+ return ",";
+ }
+
+ /**
+ * Dump a comma-separated line of values for terse checkin mode.
+ *
+ * @param pw the PageWriter to dump log to
+ * @param category category of data (e.g. "total", "last", "unplugged", "current" )
+ * @param type type of data (e.g. "wakelock", "sensor", "process", "apk" , "process", "network")
+ * @param args type-dependent data arguments
+ */
+ private static final void dumpLine(PrintWriter pw, int uid, String category, String type,
+ Object... args ) {
+ pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+ pw.print(uid); pw.print(',');
+ pw.print(category); pw.print(',');
+ pw.print(type);
+
+ for (Object arg : args) {
+ pw.print(',');
+ pw.print(arg);
+ }
+ pw.print('\n');
+ }
+
+ /**
+ * Checkin server version of dump to produce more compact, computer-readable log.
+ *
+ * NOTE: all times are expressed in 'ms'.
+ * @param fd
+ * @param pw
+ * @param which
+ */
+ private final void dumpCheckinLocked(PrintWriter pw, int which) {
+ final long rawUptime = SystemClock.uptimeMillis() * 1000;
+ final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+ final long batteryUptime = getBatteryUptime(rawUptime);
+ final long batteryRealtime = getBatteryRealtime(rawRealtime);
+ final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
+ final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
+ final long totalRealtime = computeRealtime(rawRealtime, which);
+ final long totalUptime = computeUptime(rawUptime, which);
+ final long screenOnTime = getScreenOnTime(batteryRealtime, which);
+ final long phoneOnTime = getPhoneOnTime(batteryRealtime, which);
+
+ StringBuilder sb = new StringBuilder(128);
+
+ String category = STAT_NAMES[which];
+
+ // Dump "battery" stat
+ dumpLine(pw, 0 /* uid */, category, BATTERY_DATA,
+ which == STATS_TOTAL ? getStartCount() : "N/A",
+ whichBatteryUptime / 1000, whichBatteryRealtime / 1000,
+ totalUptime / 1000, totalRealtime / 1000);
+
+ // Dump misc stats
+ dumpLine(pw, 0 /* uid */, category, MISC_DATA,
+ screenOnTime / 1000, phoneOnTime / 1000);
+
+ SparseArray<? extends Uid> uidStats = getUidStats();
+ final int NU = uidStats.size();
+ for (int iu = 0; iu < NU; iu++) {
+ final int uid = uidStats.keyAt(iu);
+ Uid u = uidStats.valueAt(iu);
+ // Dump Network stats per uid, if any
+ long rx = u.getTcpBytesReceived(which);
+ long tx = u.getTcpBytesSent(which);
+ if (rx > 0 || tx > 0) dumpLine(pw, uid, category, NETWORK_DATA, rx, tx);
- @SuppressWarnings("unused")
- private final void dumpLocked(FileDescriptor fd, PrintWriter pw, String prefix, int which) {
- long uSecTime = SystemClock.elapsedRealtime() * 1000;
- final long uSecNow = getBatteryUptime(uSecTime);
+ Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
+ if (wakelocks.size() > 0) {
+ for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent
+ : wakelocks.entrySet()) {
+ Uid.Wakelock wl = ent.getValue();
+ String linePrefix = "";
+ sb.setLength(0);
+ linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_FULL), batteryRealtime,
+ "full", which, linePrefix);
+ linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), batteryRealtime,
+ "partial", which, linePrefix);
+ linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), batteryRealtime,
+ "window", which, linePrefix);
+
+ // Only log if we had at lease one wakelock...
+ if (sb.length() > 0) {
+ dumpLine(pw, uid, category, WAKELOCK_DATA, ent.getKey(), sb.toString());
+ }
+ }
+ }
+
+ Map<Integer, ? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+ if (sensors.size() > 0) {
+ for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> ent
+ : sensors.entrySet()) {
+ Uid.Sensor se = ent.getValue();
+ int sensorNumber = ent.getKey();
+ Timer timer = se.getSensorTime();
+ if (timer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (timer.getTotalTime(batteryRealtime, which) + 500) / 1000;
+ int count = timer.getCount(which);
+ if (totalTime != 0) {
+ dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime, count);
+ }
+ }
+ }
+ }
- StringBuilder sb = new StringBuilder(128);
- if (which == STATS_TOTAL) {
- pw.println(prefix + "Current and Historic Battery Usage Statistics:");
- pw.println(prefix + " System starts: " + getStartCount());
- } else if (which == STATS_LAST) {
- pw.println(prefix + "Last Battery Usage Statistics:");
- } else {
- pw.println(prefix + "Current Battery Usage Statistics:");
+ Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
+ if (processStats.size() > 0) {
+ for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
+ : processStats.entrySet()) {
+ Uid.Proc ps = ent.getValue();
+
+ long userTime = ps.getUserTime(which);
+ long systemTime = ps.getSystemTime(which);
+ int starts = ps.getStarts(which);
+
+ if (userTime != 0 || systemTime != 0 || starts != 0) {
+ dumpLine(pw, uid, category, PROCESS_DATA,
+ ent.getKey(), // proc
+ userTime * 10, // cpu time in ms
+ systemTime * 10, // user time in ms
+ starts); // process starts
+ }
+ }
+ }
+
+ Map<String, ? extends BatteryStats.Uid.Pkg> packageStats = u.getPackageStats();
+ if (packageStats.size() > 0) {
+ for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg> ent
+ : packageStats.entrySet()) {
+
+ Uid.Pkg ps = ent.getValue();
+ int wakeups = ps.getWakeups(which);
+ Map<String, ? extends Uid.Pkg.Serv> serviceStats = ps.getServiceStats();
+ for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg.Serv> sent
+ : serviceStats.entrySet()) {
+ BatteryStats.Uid.Pkg.Serv ss = sent.getValue();
+ long startTime = ss.getStartTime(batteryUptime, which);
+ int starts = ss.getStarts(which);
+ int launches = ss.getLaunches(which);
+ if (startTime != 0 || starts != 0 || launches != 0) {
+ dumpLine(pw, uid, category, APK_DATA,
+ wakeups, // wakeup alarms
+ ent.getKey(), // Apk
+ sent.getKey(), // service
+ startTime / 1000, // time spent started, in ms
+ starts,
+ launches);
+ }
+ }
+ }
+ }
}
- long batteryUptime = computeBatteryUptime(uSecNow, which);
- long batteryRealtime = computeBatteryRealtime(getBatteryRealtime(uSecTime), which);
- long elapsedRealtime = computeRealtime(uSecTime, which);
- long uptime = computeUptime(SystemClock.uptimeMillis() * 1000, which);
+ }
+
+ @SuppressWarnings("unused")
+ private final void dumpLocked(Printer pw, String prefix, int which) {
+ final long rawUptime = SystemClock.uptimeMillis() * 1000;
+ final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+ final long batteryUptime = getBatteryUptime(rawUptime);
+ final long batteryRealtime = getBatteryUptime(rawRealtime);
+
+ final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
+ final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
+ final long totalRealtime = computeRealtime(rawRealtime, which);
+ final long totalUptime = computeUptime(rawUptime, which);
+
+ StringBuilder sb = new StringBuilder(128);
pw.println(prefix
- + " On battery: " + formatTimeMs(batteryUptime / 1000) + "("
- + formatRatioLocked(batteryUptime, batteryRealtime)
+ + " Time on battery: " + formatTimeMs(whichBatteryUptime / 1000)
+ + "(" + formatRatioLocked(whichBatteryUptime, totalRealtime)
+ ") uptime, "
- + formatTimeMs(batteryRealtime / 1000) + "("
- + formatRatioLocked(batteryRealtime, elapsedRealtime)
+ + formatTimeMs(whichBatteryRealtime / 1000) + "("
+ + formatRatioLocked(whichBatteryRealtime, totalRealtime)
+ ") realtime");
pw.println(prefix
+ " Total: "
- + formatTimeMs(uptime / 1000)
+ + formatTimeMs(totalUptime / 1000)
+ "uptime, "
- + formatTimeMs(elapsedRealtime / 1000)
+ + formatTimeMs(totalRealtime / 1000)
+ "realtime");
-
+
+ long screenOnTime = getScreenOnTime(batteryRealtime, which);
+ long phoneOnTime = getPhoneOnTime(batteryRealtime, which);
+ pw.println(prefix
+ + " Time with screen on: " + formatTimeMs(screenOnTime / 1000)
+ + "(" + formatRatioLocked(screenOnTime, whichBatteryRealtime)
+ + "), time with phone on: " + formatTimeMs(phoneOnTime / 1000)
+ + "(" + formatRatioLocked(phoneOnTime, whichBatteryRealtime) + ")");
+
pw.println(" ");
SparseArray<? extends Uid> uidStats = getUidStats();
@@ -380,6 +641,13 @@ public abstract class BatteryStats {
Uid u = uidStats.valueAt(iu);
pw.println(prefix + " #" + uid + ":");
boolean uidActivity = false;
+
+ long tcpReceived = u.getTcpBytesReceived(which);
+ long tcpSent = u.getTcpBytesSent(which);
+ if (tcpReceived != 0 || tcpSent != 0) {
+ pw.println(prefix + " Network: " + tcpReceived + " bytes received, "
+ + tcpSent + " bytes sent");
+ }
Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
if (wakelocks.size() > 0) {
@@ -391,13 +659,15 @@ public abstract class BatteryStats {
sb.append(prefix);
sb.append(" Wake lock ");
sb.append(ent.getKey());
- linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), uSecNow,
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), batteryRealtime,
"full", which, linePrefix);
- linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), uSecNow,
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), batteryRealtime,
"partial", which, linePrefix);
- linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), uSecNow,
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), batteryRealtime,
"window", which, linePrefix);
- if (linePrefix.equals(": ")) {
+ if (!linePrefix.equals(": ")) {
+ sb.append(" realtime");
+ } else {
sb.append(": (nothing executed)");
}
pw.println(sb.toString());
@@ -414,23 +684,30 @@ public abstract class BatteryStats {
sb.setLength(0);
sb.append(prefix);
sb.append(" Sensor ");
- sb.append(sensorNumber);
+ int handle = se.getHandle();
+ if (handle == Uid.Sensor.GPS) {
+ sb.append("GPS");
+ } else {
+ sb.append(handle);
+ }
+ sb.append(": ");
Timer timer = se.getSensorTime();
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTime = (timer.getTotalTime(uSecNow, which) + 500) / 1000;
+ long totalTime = (timer.getTotalTime(batteryRealtime, which) + 500) / 1000;
int count = timer.getCount(which);
+ //timer.logState();
if (totalTime != 0) {
- sb.append(": ");
sb.append(formatTimeMs(totalTime));
- sb.append(' ');
- sb.append('(');
+ sb.append("realtime (");
sb.append(count);
sb.append(" times)");
+ } else {
+ sb.append("(not used)");
}
} else {
- sb.append(": (none used)");
+ sb.append("(not used)");
}
pw.println(sb.toString());
@@ -478,13 +755,14 @@ public abstract class BatteryStats {
for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg.Serv> sent
: serviceStats.entrySet()) {
BatteryStats.Uid.Pkg.Serv ss = sent.getValue();
- long startTime = ss.getStartTime(uSecNow, which);
+ long startTime = ss.getStartTime(batteryUptime, which);
int starts = ss.getStarts(which);
int launches = ss.getLaunches(which);
if (startTime != 0 || starts != 0 || launches != 0) {
pw.println(prefix + " Service " + sent.getKey() + ":");
- pw.println(prefix + " Time spent started: "
- + formatTimeMs(startTime / 1000));
+ pw.println(prefix + " Created for: "
+ + formatTimeMs(startTime / 1000)
+ + " uptime");
pw.println(prefix + " Starts: " + starts
+ ", launches: " + launches);
apkActivity = true;
@@ -506,18 +784,45 @@ public abstract class BatteryStats {
/**
* Dumps a human-readable summary of the battery statistics to the given PrintWriter.
*
- * @param fd a FileDescriptor, currently unused.
- * @param pw a PrintWriter to receive the dump output.
- * @param args an array of Strings, currently unused.
+ * @param pw a Printer to receive the dump output.
*/
@SuppressWarnings("unused")
- public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
- synchronized (this) {
- dumpLocked(fd, pw, "", STATS_TOTAL);
- pw.println("");
- dumpLocked(fd, pw, "", STATS_LAST);
- pw.println("");
- dumpLocked(fd, pw, "", STATS_CURRENT);
+ public void dumpLocked(Printer pw) {
+ pw.println("Total Statistics (Current and Historic):");
+ pw.println(" System starts: " + getStartCount()
+ + ", currently on battery: " + getIsOnBattery());
+ dumpLocked(pw, "", STATS_TOTAL);
+ pw.println("");
+ pw.println("Last Run Statistics (Previous run of system):");
+ dumpLocked(pw, "", STATS_LAST);
+ pw.println("");
+ pw.println("Current Battery Statistics (Currently running system):");
+ dumpLocked(pw, "", STATS_CURRENT);
+ pw.println("");
+ pw.println("Unplugged Statistics (Since last unplugged from power):");
+ dumpLocked(pw, "", STATS_UNPLUGGED);
+ }
+
+ @SuppressWarnings("unused")
+ public void dumpCheckinLocked(PrintWriter pw, String[] args) {
+ boolean isUnpluggedOnly = false;
+
+ for (String arg : args) {
+ if ("-u".equals(arg)) {
+ if (LOCAL_LOGV) Log.v("BatteryStats", "Dumping unplugged data");
+ isUnpluggedOnly = true;
+ }
+ }
+
+ if (isUnpluggedOnly) {
+ dumpCheckinLocked(pw, STATS_UNPLUGGED);
+ }
+ else {
+ dumpCheckinLocked(pw, STATS_TOTAL);
+ dumpCheckinLocked(pw, STATS_LAST);
+ dumpCheckinLocked(pw, STATS_UNPLUGGED);
+ dumpCheckinLocked(pw, STATS_CURRENT);
}
}
+
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 528e6bd..df10c6a 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -33,7 +33,7 @@ import java.lang.reflect.Modifier;
* the standard support creating a local implementation of such an object.
*
* <p>Most developers will not implement this class directly, instead using the
- * <a href="{@docRoot}reference/aidl.html">aidl</a> tool to describe the desired
+ * <a href="{@docRoot}guide/developing/tools/aidl.html">aidl</a> tool to describe the desired
* interface, having it generate the appropriate Binder subclass. You can,
* however, derive directly from Binder to implement your own custom RPC
* protocol or simply instantiate a raw Binder object directly to use as a
@@ -194,18 +194,15 @@ public class Binder implements IBinder {
return true;
} else if (code == DUMP_TRANSACTION) {
ParcelFileDescriptor fd = data.readFileDescriptor();
- FileOutputStream fout = fd != null
- ? new FileOutputStream(fd.getFileDescriptor()) : null;
- PrintWriter pw = fout != null ? new PrintWriter(fout) : null;
- if (pw != null) {
- String[] args = data.readStringArray();
- dump(fd.getFileDescriptor(), pw, args);
- pw.flush();
- }
+ String[] args = data.readStringArray();
if (fd != null) {
try {
- fd.close();
- } catch (IOException e) {
+ dump(fd.getFileDescriptor(), args);
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
}
}
return true;
@@ -214,6 +211,20 @@ public class Binder implements IBinder {
}
/**
+ * Implemented to call the more convenient version
+ * {@link #dump(FileDescriptor, PrintWriter, String[])}.
+ */
+ public void dump(FileDescriptor fd, String[] args) {
+ FileOutputStream fout = new FileOutputStream(fd);
+ PrintWriter pw = new PrintWriter(fout);
+ try {
+ dump(fd, pw, args);
+ } finally {
+ pw.flush();
+ }
+ }
+
+ /**
* Print the object's state into the given stream.
*
* @param fd The raw file descriptor that the dump is being sent to.
@@ -302,6 +313,17 @@ final class BinderProxy implements IBinder {
throws RemoteException;
public native boolean unlinkToDeath(DeathRecipient recipient, int flags);
+ public void dump(FileDescriptor fd, String[] args) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeFileDescriptor(fd);
+ data.writeStringArray(args);
+ try {
+ transact(DUMP_TRANSACTION, data, null, 0);
+ } finally {
+ data.recycle();
+ }
+ }
+
BinderProxy() {
mSelf = new WeakReference(this);
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index cdf907b..467c17f 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -26,6 +26,9 @@ public class Build {
/** Either a changelist number, or a label like "M4-rc20". */
public static final String ID = getString("ro.build.id");
+ /** A build ID string meant for displaying to the user */
+ public static final String DISPLAY = getString("ro.build.display.id");
+
/** The name of the overall product. */
public static final String PRODUCT = getString("ro.product.name");
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 5f7f91f..950bb09 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -17,6 +17,7 @@
package android.os;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
@@ -28,12 +29,13 @@ import dalvik.bytecode.Opcodes;
import dalvik.system.VMDebug;
-/** Provides various debugging functions for Android applications, including
+/**
+ * Provides various debugging functions for Android applications, including
* tracing and allocation counts.
* <p><strong>Logging Trace Files</strong></p>
* <p>Debug can create log files that give details about an application, such as
* a call stack and start/stop times for any running methods. See <a
-href="{@docRoot}reference/traceview.html">Running the Traceview Debugging Program</a> for
+href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
* information about reading trace files. To start logging trace files, call one
* of the startMethodTracing() methods. To stop tracing, call
* {@link #stopMethodTracing()}.
@@ -285,7 +287,7 @@ public final class Debug
/**
* Start method tracing with default log name and buffer size. See <a
-href="{@docRoot}reference/traceview.html">Running the Traceview Debugging Program</a> for
+href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
* information about reading these files. Call stopMethodTracing() to stop
* tracing.
*/
@@ -297,7 +299,7 @@ href="{@docRoot}reference/traceview.html">Running the Traceview Debugging Progra
* Start method tracing, specifying the trace log file name. The trace
* file will be put under "/sdcard" unless an absolute path is given.
* See <a
- href="{@docRoot}reference/traceview.html">Running the Traceview Debugging Program</a> for
+ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
* information about reading trace files.
*
* @param traceName Name for the trace log file to create.
@@ -313,7 +315,7 @@ href="{@docRoot}reference/traceview.html">Running the Traceview Debugging Progra
* Start method tracing, specifying the trace log file name and the
* buffer size. The trace files will be put under "/sdcard" unless an
* absolute path is given. See <a
- href="{@docRoot}reference/traceview.html">Running the Traceview Debugging Program</a> for
+ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
* information about reading trace files.
* @param traceName Name for the trace log file to create.
* If no name argument is given, this value defaults to "/sdcard/dmtrace.trace".
@@ -330,7 +332,7 @@ href="{@docRoot}reference/traceview.html">Running the Traceview Debugging Progra
* Start method tracing, specifying the trace log file name and the
* buffer size. The trace files will be put under "/sdcard" unless an
* absolute path is given. See <a
- href="{@docRoot}reference/traceview.html">Running the Traceview Debugging Program</a> for
+ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Log Viewer</a> for
* information about reading trace files.
*
* <p>
@@ -581,6 +583,18 @@ href="{@docRoot}reference/traceview.html">Running the Traceview Debugging Progra
}
/**
+ * Dump "hprof" data to the specified file. This will cause a GC.
+ *
+ * @param fileName Full pathname of output file (e.g. "/sdcard/dump.hprof").
+ * @throws UnsupportedOperationException if the VM was built without
+ * HPROF support.
+ * @throws IOException if an error occurs while opening or writing files.
+ */
+ public static void dumpHprofData(String fileName) throws IOException {
+ VMDebug.dumpHprofData(fileName);
+ }
+
+ /**
* Returns the number of sent transactions from this process.
* @return The number of sent transactions or -1 if it could not read t.
*/
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index e37b551..f761e8e 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -75,6 +75,18 @@ public class Environment {
public static final String MEDIA_UNMOUNTED = "unmounted";
/**
+ * getExternalStorageState() returns MEDIA_CHECKING if the media is present
+ * and being disk-checked
+ */
+ public static final String MEDIA_CHECKING = "checking";
+
+ /**
+ * getExternalStorageState() returns MEDIA_NOFS if the media is present
+ * but is blank or is using an unsupported filesystem
+ */
+ public static final String MEDIA_NOFS = "nofs";
+
+ /**
* getExternalStorageState() returns MEDIA_MOUNTED if the media is present
* and mounted at its mount point with read/write access.
*/
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 3ec0e9b..5c40c9a0 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -16,6 +16,9 @@
package android.os;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
/**
* Base interface for a remotable object, the core part of a lightweight
* remote procedure call mechanism designed for high performance when
@@ -145,6 +148,14 @@ public interface IBinder {
public IInterface queryLocalInterface(String descriptor);
/**
+ * Print the object's state into the given stream.
+ *
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param args additional arguments to the dump request.
+ */
+ public void dump(FileDescriptor fd, String[] args) throws RemoteException;
+
+ /**
* Perform a generic operation with the object.
*
* @param code The action to perform. This should
diff --git a/core/java/android/os/ICheckinService.aidl b/core/java/android/os/ICheckinService.aidl
index 70ad28e..e56b55d 100644
--- a/core/java/android/os/ICheckinService.aidl
+++ b/core/java/android/os/ICheckinService.aidl
@@ -26,6 +26,15 @@ import android.os.IParentalControlCallback;
* {@hide}
*/
interface ICheckinService {
+ /** Synchronously attempt a checkin with the server, return true
+ * on success.
+ * @throws IllegalStateException whenever an error occurs. The
+ * cause of the exception will be the real exception:
+ * IOException for network errors, JSONException for invalid
+ * server responses, etc.
+ */
+ boolean checkin();
+
/** Direct submission of crash data; returns after writing the crash. */
void reportCrashSync(in byte[] crashData);
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index 0397446..96d44b6 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -48,4 +48,31 @@ interface IMountService
* Safely unmount external storage at given mount point.
*/
void unmountMedia(String mountPoint);
+
+ /**
+ * Format external storage given a mount point.
+ */
+ void formatMedia(String mountPoint);
+
+ /**
+ * Returns true if media notification sounds are enabled.
+ */
+ boolean getPlayNotificationSounds();
+
+ /**
+ * Sets whether or not media notification sounds are played.
+ */
+ void setPlayNotificationSounds(boolean value);
+
+ /**
+ * Returns true if USB Mass Storage is automatically started
+ * when a UMS host is detected.
+ */
+ boolean getAutoStartUms();
+
+ /**
+ * Sets whether or not USB Mass Storage is automatically started
+ * when a UMS host is detected.
+ */
+ void setAutoStartUms(boolean value);
}
diff --git a/core/java/android/os/INetStatService.aidl b/core/java/android/os/INetStatService.aidl
index fb840d8..a8f3de0 100644
--- a/core/java/android/os/INetStatService.aidl
+++ b/core/java/android/os/INetStatService.aidl
@@ -17,14 +17,19 @@
package android.os;
/**
- * Retrieves packet and byte counts for the phone data interface.
+ * Retrieves packet and byte counts for the phone data interface,
+ * and for all interfaces.
* Used for the data activity icon and the phone status in Settings.
*
* {@hide}
*/
interface INetStatService {
- int getTxPackets();
- int getRxPackets();
- int getTxBytes();
- int getRxBytes();
+ long getMobileTxPackets();
+ long getMobileRxPackets();
+ long getMobileTxBytes();
+ long getMobileRxBytes();
+ long getTotalTxPackets();
+ long getTotalRxPackets();
+ long getTotalTxBytes();
+ long getTotalRxBytes();
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index e48f152..5486920 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -29,4 +29,5 @@ interface IPowerManager
void setStayOnSetting(int val);
long getScreenOnTime();
void preventScreenOn(boolean prevent);
+ void setScreenBrightnessOverride(int brightness);
}
diff --git a/core/java/android/os/NetStat.java b/core/java/android/os/NetStat.java
index 7312236..e294cdf 100644
--- a/core/java/android/os/NetStat.java
+++ b/core/java/android/os/NetStat.java
@@ -16,36 +16,232 @@
package android.os;
+import android.util.Log;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.io.IOException;
+
/** @hide */
-public class NetStat{
+public class NetStat {
+
+ // Logging tag.
+ private final static String TAG = "netstat";
+
+ // We pre-create all the File objects so we don't spend a lot of
+ // CPU at runtime converting from Java Strings to byte[] for the
+ // kernel calls.
+ private final static File[] MOBILE_TX_PACKETS = mobileFiles("tx_packets");
+ private final static File[] MOBILE_RX_PACKETS = mobileFiles("rx_packets");
+ private final static File[] MOBILE_TX_BYTES = mobileFiles("tx_bytes");
+ private final static File[] MOBILE_RX_BYTES = mobileFiles("rx_bytes");
+ private final static File SYS_CLASS_NET_DIR = new File("/sys/class/net");
/**
- * Get total number of tx packets sent through ppp0
+ * Get total number of tx packets sent through rmnet0 or ppp0
*
- * @return number of Tx packets through ppp0
+ * @return number of Tx packets through rmnet0 or ppp0
*/
+ public static long getMobileTxPkts() {
+ return getMobileStat(MOBILE_TX_PACKETS);
+ }
- public native static int netStatGetTxPkts();
+ /**
+ * Get total number of rx packets received through rmnet0 or ppp0
+ *
+ * @return number of Rx packets through rmnet0 or ppp0
+ */
+ public static long getMobileRxPkts() {
+ return getMobileStat(MOBILE_RX_PACKETS);
+ }
/**
- * Get total number of rx packets received through ppp0
+ * Get total number of tx bytes received through rmnet0 or ppp0
*
- * @return number of Rx packets through ppp0
+ * @return number of Tx bytes through rmnet0 or ppp0
*/
- public native static int netStatGetRxPkts();
+ public static long getMobileTxBytes() {
+ return getMobileStat(MOBILE_TX_BYTES);
+ }
- /**
- * Get total number of tx bytes received through ppp0
+ /**
+ * Get total number of rx bytes received through rmnet0 or ppp0
*
- * @return number of Tx bytes through ppp0
+ * @return number of Rx bytes through rmnet0 or ppp0
*/
- public native static int netStatGetTxBytes();
+ public static long getMobileRxBytes() {
+ return getMobileStat(MOBILE_RX_BYTES);
+ }
/**
- * Get total number of rx bytes received through ppp0
+ * Get the total number of packets sent through all network interfaces.
*
- * @return number of Rx bytes through ppp0
+ * @return the number of packets sent through all network interfaces
*/
- public native static int netStatGetRxBytes();
+ public static long getTotalTxPkts() {
+ return getTotalStat("tx_packets");
+ }
+
+ /**
+ * Get the total number of packets received through all network interfaces.
+ *
+ * @return the number of packets received through all network interfaces
+ */
+ public static long getTotalRxPkts() {
+ return getTotalStat("rx_packets");
+ }
+
+ /**
+ * Get the total number of bytes sent through all network interfaces.
+ *
+ * @return the number of bytes sent through all network interfaces
+ */
+ public static long getTotalTxBytes() {
+ return getTotalStat("tx_bytes");
+ }
+
+ /**
+ * Get the total number of bytes received through all network interfaces.
+ *
+ * @return the number of bytes received through all network interfaces
+ */
+ public static long getTotalRxBytes() {
+ return getTotalStat("rx_bytes");
+ }
+
+ /**
+ * Gets network bytes sent for this UID.
+ * The statistics are across all interfaces.
+ * The statistics come from /proc/uid_stat.
+ *
+ * {@see android.os.Process#myUid()}.
+ *
+ * @param uid
+ * @return byte count
+ */
+ public static long getUidTxBytes(int uid) {
+ return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_snd");
+ }
+
+ /**
+ * Gets network bytes received for this UID.
+ * The statistics are across all interfaces.
+ * The statistics come from /proc/uid_stat.
+ *
+ * {@see android.os.Process#myUid()}.
+ *
+ * @param uid
+ * @return byte count
+ */
+ public static long getUidRxBytes(int uid) {
+ return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_rcv");
+ }
+
+ /**
+ * Returns the array of two possible File locations for a given
+ * statistic.
+ */
+ private static File[] mobileFiles(String whatStat) {
+ // Note that we stat them at runtime to see which is
+ // available, rather than here, to guard against the files
+ // coming & going later as modules shut down (e.g. airplane
+ // mode) and whatnot. The runtime stat() isn't expensive compared
+ // to the previous charset conversion that happened before we
+ // were reusing File instances.
+ File[] files = new File[2];
+ files[0] = new File("/sys/class/net/rmnet0/statistics/" + whatStat);
+ files[1] = new File("/sys/class/net/ppp0/statistics/" + whatStat);
+ return files;
+ }
+
+ private static long getTotalStat(String whatStat) {
+ File netdir = new File("/sys/class/net");
+
+ File[] nets = SYS_CLASS_NET_DIR.listFiles();
+ if (nets == null) {
+ return 0;
+ }
+ long total = 0;
+ StringBuffer strbuf = new StringBuffer();
+ for (File net : nets) {
+ strbuf.append(net.getPath()).append(File.separator).append("statistics")
+ .append(File.separator).append(whatStat);
+ total += getNumberFromFilePath(strbuf.toString());
+ strbuf.setLength(0);
+ }
+ return total;
+ }
+
+ private static long getMobileStat(File[] files) {
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (!file.exists()) {
+ continue;
+ }
+ try {
+ RandomAccessFile raf = new RandomAccessFile(file, "r");
+ return getNumberFromFile(raf, file.getAbsolutePath());
+ } catch (IOException e) {
+ Log.w(TAG,
+ "Exception opening TCP statistics file " + file.getAbsolutePath(),
+ e);
+ }
+ }
+ return 0L;
+ }
+
+ // File will have format <number><newline>
+ private static long getNumberFromFilePath(String filename) {
+ RandomAccessFile raf = getFile(filename);
+ if (raf == null) {
+ return 0L;
+ }
+ return getNumberFromFile(raf, filename);
+ }
+
+ // Private buffer for getNumberFromFile. Safe for re-use because
+ // getNumberFromFile is synchronized.
+ private final static byte[] buf = new byte[16];
+
+ private static synchronized long getNumberFromFile(RandomAccessFile raf, String filename) {
+ try {
+ raf.read(buf);
+ raf.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Exception getting TCP bytes from " + filename, e);
+ return 0L;
+ } finally {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Exception closing " + filename, e);
+ }
+ }
+ }
+
+ long num = 0L;
+ for (int i = 0; i < buf.length; i++) {
+ if (buf[i] < '0' || buf[i] > '9') {
+ break;
+ }
+ num *= 10;
+ num += buf[i] - '0';
+ }
+ return num;
+ }
+
+ private static RandomAccessFile getFile(String filename) {
+ File f = new File(filename);
+ if (!f.canRead()) {
+ return null;
+ }
+ try {
+ return new RandomAccessFile(f, "r");
+ } catch (IOException e) {
+ Log.w(TAG, "Exception opening TCP statistics file " + filename, e);
+ return null;
+ }
+ }
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index ed138cb..3fcb18e 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -76,6 +76,11 @@ public class ParcelFileDescriptor implements Parcelable {
public static final int MODE_TRUNCATE = 0x04000000;
/**
+ * For use with {@link #open}: append to end of file while writing.
+ */
+ public static final int MODE_APPEND = 0x02000000;
+
+ /**
* Create a new ParcelFileDescriptor accessing a given file.
*
* @param file The file to be opened.
@@ -138,6 +143,19 @@ public class ParcelFileDescriptor implements Parcelable {
}
/**
+ * Return the total size of the file representing this fd, as determined
+ * by stat(). Returns -1 if the fd is not a file.
+ */
+ public native long getStatSize();
+
+ /**
+ * This is needed for implementing AssetFileDescriptor.AutoCloseOutputStream,
+ * and I really don't think we want it to be public.
+ * @hide
+ */
+ public native long seekTo(long pos);
+
+ /**
* Close the ParcelFileDescriptor. This implementation closes the underlying
* OS resources allocated to represent this stream.
*
diff --git a/core/java/android/os/ResultReceiver.aidl b/core/java/android/os/ResultReceiver.aidl
new file mode 100644
index 0000000..28ce6d5
--- /dev/null
+++ b/core/java/android/os/ResultReceiver.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/os/ParcelFileDescriptor.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+parcelable ResultReceiver;
diff --git a/core/java/android/os/ResultReceiver.java b/core/java/android/os/ResultReceiver.java
new file mode 100644
index 0000000..711d4d9
--- /dev/null
+++ b/core/java/android/os/ResultReceiver.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.android.internal.os.IResultReceiver;
+
+/**
+ * Generic interface for receiving a callback result from someone. Use this
+ * by creating a subclass and implement {@link #onReceiveResult}, which you can
+ * then pass to others and send through IPC, and receive results they
+ * supply with {@link #send}.
+ */
+public class ResultReceiver implements Parcelable {
+ final boolean mLocal;
+ final Handler mHandler;
+
+ IResultReceiver mReceiver;
+
+ class MyRunnable implements Runnable {
+ final int mResultCode;
+ final Bundle mResultData;
+
+ MyRunnable(int resultCode, Bundle resultData) {
+ mResultCode = resultCode;
+ mResultData = resultData;
+ }
+
+ public void run() {
+ onReceiveResult(mResultCode, mResultData);
+ }
+ }
+
+ class MyResultReceiver extends IResultReceiver.Stub {
+ public void send(int resultCode, Bundle resultData) {
+ if (mHandler != null) {
+ mHandler.post(new MyRunnable(resultCode, resultData));
+ } else {
+ onReceiveResult(resultCode, resultData);
+ }
+ }
+ }
+
+ /**
+ * Create a new ResultReceive to receive results. Your
+ * {@link #onReceiveResult} method will be called from the thread running
+ * <var>handler</var> if given, or from an arbitrary thread if null.
+ */
+ public ResultReceiver(Handler handler) {
+ mLocal = true;
+ mHandler = handler;
+ }
+
+ /**
+ * Deliver a result to this receiver. Will call {@link #onReceiveResult},
+ * always asynchronously if the receiver has supplied a Handler in which
+ * to dispatch the result.
+ * @param resultCode Arbitrary result code to deliver, as defined by you.
+ * @param resultData Any additional data provided by you.
+ */
+ public void send(int resultCode, Bundle resultData) {
+ if (mLocal) {
+ if (mHandler != null) {
+ mHandler.post(new MyRunnable(resultCode, resultData));
+ } else {
+ onReceiveResult(resultCode, resultData);
+ }
+ return;
+ }
+
+ if (mReceiver != null) {
+ try {
+ mReceiver.send(resultCode, resultData);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Override to receive results delivered to this object.
+ *
+ * @param resultCode Arbitrary result code delivered by the sender, as
+ * defined by the sender.
+ * @param resultData Any additional data provided by the sender.
+ */
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ synchronized (this) {
+ if (mReceiver == null) {
+ mReceiver = new MyResultReceiver();
+ }
+ out.writeStrongBinder(mReceiver.asBinder());
+ }
+ }
+
+ ResultReceiver(Parcel in) {
+ mLocal = false;
+ mHandler = null;
+ mReceiver = IResultReceiver.Stub.asInterface(in.readStrongBinder());
+ }
+
+ public static final Parcelable.Creator<ResultReceiver> CREATOR
+ = new Parcelable.Creator<ResultReceiver>() {
+ public ResultReceiver createFromParcel(Parcel in) {
+ return new ResultReceiver(in);
+ }
+ public ResultReceiver[] newArray(int size) {
+ return new ResultReceiver[size];
+ }
+ };
+}
diff --git a/core/java/android/package.html b/core/java/android/package.html
index b6d2999..1f1be2d 100644
--- a/core/java/android/package.html
+++ b/core/java/android/package.html
@@ -5,6 +5,6 @@ Contains the resource classes used by standard Android applications.
This package contains the resource classes that Android defines to be used in
Android applications. Third party developers can use many of them also for their applications.
To learn more about how to use these classes, and what a
-resource is, see <a href="{@docRoot}devel/resources-i18n.html">Resources</a>.
+resource is, see <a href="{@docRoot}guide/topics/resources/index.html">Resources and Assets</a>.
</BODY>
</HTML>
diff --git a/core/java/android/pim/ICalendar.java b/core/java/android/pim/ICalendar.java
index 4a5d7e4..cc0f45e 100644
--- a/core/java/android/pim/ICalendar.java
+++ b/core/java/android/pim/ICalendar.java
@@ -405,13 +405,15 @@ public class ICalendar {
// TODO: get rid of this -- handle all of the parsing in one pass through
// the text.
private static String normalizeText(String text) {
- // first we deal with line folding, by replacing all "\r\n " strings
- // with nothing
- text = text.replaceAll("\r\n ", "");
-
// it's supposed to be \r\n, but not everyone does that
text = text.replaceAll("\r\n", "\n");
text = text.replaceAll("\r", "\n");
+
+ // we deal with line folding, by replacing all "\n " strings
+ // with nothing. The RFC specifies "\r\n " to be folded, but
+ // we handle "\n " and "\r " too because we can get those.
+ text = text.replaceAll("\n ", "");
+
return text;
}
@@ -440,7 +442,7 @@ public class ICalendar {
current = parseLine(line, state, current);
// if the provided component was null, we will return the root
// NOTE: in this case, if the first line is not a BEGIN, a
- // FormatException will get thrown.
+ // FormatException will get thrown.
if (component == null) {
component = current;
}
@@ -524,8 +526,7 @@ public class ICalendar {
private static String extractValue(ParserState state)
throws FormatException {
String line = state.line;
- char c = line.charAt(state.index);
- if (c != ':') {
+ if (state.index >= line.length() || line.charAt(state.index) != ':') {
throw new FormatException("Expected ':' before end of line in "
+ line);
}
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index c6615da..1a287c8 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -140,7 +140,6 @@ public class RecurrenceSet {
recurrence = recurrence.substring(tzidx + 1);
}
Time time = new Time(tz);
- boolean rdateNotInUtc = !tz.equals(Time.TIMEZONE_UTC);
String[] rawDates = recurrence.split(",");
int n = rawDates.length;
long[] dates = new long[n];
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 3820f28..a255438 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -667,7 +667,6 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* {@link SharedPreferences}. This should be unique for the package.
*
* @param key The key for the preference.
- * @see #getId()
*/
public void setKey(String key) {
mKey = key;
@@ -1460,7 +1459,6 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* @param container The Bundle in which to save the instance of this Preference.
*
* @see #restoreHierarchyState
- * @see #dispatchSaveInstanceState
* @see #onSaveInstanceState
*/
public void saveHierarchyState(Bundle container) {
@@ -1474,7 +1472,6 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
*
* @param container The Bundle in which to save the instance of this Preference.
*
- * @see #dispatchRestoreInstanceState
* @see #saveHierarchyState
* @see #onSaveInstanceState
*/
@@ -1503,7 +1500,6 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* The default implementation returns null.
* @see #onRestoreInstanceState
* @see #saveHierarchyState
- * @see #dispatchSaveInstanceState
*/
protected Parcelable onSaveInstanceState() {
mBaseMethodCalled = true;
@@ -1516,7 +1512,6 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* @param container The Bundle that holds the previously saved state.
*
* @see #saveHierarchyState
- * @see #dispatchRestoreInstanceState
* @see #onRestoreInstanceState
*/
public void restoreHierarchyState(Bundle container) {
@@ -1530,7 +1525,6 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* not want to save state for their children.
*
* @param container The Bundle that holds the previously saved state.
- * @see #dispatchSaveInstanceState
* @see #restoreHierarchyState
* @see #onRestoreInstanceState
*/
@@ -1557,7 +1551,6 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* {@link #onSaveInstanceState}.
* @see #onSaveInstanceState
* @see #restoreHierarchyState
- * @see #dispatchRestoreInstanceState
*/
protected void onRestoreInstanceState(Parcelable state) {
mBaseMethodCalled = true;
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 95970ea..837ce91 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -103,8 +103,6 @@ public abstract class PreferenceActivity extends ListActivity implements
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
-
setContentView(com.android.internal.R.layout.preference_list_content);
mPreferenceManager = onCreatePreferenceManager();
@@ -214,6 +212,11 @@ public abstract class PreferenceActivity extends ListActivity implements
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
postBindPreferences();
+ CharSequence title = getPreferenceScreen().getTitle();
+ // Set the title of the activity
+ if (title != null) {
+ setTitle(title);
+ }
}
}
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
index 4258b41..d008fd6 100644
--- a/core/java/android/preference/PreferenceGroup.java
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -25,6 +25,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.AttributeSet;
/**
@@ -223,6 +224,9 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla
* @return The {@link Preference} with the key, or null.
*/
public Preference findPreference(CharSequence key) {
+ if (TextUtils.equals(getKey(), key)) {
+ return this;
+ }
final int preferenceCount = getPreferenceCount();
for (int i = 0; i < preferenceCount; i++) {
final Preference preference = getPreference(i);
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 05c2952..14c0054 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -88,6 +88,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
mPreferenceGroup = preferenceGroup;
+ // If this group gets or loses any children, let us know
+ mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
+
mPreferenceList = new ArrayList<Preference>();
mPreferenceClassNames = new ArrayList<String>();
@@ -239,7 +242,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
mHasReturnedViewTypeCount = true;
}
- return mPreferenceClassNames.size();
+ return Math.max(1, mPreferenceClassNames.size());
}
}
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index 9929b96..5353b53 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -100,7 +100,6 @@ public final class PreferenceScreen extends PreferenceGroup implements AdapterVi
*
* @return An adapter that provides the {@link Preference} contained in this
* {@link PreferenceScreen}.
- * @see PreferenceGroupAdapter
*/
public ListAdapter getRootAdapter() {
if (mRootAdapter == null) {
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index 6e215dc..20702a1 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -194,13 +194,6 @@ public class VolumePreference extends SeekBarPreference implements
}
private void sample() {
-
- // Only play a preview sample when controlling the ringer stream
- if (mStreamType != AudioManager.STREAM_RING
- && mStreamType != AudioManager.STREAM_NOTIFICATION) {
- return;
- }
-
onSampleStarting(this);
mRingtone.play();
}
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index 76aa51d..c597b3c 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -43,6 +43,18 @@ public class Browser {
*/
public static final String INITIAL_ZOOM_LEVEL = "browser.initialZoomLevel";
+ /**
+ * The name of the extra data when starting the Browser from another
+ * application.
+ * <p>
+ * The value is a unique identification string that will be used to
+ * indentify the calling application. The Browser will attempt to reuse the
+ * same window each time the application launches the Browser with the same
+ * identifier.
+ */
+ public static final String EXTRA_APPLICATION_ID =
+ "com.android.browser.application_id";
+
/* if you change column order you must also change indices
below */
public static final String[] HISTORY_PROJECTION = new String[] {
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java
index ef5eded..d11a9c5 100644
--- a/core/java/android/provider/Checkin.java
+++ b/core/java/android/provider/Checkin.java
@@ -30,10 +30,13 @@ import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
/**
- * Contract class for {@link android.server.checkin.CheckinProvider}.
+ * Contract class for the checkin provider, used to store events and
+ * statistics that will be uploaded to a checkin server eventually.
* Describes the exposed database schema, and offers methods to add
* events and statistics to be uploaded.
*
+ * TODO: Move this to vendor/google when we have a home for it.
+ *
* @hide
*/
public final class Checkin {
@@ -56,6 +59,15 @@ public final class Checkin {
/** Valid tag values. Extend as necessary for your needs. */
public enum Tag {
+ AUTOTEST_FAILURE,
+ AUTOTEST_SEQUENCE_BEGIN,
+ AUTOTEST_SUITE_BEGIN,
+ AUTOTEST_TCPDUMP_BEGIN,
+ AUTOTEST_TCPDUMP_DATA,
+ AUTOTEST_TCPDUMP_END,
+ AUTOTEST_TEST_BEGIN,
+ AUTOTEST_TEST_FAILURE,
+ AUTOTEST_TEST_SUCCESS,
BROWSER_BUG_REPORT,
CARRIER_BUG_REPORT,
CHECKIN_FAILURE,
@@ -88,14 +100,15 @@ public final class Checkin {
SETUP_RETRIES_EXHAUSTED,
SETUP_SERVER_ERROR,
SETUP_SERVER_TIMEOUT,
- SYSTEM_APP_NOT_RESPONDING,
+ SETUP_NO_DATA_NETWORK,
SYSTEM_BOOT,
SYSTEM_LAST_KMSG,
SYSTEM_RECOVERY_LOG,
SYSTEM_RESTART,
SYSTEM_SERVICE_LOOPING,
SYSTEM_TOMBSTONE,
- TEST,
+ TEST,
+ BATTERY_DISCHARGE_INFO,
}
}
@@ -116,6 +129,9 @@ public final class Checkin {
/** Valid tag values. Extend as necessary for your needs. */
public enum Tag {
+ BROWSER_SNAP_CENTER,
+ BROWSER_TEXT_SIZE_CHANGE,
+ BROWSER_ZOOM_OVERVIEW,
CRASHES_REPORTED,
CRASHES_TRUNCATED,
ELAPSED_REALTIME_SEC,
@@ -175,6 +191,9 @@ public final class Checkin {
// The category is used for GTalk service messages
public static final String CATEGORY = "android.server.checkin.CHECKIN";
+
+ // If true indicates that the checkin should only transfer market related data
+ public static final String EXTRA_MARKET_ONLY = "market_only";
}
private static final String TAG = "Checkin";
@@ -195,8 +214,11 @@ public final class Checkin {
values.put(Events.TAG, tag.toString());
if (value != null) values.put(Events.VALUE, value);
return resolver.insert(Events.CONTENT_URI, values);
+ } catch (IllegalArgumentException e) { // thrown when provider is unavailable.
+ Log.w(TAG, "Can't log event " + tag + ": " + e);
+ return null;
} catch (SQLException e) {
- Log.e(TAG, "Can't log event: " + tag, e); // Database errors are not fatal.
+ Log.e(TAG, "Can't log event " + tag, e); // Database errors are not fatal.
return null;
}
}
@@ -218,8 +240,11 @@ public final class Checkin {
if (count != 0) values.put(Stats.COUNT, count);
if (sum != 0.0) values.put(Stats.SUM, sum);
return resolver.insert(Stats.CONTENT_URI, values);
+ } catch (IllegalArgumentException e) { // thrown when provider is unavailable.
+ Log.w(TAG, "Can't update stat " + tag + ": " + e);
+ return null;
} catch (SQLException e) {
- Log.e(TAG, "Can't update stat: " + tag, e); // Database errors are not fatal.
+ Log.e(TAG, "Can't update stat " + tag, e); // Database errors are not fatal.
return null;
}
}
@@ -285,4 +310,3 @@ public final class Checkin {
}
}
}
-
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index e006d8c..e132bee 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -182,7 +182,7 @@ public class Contacts {
* <p>Type: TEXT</P>
*/
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.
@@ -191,6 +191,14 @@ public class Contacts {
public static final String DISPLAY_NAME = "display_name";
/**
+ * The field for sorting list phonetically. The content of this field
+ * may not be human readable but phonetically sortable.
+ * <P>Type: TEXT</p>
+ * @hide Used only in Contacts application for now.
+ */
+ public static final String SORT_STRING = "sort_string";
+
+ /**
* Notes about the person.
* <P>Type: TEXT</P>
*/
@@ -231,7 +239,7 @@ public class Contacts {
* The server version of the photo
* <P>Type: TEXT (the version number portion of the photo URI)</P>
*/
- public static final String PHOTO_VERSION = "photo_version";
+ public static final String PHOTO_VERSION = "photo_version";
}
/**
@@ -932,27 +940,33 @@ public class Contacts {
}
/**
- * This looks up the provider category defined in
- * {@link android.provider.Im.ProviderCategories} from the predefined IM protocol id.
+ * This looks up the provider name defined in
+ * {@link android.provider.Im.ProviderNames} from the predefined IM protocol id.
* This is used for interacting with the IM application.
- *
+ *
* @param protocol the protocol ID
- * @return the provider category the IM app uses for the given protocol, or null if no
+ * @return the provider name the IM app uses for the given protocol, or null if no
* provider is defined for the given protocol
* @hide
*/
- public static String lookupProviderCategoryFromId(int protocol) {
+ public static String lookupProviderNameFromId(int protocol) {
switch (protocol) {
case PROTOCOL_GOOGLE_TALK:
- return Im.ProviderCategories.GTALK;
+ return Im.ProviderNames.GTALK;
case PROTOCOL_AIM:
- return Im.ProviderCategories.AIM;
+ return Im.ProviderNames.AIM;
case PROTOCOL_MSN:
- return Im.ProviderCategories.MSN;
+ return Im.ProviderNames.MSN;
case PROTOCOL_YAHOO:
- return Im.ProviderCategories.YAHOO;
+ return Im.ProviderNames.YAHOO;
case PROTOCOL_ICQ:
- return Im.ProviderCategories.ICQ;
+ return Im.ProviderNames.ICQ;
+ case PROTOCOL_JABBER:
+ return Im.ProviderNames.JABBER;
+ case PROTOCOL_SKYPE:
+ return Im.ProviderNames.SKYPE;
+ case PROTOCOL_QQ:
+ return Im.ProviderNames.QQ;
}
return null;
}
@@ -1417,7 +1431,42 @@ public class Contacts {
*/
public static final String ATTACH_IMAGE =
"com.android.contacts.action.ATTACH_IMAGE";
-
+
+ /**
+ * Takes as input a data URI with a mailto: or tel: scheme. If a single
+ * contact exists with the given data it will be shown. If no contact
+ * exists, a dialog will ask the user if they want to create a new
+ * contact with the provided details filled in. If multiple contacts
+ * share the data the user will be prompted to pick which contact they
+ * want to view.
+ * <p>
+ * For <code>mailto:</code> URIs, the scheme specific portion must be a
+ * raw email address, such as one built using
+ * {@link Uri#fromParts(String, String, String)}.
+ * <p>
+ * For <code>tel:</code> URIs, the scheme specific portion is compared
+ * to existing numbers using the standard caller ID lookup algorithm.
+ * The number must be properly encoded, for example using
+ * {@link Uri#fromParts(String, String, String)}.
+ * <p>
+ * Any extras from the {@link Insert} class will be passed along to the
+ * create activity if there are no contacts to show.
+ * <p>
+ * Passing true for the {@link #EXTRA_FORCE_CREATE} extra will skip
+ * prompting the user when the contact doesn't exist.
+ */
+ public static final String SHOW_OR_CREATE_CONTACT =
+ "com.android.contacts.action.SHOW_OR_CREATE_CONTACT";
+
+ /**
+ * Used with {@link #SHOW_OR_CREATE_CONTACT} to force creating a new contact if no matching
+ * contact found. Otherwise, default behavior is to prompt user with dialog before creating.
+ *
+ * <P>Type: BOOLEAN</P>
+ */
+ public static final String EXTRA_FORCE_CREATE =
+ "com.android.contacts.action.FORCE_CREATE";
+
/**
* Intents related to the Contacts app UI.
*/
diff --git a/core/java/android/provider/Gmail.java b/core/java/android/provider/Gmail.java
index 325f19d..cc03968 100644
--- a/core/java/android/provider/Gmail.java
+++ b/core/java/android/provider/Gmail.java
@@ -370,6 +370,25 @@ public final class Gmail {
"maxAttachmentSize";
}
+ /**
+ * These flags can be included as Selection Arguments when
+ * querying the provider.
+ */
+ public static class SelectionArguments {
+ private SelectionArguments() {
+ // forbid instantiation
+ }
+
+ /**
+ * Specifies that you do NOT wish the returned cursor to
+ * become the Active Network Cursor. If you do not include
+ * this flag as a selectionArg, the new cursor will become the
+ * Active Network Cursor by default.
+ */
+ public static final String DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR =
+ "SELECTION_ARGUMENT_DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR";
+ }
+
// These are the projections that we need when getting cursors from the
// content provider.
private static String[] CONVERSATION_PROJECTION = {
@@ -436,6 +455,28 @@ public final class Gmail {
}
/**
+ * Behavior for a new cursor: should it become the Active Network
+ * Cursor? This could potentially lead to bad behavior if someone
+ * else is using the Active Network Cursor, since theirs will stop
+ * being the Active Network Cursor.
+ */
+ public static enum BecomeActiveNetworkCursor {
+ /**
+ * The new cursor should become the one and only Active
+ * Network Cursor. Any other cursor that might already be the
+ * Active Network Cursor will cease to be so.
+ */
+ YES,
+
+ /**
+ * The new cursor should not become the Active Network
+ * Cursor. Any other cursor that might already be the Active
+ * Network Cursor will continue to be so.
+ */
+ NO
+ }
+
+ /**
* Wraps a Cursor in a ConversationCursor
*
* @param account the account the cursor is associated with
@@ -450,6 +491,20 @@ public final class Gmail {
}
/**
+ * Creates an array of SelectionArguments suitable for passing to the provider's query.
+ * Currently this only handles one flag, but it could be expanded in the future.
+ */
+ private static String[] getSelectionArguments(
+ BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
+ if (BecomeActiveNetworkCursor.NO == becomeActiveNetworkCursor) {
+ return new String[] {SelectionArguments.DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR};
+ } else {
+ // Default behavior; no args required.
+ return null;
+ }
+ }
+
+ /**
* Asynchronously gets a cursor over all conversations matching a query. The
* query is in Gmail's query syntax. When the operation is complete the handler's
* onQueryComplete() method is called with the resulting Cursor.
@@ -458,14 +513,17 @@ public final class Gmail {
* @param handler An AsyncQueryHanlder that will be used to run the query
* @param token The token to pass to startQuery, which will be passed back to onQueryComplete
* @param query a query in Gmail's query syntax
+ * @param becomeActiveNetworkCursor whether or not the returned
+ * cursor should become the Active Network Cursor
*/
public void runQueryForConversations(String account, AsyncQueryHandler handler, int token,
- String query) {
+ String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
if (TextUtils.isEmpty(account)) {
throw new IllegalArgumentException("account is empty");
}
+ String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
handler.startQuery(token, null, Uri.withAppendedPath(CONVERSATIONS_URI, account),
- CONVERSATION_PROJECTION, query, null, null);
+ CONVERSATION_PROJECTION, query, selectionArgs, null);
}
/**
@@ -474,11 +532,15 @@ public final class Gmail {
*
* @param account run the query on this account
* @param query a query in Gmail's query syntax
+ * @param becomeActiveNetworkCursor whether or not the returned
+ * cursor should become the Active Network Cursor
*/
- public ConversationCursor getConversationCursorForQuery(String account, String query) {
+ public ConversationCursor getConversationCursorForQuery(
+ String account, String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
+ String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
Cursor cursor = mContentResolver.query(
Uri.withAppendedPath(CONVERSATIONS_URI, account), CONVERSATION_PROJECTION,
- query, null, null);
+ query, selectionArgs, null);
return new ConversationCursor(this, account, cursor);
}
@@ -559,12 +621,10 @@ public final class Gmail {
* server message id.
* @param label the label to add or remove
* @param add true to add the label, false to remove it
- * @throws NonexistentLabelException thrown if the label does not exist
*/
public void addOrRemoveLabelOnConversation(
String account, long conversationId, long maxServerMessageId, String label,
- boolean add)
- throws NonexistentLabelException {
+ boolean add) {
if (TextUtils.isEmpty(account)) {
throw new IllegalArgumentException("account is empty");
}
@@ -599,7 +659,6 @@ public final class Gmail {
* @param messageId the id of the message to whose labels should be changed
* @param label the label to add or remove
* @param add true to add the label, false to remove it
- * @throws NonexistentLabelException thrown if the label does not exist
*/
public static void addOrRemoveLabelOnMessage(ContentResolver contentResolver, String account,
long conversationId, long messageId, String label, boolean add) {
@@ -1175,19 +1234,6 @@ public final class Gmail {
}
/**
- * Thrown when an operation is requested with a label that does not exist.
- *
- * TODO: this is here because I wanted a checked exception. However, I don't
- * think that that is appropriate. In fact, I don't think that we should
- * throw an exception at all because the label might have been valid when
- * the caller presented it to the user but removed as a result of a sync.
- * Maybe we should kill this and eat the errors.
- */
- public static class NonexistentLabelException extends Exception {
- // TODO: Add label name?
- }
-
- /**
* A cursor over labels.
*/
public final class LabelCursor extends MailCursor {
@@ -1431,10 +1477,20 @@ public final class Gmail {
LABEL_OUTBOX, LABEL_DRAFT, LABEL_ALL,
LABEL_SPAM, LABEL_TRASH);
+
+ private static final Set<String> USER_MEANINGFUL_SYSTEM_LABELS_SET =
+ Sets.newHashSet(
+ SORTED_USER_MEANINGFUL_SYSTEM_LABELS.toArray(
+ new String[]{}));
+
public static List<String> getSortedUserMeaningfulSystemLabels() {
return SORTED_USER_MEANINGFUL_SYSTEM_LABELS;
}
+ public static Set<String> getUserMeaningfulSystemLabelsSet() {
+ return USER_MEANINGFUL_SYSTEM_LABELS_SET;
+ }
+
/**
* If you are ever tempted to remove outbox or draft from this set make sure you have a
* way to stop draft and outbox messages from getting purged before they are sent to the
@@ -1484,7 +1540,15 @@ public final class Gmail {
/** Returns the number of unread conversation with a given label. */
public int getNumUnreadConversations(long labelId) {
- return getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_UNREAD_CONVERSATIONS);
+ Integer unreadConversations =
+ getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_UNREAD_CONVERSATIONS);
+ // There seems to be a race condition here that can get the label maps into a bad
+ // state and lose state on a particular label.
+ if (unreadConversations == null) {
+ return 0;
+ } else {
+ return unreadConversations;
+ }
}
/**
@@ -2021,10 +2085,8 @@ public final class Gmail {
*
* @param label the label to add or remove
* @param add whether to add or remove the label
- * @throws NonexistentLabelException thrown if the named label does not
- * exist
*/
- public void addOrRemoveLabel(String label, boolean add) throws NonexistentLabelException {
+ public void addOrRemoveLabel(String label, boolean add) {
addOrRemoveLabelOnMessage(mContentResolver, mAccount, getConversationId(),
getMessageId(), label, add);
}
diff --git a/core/java/android/provider/Im.java b/core/java/android/provider/Im.java
index 68b2acd..19ad158 100644
--- a/core/java/android/provider/Im.java
+++ b/core/java/android/provider/Im.java
@@ -78,21 +78,10 @@ public class Im {
String MSN = "MSN";
String ICQ = "ICQ";
String AIM = "AIM";
- }
-
- /**
- * The ProviderCategories definitions are used for the Intent category for the Intent
- *
- * Intent intent = new Intent(Intent.ACTION_SENDTO,
- * Uri.fromParts("im", data, null)).
- * addCategory(category);
- */
- public interface ProviderCategories {
- String GTALK = "com.android.im.category.GTALK";
- String AIM = "com.android.im.category.AIM";
- String MSN = "com.android.im.category.MSN";
- String YAHOO = "com.android.im.category.YAHOO";
- String ICQ = "com.android.im.category.ICQ";
+ String XMPP = "XMPP";
+ String JABBER = "JABBER";
+ String SKYPE = "SKYPE";
+ String QQ = "QQ";
}
/**
@@ -140,54 +129,6 @@ public class Im {
return retVal;
}
- /**
- * This returns the provider name given a provider category.
- *
- * @param providerCategory the provider category defined in {@link ProviderCategories}.
- * @return the corresponding provider name defined in {@link ProviderNames}.
- */
- public static String getProviderNameForCategory(String providerCategory) {
- if (providerCategory != null) {
- if (providerCategory.equalsIgnoreCase(ProviderCategories.GTALK)) {
- return ProviderNames.GTALK;
- } else if (providerCategory.equalsIgnoreCase(ProviderCategories.AIM)) {
- return ProviderNames.AIM;
- } else if (providerCategory.equalsIgnoreCase(ProviderCategories.MSN)) {
- return ProviderNames.MSN;
- } else if (providerCategory.equalsIgnoreCase(ProviderCategories.YAHOO)) {
- return ProviderNames.YAHOO;
- } else if (providerCategory.equalsIgnoreCase(ProviderCategories.ICQ)) {
- return ProviderNames.ICQ;
- }
- }
-
- return null;
- }
-
- /**
- * This returns the provider category given a provider name.
- *
- * @param providername the provider name defined in {@link ProviderNames}.
- * @return the provider category defined in {@link ProviderCategories}.
- */
- public static String getProviderCategoryFromName(String providername) {
- if (providername != null) {
- if (providername.equalsIgnoreCase(Im.ProviderNames.GTALK)) {
- return Im.ProviderCategories.GTALK;
- } else if (providername.equalsIgnoreCase(Im.ProviderNames.AIM)) {
- return Im.ProviderCategories.AIM;
- } else if (providername.equalsIgnoreCase(Im.ProviderNames.MSN)) {
- return Im.ProviderCategories.MSN;
- } else if (providername.equalsIgnoreCase(Im.ProviderNames.YAHOO)) {
- return Im.ProviderCategories.YAHOO;
- } else if (providername.equalsIgnoreCase(Im.ProviderNames.ICQ)) {
- return Im.ProviderCategories.ICQ;
- }
- }
-
- return null;
- }
-
private static final String[] PROVIDER_PROJECTION = new String[] {
_ID,
NAME
@@ -797,6 +738,13 @@ public class Im {
String ETAG = "etag";
/**
+ * The OTR etag, computed by the server, stored on the client. There is one OTR etag
+ * per account roster.
+ * <P>Type: TEXT</P>
+ */
+ String OTR_ETAG = "otr_etag";
+
+ /**
* The account id for the etag.
* <P> Type: INTEGER </P>
*/
@@ -837,12 +785,38 @@ public class Im {
return retVal;
}
+ public static final String getOtrEtag(ContentResolver resolver, long accountId) {
+ String retVal = null;
+
+ Cursor c = resolver.query(CONTENT_URI,
+ CONTACT_OTR_ETAG_PROJECTION,
+ ACCOUNT + "=" + accountId,
+ null /* selection args */,
+ null /* sort order */);
+
+ try {
+ if (c.moveToFirst()) {
+ retVal = c.getString(COLUMN_OTR_ETAG);
+ }
+ } finally {
+ c.close();
+ }
+
+ return retVal;
+ }
+
private static final String[] CONTACT_ETAG_PROJECTION = new String[] {
Im.ContactsEtag.ETAG // 0
};
private static int COLUMN_ETAG = 0;
+ private static final String[] CONTACT_OTR_ETAG_PROJECTION = new String[] {
+ Im.ContactsEtag.OTR_ETAG // 0
+ };
+
+ private static int COLUMN_OTR_ETAG = 0;
+
/**
* The content:// style URL for this table
*/
@@ -1271,9 +1245,9 @@ public class Im {
}
/**
- * Columns shared between the IM and contacts presence tables
+ * Common presence columns shared between the IM and contacts presence tables
*/
- interface CommonPresenceColumns {
+ public interface CommonPresenceColumns {
/**
* The priority, an integer, used by XMPP presence
* <P>Type: INTEGER</P>
@@ -2070,4 +2044,37 @@ public class Im {
*/
public static final Uri CONTENT_URI = Uri.parse("content://im/lastRmqId");
}
+
+ /**
+ * Columns for IM branding resource map cache table. This table caches the result of
+ * loading the branding resources to speed up IM landing page start.
+ */
+ public interface BrandingResourceMapCacheColumns {
+ /**
+ * The provider ID
+ * <P>Type: INTEGER</P>
+ */
+ String PROVIDER_ID = "provider_id";
+ /**
+ * The application resource ID
+ * <P>Type: INTEGER</P>
+ */
+ String APP_RES_ID = "app_res_id";
+ /**
+ * The plugin resource ID
+ * <P>Type: INTEGER</P>
+ */
+ String PLUGIN_RES_ID = "plugin_res_id";
+ }
+
+ /**
+ * The table for caching the result of loading IM branding resources.
+ */
+ public static final class BrandingResourceMapCache
+ implements BaseColumns, BrandingResourceMapCacheColumns {
+ /**
+ * The content:// style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://im/brandingResMapCache");
+ }
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 87a02e6..b91bc9d 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -91,14 +91,14 @@ public final class MediaStore
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.
+ * The name of an Intent-extra used to control the UI 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.
+ * The name of an Intent-extra used to control the UI of a ViewImage.
* This is a boolean property that specifies whether or not to show action icons.
* @hide
*/
@@ -117,25 +117,36 @@ public final class MediaStore
*/
public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
-
/**
* The name of the Intent action used to launch a camera in video mode.
*/
public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA";
/**
- * Standard Intent action that can be sent to have the media application
- * capture an image and return it. The image is returned as a Bitmap
- * object in the extra field.
- * @hide
+ * Standard Intent action that can be sent to have the camera application
+ * capture an image and return it.
+ * <p>
+ * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
+ * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
+ * object in the extra field. This is useful for applications that only need a small image.
+ * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
+ * value of EXTRA_OUTPUT.
+ * @see #EXTRA_OUTPUT
+ * @see #EXTRA_VIDEO_QUALITY
*/
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
+ * Standard Intent action that can be sent to have the camera application
+ * capture an video and return it.
+ * <p>
+ * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality.
+ * <p>
+ * The caller may pass in an extra EXTRA_OUTPUT to control
+ * where the video is written. If EXTRA_OUTPUT is not present the video will be
+ * written to the standard location for videos, and the Uri of that location will be
+ * returned in the data field of the Uri.
+ * @see #EXTRA_OUTPUT
*/
public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
@@ -143,15 +154,19 @@ public final class MediaStore
* 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";
/**
- * The name of the Intent-extra used to indicate a Uri to be used to
- * store the requested image or video.
+ * Specify the maximum allowed size.
* @hide
*/
+ public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit";
+
+ /**
+ * The name of the Intent-extra used to indicate a content resolver Uri to be used to
+ * store the requested image or video.
+ */
public final static String EXTRA_OUTPUT = "output";
/**
@@ -471,7 +486,7 @@ public final class MediaStore
/**
* The default sort order for this table
*/
- public static final String DEFAULT_SORT_ORDER = "name ASC";
+ public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
}
public static class Thumbnails implements BaseColumns
@@ -582,6 +597,14 @@ public final class MediaStore
public static final String DURATION = "duration";
/**
+ * The position, in ms, playback was at when playback for this file
+ * was last stopped.
+ * <P>Type: INTEGER (long)</P>
+ * @hide
+ */
+ public static final String BOOKMARK = "bookmark";
+
+ /**
* The id of the artist who created the audio file, if any
* <P>Type: INTEGER (long)</P>
*/
@@ -654,6 +677,13 @@ public final class MediaStore
public static final String IS_MUSIC = "is_music";
/**
+ * Non-zero if the audio file is a podcast
+ * <P>Type: INTEGER (boolean)</P>
+ * @hide
+ */
+ public static final String IS_PODCAST = "is_podcast";
+
+ /**
* Non-zero id the audio file may be a ringtone
* <P>Type: INTEGER (boolean)</P>
*/
@@ -1198,22 +1228,15 @@ public final class MediaStore
}
public static final class Video {
- /**
- * 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
+ * The default sort order for this table.
*/
- public static final String DEFAULT_SORT_ORDER2 = MediaColumns.DISPLAY_NAME;
+ public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
{
- return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER2);
+ return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
}
public interface VideoColumns extends MediaColumns {
@@ -1316,7 +1339,6 @@ public final class MediaStore
* video should start playing at the next time it is opened. If the value is null or
* out of the range 0..DURATION-1 then the video should start playing from the
* beginning.
- * @hide
* <P>Type: INTEGER</P>
*/
public static final String BOOKMARK = "bookmark";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index abbfd5b..b0ee479 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -235,6 +235,35 @@ public final class Settings {
"android.settings.LOCALE_SETTINGS";
/**
+ * Activity Action: Show settings to configure input methods, in particular
+ * allowing the user to enable input methods.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INPUT_METHOD_SETTINGS =
+ "android.settings.INPUT_METHOD_SETTINGS";
+
+ /**
+ * Activity Action: Show settings to manage the user input dictionary.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_USER_DICTIONARY_SETTINGS =
+ "android.settings.USER_DICTIONARY_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of application-related settings.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -850,15 +879,6 @@ public final class Settings {
public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios";
/**
- * The interval in milliseconds after which Wi-Fi is considered idle.
- * When idle, it is possible for the device to be switched from Wi-Fi to
- * the mobile data network.
- *
- * @hide pending API Council approval
- */
- public static final String WIFI_IDLE_MS = "wifi_idle_ms";
-
- /**
* The policy for deciding when Wi-Fi should go to sleep (which will in
* turn switch to using the mobile data as an Internet connection).
* <p>
@@ -1233,6 +1253,13 @@ public final class Settings {
public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
/**
+ * Scaling factor for normal window animations. Setting to 0 will disable window
+ * animations.
+ * @hide
+ */
+ public static final String FANCY_IME_ANIMATIONS = "fancy_ime_animations";
+
+ /**
* Control whether the accelerometer will be used to change screen
* orientation. If 0, it will not be used unless explicitly requested
* by the application; if 1, it will be used by default unless explicitly
@@ -1252,6 +1279,12 @@ public final class Settings {
*/
public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
+ /**
+ * Whether the haptic feedback (long presses, ...) are enabled. The value is
+ * boolean (1 or 0).
+ */
+ public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled";
+
// Settings moved to Settings.Secure
/**
@@ -2008,6 +2041,14 @@ public final class Settings {
*/
public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
"wifi_mobile_data_transition_wakelock_timeout_ms";
+
+ /**
+ * Whether background data usage is allowed by the user. See
+ * ConnectivityManager for more info.
+ *
+ * @hide pending API council
+ */
+ public static final String BACKGROUND_DATA = "background_data";
}
/**
@@ -2130,6 +2171,11 @@ public final class Settings {
* Event tags from the kernel event log to upload during checkin.
*/
public static final String CHECKIN_EVENTS = "checkin_events";
+
+ /**
+ * Event tags for list of services to upload during checkin.
+ */
+ public static final String CHECKIN_DUMPSYS_LIST = "checkin_dumpsys_list";
/**
* The interval (in seconds) between periodic checkin attempts.
@@ -2137,6 +2183,12 @@ public final class Settings {
public static final String CHECKIN_INTERVAL = "checkin_interval";
/**
+ * Boolean indicating if the market app should force market only checkins on
+ * install/uninstall. Any non-0 value is considered true.
+ */
+ public static final String MARKET_FORCE_CHECKIN = "market_force_checkin";
+
+ /**
* How frequently (in seconds) to check the memory status of the
* device.
*/
@@ -2275,6 +2327,13 @@ public final class Settings {
public static final String GMAIL_SEND_IMMEDIATELY = "gmail_send_immediately";
/**
+ * Controls whether gmail buffers server responses. Possible values are "memory", for a
+ * memory-based buffer, or "file", for a temp-file-based buffer. All other values
+ * (including not set) disable buffering.
+ */
+ public static final String GMAIL_BUFFER_SERVER_RESPONSE = "gmail_buffer_server_response";
+
+ /**
* Hostname of the GTalk server.
*/
public static final String GTALK_SERVICE_HOSTNAME = "gtalk_hostname";
@@ -2402,6 +2461,14 @@ public final class Settings {
"gtalk_ssl_handshake_timeout_ms";
/**
+ * Enable use of ssl session caching.
+ * 'db' - save each session in a (per process) database
+ * 'file' - save each session in a (per process) file
+ * not set or any other value - normal java in-memory caching
+ */
+ public static final String SSL_SESSION_CACHE = "ssl_session_cache";
+
+ /**
* How many bytes long a message has to be, in order to be gzipped.
*/
public static final String SYNC_MIN_GZIP_BYTES =
@@ -2496,28 +2563,28 @@ public final class Settings {
*/
public static final String SETTINGS_CONTRIBUTORS_PRETTY_URL =
"settings_contributors_pretty_url";
-
+
/**
* URL that points to the Terms Of Service for the device.
* <p>
- * This should be a pretty http URL.
+ * This should be a pretty http URL.
*/
public static final String SETUP_GOOGLE_TOS_URL = "setup_google_tos_url";
-
+
/**
* URL that points to the Android privacy policy for the device.
* <p>
* This should be a pretty http URL.
*/
public static final String SETUP_ANDROID_PRIVACY_URL = "setup_android_privacy_url";
-
+
/**
* URL that points to the Google privacy policy for the device.
* <p>
- * This should be a pretty http URL.
+ * This should be a pretty http URL.
*/
public static final String SETUP_GOOGLE_PRIVACY_URL = "setup_google_privacy_url";
-
+
/**
* Request an MSISDN token for various Google services.
*/
@@ -2683,6 +2750,13 @@ public final class Settings {
"gprs_register_check_period_ms";
/**
+ * The interval in milliseconds after which Wi-Fi is considered idle.
+ * When idle, it is possible for the device to be switched from Wi-Fi to
+ * the mobile data network.
+ */
+ public static final String WIFI_IDLE_MS = "wifi_idle_ms";
+
+ /**
* Screen timeout in milliseconds corresponding to the
* PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest
* possible screen timeout behavior.)
@@ -2704,8 +2778,59 @@ public final class Settings {
* Speech encoding used with voice search on WIFI networks. To be factored out of this class.
*/
public static final String VOICE_SEARCH_ENCODING_WIFI = "voice_search_encoding_wifi";
+
+ /**
+ * Whether to use automatic gain control in voice search (0 = disable, 1 = enable).
+ * To be factored out of this class.
+ */
+ public static final String VOICE_SEARCH_ENABLE_AGC = "voice_search_enable_agc";
+
+ /**
+ * Whether to use noise suppression in voice search (0 = disable, 1 = enable).
+ * To be factored out of this class.
+ */
+ public static final String VOICE_SEARCH_ENABLE_NS = "voice_search_enable_ns";
+
+ /**
+ * Whether to use the IIR filter in voice search (0 = disable, 1 = enable).
+ * To be factored out of this class.
+ */
+ public static final String VOICE_SEARCH_ENABLE_IIR = "voice_search_enable_iir";
+
+ /**
+ * List of test suites (local disk filename) for the automatic instrumentation test runner.
+ * The file format is similar to automated_suites.xml, see AutoTesterService.
+ * If this setting is missing or empty, the automatic test runner will not start.
+ */
+ public static final String AUTOTEST_SUITES_FILE = "autotest_suites_file";
+
+ /**
+ * Interval between synchronous checkins forced by the automatic test runner.
+ * If you set this to a value smaller than CHECKIN_INTERVAL, then the test runner's
+ * frequent checkins will prevent asynchronous background checkins from interfering
+ * with any performance measurements.
+ */
+ public static final String AUTOTEST_CHECKIN_SECONDS = "autotest_checkin_seconds";
+
+ /**
+ * Interval between reboots forced by the automatic test runner.
+ */
+ public static final String AUTOTEST_REBOOT_SECONDS = "autotest_reboot_seconds";
+
+
+ /**
+ * Threshold values for the duration and level of a discharge cycle, under
+ * which we log discharge cycle info.
+ */
+ public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
+ "battery_discharge_duration_threshold";
+ public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
-
+ /**
+ * An email address that anr bugreports should be sent to.
+ */
+ public static final String ANR_BUGREPORT_RECIPIENT = "anr_bugreport_recipient";
+
/**
* @deprecated
* @hide
@@ -2772,7 +2897,7 @@ public final class Settings {
* Arbitrary string (displayed to the user) that allows bookmarks to be
* organized into categories. There are some special names for
* standard folders, which all start with '@'. The label displayed for
- * the folder changes with the locale (via {@link #labelForFolder}) but
+ * the folder changes with the locale (via {@link #getLabelForFolder}) but
* the folder name does not change so you can consistently query for
* the folder regardless of the current locale.
*
@@ -2912,9 +3037,10 @@ public final class Settings {
*
* @param context A context.
* @param cursor A cursor pointing to the row whose title should be
- * returned. The cursor must contain at least the
- * {@link #TITLE} and {@link #INTENT} columns.
- * @return A title that is localized and can be displayed to the user.
+ * returned. The cursor must contain at least the {@link #TITLE}
+ * and {@link #INTENT} columns.
+ * @return A title that is localized and can be displayed to the user,
+ * or the empty string if one could not be found.
*/
public static CharSequence getTitle(Context context, Cursor cursor) {
int titleColumn = cursor.getColumnIndex(TITLE);
@@ -2943,7 +3069,7 @@ public final class Settings {
PackageManager packageManager = context.getPackageManager();
ResolveInfo info = packageManager.resolveActivity(intent, 0);
- return info.loadLabel(packageManager);
+ return info != null ? info.loadLabel(packageManager) : "";
}
}
diff --git a/core/java/android/provider/Sync.java b/core/java/android/provider/Sync.java
index 94bf807..628852f 100644
--- a/core/java/android/provider/Sync.java
+++ b/core/java/android/provider/Sync.java
@@ -273,6 +273,7 @@ public final class Sync {
public static final int ERROR_CONFLICT = 5;
public static final int ERROR_TOO_MANY_DELETIONS = 6;
public static final int ERROR_TOO_MANY_RETRIES = 7;
+ public static final int ERROR_INTERNAL = 8;
// The MESG column will contain one of these or one of the Error types.
public static final String MESG_SUCCESS = "success";
@@ -292,6 +293,7 @@ public final class Sync {
case ERROR_CONFLICT: return "conflict detected";
case ERROR_TOO_MANY_DELETIONS: return "too many deletions";
case ERROR_TOO_MANY_RETRIES: return "too many retries";
+ case ERROR_INTERNAL: return "internal error";
default: return "unknown error";
}
}
@@ -491,11 +493,6 @@ public final class Sync {
/** controls whether or not the device listens for sync tickles */
public static final String SETTING_LISTEN_FOR_TICKLES = "listen_for_tickles";
- /** controls whether or not the device connect to Google in background for various
- * stuff, including GTalk, checkin, Market and data sync ...
- */
- public static final String SETTING_BACKGROUND_DATA = "background_data";
-
/** controls whether or not the individual provider is synced when tickles are received */
public static final String SETTING_SYNC_PROVIDER_PREFIX = "sync_provider_";
@@ -572,18 +569,6 @@ public final class Sync {
putBoolean(contentResolver, SETTING_LISTEN_FOR_TICKLES, flag);
}
- /**
- * A convenience method to set whether or not the device should connect to Google
- * in background.
- *
- * @param contentResolver the ContentResolver to use to access the settings table
- * @param flag true if it should connect.
- */
- static public void setBackgroundData(ContentResolver contentResolver,
- boolean flag) {
- putBoolean(contentResolver, SETTING_BACKGROUND_DATA, flag);
- }
-
public static class QueryMap extends ContentQueryMap {
private ContentResolver mContentResolver;
@@ -632,24 +617,6 @@ public final class Sync {
}
/**
- * Set whether or not the device should connect to Google in background
- *
- * @param flag true if it should
- */
- public void setBackgroundData(boolean flag) {
- Settings.setBackgroundData(mContentResolver, flag);
- }
-
- /**
- * Check if the device should connect to Google in background.
-
- * @return true if it should
- */
- public boolean getBackgroundData() {
- return getBoolean(SETTING_BACKGROUND_DATA, true);
- }
-
- /**
* Convenience function for retrieving a single settings value
* as a boolean.
*
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 18c64ed..d802c14 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -1023,6 +1023,11 @@ public final class Telephony {
* <P>Type: INTEGER</P>
*/
public static final String ERROR = "error";
+ /**
+ * Indicates whether this thread contains any attachments.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String HAS_ATTACHMENT = "has_attachment";
}
/**
diff --git a/core/java/android/provider/UserDictionary.java b/core/java/android/provider/UserDictionary.java
index 58e5731..5a7ef85 100644
--- a/core/java/android/provider/UserDictionary.java
+++ b/core/java/android/provider/UserDictionary.java
@@ -25,10 +25,13 @@ import android.net.Uri;
import android.text.TextUtils;
/**
- *
- * @hide Pending API council approval
+ * A provider of user defined words for input methods to use for predictive text input.
+ * Applications and input methods may add words into the dictionary. Words can have associated
+ * frequency information and locale information.
*/
public class UserDictionary {
+
+ /** Authority string for this provider. */
public static final String AUTHORITY = "user_dictionary";
/**
@@ -39,7 +42,6 @@ public class UserDictionary {
/**
* Contains the user defined words.
- * @hide Pending API council approval
*/
public static class Words implements BaseColumns {
/**
@@ -67,14 +69,14 @@ public class UserDictionary {
public static final String WORD = "word";
/**
- * The frequency column. A value between 1 and 255.
+ * The frequency column. A value between 1 and 255. Higher values imply higher frequency.
* <p>TYPE: INTEGER</p>
*/
public static final String FREQUENCY = "frequency";
/**
* The locale that this word belongs to. Null if it pertains to all
- * locales. Locale is a 5 letter string such as <pre>en_US</pre>.
+ * locales. Locale is as defined by the string returned by Locale.toString().
* <p>TYPE: TEXT</p>
*/
public static final String LOCALE = "locale";
@@ -85,8 +87,10 @@ public class UserDictionary {
*/
public static final String APP_ID = "appid";
+ /** The locale type to specify that the word is common to all locales. */
public static final int LOCALE_TYPE_ALL = 0;
+ /** The locale type to specify that the word is for the current locale. */
public static final int LOCALE_TYPE_CURRENT = 1;
/**
@@ -94,7 +98,14 @@ public class UserDictionary {
*/
public static final String DEFAULT_SORT_ORDER = FREQUENCY + " DESC";
-
+ /** Adds a word to the dictionary, with the given frequency and the specified
+ * specified locale type.
+ * @param context the current application context
+ * @param word the word to add to the dictionary. This should not be null or
+ * empty.
+ * @param localeType the locale type for this word. It should be one of
+ * {@link #LOCALE_TYPE_ALL} or {@link #LOCALE_TYPE_CURRENT}.
+ */
public static void addWord(Context context, String word,
int frequency, int localeType) {
final ContentResolver resolver = context.getContentResolver();
diff --git a/core/java/android/provider/package.html b/core/java/android/provider/package.html
index a553592..055b037 100644
--- a/core/java/android/provider/package.html
+++ b/core/java/android/provider/package.html
@@ -6,6 +6,6 @@ Android.
as contact informations, calendar information, and media files. These classes
provide simplified methods of adding or retrieving data from these content
providers. For information about how to use a content provider, see <a
-href="{@docRoot}devel/data.html">Reading and Writing Persistent Data</a>.
+href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>.
</BODY>
</HTML>
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 8486e4b..3aa4078 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -15,8 +15,7 @@
*/
/**
- * TODO: Move this to
- * java/services/com/android/server/BluetoothA2dpService.java
+ * TODO: Move this to services.jar
* and make the contructor package private again.
* @hide
*/
@@ -35,15 +34,16 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.List;
import java.util.HashMap;
-import java.util.Iterator;
+import java.util.List;
public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private static final String TAG = "BluetoothA2dpService";
@@ -55,6 +55,9 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
private static final String A2DP_SINK_ADDRESS = "a2dp_sink_address";
+ private static final String BLUETOOTH_ENABLED = "bluetooth_enabled";
+
+ private static final int MESSAGE_CONNECT_TO = 1;
private final Context mContext;
private final IntentFilter mIntentFilter;
@@ -85,6 +88,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
mIntentFilter = new IntentFilter(BluetoothIntent.ENABLED_ACTION);
mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
mIntentFilter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
mContext.registerReceiver(mReceiver, mIntentFilter);
if (device.isEnabled()) {
@@ -122,6 +126,37 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
setSinkPriority(address, BluetoothA2dp.PRIORITY_OFF);
break;
}
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION)) {
+ if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF) {
+ // This device is a preferred sink. Make an A2DP connection
+ // after a delay. We delay to avoid connection collisions,
+ // and to give other profiles such as HFP a chance to
+ // connect first.
+ Message msg = Message.obtain(mHandler, MESSAGE_CONNECT_TO, address);
+ mHandler.sendMessageDelayed(msg, 6000);
+ }
+ }
+ }
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_CONNECT_TO:
+ String address = (String)msg.obj;
+ // check device is still preferred, and nothing is currently
+ // connected
+ if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF &&
+ lookupSinksMatchingStates(new int[] {
+ BluetoothA2dp.STATE_CONNECTING,
+ BluetoothA2dp.STATE_CONNECTED,
+ BluetoothA2dp.STATE_PLAYING,
+ BluetoothA2dp.STATE_DISCONNECTING}).size() == 0) {
+ log("Auto-connecting A2DP to sink " + address);
+ connectSink(address);
+ }
+ break;
}
}
};
@@ -136,10 +171,31 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
BluetoothA2dp.STATE_DISCONNECTED));
}
}
+ mAudioManager.setParameter(BLUETOOTH_ENABLED, "true");
}
private synchronized void onBluetoothDisable() {
- mAudioDevices = null;
+ if (mAudioDevices != null) {
+ // copy to allow modification during iteration
+ String[] paths = new String[mAudioDevices.size()];
+ paths = mAudioDevices.keySet().toArray(paths);
+ for (String path : paths) {
+ switch (mAudioDevices.get(path).state) {
+ case BluetoothA2dp.STATE_CONNECTING:
+ case BluetoothA2dp.STATE_CONNECTED:
+ case BluetoothA2dp.STATE_PLAYING:
+ disconnectSinkNative(path);
+ updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+ break;
+ case BluetoothA2dp.STATE_DISCONNECTING:
+ updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+ break;
+ }
+ }
+ mAudioDevices = null;
+ }
+ mAudioManager.setBluetoothA2dpOn(false);
+ mAudioManager.setParameter(BLUETOOTH_ENABLED, "false");
}
public synchronized int connectSink(String address) {
@@ -152,6 +208,15 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
if (mAudioDevices == null) {
return BluetoothError.ERROR;
}
+ // ignore if there are any active sinks
+ if (lookupSinksMatchingStates(new int[] {
+ BluetoothA2dp.STATE_CONNECTING,
+ BluetoothA2dp.STATE_CONNECTED,
+ BluetoothA2dp.STATE_PLAYING,
+ BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) {
+ return BluetoothError.ERROR;
+ }
+
String path = lookupPath(address);
if (path == null) {
path = createHeadsetNative(address);
@@ -215,17 +280,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
public synchronized List<String> listConnectedSinks() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<String> connectedSinks = new ArrayList<String>();
- if (mAudioDevices == null) {
- return connectedSinks;
- }
- for (SinkState sink : mAudioDevices.values()) {
- if (sink.state == BluetoothA2dp.STATE_CONNECTED ||
- sink.state == BluetoothA2dp.STATE_PLAYING) {
- connectedSinks.add(sink.address);
- }
- }
- return connectedSinks;
+ return lookupSinksMatchingStates(new int[] {BluetoothA2dp.STATE_CONNECTED,
+ BluetoothA2dp.STATE_PLAYING});
}
public synchronized int getSinkState(String address) {
@@ -279,7 +335,11 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
// bluez 3.36 quietly disconnects the previous sink when a new sink
// is connected, so we need to mark all previously connected sinks as
// disconnected
- for (String oldPath : mAudioDevices.keySet()) {
+
+ // copy to allow modification during iteration
+ String[] paths = new String[mAudioDevices.size()];
+ paths = mAudioDevices.keySet().toArray(paths);
+ for (String oldPath : paths) {
if (path.equals(oldPath)) {
continue;
}
@@ -289,6 +349,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
}
}
+ updateState(path, BluetoothA2dp.STATE_CONNECTING);
mAudioManager.setParameter(A2DP_SINK_ADDRESS, lookupAddress(path));
mAudioManager.setBluetoothA2dpOn(true);
updateState(path, BluetoothA2dp.STATE_CONNECTED);
@@ -309,6 +370,11 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private synchronized final String lookupAddress(String path) {
if (mAudioDevices == null) return null;
+ SinkState sink = mAudioDevices.get(path);
+ if (sink == null) {
+ Log.w(TAG, "lookupAddress() called for unknown device " + path);
+ updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+ }
String address = mAudioDevices.get(path).address;
if (address == null) Log.e(TAG, "Can't find address for " + path);
return address;
@@ -325,6 +391,22 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return null;
}
+ private synchronized List<String> lookupSinksMatchingStates(int[] states) {
+ List<String> sinks = new ArrayList<String>();
+ if (mAudioDevices == null) {
+ return sinks;
+ }
+ for (SinkState sink : mAudioDevices.values()) {
+ for (int state : states) {
+ if (sink.state == state) {
+ sinks.add(sink.address);
+ break;
+ }
+ }
+ }
+ return sinks;
+ }
+
private synchronized void updateState(String path, int state) {
if (mAudioDevices == null) return;
@@ -349,6 +431,15 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
intent.putExtra(BluetoothA2dp.SINK_PREVIOUS_STATE, prevState);
intent.putExtra(BluetoothA2dp.SINK_STATE, state);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+ if ((prevState == BluetoothA2dp.STATE_CONNECTED ||
+ prevState == BluetoothA2dp.STATE_PLAYING) &&
+ (state != BluetoothA2dp.STATE_CONNECTED &&
+ state != BluetoothA2dp.STATE_PLAYING)) {
+ // disconnected
+ intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+ mContext.sendBroadcast(intent);
+ }
}
}
diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java
index 3ce34c3..9e9ba62 100644
--- a/core/java/android/server/BluetoothDeviceService.java
+++ b/core/java/android/server/BluetoothDeviceService.java
@@ -25,8 +25,8 @@
package android.server;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset; // just for dump()
import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothIntent;
import android.bluetooth.IBluetoothDevice;
import android.bluetooth.IBluetoothDeviceCallback;
@@ -35,22 +35,19 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.util.Log;
import android.os.Binder;
import android.os.Handler;
import android.os.Message;
+import android.os.RemoteException;
import android.os.SystemService;
+import android.provider.Settings;
+import android.util.Log;
-import java.io.IOException;
import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -84,15 +81,16 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
*/
public synchronized void init() {
initializeNativeDataNative();
- mIsEnabled = (isEnabledNative() == 1);
- if (mIsEnabled) {
- mBondState.loadBondState();
+
+ if (isEnabledNative() == 1) {
+ Log.w(TAG, "Bluetooth daemons already running - runtime restart? ");
+ disableNative();
}
+
+ mIsEnabled = false;
mIsDiscovering = false;
mEventLoop = new BluetoothEventLoop(mContext, this);
registerForAirplaneMode();
-
- disableEsco(); // TODO: enable eSCO support once its fully supported
}
private native void initializeNativeDataNative();
@@ -116,12 +114,22 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native int isEnabledNative();
/**
- * Disable bluetooth. Returns true on success.
+ * Bring down bluetooth and disable BT in settings. Returns true on success.
*/
- public synchronized boolean disable() {
+ public boolean disable() {
+ return disable(true);
+ }
+
+ /**
+ * Bring down bluetooth. Returns true on success.
+ *
+ * @param saveSetting If true, disable BT in settings
+ *
+ */
+ public synchronized boolean disable(boolean saveSetting) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
-
+
if (mEnableThread != null && mEnableThread.isAlive()) {
return false;
}
@@ -130,22 +138,63 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
}
mEventLoop.stop();
disableNative();
+
+ // mark in progress bondings as cancelled
+ for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) {
+ mBondState.setBondState(address, BluetoothDevice.BOND_NOT_BONDED,
+ BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
+ }
+
+ // Remove remoteServiceChannelCallbacks
+ HashMap<String, IBluetoothDeviceCallback> callbacksMap =
+ mEventLoop.getRemoteServiceChannelCallbacks();
+ IBluetoothDeviceCallback callback;
+
+ for (String address : callbacksMap.keySet()) {
+ callback = callbacksMap.get(address);
+ try {
+ callback.onGetRemoteServiceChannelResult(address, BluetoothError.ERROR_DISABLED);
+ } catch (RemoteException e) {}
+ callbacksMap.remove(address);
+ }
+
+ // update mode
+ Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
+ intent.putExtra(BluetoothIntent.SCAN_MODE, BluetoothDevice.SCAN_MODE_NONE);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
mIsEnabled = false;
- Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON, 0);
+ if (saveSetting) {
+ persistBluetoothOnSetting(false);
+ }
mIsDiscovering = false;
- Intent intent = new Intent(BluetoothIntent.DISABLED_ACTION);
+ intent = new Intent(BluetoothIntent.DISABLED_ACTION);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
return true;
}
/**
+ * Bring up bluetooth, asynchronously, and enable BT in settings.
+ * This turns on/off the underlying hardware.
+ *
+ * @return True on success (so far), guaranteeing the callback with be
+ * notified when complete.
+ */
+ public boolean enable(IBluetoothDeviceCallback callback) {
+ return enable(callback, true);
+ }
+
+ /**
* Enable this Bluetooth device, asynchronously.
* This turns on/off the underlying hardware.
*
- * @return True on success (so far), guarenteeing the callback with be
+ * @param saveSetting If true, enable BT in settings
+ *
+ * @return True on success (so far), guaranteeing the callback with be
* notified when complete.
*/
- public synchronized boolean enable(IBluetoothDeviceCallback callback) {
+ public synchronized boolean enable(IBluetoothDeviceCallback callback,
+ boolean saveSetting) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
@@ -159,7 +208,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
if (mEnableThread != null && mEnableThread.isAlive()) {
return false;
}
- mEnableThread = new EnableThread(callback);
+ mEnableThread = new EnableThread(callback, saveSetting);
mEnableThread.start();
return true;
}
@@ -183,13 +232,36 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private class EnableThread extends Thread {
private final IBluetoothDeviceCallback mEnableCallback;
- public EnableThread(IBluetoothDeviceCallback callback) {
+ private final boolean mSaveSetting;
+ public EnableThread(IBluetoothDeviceCallback callback, boolean saveSetting) {
mEnableCallback = callback;
+ mSaveSetting = saveSetting;
}
public void run() {
boolean res = (enableNative() == 0);
if (res) {
- mEventLoop.start();
+ int retryCount = 2;
+ boolean running = false;
+ while ((retryCount-- > 0) && !running) {
+ mEventLoop.start();
+ // it may take a momement for the other thread to do its
+ // thing. Check periodically for a while.
+ int pollCount = 5;
+ while ((pollCount-- > 0) && !running) {
+ if (mEventLoop.isEventLoopRunning()) {
+ running = true;
+ break;
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {}
+ }
+ }
+ if (!running) {
+ log("bt EnableThread giving up");
+ res = false;
+ disableNative();
+ }
}
if (mEnableCallback != null) {
@@ -202,17 +274,35 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
if (res) {
mIsEnabled = true;
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.BLUETOOTH_ON, 1);
+ if (mSaveSetting) {
+ persistBluetoothOnSetting(true);
+ }
mIsDiscovering = false;
- Intent intent = new Intent(BluetoothIntent.ENABLED_ACTION);
mBondState.loadBondState();
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS), 3000);
+
+ // Update mode
+ mEventLoop.onModeChanged(getModeNative());
+ }
+ Intent intent = null;
+ if (res) {
+ intent = new Intent(BluetoothIntent.ENABLED_ACTION);
+ } else {
+ intent = new Intent(BluetoothIntent.DISABLED_ACTION);
}
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
mEnableThread = null;
}
- };
+ }
+
+ private void persistBluetoothOnSetting(boolean bluetoothOn) {
+ long origCallerIdentityToken = Binder.clearCallingIdentity();
+ Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON,
+ bluetoothOn ? 1 : 0);
+ Binder.restoreCallingIdentity(origCallerIdentityToken);
+ }
private native int enableNative();
private native int disableNative();
@@ -229,6 +319,16 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
public class BondState {
private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
+ private final ArrayList<String> mAutoPairingFailures = new ArrayList<String>();
+ // List of all the vendor_id prefix of Bluetooth addresses for which
+ // auto pairing is not attempted
+ private final ArrayList<String> mAutoPairingBlacklisted =
+ new ArrayList<String>(Arrays.asList(
+ "00:02:C7", "00:16:FE", "00:19:C1", "00:1B:FB", "00:1E:3D", //ALPS
+ "00:21:4F", "00:23:06", "00:24:33", "00:A0:79", // ALPS
+ "00:0E:6D", "00:13:E0", "00:21:E8", "00:60:57",// Murata for Prius 2007
+ "00:0E:9F" // TEMIC SDS for Porsche
+ ));
public synchronized void loadBondState() {
if (!mIsEnabled) {
@@ -263,8 +363,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
intent.putExtra(BluetoothIntent.BOND_PREVIOUS_STATE, oldState);
if (state == BluetoothDevice.BOND_NOT_BONDED) {
if (reason <= 0) {
- Log.w(TAG, "setBondState() called to unbond device with invalid reason code " +
- "Setting reason = BOND_RESULT_REMOVED");
+ Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
+ "invalid. Overriding reason code with BOND_RESULT_REMOVED");
reason = BluetoothDevice.UNBOND_REASON_REMOVED;
}
intent.putExtra(BluetoothIntent.REASON, reason);
@@ -272,14 +372,17 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
} else {
mState.put(address, state);
}
- if (state == BluetoothDevice.BOND_BONDING) {
- mPinAttempt.put(address, Integer.valueOf(0));
- } else {
- mPinAttempt.remove(address);
- }
+
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
+ public boolean isAutoPairingBlacklisted(String address) {
+ for (String blacklistAddress : mAutoPairingBlacklisted) {
+ if (address.startsWith(blacklistAddress)) return true;
+ }
+ return false;
+ }
+
public synchronized int getBondState(String address) {
Integer state = mState.get(address);
if (state == null) {
@@ -288,16 +391,34 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
return state.intValue();
}
- public synchronized String[] listBonds() {
+ private synchronized String[] listInState(int state) {
ArrayList<String> result = new ArrayList<String>(mState.size());
for (Map.Entry<String, Integer> e : mState.entrySet()) {
- if (e.getValue().intValue() == BluetoothDevice.BOND_BONDED) {
+ if (e.getValue().intValue() == state) {
result.add(e.getKey());
}
}
return result.toArray(new String[result.size()]);
}
+ public synchronized void addAutoPairingFailure(String address) {
+ if (!mAutoPairingFailures.contains(address)) {
+ mAutoPairingFailures.add(address);
+ }
+ }
+
+ public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
+ return getAttempt(address) != 0;
+ }
+
+ public synchronized void clearPinAttempts(String address) {
+ mPinAttempt.remove(address);
+ }
+
+ public synchronized boolean hasAutoPairingFailed(String address) {
+ return mAutoPairingFailures.contains(address);
+ }
+
public synchronized int getAttempt(String address) {
Integer attempt = mPinAttempt.get(address);
if (attempt == null) {
@@ -308,10 +429,13 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
public synchronized void attempt(String address) {
Integer attempt = mPinAttempt.get(address);
+ int newAttempt;
if (attempt == null) {
- return;
+ newAttempt = 1;
+ } else {
+ newAttempt = attempt.intValue() + 1;
}
- mPinAttempt.put(address, new Integer(attempt.intValue() + 1));
+ mPinAttempt.put(address, new Integer(newAttempt));
}
}
@@ -353,18 +477,6 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
}
private native boolean setNameNative(String name);
- public synchronized String getMajorClass() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return getMajorClassNative();
- }
- private native String getMajorClassNative();
-
- public synchronized String getMinorClass() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return getMinorClassNative();
- }
- private native String getMinorClassNative();
-
/**
* Returns the user-friendly name of a remote device. This value is
* retrned from our local cache, which is updated during device discovery.
@@ -386,24 +498,6 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
/* pacakge */ native String getAdapterPathNative();
- /**
- * Initiate a remote-device-discovery procedure. This procedure may be
- * canceled by calling {@link #stopDiscovery}. Remote-device discoveries
- * are returned as intents
- * <p>
- * Typically, when a remote device is found, your
- * android.bluetooth.DiscoveryEventNotifier#notifyRemoteDeviceFound
- * method will be invoked, and subsequently, your
- * android.bluetooth.RemoteDeviceEventNotifier#notifyRemoteNameUpdated
- * will tell you the user-friendly name of the remote device. However,
- * it is possible that the name update may fail for various reasons, so you
- * should display the device's Bluetooth address as soon as you get a
- * notifyRemoteDeviceFound event, and update the name when you get the
- * remote name.
- *
- * @return true if discovery has started,
- * false otherwise.
- */
public synchronized boolean startDiscovery(boolean resolveNames) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
@@ -411,12 +505,6 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
}
private native boolean startDiscoveryNative(boolean resolveNames);
- /**
- * Cancel a remote-device discovery.
- *
- * Note: you may safely call this method even when discovery has not been
- * started.
- */
public synchronized boolean cancelDiscovery() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
@@ -492,171 +580,23 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
}
private native boolean isConnectedNative(String address);
- /**
- * Detetermines whether this device is connectable (that is, whether remote
- * devices can connect to it.)
- * <p>
- * Note: A Bluetooth adapter has separate connectable and discoverable
- * states, and you could have any combination of those. Although
- * any combination is possible (such as discoverable but not
- * connectable), we restrict the possible combinations to one of
- * three possibilities: discoverable and connectable, connectable
- * but not discoverable, and neither connectable nor discoverable.
- *
- * @return true if this adapter is connectable
- * false otherwise
- *
- * @see #isDiscoverable
- * @see #getMode
- * @see #setMode
- */
- public synchronized boolean isConnectable() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return isConnectableNative();
- }
- private native boolean isConnectableNative();
-
- /**
- * Detetermines whether this device is discoverable.
- *
- * Note: a Bluetooth adapter has separate connectable and discoverable
- * states, and you could have any combination of those. Although
- * any combination is possible (such as discoverable but not
- * connectable), we restrict the possible combinations to one of
- * three possibilities: discoverable and connectable, connectable
- * but not discoverable, and neither connectable nor discoverable.
- *
- * @return true if this adapter is discoverable
- * false otherwise
- *
- * @see #isConnectable
- * @see #getMode
- * @see #setMode
- */
- public synchronized boolean isDiscoverable() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return isDiscoverableNative();
- }
- private native boolean isDiscoverableNative();
-
- /**
- * Determines which one of three modes this adapter is in: discoverable and
- * connectable, not discoverable but connectable, or neither.
- *
- * @return Mode enumeration containing the current mode.
- *
- * @see #setMode
- */
- public synchronized int getMode() {
+ public synchronized int getScanMode() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- String mode = getModeNative();
- if (mode == null) {
- return BluetoothDevice.MODE_UNKNOWN;
- }
- if (mode.equalsIgnoreCase("off")) {
- return BluetoothDevice.MODE_OFF;
- }
- else if (mode.equalsIgnoreCase("connectable")) {
- return BluetoothDevice.MODE_CONNECTABLE;
- }
- else if (mode.equalsIgnoreCase("discoverable")) {
- return BluetoothDevice.MODE_DISCOVERABLE;
- }
- else {
- return BluetoothDevice.MODE_UNKNOWN;
- }
+ return bluezStringToScanMode(getModeNative());
}
private native String getModeNative();
- /**
- * Set the discoverability and connectability mode of this adapter. The
- * possibilities are discoverable and connectable (MODE_DISCOVERABLE),
- * connectable but not discoverable (MODE_CONNECTABLE), and neither
- * (MODE_OFF).
- *
- * Note: MODE_OFF does not mean that the adapter is physically off. It
- * may be neither discoverable nor connectable, but it could still
- * initiate outgoing connections, or could participate in a
- * connection initiated by a remote device before its mode was set
- * to MODE_OFF.
- *
- * @param mode the new mode
- * @see #getMode
- */
- public synchronized boolean setMode(int mode) {
+ public synchronized boolean setScanMode(int mode) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
- switch (mode) {
- case BluetoothDevice.MODE_OFF:
- return setModeNative("off");
- case BluetoothDevice.MODE_CONNECTABLE:
- return setModeNative("connectable");
- case BluetoothDevice.MODE_DISCOVERABLE:
- return setModeNative("discoverable");
+ String bluezMode = scanModeToBluezString(mode);
+ if (bluezMode != null) {
+ return setModeNative(bluezMode);
}
return false;
}
private native boolean setModeNative(String mode);
- /**
- * Retrieves the alias of a remote device. The alias is a local feature,
- * and allows us to associate a name with a remote device that is different
- * from that remote device's user-friendly name. The remote device knows
- * nothing about this. The alias can be changed with
- * {@link #setRemoteAlias}, and it may be removed with
- * {@link #clearRemoteAlias}
- *
- * @param address Bluetooth address of remote device.
- *
- * @return The alias of the remote device.
- */
- public synchronized String getRemoteAlias(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- return null;
- }
- return getRemoteAliasNative(address);
- }
- private native String getRemoteAliasNative(String address);
-
- /**
- * Changes the alias of a remote device. The alias is a local feature,
- * from that remote device's user-friendly name. The remote device knows
- * nothing about this. The alias can be retrieved with
- * {@link #getRemoteAlias}, and it may be removed with
- * {@link #clearRemoteAlias}.
- *
- * @param address Bluetooth address of remote device
- * @param alias Alias for the remote device
- */
- public synchronized boolean setRemoteAlias(String address, String alias) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (alias == null || !BluetoothDevice.checkBluetoothAddress(address)) {
- return false;
- }
- return setRemoteAliasNative(address, alias);
- }
- private native boolean setRemoteAliasNative(String address, String alias);
-
- /**
- * Removes the alias of a remote device. The alias is a local feature,
- * from that remote device's user-friendly name. The remote device knows
- * nothing about this. The alias can be retrieved with
- * {@link #getRemoteAlias}.
- *
- * @param address Bluetooth address of remote device
- */
- public synchronized boolean clearRemoteAlias(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- return false;
- }
- return clearRemoteAliasNative(address);
- }
- private native boolean clearRemoteAliasNative(String address);
-
public synchronized boolean disconnectRemoteDeviceAcl(String address) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
@@ -674,7 +614,19 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
return false;
}
address = address.toUpperCase();
- if (mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) {
+
+ String[] bonding = mBondState.listInState(BluetoothDevice.BOND_BONDING);
+ if (bonding.length > 0 && !bonding[0].equals(address)) {
+ log("Ignoring createBond(): another device is bonding");
+ // a different device is currently bonding, fail
+ return false;
+ }
+
+ // Check for bond state only if we are not performing auto
+ // pairing exponential back-off attempts.
+ if (!mBondState.isAutoPairingAttemptsInProgress(address) &&
+ mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) {
+ log("Ignoring createBond(): this device is already bonding or bonded");
return false;
}
@@ -699,7 +651,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
}
mBondState.setBondState(address, BluetoothDevice.BOND_NOT_BONDED,
- BluetoothDevice.UNBOND_REASON_CANCELLED);
+ BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
cancelBondingProcessNative(address);
return true;
}
@@ -717,7 +669,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
public synchronized String[] listBonds() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBondState.listBonds();
+ return mBondState.listInState(BluetoothDevice.BOND_BONDED);
}
public synchronized int getBondState(String address) {
@@ -919,69 +871,6 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native String lastUsedNative(String address);
/**
- * Gets the major device class of the specified device.
- * Example: "computer"
- *
- * Note: This is simply a string desciption of the major class of the
- * device-class information, which is returned as a 32-bit value
- * during device discovery.
- *
- * @param address The Bluetooth address of the remote device.
- *
- * @return remote-device major class
- *
- * @see #getRemoteClass
- */
- public synchronized String getRemoteMajorClass(String address) {
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return null;
- }
- return getRemoteMajorClassNative(address);
- }
- private native String getRemoteMajorClassNative(String address);
-
- /**
- * Gets the minor device class of the specified device.
- * Example: "laptop"
- *
- * Note: This is simply a string desciption of the minor class of the
- * device-class information, which is returned as a 32-bit value
- * during device discovery.
- *
- * @param address The Bluetooth address of the remote device.
- *
- * @return remote-device minor class
- *
- * @see #getRemoteClass
- */
- public synchronized String getRemoteMinorClass(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- return null;
- }
- return getRemoteMinorClassNative(address);
- }
- private native String getRemoteMinorClassNative(String address);
-
- /**
- * Gets the service classes of the specified device.
- * Example: ["networking", "object transfer"]
- *
- * @return a String array with the descriptions of the service classes.
- *
- * @see #getRemoteClass
- */
- public synchronized String[] getRemoteServiceClasses(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothDevice.checkBluetoothAddress(address)) {
- return null;
- }
- return getRemoteServiceClassesNative(address);
- }
- private native String[] getRemoteServiceClassesNative(String address);
-
- /**
* Gets the remote major, minor, and service classes encoded as a 32-bit
* integer.
*
@@ -1162,9 +1051,9 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
// If bluetooth is currently expected to be on, then enable or disable bluetooth
if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) {
if (enabled) {
- enable(null);
+ enable(null, false);
} else {
- disable();
+ disable(false);
}
}
}
@@ -1188,16 +1077,6 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
Settings.System.AIRPLANE_MODE_ON, 0) == 1;
}
- private static final String DISABLE_ESCO_PATH = "/sys/module/sco/parameters/disable_esco";
- private static void disableEsco() {
- try {
- FileWriter file = new FileWriter(DISABLE_ESCO_PATH);
- file.write("Y");
- file.close();
- } catch (FileNotFoundException e) {
- } catch (IOException e) {}
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mIsEnabled) {
@@ -1248,6 +1127,34 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
pw.println("\nmIsAirplaneSensitive = " + mIsAirplaneSensitive);
}
+ /* package */ static int bluezStringToScanMode(String mode) {
+ if (mode == null) {
+ return BluetoothError.ERROR;
+ }
+ mode = mode.toLowerCase();
+ if (mode.equals("off")) {
+ return BluetoothDevice.SCAN_MODE_NONE;
+ } else if (mode.equals("connectable")) {
+ return BluetoothDevice.SCAN_MODE_CONNECTABLE;
+ } else if (mode.equals("discoverable")) {
+ return BluetoothDevice.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
+ } else {
+ return BluetoothError.ERROR;
+ }
+ }
+
+ /* package */ static String scanModeToBluezString(int mode) {
+ switch (mode) {
+ case BluetoothDevice.SCAN_MODE_NONE:
+ return "off";
+ case BluetoothDevice.SCAN_MODE_CONNECTABLE:
+ return "connectable";
+ case BluetoothDevice.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
+ return "discoverable";
+ }
+ return null;
+ }
+
private static void log(String msg) {
Log.d(TAG, msg);
}
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 4f63f98..8e77eed 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -16,6 +16,7 @@
package android.server;
+import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothError;
@@ -23,6 +24,8 @@ import android.bluetooth.BluetoothIntent;
import android.bluetooth.IBluetoothDeviceCallback;
import android.content.Context;
import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
@@ -41,15 +44,45 @@ class BluetoothEventLoop {
private int mNativeData;
private Thread mThread;
+ private boolean mStarted;
private boolean mInterrupted;
private HashMap<String, Integer> mPasskeyAgentRequestData;
private HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks;
private BluetoothDeviceService mBluetoothService;
private Context mContext;
+ private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
+ private static final int EVENT_RESTART_BLUETOOTH = 2;
+
+ // The time (in millisecs) to delay the pairing attempt after the first
+ // auto pairing attempt fails. We use an exponential delay with
+ // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
+ // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
+ private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
+ private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
+
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
+ String address = (String)msg.obj;
+ if (address != null) {
+ mBluetoothService.createBond(address);
+ return;
+ }
+ break;
+ case EVENT_RESTART_BLUETOOTH:
+ mBluetoothService.disable();
+ mBluetoothService.enable(null);
+ break;
+ }
+ }
+ };
+
static { classInitNative(); }
private static native void classInitNative();
@@ -95,14 +128,18 @@ class BluetoothEventLoop {
public void run() {
try {
if (setUpEventLoopNative()) {
+ mStarted = true;
while (!mInterrupted) {
waitForAndDispatchEvent(0);
sleep(500);
}
- tearDownEventLoopNative();
}
+ // tear down even in the error case to clean
+ // up anything we started to setup
+ tearDownEventLoopNative();
} catch (InterruptedException e) { }
if (DBG) log("Event Loop thread finished");
+ mThread = null;
}
};
if (DBG) log("Starting Event Loop thread");
@@ -114,9 +151,7 @@ class BluetoothEventLoop {
public synchronized void stop() {
if (mThread != null) {
-
mInterrupted = true;
-
try {
mThread.join();
mThread = null;
@@ -127,135 +162,150 @@ class BluetoothEventLoop {
}
public synchronized boolean isEventLoopRunning() {
- return mThread != null;
+ return mThread != null && mStarted;
}
- public void onModeChanged(String mode) {
- Intent intent = new Intent(BluetoothIntent.MODE_CHANGED_ACTION);
- int intMode = BluetoothDevice.MODE_UNKNOWN;
- if (mode.equalsIgnoreCase("off")) {
- intMode = BluetoothDevice.MODE_OFF;
+ /*package*/ void onModeChanged(String bluezMode) {
+ int mode = BluetoothDeviceService.bluezStringToScanMode(bluezMode);
+ if (mode >= 0) {
+ Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
+ intent.putExtra(BluetoothIntent.SCAN_MODE, mode);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- else if (mode.equalsIgnoreCase("connectable")) {
- intMode = BluetoothDevice.MODE_CONNECTABLE;
- }
- else if (mode.equalsIgnoreCase("discoverable")) {
- intMode = BluetoothDevice.MODE_DISCOVERABLE;
- }
- intent.putExtra(BluetoothIntent.MODE, intMode);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onDiscoveryStarted() {
+ private void onDiscoveryStarted() {
mBluetoothService.setIsDiscovering(true);
Intent intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onDiscoveryCompleted() {
+ private void onDiscoveryCompleted() {
mBluetoothService.setIsDiscovering(false);
Intent intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onPairingRequest() {
- Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- }
-
- public void onPairingCancel() {
- Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- }
-
- public void onRemoteDeviceFound(String address, int deviceClass, short rssi) {
+ private void onRemoteDeviceFound(String address, int deviceClass, short rssi) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
intent.putExtra(BluetoothIntent.CLASS, deviceClass);
intent.putExtra(BluetoothIntent.RSSI, rssi);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onRemoteDeviceDisappeared(String address) {
+ private void onRemoteDeviceDisappeared(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onRemoteClassUpdated(String address, int deviceClass) {
+ private void onRemoteClassUpdated(String address, int deviceClass) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
intent.putExtra(BluetoothIntent.CLASS, deviceClass);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onRemoteDeviceConnected(String address) {
+ private void onRemoteDeviceConnected(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onRemoteDeviceDisconnectRequested(String address) {
+ private void onRemoteDeviceDisconnectRequested(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onRemoteDeviceDisconnected(String address) {
+ private void onRemoteDeviceDisconnected(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onRemoteNameUpdated(String address, String name) {
+ private void onRemoteNameUpdated(String address, String name) {
Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
intent.putExtra(BluetoothIntent.NAME, name);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onRemoteNameFailed(String address) {
+ private void onRemoteNameFailed(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_FAILED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onRemoteNameChanged(String address, String name) {
+ private void onRemoteNameChanged(String address, String name) {
Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
intent.putExtra(BluetoothIntent.NAME, name);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onRemoteAliasChanged(String address, String alias) {
- Intent intent = new Intent(BluetoothIntent.REMOTE_ALIAS_CHANGED_ACTION);
- intent.putExtra(BluetoothIntent.ADDRESS, address);
- intent.putExtra(BluetoothIntent.ALIAS, alias);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- }
- public void onRemoteAliasCleared(String address) {
- Intent intent = new Intent(BluetoothIntent.REMOTE_ALIAS_CLEARED_ACTION);
- intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- }
private void onCreateBondingResult(String address, int result) {
address = address.toUpperCase();
if (result == BluetoothError.SUCCESS) {
mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
+ if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
+ mBluetoothService.getBondState().clearPinAttempts(address);
+ }
+ } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
+ mBluetoothService.getBondState().getAttempt(address) == 1) {
+ mBluetoothService.getBondState().addAutoPairingFailure(address);
+ pairingAttempt(address, result);
+ } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
+ mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
+ pairingAttempt(address, result);
} else {
mBluetoothService.getBondState().setBondState(address,
BluetoothDevice.BOND_NOT_BONDED, result);
+ if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
+ mBluetoothService.getBondState().clearPinAttempts(address);
+ }
+ }
+ }
+
+ private void pairingAttempt(String address, int result) {
+ // This happens when our initial guess of "0000" as the pass key
+ // fails. Try to create the bond again and display the pin dialog
+ // to the user. Use back-off while posting the delayed
+ // message. The initial value is
+ // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
+ // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
+ // reached, display an error to the user.
+ int attempt = mBluetoothService.getBondState().getAttempt(address);
+ if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
+ MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
+ mBluetoothService.getBondState().clearPinAttempts(address);
+ mBluetoothService.getBondState().setBondState(address,
+ BluetoothDevice.BOND_NOT_BONDED, result);
+ return;
+ }
+
+ Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+ message.obj = address;
+ boolean postResult = mHandler.sendMessageDelayed(message,
+ attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+ if (!postResult) {
+ mBluetoothService.getBondState().clearPinAttempts(address);
+ mBluetoothService.getBondState().setBondState(address,
+ BluetoothDevice.BOND_NOT_BONDED, result);
+ return;
}
+ mBluetoothService.getBondState().attempt(address);
}
- public void onBondingCreated(String address) {
+ private void onBondingCreated(String address) {
mBluetoothService.getBondState().setBondState(address.toUpperCase(),
BluetoothDevice.BOND_BONDED);
}
- public void onBondingRemoved(String address) {
+ private void onBondingRemoved(String address) {
mBluetoothService.getBondState().setBondState(address.toUpperCase(),
BluetoothDevice.BOND_NOT_BONDED, BluetoothDevice.UNBOND_REASON_REMOVED);
}
- public void onNameChanged(String name) {
+ private void onNameChanged(String name) {
Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION);
intent.putExtra(BluetoothIntent.NAME, name);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
- public void onPasskeyAgentRequest(String address, int nativeData) {
+ private void onPasskeyAgentRequest(String address, int nativeData) {
address = address.toUpperCase();
mPasskeyAgentRequestData.put(address, new Integer(nativeData));
@@ -272,35 +322,65 @@ class BluetoothEventLoop {
case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
- if (mBluetoothService.getBondState().getAttempt(address) < 1) {
+ if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) &&
+ !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) {
mBluetoothService.getBondState().attempt(address);
mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
return;
}
- }
+ }
}
Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
- public void onPasskeyAgentCancel(String address) {
+ private void onPasskeyAgentCancel(String address) {
address = address.toUpperCase();
mPasskeyAgentRequestData.remove(address);
Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_NOT_BONDED,
- BluetoothDevice.UNBOND_REASON_CANCELLED);
+ BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
+ }
+
+ private boolean onAuthAgentAuthorize(String address, String service, String uuid) {
+ boolean authorized = false;
+ if (service.endsWith("service_audio")) {
+ BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
+ authorized = a2dp.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF;
+ if (authorized) {
+ Log.i(TAG, "Allowing incoming A2DP connection from " + address);
+ } else {
+ Log.i(TAG, "Rejecting incoming A2DP connection from " + address);
+ }
+ } else {
+ Log.i(TAG, "Rejecting incoming " + service + " connection from " + address);
+ }
+ return authorized;
+ }
+
+ private void onAuthAgentCancel(String address, String service, String uuid) {
+ // We immediately response to DBUS Authorize() so this should not
+ // usually happen
+ log("onAuthAgentCancel(" + address + ", " + service + ", " + uuid + ")");
}
private void onGetRemoteServiceChannelResult(String address, int channel) {
IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address);
if (callback != null) {
+ mGetRemoteServiceChannelCallbacks.remove(address);
try {
callback.onGetRemoteServiceChannelResult(address, channel);
} catch (RemoteException e) {}
- mGetRemoteServiceChannelCallbacks.remove(address);
+ }
+ }
+
+ private void onRestartRequired() {
+ if (mBluetoothService.isEnabled()) {
+ Log.e(TAG, "*** A serious error occured (did hcid crash?) - restarting Bluetooth ***");
+ mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH);
}
}
diff --git a/core/java/android/server/checkin/CheckinProvider.java b/core/java/android/server/checkin/CheckinProvider.java
deleted file mode 100644
index 86ece4a..0000000
--- a/core/java/android/server/checkin/CheckinProvider.java
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- * 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.server.checkin;
-
-import android.content.ContentProvider;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.BaseColumns;
-import android.provider.Checkin;
-import android.util.Log;
-
-import java.io.File;
-
-/**
- * Content provider for the database used to store events and statistics
- * while they wait to be uploaded by the checkin service.
- */
-public class CheckinProvider extends ContentProvider {
- /** Class identifier for logging. */
- private static final String TAG = "CheckinProvider";
-
- /** Filename of database (in /data directory). */
- private static final String DATABASE_FILENAME = "checkin.db";
-
- /** Version of database schema. */
- private static final int DATABASE_VERSION = 1;
-
- /** Maximum number of events recorded. */
- private static final int EVENT_LIMIT = 1000;
-
- /** Maximum size of individual event data. */
- private static final int EVENT_SIZE = 8192;
-
- /** Maximum number of crashes recorded. */
- private static final int CRASH_LIMIT = 25;
-
- /** Maximum size of individual crashes recorded. */
- private static final int CRASH_SIZE = 16384;
-
- /** Permission required for access to the 'properties' database. */
- private static final String PROPERTIES_PERMISSION =
- "android.permission.ACCESS_CHECKIN_PROPERTIES";
-
- /** Lock for stats read-modify-write update cycle (see {@link #insert}). */
- private final Object mStatsLock = new Object();
-
- /** The underlying SQLite database. */
- private SQLiteOpenHelper mOpenHelper;
-
- private static class OpenHelper extends SQLiteOpenHelper {
- public OpenHelper(Context context) {
- super(context, DATABASE_FILENAME, null, DATABASE_VERSION);
-
- // The database used to live in /data/checkin.db.
- File oldLocation = Environment.getDataDirectory();
- File old = new File(oldLocation, DATABASE_FILENAME);
- File file = context.getDatabasePath(DATABASE_FILENAME);
-
- // Try to move the file to the new location.
- // TODO: Remove this code before shipping.
- if (old.exists() && !file.exists() && !old.renameTo(file)) {
- Log.e(TAG, "Can't rename " + old + " to " + file);
- }
- if (old.exists() && !old.delete()) {
- // Clean up the old data file in any case.
- Log.e(TAG, "Can't remove " + old);
- }
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + Checkin.Events.TABLE_NAME + " (" +
- BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Checkin.Events.TAG + " TEXT NOT NULL," +
- Checkin.Events.VALUE + " TEXT DEFAULT \"\"," +
- Checkin.Events.DATE + " INTEGER NOT NULL)");
-
- db.execSQL("CREATE INDEX events_index ON " +
- Checkin.Events.TABLE_NAME + " (" +
- Checkin.Events.TAG + ")");
-
- db.execSQL("CREATE TABLE " + Checkin.Stats.TABLE_NAME + " (" +
- BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Checkin.Stats.TAG + " TEXT UNIQUE," +
- Checkin.Stats.COUNT + " INTEGER DEFAULT 0," +
- Checkin.Stats.SUM + " REAL DEFAULT 0.0)");
-
- db.execSQL("CREATE TABLE " + Checkin.Crashes.TABLE_NAME + " (" +
- BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Checkin.Crashes.DATA + " TEXT NOT NULL," +
- Checkin.Crashes.LOGS + " TEXT)");
-
- db.execSQL("CREATE TABLE " + Checkin.Properties.TABLE_NAME + " (" +
- BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Checkin.Properties.TAG + " TEXT UNIQUE ON CONFLICT REPLACE,"
- + Checkin.Properties.VALUE + " TEXT DEFAULT \"\")");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int old, int version) {
- db.execSQL("DROP TABLE IF EXISTS " + Checkin.Events.TABLE_NAME);
- db.execSQL("DROP TABLE IF EXISTS " + Checkin.Stats.TABLE_NAME);
- db.execSQL("DROP TABLE IF EXISTS " + Checkin.Crashes.TABLE_NAME);
- db.execSQL("DROP TABLE IF EXISTS " + Checkin.Properties.TABLE_NAME);
- onCreate(db);
- }
- }
-
- @Override public boolean onCreate() {
- mOpenHelper = new OpenHelper(getContext());
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] select,
- String where, String[] args, String sort) {
- checkPermissions(uri);
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables(uri.getPathSegments().get(0));
- if (uri.getPathSegments().size() == 2) {
- qb.appendWhere("_id=" + ContentUris.parseId(uri));
- } else if (uri.getPathSegments().size() != 1) {
- throw new IllegalArgumentException("Invalid query URI: " + uri);
- }
-
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- Cursor cursor = qb.query(db, select, where, args, null, null, sort);
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
- return cursor;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- checkPermissions(uri);
- if (uri.getPathSegments().size() != 1) {
- throw new IllegalArgumentException("Invalid insert URI: " + uri);
- }
-
- long id;
- String table = uri.getPathSegments().get(0);
- if (Checkin.Events.TABLE_NAME.equals(table)) {
- id = insertEvent(values);
- } else if (Checkin.Stats.TABLE_NAME.equals(table)) {
- id = insertStats(values);
- } else if (Checkin.Crashes.TABLE_NAME.equals(table)) {
- id = insertCrash(values);
- } else {
- id = mOpenHelper.getWritableDatabase().insert(table, null, values);
- }
-
- if (id < 0) {
- return null;
- } else {
- uri = ContentUris.withAppendedId(uri, id);
- getContext().getContentResolver().notifyChange(uri, null);
- return uri;
- }
- }
-
- /**
- * Insert an entry into the events table.
- * Trims old events from the table to keep the size bounded.
- * @param values to insert
- * @return the row ID of the new entry
- */
- private long insertEvent(ContentValues values) {
- String value = values.getAsString(Checkin.Events.VALUE);
- if (value != null && value.length() > EVENT_SIZE) {
- // Event values are readable text, so they can be truncated.
- value = value.substring(0, EVENT_SIZE - 3) + "...";
- values.put(Checkin.Events.VALUE, value);
- }
-
- if (!values.containsKey(Checkin.Events.DATE)) {
- values.put(Checkin.Events.DATE, System.currentTimeMillis());
- }
-
- // TODO: Make this more efficient; don't do it on every insert.
- // Also, consider keeping the most recent instance of every tag,
- // and possibly update a counter when events are deleted.
-
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- db.execSQL("DELETE FROM " +
- Checkin.Events.TABLE_NAME + " WHERE " +
- Checkin.Events._ID + " IN (SELECT " +
- Checkin.Events._ID + " FROM " +
- Checkin.Events.TABLE_NAME + " ORDER BY " +
- Checkin.Events.DATE + " DESC LIMIT -1 OFFSET " +
- (EVENT_LIMIT - 1) + ")");
- return db.insert(Checkin.Events.TABLE_NAME, null, values);
- }
-
- /**
- * Add an entry into the stats table.
- * For statistics, instead of just inserting a row into the database,
- * we add the count and sum values to the existing values (if any)
- * for the specified tag. This must be done with a lock held,
- * to avoid a race condition during the read-modify-write update.
- * @param values to insert
- * @return the row ID of the modified entry
- */
- private long insertStats(ContentValues values) {
- synchronized (mStatsLock) {
- String tag = values.getAsString(Checkin.Stats.TAG);
- if (tag == null) {
- throw new IllegalArgumentException("Tag required:" + values);
- }
-
- // Look for existing values with this tag.
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Cursor cursor = db.query(false,
- Checkin.Stats.TABLE_NAME,
- new String[] {
- Checkin.Stats._ID,
- Checkin.Stats.COUNT,
- Checkin.Stats.SUM
- },
- Checkin.Stats.TAG + "=?",
- new String[] { tag },
- null, null, null, null /* limit */);
-
- try {
- if (cursor == null || !cursor.moveToNext()) {
- // This is a new statistic, insert it directly.
- return db.insert(Checkin.Stats.TABLE_NAME, null, values);
- } else {
- // Depend on SELECT column order to avoid getColumnIndex()
- long id = cursor.getLong(0);
- int count = cursor.getInt(1);
- double sum = cursor.getDouble(2);
-
- Integer countAdd = values.getAsInteger(Checkin.Stats.COUNT);
- if (countAdd != null) count += countAdd.intValue();
-
- Double sumAdd = values.getAsDouble(Checkin.Stats.SUM);
- if (sumAdd != null) sum += sumAdd.doubleValue();
-
- if (count <= 0 && sum == 0.0) {
- // Updated to nothing: delete the row!
- cursor.deleteRow();
- getContext().getContentResolver().notifyChange(
- ContentUris.withAppendedId(Checkin.Stats.CONTENT_URI, id), null);
- return -1;
- } else {
- if (countAdd != null) cursor.updateInt(1, count);
- if (sumAdd != null) cursor.updateDouble(2, sum);
- cursor.commitUpdates();
- return id;
- }
- }
- } finally {
- // Always clean up the cursor.
- if (cursor != null) cursor.close();
- }
- }
- }
-
- /**
- * Add an entry into the crashes table.
- * @param values to insert
- * @return the row ID of the modified entry
- */
- private long insertCrash(ContentValues values) {
- try {
- int crashSize = values.getAsString(Checkin.Crashes.DATA).length();
- if (crashSize > CRASH_SIZE) {
- // The crash is too big. Don't report it, but do log a stat.
- Checkin.updateStats(getContext().getContentResolver(),
- Checkin.Stats.Tag.CRASHES_TRUNCATED, 1, 0.0);
- throw new IllegalArgumentException("Too big: " + crashSize);
- }
-
- // Count the number of crashes reported, even if they roll over.
- Checkin.updateStats(getContext().getContentResolver(),
- Checkin.Stats.Tag.CRASHES_REPORTED, 1, 0.0);
-
- // Trim the crashes database, if needed.
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- db.execSQL("DELETE FROM " +
- Checkin.Crashes.TABLE_NAME + " WHERE " +
- Checkin.Crashes._ID + " IN (SELECT " +
- Checkin.Crashes._ID + " FROM " +
- Checkin.Crashes.TABLE_NAME + " ORDER BY " +
- Checkin.Crashes._ID + " DESC LIMIT -1 OFFSET " +
- (CRASH_LIMIT - 1) + ")");
-
- return db.insert(Checkin.Crashes.TABLE_NAME, null, values);
- } catch (Throwable t) {
- // To avoid an infinite crash-reporting loop, swallow the error.
- Log.e("CheckinProvider", "Error inserting crash: " + t);
- return -1;
- }
- }
-
- // TODO: optimize bulkInsert, especially for stats?
-
- @Override
- public int update(Uri uri, ContentValues values,
- String where, String[] args) {
- checkPermissions(uri);
- if (uri.getPathSegments().size() == 2) {
- if (where != null && where.length() > 0) {
- throw new UnsupportedOperationException(
- "WHERE clause not supported for update: " + uri);
- }
- where = "_id=" + ContentUris.parseId(uri);
- args = null;
- } else if (uri.getPathSegments().size() != 1) {
- throw new IllegalArgumentException("Invalid update URI: " + uri);
- }
-
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- int count = db.update(uri.getPathSegments().get(0), values, where, args);
- getContext().getContentResolver().notifyChange(uri, null);
- return count;
- }
-
- @Override
- public int delete(Uri uri, String where, String[] args) {
- checkPermissions(uri);
- if (uri.getPathSegments().size() == 2) {
- if (where != null && where.length() > 0) {
- throw new UnsupportedOperationException(
- "WHERE clause not supported for delete: " + uri);
- }
- where = "_id=" + ContentUris.parseId(uri);
- args = null;
- } else if (uri.getPathSegments().size() != 1) {
- throw new IllegalArgumentException("Invalid delete URI: " + uri);
- }
-
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- int count = db.delete(uri.getPathSegments().get(0), where, args);
- getContext().getContentResolver().notifyChange(uri, null);
- return count;
- }
-
- @Override
- public String getType(Uri uri) {
- if (uri.getPathSegments().size() == 1) {
- return "vnd.android.cursor.dir/" + uri.getPathSegments().get(0);
- } else if (uri.getPathSegments().size() == 2) {
- return "vnd.android.cursor.item/" + uri.getPathSegments().get(0);
- } else {
- throw new IllegalArgumentException("Invalid URI: " + uri);
- }
- }
-
- /**
- * Make sure the caller has permission to the database.
- * @param uri the caller is requesting access to
- * @throws SecurityException if the caller is forbidden.
- */
- private void checkPermissions(Uri uri) {
- if (uri.getPathSegments().size() < 1) {
- throw new IllegalArgumentException("Invalid query URI: " + uri);
- }
-
- String table = uri.getPathSegments().get(0);
- if (table.equals(Checkin.Properties.TABLE_NAME) &&
- getContext().checkCallingOrSelfPermission(PROPERTIES_PERMISSION) !=
- PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Cannot access checkin properties");
- }
- }
-}
diff --git a/core/java/android/server/checkin/FallbackCheckinService.java b/core/java/android/server/checkin/FallbackCheckinService.java
deleted file mode 100644
index 65921af..0000000
--- a/core/java/android/server/checkin/FallbackCheckinService.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.server.checkin;
-
-import android.os.ICheckinService;
-import android.os.RemoteException;
-import android.os.IParentalControlCallback;
-import com.google.android.net.ParentalControlState;
-
-/**
- * @hide
- */
-public final class FallbackCheckinService extends ICheckinService.Stub {
- public FallbackCheckinService() {
- }
-
- public void reportCrashSync(byte[] crashData) throws RemoteException {
- }
-
- public void reportCrashAsync(byte[] crashData) throws RemoteException {
- }
-
- public void masterClear() throws RemoteException {
- }
-
- public void getParentalControlState(IParentalControlCallback p) throws RemoteException {
- ParentalControlState state = new ParentalControlState();
- state.isEnabled = false;
- p.onResult(state);
- }
-
- public void getParentalControlState(IParentalControlCallback p, String requestingApp)
- throws android.os.RemoteException {
- }
-}
diff --git a/core/java/android/server/checkin/package.html b/core/java/android/server/checkin/package.html
deleted file mode 100644
index 1c9bf9d..0000000
--- a/core/java/android/server/checkin/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
-<body>
- {@hide}
-</body>
-</html>
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index c8f395e..0c04839 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -35,6 +35,7 @@ import android.text.InputType;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
+import android.view.inputmethod.EditorInfo;
import java.io.IOException;
import java.util.ArrayList;
@@ -77,6 +78,7 @@ public final class SearchableInfo implements Parcelable {
private int mIconId = 0;
private int mSearchButtonText = 0;
private int mSearchInputType = 0;
+ private int mSearchImeOptions = 0;
private String mSuggestAuthority = null;
private String mSuggestPath = null;
private String mSuggestSelection = null;
@@ -86,6 +88,16 @@ public final class SearchableInfo implements Parcelable {
private String mSuggestProviderPackage = null;
private Context mCacheActivityContext = null; // use during setup only - don't hold memory!
+ // Flag values for Searchable_voiceSearchMode
+ private static int VOICE_SEARCH_SHOW_BUTTON = 1;
+ private static int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
+ private static int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
+ private int mVoiceSearchMode = 0;
+ private int mVoiceLanguageModeId; // voiceLanguageModel
+ private int mVoicePromptTextId; // voicePromptText
+ private int mVoiceLanguageId; // voiceLanguage
+ private int mVoiceMaxResults; // voiceMaxResults
+
/**
* Set the default searchable activity (when none is specified).
*/
@@ -419,8 +431,9 @@ public final class SearchableInfo implements Parcelable {
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);
+ InputType.TYPE_TEXT_VARIATION_NORMAL);
+ mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions,
+ EditorInfo.IME_ACTION_SEARCH);
setSearchModeFlags();
if (DBG_INHIBIT_SUGGESTIONS == 0) {
@@ -435,6 +448,18 @@ public final class SearchableInfo implements Parcelable {
mSuggestIntentData = a.getString(
com.android.internal.R.styleable.Searchable_searchSuggestIntentData);
}
+ mVoiceSearchMode =
+ a.getInt(com.android.internal.R.styleable.Searchable_voiceSearchMode, 0);
+ // TODO this didn't work - came back zero from YouTube
+ mVoiceLanguageModeId =
+ a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguageModel, 0);
+ mVoicePromptTextId =
+ a.getResourceId(com.android.internal.R.styleable.Searchable_voicePromptText, 0);
+ mVoiceLanguageId =
+ a.getResourceId(com.android.internal.R.styleable.Searchable_voiceLanguage, 0);
+ mVoiceMaxResults =
+ a.getInt(com.android.internal.R.styleable.Searchable_voiceMaxResults, 0);
+
a.recycle();
// get package info for suggestions provider (if any)
@@ -462,11 +487,6 @@ public final class SearchableInfo implements Parcelable {
* Convert searchmode to flags.
*/
private void setSearchModeFlags() {
- // decompose searchMode attribute
- // TODO How do I reconcile these hardcoded values with the flag bits defined in
- // in attrs.xml? e.g. android.R.id.filterMode = 0x010200a4 instead of just "1"
- /* mFilterMode = (0 != (mSearchMode & 1)); */
- /* mQuickStart = (0 != (mSearchMode & 2)); */
mBadgeLabel = (0 != (mSearchMode & 4));
mBadgeIcon = (0 != (mSearchMode & 8)) && (mIconId != 0);
mQueryRewriteFromData = (0 != (mSearchMode & 0x10));
@@ -654,6 +674,59 @@ public final class SearchableInfo implements Parcelable {
}
/**
+ * @return true if android:voiceSearchMode="showVoiceSearchButton"
+ */
+ public boolean getVoiceSearchEnabled() {
+ return 0 != (mVoiceSearchMode & VOICE_SEARCH_SHOW_BUTTON);
+ }
+
+ /**
+ * @return true if android:voiceSearchMode="launchWebSearch"
+ */
+ public boolean getVoiceSearchLaunchWebSearch() {
+ return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_WEB_SEARCH);
+ }
+
+ /**
+ * @return true if android:voiceSearchMode="launchRecognizer"
+ */
+ public boolean getVoiceSearchLaunchRecognizer() {
+ return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_RECOGNIZER);
+ }
+
+ /**
+ * @return the resource Id of the language model string, if specified in the searchable
+ * activity's metadata, or 0 if not specified.
+ */
+ public int getVoiceLanguageModeId() {
+ return mVoiceLanguageModeId;
+ }
+
+ /**
+ * @return the resource Id of the voice prompt text string, if specified in the searchable
+ * activity's metadata, or 0 if not specified.
+ */
+ public int getVoicePromptTextId() {
+ return mVoicePromptTextId;
+ }
+
+ /**
+ * @return the resource Id of the spoken langauge, if specified in the searchable
+ * activity's metadata, or 0 if not specified.
+ */
+ public int getVoiceLanguageId() {
+ return mVoiceLanguageId;
+ }
+
+ /**
+ * @return the max results count, if specified in the searchable
+ * activity's metadata, or 0 if not specified.
+ */
+ public int getVoiceMaxResults() {
+ return mVoiceMaxResults;
+ }
+
+ /**
* Return the resource Id of replacement text for the "Search" button.
*
* @return Returns the resource Id, or 0 if not specified by this package.
@@ -673,6 +746,17 @@ public final class SearchableInfo implements Parcelable {
}
/**
+ * Return the input method options specified in the searchable attributes.
+ * This will default to EditorInfo.ACTION_SEARCH if not specified (which is
+ * appropriate for a search box).
+ *
+ * @return the input type
+ */
+ public int getImeOptions() {
+ return mSearchImeOptions;
+ }
+
+ /**
* Return the list of searchable activities, for use in the drop-down.
*/
public static ArrayList<SearchableInfo> getSearchablesList() {
@@ -712,6 +796,7 @@ public final class SearchableInfo implements Parcelable {
mIconId = in.readInt();
mSearchButtonText = in.readInt();
mSearchInputType = in.readInt();
+ mSearchImeOptions = in.readInt();
setSearchModeFlags();
mSuggestAuthority = in.readString();
@@ -727,6 +812,12 @@ public final class SearchableInfo implements Parcelable {
}
mSuggestProviderPackage = in.readString();
+
+ mVoiceSearchMode = in.readInt();
+ mVoiceLanguageModeId = in.readInt();
+ mVoicePromptTextId = in.readInt();
+ mVoiceLanguageId = in.readInt();
+ mVoiceMaxResults = in.readInt();
}
public int describeContents() {
@@ -742,6 +833,7 @@ public final class SearchableInfo implements Parcelable {
dest.writeInt(mIconId);
dest.writeInt(mSearchButtonText);
dest.writeInt(mSearchInputType);
+ dest.writeInt(mSearchImeOptions);
dest.writeString(mSuggestAuthority);
dest.writeString(mSuggestPath);
@@ -764,5 +856,11 @@ public final class SearchableInfo implements Parcelable {
}
dest.writeString(mSuggestProviderPackage);
+
+ dest.writeInt(mVoiceSearchMode);
+ dest.writeInt(mVoiceLanguageModeId);
+ dest.writeInt(mVoicePromptTextId);
+ dest.writeInt(mVoiceLanguageId);
+ dest.writeInt(mVoiceMaxResults);
}
}
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index abbf8a7..987e763 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -22,8 +22,6 @@ import android.content.Intent;
/**
* Constants for supporting speech recognition through starting an {@link Intent}
- *
- * @hide {pending API council review}
*/
public class RecognizerIntent {
private RecognizerIntent() {
@@ -32,7 +30,8 @@ public class RecognizerIntent {
/**
* Starts an activity that will prompt the user for speech and sends it through a
- * speech recognizer.
+ * speech recognizer. The results will be returned via activity results, or forwarded
+ * via a PendingIntent if one is provided.
*
* <p>Required extras:
* <ul>
@@ -41,9 +40,11 @@ public class RecognizerIntent {
*
* <p>Optional extras:
* <ul>
- * <li>{@link Intent#EXTRA_PROMPT}
+ * <li>{@link #EXTRA_PROMPT}
* <li>{@link #EXTRA_LANGUAGE}
* <li>{@link #EXTRA_MAX_RESULTS}
+ * <li>{@link #EXTRA_RESULTS_PENDINGINTENT}
+ * <li>{@link #EXTRA_RESULTS_PENDINGINTENT_BUNDLE}
* </ul>
*
* <p> Result extras:
@@ -57,6 +58,32 @@ public class RecognizerIntent {
public static final String ACTION_RECOGNIZE_SPEECH = "android.speech.action.RECOGNIZE_SPEECH";
/**
+ * Starts an activity that will prompt the user for speech, sends it through a
+ * speech recognizer, and invokes and displays a web search result.
+ *
+ * <p>Required extras:
+ * <ul>
+ * <li>{@link #EXTRA_LANGUAGE_MODEL}
+ * </ul>
+ *
+ * <p>Optional extras:
+ * <ul>
+ * <li>{@link #EXTRA_PROMPT}
+ * <li>{@link #EXTRA_LANGUAGE}
+ * <li>{@link #EXTRA_MAX_RESULTS}
+ * </ul>
+ *
+ * <p> Result extras:
+ * <ul>
+ * <li>{@link #EXTRA_RESULTS}
+ * </ul>
+ *
+ * <p>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_WEB_SEARCH = "android.speech.action.WEB_SEARCH";
+
+ /**
* 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
@@ -65,27 +92,51 @@ public class RecognizerIntent {
* @see #LANGUAGE_MODEL_FREE_FORM
* @see #LANGUAGE_MODEL_WEB_SEARCH
*/
- public static final String EXTRA_LANGUAGE_MODEL = "language_model";
+ public static final String EXTRA_LANGUAGE_MODEL = "android.speech.extra.LANGUAGE_MODEL";
- /** Free form speech recognition */
+ /**
+ * Use a language model based on free-form speech recognition. This is a value to use for
+ * {@link #EXTRA_LANGUAGE_MODEL}.
+ * @see #EXTRA_LANGUAGE_MODEL
+ */
public static final String LANGUAGE_MODEL_FREE_FORM = "free_form";
- /** Use a language model based on web search terms */
+ /**
+ * Use a language model based on web search terms. This is a value to use for
+ * {@link #EXTRA_LANGUAGE_MODEL}.
+ * @see #EXTRA_LANGUAGE_MODEL
+ */
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";
+ public static final String EXTRA_PROMPT = "android.speech.extra.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";
+ public static final String EXTRA_LANGUAGE = "android.speech.extra.LANGUAGE";
/**
* 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";
+ public static final String EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS";
+
+ /**
+ * When the intent is {@link #ACTION_RECOGNIZE_SPEECH}, the speech input activity will
+ * return results to you via the activity results mechanism. Alternatively, if you use this
+ * extra to supply a PendingIntent, the results will be added to its bundle and the
+ * PendingIntent will be sent to its target.
+ */
+ public static final String EXTRA_RESULTS_PENDINGINTENT =
+ "android.speech.extra.RESULTS_PENDINGINTENT";
+ /**
+ * If you use {@link #EXTRA_RESULTS_PENDINGINTENT} to supply a forwarding intent, you can
+ * also use this extra to supply additional extras for the final intent. The search results
+ * will be added to this bundle, and the combined bundle will be sent to the target.
+ */
+ public static final String EXTRA_RESULTS_PENDINGINTENT_BUNDLE =
+ "android.speech.extra.RESULTS_PENDINGINTENT_BUNDLE";
/** Result code returned when no matches are found for the given speech */
public static final int RESULT_NO_MATCH = Activity.RESULT_FIRST_USER;
@@ -102,5 +153,5 @@ public class RecognizerIntent {
* An ArrayList<String> 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";
+ public static final String EXTRA_RESULTS = "android.speech.extra.RESULTS";
}
diff --git a/core/java/android/speech/srec/MicrophoneInputStream.java b/core/java/android/speech/srec/MicrophoneInputStream.java
index 160a003..fab77a9 100644
--- a/core/java/android/speech/srec/MicrophoneInputStream.java
+++ b/core/java/android/speech/srec/MicrophoneInputStream.java
@@ -1,5 +1,5 @@
/*---------------------------------------------------------------------------*
- * MicrophoneInputStream.java *
+ * MicrophoneInputStream.java *
* *
* Copyright 2007 Nuance Communciations, Inc. *
* *
@@ -45,8 +45,12 @@ public final class MicrophoneInputStream extends InputStream {
*/
public MicrophoneInputStream(int sampleRate, int fifoDepth) throws IOException {
mAudioRecord = AudioRecordNew(sampleRate, fifoDepth);
- if (mAudioRecord == 0) throw new IllegalStateException("not open");
- AudioRecordStart(mAudioRecord);
+ if (mAudioRecord == 0) throw new IOException("AudioRecord constructor failed - busy?");
+ int status = AudioRecordStart(mAudioRecord);
+ if (status != 0) {
+ close();
+ throw new IOException("AudioRecord start failed: " + status);
+ }
}
@Override
@@ -99,7 +103,7 @@ public final class MicrophoneInputStream extends InputStream {
// AudioRecord JNI interface
//
private static native int AudioRecordNew(int sampleRate, int fifoDepth);
- private static native void AudioRecordStart(int audioRecord);
+ private static native int AudioRecordStart(int audioRecord);
private static native int AudioRecordRead(int audioRecord, byte[] b, int offset, int length) throws IOException;
private static native void AudioRecordStop(int audioRecord) throws IOException;
private static native void AudioRecordDelete(int audioRecord) throws IOException;
diff --git a/core/java/android/speech/srec/package.html b/core/java/android/speech/srec/package.html
index 723b30b..9a99df8 100644
--- a/core/java/android/speech/srec/package.html
+++ b/core/java/android/speech/srec/package.html
@@ -1,5 +1,6 @@
<HTML>
<BODY>
Simple, synchronous SREC speech recognition API.
+@hide
</BODY>
</HTML>
diff --git a/core/java/android/text/Annotation.java b/core/java/android/text/Annotation.java
index a3812a8..dbc290b 100644
--- a/core/java/android/text/Annotation.java
+++ b/core/java/android/text/Annotation.java
@@ -16,20 +16,40 @@
package android.text;
+import android.os.Parcel;
+
/**
* Annotations are simple key-value pairs that are preserved across
* TextView save/restore cycles and can be used to keep application-specific
* data that needs to be maintained for regions of text.
*/
-public class Annotation {
- private String mKey;
- private String mValue;
+public class Annotation implements ParcelableSpan {
+ private final String mKey;
+ private final String mValue;
public Annotation(String key, String value) {
mKey = key;
mValue = value;
}
+ public Annotation(Parcel src) {
+ mKey = src.readString();
+ mValue = src.readString();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.ANNOTATION;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mKey);
+ dest.writeString(mValue);
+ }
+
public String getKey() {
return mKey;
}
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 90f5e4c..8495714 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -700,7 +700,41 @@ class HtmlToSpannedConverter implements ContentHandler {
}
public void characters(char ch[], int start, int length) throws SAXException {
- mSpannableStringBuilder.append(CharBuffer.wrap(ch, start, length));
+ StringBuilder sb = new StringBuilder();
+
+ /*
+ * Ignore whitespace that immediately follows other whitespace;
+ * newlines count as spaces.
+ */
+
+ for (int i = 0; i < length; i++) {
+ char c = ch[i + start];
+
+ if (c == ' ' || c == '\n') {
+ char pred;
+ int len = sb.length();
+
+ if (len == 0) {
+ len = mSpannableStringBuilder.length();
+
+ if (len == 0) {
+ pred = '\n';
+ } else {
+ pred = mSpannableStringBuilder.charAt(len - 1);
+ }
+ } else {
+ pred = sb.charAt(len - 1);
+ }
+
+ if (pred != ' ' && pred != '\n') {
+ sb.append(' ');
+ }
+ } else {
+ sb.append(c);
+ }
+ }
+
+ mSpannableStringBuilder.append(sb);
}
public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
index bd86834..d50684a 100644
--- a/core/java/android/text/InputType.java
+++ b/core/java/android/text/InputType.java
@@ -116,14 +116,17 @@ public interface InputType {
/**
* Flag for {@link #TYPE_CLASS_TEXT}: multiple lines of text can be
- * entered into the field.
+ * entered into the field. If this flag is not set, the text field
+ * will be constrained to a single line.
*/
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
+ * Flag for {@link #TYPE_CLASS_TEXT}: the regular text view associated
+ * with this should not be multi-line, but when a fullscreen input method
+ * is providing text it should use multiple lines if it can.
*/
- public static final int TYPE_TEXT_FLAG_SEARCH = 0x00040000;
+ public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 0x00040000;
// ----------------------------------------------------------------------
@@ -149,35 +152,54 @@ public interface InputType {
public static final int TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x00000030;
/**
- * Variation of {@link #TYPE_CLASS_TEXT}: entering the content of
- * an e-mail.
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering a short, possibly informal
+ * message such as an instant message or a text message.
*/
- public static final int TYPE_TEXT_VARIATION_EMAIL_CONTENT = 0x00000040;
+ public static final int TYPE_TEXT_VARIATION_SHORT_MESSAGE = 0x00000040;
/**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering the content of a long, possibly
+ * formal message such as the body of an e-mail.
+ */
+ public static final int TYPE_TEXT_VARIATION_LONG_MESSAGE = 0x00000050;
+
+ /**
* Variation of {@link #TYPE_CLASS_TEXT}: entering the name of a person.
*/
- public static final int TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000050;
+ public static final int TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000060;
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing address.
*/
- public static final int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000060;
+ public static final int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000070;
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering a password.
*/
- public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000070;
+ public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000080;
/**
- * Variation of {@link #TYPE_CLASS_TEXT}: entering a simple text search (e.g. web search)
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering a password, which should
+ * be visible to the user.
*/
- public static final int TYPE_TEXT_VARIATION_SEARCH_STRING = 0x00000080;
+ public static final int TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090;
/**
* Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form.
*/
- public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x00000090;
+ public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x000000a0;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering text to filter contents
+ * of a list etc.
+ */
+ public static final int TYPE_TEXT_VARIATION_FILTER = 0x000000b0;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering text for phonetic
+ * pronunciation, such as a phonetic name field in contacts.
+ */
+ public static final int TYPE_TEXT_VARIATION_PHONETIC = 0x000000c0;
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
diff --git a/core/java/android/text/NoCopySpan.java b/core/java/android/text/NoCopySpan.java
new file mode 100644
index 0000000..0855c0b
--- /dev/null
+++ b/core/java/android/text/NoCopySpan.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+/**
+ * This interface should be added to a span object that should not be copied
+ * into a new Spenned when performing a slice or copy operation on the original
+ * Spanned it was placed in.
+ */
+public interface NoCopySpan {
+ /**
+ * Convenience equivalent for when you would just want a new Object() for
+ * a span but want it to be no-copy. Use this instead.
+ */
+ public class Concrete implements NoCopySpan {
+ }
+}
diff --git a/core/java/android/text/ParcelableSpan.java b/core/java/android/text/ParcelableSpan.java
new file mode 100644
index 0000000..224511a
--- /dev/null
+++ b/core/java/android/text/ParcelableSpan.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.os.Parcelable;
+
+/**
+ * A special kind of Parcelable for objects that will serve as text spans.
+ * This can only be used by code in the framework; it is not intended for
+ * applications to implement their own Parcelable spans.
+ */
+public interface ParcelableSpan extends Parcelable {
+ /**
+ * Return a special type identifier for this span class.
+ */
+ public abstract int getSpanTypeId();
+}
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 44469ec..bb98bce 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -72,7 +72,7 @@ public class Selection {
if (ostart != start || oend != stop) {
text.setSpan(SELECTION_START, start, start,
- Spanned.SPAN_POINT_POINT);
+ Spanned.SPAN_POINT_POINT|Spanned.SPAN_INTERMEDIATE);
text.setSpan(SELECTION_END, stop, stop,
Spanned.SPAN_POINT_POINT);
}
@@ -417,8 +417,8 @@ public class Selection {
}
}
- private static final class START { };
- private static final class END { };
+ private static final class START implements NoCopySpan { };
+ private static final class END implements NoCopySpan { };
/*
* Public constants
diff --git a/core/java/android/text/SpanWatcher.java b/core/java/android/text/SpanWatcher.java
index f99882a..01e82c8 100644
--- a/core/java/android/text/SpanWatcher.java
+++ b/core/java/android/text/SpanWatcher.java
@@ -21,7 +21,7 @@ package android.text;
* will be called to notify it that other markup objects have been
* added, changed, or removed.
*/
-public interface SpanWatcher {
+public interface SpanWatcher extends NoCopySpan {
/**
* This method is called to notify you that the specified object
* has been attached to the specified range of the text.
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 223ce2f..caaafa1 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -70,6 +70,10 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
Object[] spans = sp.getSpans(start, end, Object.class);
for (int i = 0; i < spans.length; i++) {
+ if (spans[i] instanceof NoCopySpan) {
+ continue;
+ }
+
int st = sp.getSpanStart(spans[i]) - start;
int en = sp.getSpanEnd(spans[i]) - start;
int fl = sp.getSpanFlags(spans[i]);
diff --git a/core/java/android/text/Spanned.java b/core/java/android/text/Spanned.java
index bd0a16b..154497d 100644
--- a/core/java/android/text/Spanned.java
+++ b/core/java/android/text/Spanned.java
@@ -106,6 +106,14 @@ extends CharSequence
public static final int SPAN_COMPOSING = 0x100;
/**
+ * This flag will be set for intermediate span changes, meaning there
+ * is guaranteed to be another change following it. Typically it is
+ * used for {@link Selection} which automatically uses this with the first
+ * offset it sets when updating the selection.
+ */
+ public static final int SPAN_INTERMEDIATE = 0x200;
+
+ /**
* The bits numbered SPAN_USER_SHIFT and above are available
* for callers to use to store scalar data associated with their
* span object.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index ceb9f4f..0fef40b 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -576,7 +576,28 @@ extends Layout
if (fmbottom > fitbottom)
fitbottom = fmbottom;
- if (c == ' ' || c == '\t') {
+ /*
+ * From the Unicode Line Breaking Algorithm:
+ * (at least approximately)
+ *
+ * .,:; are class IS: breakpoints
+ * except when adjacent to digits
+ * / is class SY: a breakpoint
+ * except when followed by a digit.
+ * - is class HY: a breakpoint
+ * except when followed by a digit.
+ *
+ * Ideographs are class ID: breakpoints when adjacent.
+ */
+
+ if (c == ' ' || c == '\t' ||
+ ((c == '.' || c == ',' || c == ':' || c == ';') &&
+ (j - 1 < here || !Character.isDigit(chs[j - 1 - start])) &&
+ (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+ ((c == '/' || c == '-') &&
+ (j + 1 >= next || !Character.isDigit(chs[j + 1 - start]))) ||
+ (c >= FIRST_CJK && isIdeographic(c) &&
+ j + 1 < next && isIdeographic(chs[j + 1 - start]))) {
okwidth = w;
ok = j + 1;
@@ -592,6 +613,11 @@ extends Layout
} else if (breakOnlyAtSpaces) {
if (ok != here) {
// Log.e("text", "output ok " + here + " to " +ok);
+
+ while (ok < next && chs[ok - start] == ' ') {
+ ok++;
+ }
+
v = out(source,
here, ok,
okascent, okdescent, oktop, okbottom,
@@ -623,6 +649,11 @@ extends Layout
} else {
if (ok != here) {
// Log.e("text", "output ok " + here + " to " +ok);
+
+ while (ok < next && chs[ok - start] == ' ') {
+ ok++;
+ }
+
v = out(source,
here, ok,
okascent, okdescent, oktop, okbottom,
@@ -739,6 +770,51 @@ extends Layout
}
}
+ private static final char FIRST_CJK = '\u2E80';
+ /**
+ * Returns true if the specified character is one of those specified
+ * as being Ideographic (class ID) by the Unicode Line Breaking Algorithm
+ * (http://www.unicode.org/unicode/reports/tr14/), and is therefore OK
+ * to break between a pair of.
+ */
+ private static final boolean isIdeographic(char c) {
+ if (c >= '\u2E80' && c <= '\u2FFF') {
+ return true; // CJK, KANGXI RADICALS, DESCRIPTION SYMBOLS
+ }
+ if (c == '\u3000') {
+ return true; // IDEOGRAPHIC SPACE
+ }
+ if (c >= '\u3040' && c <= '\u309F') {
+ return true; // Hiragana (except small characters)
+ }
+ if (c >= '\u30A0' && c <= '\u30FF') {
+ return true; // Katakana (except small characters)
+ }
+ if (c >= '\u3400' && c <= '\u4DB5') {
+ return true; // CJK UNIFIED IDEOGRAPHS EXTENSION A
+ }
+ if (c >= '\u4E00' && c <= '\u9FBB') {
+ return true; // CJK UNIFIED IDEOGRAPHS
+ }
+ if (c >= '\uF900' && c <= '\uFAD9') {
+ return true; // CJK COMPATIBILITY IDEOGRAPHS
+ }
+ if (c >= '\uA000' && c <= '\uA48F') {
+ return true; // YI SYLLABLES
+ }
+ if (c >= '\uA490' && c <= '\uA4CF') {
+ return true; // YI RADICALS
+ }
+ if (c >= '\uFE62' && c <= '\uFE66') {
+ return true; // SMALL PLUS SIGN to SMALL EQUALS SIGN
+ }
+ if (c >= '\uFF10' && c <= '\uFF19') {
+ return true; // WIDE DIGITS
+ }
+
+ return false;
+ }
+
/*
private static void dump(byte[] data, int count, String label) {
if (false) {
diff --git a/core/java/android/text/Styled.java b/core/java/android/text/Styled.java
index 05c27ea..0aa2004 100644
--- a/core/java/android/text/Styled.java
+++ b/core/java/android/text/Styled.java
@@ -16,25 +16,26 @@
package android.text;
-import android.graphics.Paint;
import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.graphics.Typeface;
-import android.graphics.MaskFilter;
-import android.graphics.Rasterizer;
-import android.graphics.LayerRasterizer;
-import android.text.style.*;
-
-/* package */ class Styled
+import android.graphics.Paint;
+import android.text.style.CharacterStyle;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+
+/**
+ * This class provides static methods for drawing and measuring styled texts, like
+ * {@link android.text.Spanned} object with {@link android.text.style.ReplacementSpan}.
+ * @hide
+ */
+public class Styled
{
private static float each(Canvas canvas,
Spanned text, int start, int end,
int dir, boolean reverse,
float x, int top, int y, int bottom,
- Paint.FontMetricsInt fm,
- TextPaint realPaint,
+ Paint.FontMetricsInt fmi,
TextPaint paint,
+ TextPaint workPaint,
boolean needwid) {
boolean havewid = false;
@@ -43,9 +44,9 @@ import android.text.style.*;
ReplacementSpan replacement = null;
- realPaint.bgColor = 0;
- realPaint.baselineShift = 0;
- paint.set(realPaint);
+ paint.bgColor = 0;
+ paint.baselineShift = 0;
+ workPaint.set(paint);
if (spans.length > 0) {
for (int i = 0; i < spans.length; i++) {
@@ -55,7 +56,7 @@ import android.text.style.*;
replacement = (ReplacementSpan)span;
}
else {
- span.updateDrawState(paint);
+ span.updateDrawState(workPaint);
}
}
}
@@ -74,66 +75,66 @@ import android.text.style.*;
tmpend = end;
}
- if (fm != null) {
- paint.getFontMetricsInt(fm);
+ if (fmi != null) {
+ workPaint.getFontMetricsInt(fmi);
}
if (canvas != null) {
- if (paint.bgColor != 0) {
- int c = paint.getColor();
- Paint.Style s = paint.getStyle();
- paint.setColor(paint.bgColor);
- paint.setStyle(Paint.Style.FILL);
+ if (workPaint.bgColor != 0) {
+ int c = workPaint.getColor();
+ Paint.Style s = workPaint.getStyle();
+ workPaint.setColor(workPaint.bgColor);
+ workPaint.setStyle(Paint.Style.FILL);
if (!havewid) {
- ret = paint.measureText(tmp, tmpstart, tmpend);
+ ret = workPaint.measureText(tmp, tmpstart, tmpend);
havewid = true;
}
if (dir == Layout.DIR_RIGHT_TO_LEFT)
- canvas.drawRect(x - ret, top, x, bottom, paint);
+ canvas.drawRect(x - ret, top, x, bottom, workPaint);
else
- canvas.drawRect(x, top, x + ret, bottom, paint);
+ canvas.drawRect(x, top, x + ret, bottom, workPaint);
- paint.setStyle(s);
- paint.setColor(c);
+ workPaint.setStyle(s);
+ workPaint.setColor(c);
}
if (dir == Layout.DIR_RIGHT_TO_LEFT) {
if (!havewid) {
- ret = paint.measureText(tmp, tmpstart, tmpend);
+ ret = workPaint.measureText(tmp, tmpstart, tmpend);
havewid = true;
}
canvas.drawText(tmp, tmpstart, tmpend,
- x - ret, y + paint.baselineShift, paint);
+ x - ret, y + workPaint.baselineShift, workPaint);
} else {
if (needwid) {
if (!havewid) {
- ret = paint.measureText(tmp, tmpstart, tmpend);
+ ret = workPaint.measureText(tmp, tmpstart, tmpend);
havewid = true;
}
}
canvas.drawText(tmp, tmpstart, tmpend,
- x, y + paint.baselineShift, paint);
+ x, y + workPaint.baselineShift, workPaint);
}
} else {
if (needwid && !havewid) {
- ret = paint.measureText(tmp, tmpstart, tmpend);
+ ret = workPaint.measureText(tmp, tmpstart, tmpend);
havewid = true;
}
}
} else {
- ret = replacement.getSize(paint, text, start, end, fm);
+ ret = replacement.getSize(workPaint, text, start, end, fmi);
if (canvas != null) {
if (dir == Layout.DIR_RIGHT_TO_LEFT)
replacement.draw(canvas, text, start, end,
- x - ret, top, y, bottom, paint);
+ x - ret, top, y, bottom, workPaint);
else
replacement.draw(canvas, text, start, end,
- x, top, y, bottom, paint);
+ x, top, y, bottom, workPaint);
}
}
@@ -143,15 +144,29 @@ import android.text.style.*;
return ret;
}
- public static int getTextWidths(TextPaint realPaint,
- TextPaint paint,
- Spanned text, int start, int end,
- float[] widths, Paint.FontMetricsInt fm) {
-
+ /**
+ * Return the advance widths for the characters in the string.
+ * See also {@link android.graphics.Paint#getTextWidths(CharSequence, int, int, float[])}.
+ *
+ * @param paint The main {@link TextPaint} object.
+ * @param workPaint The {@link TextPaint} object used for temporal workspace.
+ * @param text The text to measure
+ * @param start The index of the first char to to measure
+ * @param end The end of the text slice to measure
+ * @param widths Array to receive the advance widths of the characters.
+ * Must be at least a large as (end - start).
+ * @param fmi FontMetrics information. Can be null.
+ * @return The actual number of widths returned.
+ */
+ public static int getTextWidths(TextPaint paint,
+ TextPaint workPaint,
+ Spanned text, int start, int end,
+ float[] widths, Paint.FontMetricsInt fmi) {
+ // Keep workPaint as is so that developers reuse the workspace.
MetricAffectingSpan[] spans = text.getSpans(start, end, MetricAffectingSpan.class);
ReplacementSpan replacement = null;
- paint.set(realPaint);
+ workPaint.set(paint);
for (int i = 0; i < spans.length; i++) {
MetricAffectingSpan span = spans[i];
@@ -159,15 +174,15 @@ import android.text.style.*;
replacement = (ReplacementSpan)span;
}
else {
- span.updateMeasureState(paint);
+ span.updateMeasureState(workPaint);
}
}
if (replacement == null) {
- paint.getFontMetricsInt(fm);
- paint.getTextWidths(text, start, end, widths);
+ workPaint.getFontMetricsInt(fmi);
+ workPaint.getTextWidths(text, start, end, widths);
} else {
- int wid = replacement.getSize(paint, text, start, end, fm);
+ int wid = replacement.getSize(workPaint, text, start, end, fmi);
if (end > start) {
widths[0] = wid;
@@ -183,10 +198,10 @@ import android.text.style.*;
CharSequence text, int start, int end,
int dir, boolean reverse,
float x, int top, int y, int bottom,
- Paint.FontMetricsInt fm,
+ Paint.FontMetricsInt fmi,
TextPaint paint,
TextPaint workPaint,
- boolean needwid) {
+ boolean needWidth) {
if (! (text instanceof Spanned)) {
float ret = 0;
@@ -194,22 +209,22 @@ import android.text.style.*;
CharSequence tmp = TextUtils.getReverse(text, start, end);
int tmpend = end - start;
- if (canvas != null || needwid)
+ if (canvas != null || needWidth)
ret = paint.measureText(tmp, 0, tmpend);
if (canvas != null)
canvas.drawText(tmp, 0, tmpend,
x - ret, y, paint);
} else {
- if (needwid)
+ if (needWidth)
ret = paint.measureText(text, start, end);
if (canvas != null)
canvas.drawText(text, start, end, x, y, paint);
}
- if (fm != null) {
- paint.getFontMetricsInt(fm);
+ if (fmi != null) {
+ paint.getFontMetricsInt(fmi);
}
return ret * dir; //Layout.DIR_RIGHT_TO_LEFT == -1
@@ -232,67 +247,129 @@ import android.text.style.*;
next = sp.nextSpanTransition(i, end, division);
x += each(canvas, sp, i, next, dir, reverse,
- x, top, y, bottom, fm, paint, workPaint,
- needwid || next != end);
-
- if (fm != null) {
- if (fm.ascent < asc)
- asc = fm.ascent;
- if (fm.descent > desc)
- desc = fm.descent;
-
- if (fm.top < ftop)
- ftop = fm.top;
- if (fm.bottom > fbot)
- fbot = fm.bottom;
+ x, top, y, bottom, fmi, paint, workPaint,
+ needWidth || next != end);
+
+ if (fmi != null) {
+ if (fmi.ascent < asc)
+ asc = fmi.ascent;
+ if (fmi.descent > desc)
+ desc = fmi.descent;
+
+ if (fmi.top < ftop)
+ ftop = fmi.top;
+ if (fmi.bottom > fbot)
+ fbot = fmi.bottom;
}
}
- if (fm != null) {
+ if (fmi != null) {
if (start == end) {
- paint.getFontMetricsInt(fm);
+ paint.getFontMetricsInt(fmi);
} else {
- fm.ascent = asc;
- fm.descent = desc;
- fm.top = ftop;
- fm.bottom = fbot;
+ fmi.ascent = asc;
+ fmi.descent = desc;
+ fmi.top = ftop;
+ fmi.bottom = fbot;
}
}
return x - ox;
}
- public static float drawText(Canvas canvas,
- CharSequence text, int start, int end,
- int dir, boolean reverse,
- float x, int top, int y, int bottom,
- TextPaint paint,
- TextPaint workPaint,
- boolean needwid) {
- if ((dir == Layout.DIR_RIGHT_TO_LEFT && !reverse)||(reverse && dir == Layout.DIR_LEFT_TO_RIGHT)) {
+
+ /* package */ static float drawText(Canvas canvas,
+ CharSequence text, int start, int end,
+ int direction, boolean reverse,
+ float x, int top, int y, int bottom,
+ TextPaint paint,
+ TextPaint workPaint,
+ boolean needWidth) {
+ if ((direction == Layout.DIR_RIGHT_TO_LEFT && !reverse) ||
+ (reverse && direction == Layout.DIR_LEFT_TO_RIGHT)) {
float ch = foreach(null, text, start, end, Layout.DIR_LEFT_TO_RIGHT,
false, 0, 0, 0, 0, null, paint, workPaint,
true);
- ch *= dir; // DIR_RIGHT_TO_LEFT == -1
- foreach(canvas, text, start, end, -dir,
+ ch *= direction; // DIR_RIGHT_TO_LEFT == -1
+ foreach(canvas, text, start, end, -direction,
reverse, x + ch, top, y, bottom, null, paint,
workPaint, true);
return ch;
}
- return foreach(canvas, text, start, end, dir, reverse,
+ return foreach(canvas, text, start, end, direction, reverse,
x, top, y, bottom, null, paint, workPaint,
- needwid);
+ needWidth);
}
-
+
+ /**
+ * Draw the specified range of text, specified by start/end, with its origin at (x,y),
+ * in the specified Paint. The origin is interpreted based on the Align setting in the
+ * Paint.
+ *
+ * This method considers style information in the text
+ * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method
+ * correctly draws the text).
+ * See also
+ * {@link android.graphics.Canvas#drawText(CharSequence, int, int, float, float, Paint)}
+ * and
+ * {@link android.graphics.Canvas#drawRect(float, float, float, float, Paint)}.
+ *
+ * @param canvas The target canvas.
+ * @param text The text to be drawn
+ * @param start The index of the first character in text to draw
+ * @param end (end - 1) is the index of the last character in text to draw
+ * @param direction The direction of the text. This must be
+ * {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
+ * {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
+ * @param x The x-coordinate of origin for where to draw the text
+ * @param top The top side of the rectangle to be drawn
+ * @param y The y-coordinate of origin for where to draw the text
+ * @param bottom The bottom side of the rectangle to be drawn
+ * @param paint The main {@link TextPaint} object.
+ * @param workPaint The {@link TextPaint} object used for temporal workspace.
+ * @param needWidth If true, this method returns the width of drawn text.
+ * @return Width of the drawn text if needWidth is true.
+ */
+ public static float drawText(Canvas canvas,
+ CharSequence text, int start, int end,
+ int direction,
+ float x, int top, int y, int bottom,
+ TextPaint paint,
+ TextPaint workPaint,
+ boolean needWidth) {
+ // For safety.
+ direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
+ /*
+ * Hided "reverse" parameter since it is meaningless for external developers.
+ * Kept workPaint as is so that developers reuse the workspace.
+ */
+ return drawText(canvas, text, start, end, direction, false,
+ x, top, y, bottom, paint, workPaint, needWidth);
+ }
+
+ /**
+ * Return the width of the text, considering style information in the text
+ * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method
+ * correctly mesures the width of the text).
+ *
+ * @param paint The main {@link TextPaint} object.
+ * @param workPaint The {@link TextPaint} object used for temporal workspace.
+ * @param text The text to measure
+ * @param start The index of the first character to start measuring
+ * @param end 1 beyond the index of the last character to measure
+ * @param fmi FontMetrics information. Can be null
+ * @return The width of the text
+ */
public static float measureText(TextPaint paint,
TextPaint workPaint,
CharSequence text, int start, int end,
- Paint.FontMetricsInt fm) {
+ Paint.FontMetricsInt fmi) {
+ // Keep workPaint as is so that developers reuse the workspace.
return foreach(null, text, start, end,
Layout.DIR_LEFT_TO_RIGHT, false,
- 0, 0, 0, 0, fm, paint, workPaint, true);
+ 0, 0, 0, 0, fmi, paint, workPaint, true);
}
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 405d934..5b4c380 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -236,6 +236,13 @@ public class TextUtils {
return match;
}
+ /**
+ * Create a new String object containing the given range of characters
+ * from the source string. This is different than simply calling
+ * {@link CharSequence#subSequence(int, int) CharSequence.subSequence}
+ * in that it does not preserve any style runs in the source sequence,
+ * allowing a more efficient implementation.
+ */
public static String substring(CharSequence source, int start, int end) {
if (source instanceof String)
return ((String) source).substring(start, end);
@@ -447,13 +454,26 @@ public class TextUtils {
/**
* Returns true if a and b are equal, including if they are both null.
- *
+ * <p><i>Note: In platform versions 1.1 and earlier, this method only worked well if
+ * both the arguments were instances of String.</i></p>
* @param a first CharSequence to check
* @param b second CharSequence to check
* @return true if a and b are equal
*/
public static boolean equals(CharSequence a, CharSequence b) {
- return a == b || (a != null && a.equals(b));
+ if (a == b) return true;
+ int length;
+ if (a != null && b != null && (length = a.length()) == b.length()) {
+ if (a instanceof String && b instanceof String) {
+ return a.equals(b);
+ } else {
+ for (int i = 0; i < length; i++) {
+ if (a.charAt(i) != b.charAt(i)) return false;
+ }
+ return true;
+ }
+ }
+ return false;
}
// XXX currently this only reverses chars, not spans
@@ -510,24 +530,42 @@ public class TextUtils {
private int mEnd;
}
- private static final int ALIGNMENT_SPAN = 1;
- private static final int FOREGROUND_COLOR_SPAN = 2;
- private static final int RELATIVE_SIZE_SPAN = 3;
- private static final int SCALE_X_SPAN = 4;
- private static final int STRIKETHROUGH_SPAN = 5;
- private static final int UNDERLINE_SPAN = 6;
- private static final int STYLE_SPAN = 7;
- private static final int BULLET_SPAN = 8;
- private static final int QUOTE_SPAN = 9;
- private static final int LEADING_MARGIN_SPAN = 10;
- private static final int URL_SPAN = 11;
- private static final int BACKGROUND_COLOR_SPAN = 12;
- private static final int TYPEFACE_SPAN = 13;
- private static final int SUPERSCRIPT_SPAN = 14;
- private static final int SUBSCRIPT_SPAN = 15;
- private static final int ABSOLUTE_SIZE_SPAN = 16;
- private static final int TEXT_APPEARANCE_SPAN = 17;
- private static final int ANNOTATION = 18;
+ /** @hide */
+ public static final int ALIGNMENT_SPAN = 1;
+ /** @hide */
+ public static final int FOREGROUND_COLOR_SPAN = 2;
+ /** @hide */
+ public static final int RELATIVE_SIZE_SPAN = 3;
+ /** @hide */
+ public static final int SCALE_X_SPAN = 4;
+ /** @hide */
+ public static final int STRIKETHROUGH_SPAN = 5;
+ /** @hide */
+ public static final int UNDERLINE_SPAN = 6;
+ /** @hide */
+ public static final int STYLE_SPAN = 7;
+ /** @hide */
+ public static final int BULLET_SPAN = 8;
+ /** @hide */
+ public static final int QUOTE_SPAN = 9;
+ /** @hide */
+ public static final int LEADING_MARGIN_SPAN = 10;
+ /** @hide */
+ public static final int URL_SPAN = 11;
+ /** @hide */
+ public static final int BACKGROUND_COLOR_SPAN = 12;
+ /** @hide */
+ public static final int TYPEFACE_SPAN = 13;
+ /** @hide */
+ public static final int SUPERSCRIPT_SPAN = 14;
+ /** @hide */
+ public static final int SUBSCRIPT_SPAN = 15;
+ /** @hide */
+ public static final int ABSOLUTE_SIZE_SPAN = 16;
+ /** @hide */
+ public static final int TEXT_APPEARANCE_SPAN = 17;
+ /** @hide */
+ public static final int ANNOTATION = 18;
/**
* Flatten a CharSequence and whatever styles can be copied across processes
@@ -555,136 +593,10 @@ public class TextUtils {
prop = ((CharacterStyle) prop).getUnderlying();
}
- if (prop instanceof AlignmentSpan) {
- p.writeInt(ALIGNMENT_SPAN);
- p.writeString(((AlignmentSpan) prop).getAlignment().name());
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof ForegroundColorSpan) {
- p.writeInt(FOREGROUND_COLOR_SPAN);
- p.writeInt(((ForegroundColorSpan) prop).getForegroundColor());
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof RelativeSizeSpan) {
- p.writeInt(RELATIVE_SIZE_SPAN);
- p.writeFloat(((RelativeSizeSpan) prop).getSizeChange());
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof ScaleXSpan) {
- p.writeInt(SCALE_X_SPAN);
- p.writeFloat(((ScaleXSpan) prop).getScaleX());
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof StrikethroughSpan) {
- p.writeInt(STRIKETHROUGH_SPAN);
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof UnderlineSpan) {
- p.writeInt(UNDERLINE_SPAN);
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof StyleSpan) {
- p.writeInt(STYLE_SPAN);
- p.writeInt(((StyleSpan) prop).getStyle());
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof LeadingMarginSpan) {
- if (prop instanceof BulletSpan) {
- p.writeInt(BULLET_SPAN);
- writeWhere(p, sp, o);
- } else if (prop instanceof QuoteSpan) {
- p.writeInt(QUOTE_SPAN);
- p.writeInt(((QuoteSpan) prop).getColor());
- writeWhere(p, sp, o);
- } else {
- p.writeInt(LEADING_MARGIN_SPAN);
- p.writeInt(((LeadingMarginSpan) prop).
- getLeadingMargin(true));
- p.writeInt(((LeadingMarginSpan) prop).
- getLeadingMargin(false));
- writeWhere(p, sp, o);
- }
- }
-
- if (prop instanceof URLSpan) {
- p.writeInt(URL_SPAN);
- p.writeString(((URLSpan) prop).getURL());
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof BackgroundColorSpan) {
- p.writeInt(BACKGROUND_COLOR_SPAN);
- p.writeInt(((BackgroundColorSpan) prop).getBackgroundColor());
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof TypefaceSpan) {
- p.writeInt(TYPEFACE_SPAN);
- p.writeString(((TypefaceSpan) prop).getFamily());
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof SuperscriptSpan) {
- p.writeInt(SUPERSCRIPT_SPAN);
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof SubscriptSpan) {
- p.writeInt(SUBSCRIPT_SPAN);
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof AbsoluteSizeSpan) {
- p.writeInt(ABSOLUTE_SIZE_SPAN);
- p.writeInt(((AbsoluteSizeSpan) prop).getSize());
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof TextAppearanceSpan) {
- TextAppearanceSpan tas = (TextAppearanceSpan) prop;
- p.writeInt(TEXT_APPEARANCE_SPAN);
-
- String tf = tas.getFamily();
- if (tf != null) {
- p.writeInt(1);
- p.writeString(tf);
- } else {
- p.writeInt(0);
- }
-
- p.writeInt(tas.getTextStyle());
- p.writeInt(tas.getTextSize());
-
- ColorStateList csl = tas.getTextColor();
- if (csl == null) {
- p.writeInt(0);
- } else {
- p.writeInt(1);
- csl.writeToParcel(p, parcelableFlags);
- }
-
- csl = tas.getLinkTextColor();
- if (csl == null) {
- p.writeInt(0);
- } else {
- p.writeInt(1);
- csl.writeToParcel(p, parcelableFlags);
- }
-
- writeWhere(p, sp, o);
- }
-
- if (prop instanceof Annotation) {
- p.writeInt(ANNOTATION);
- p.writeString(((Annotation) prop).getKey());
- p.writeString(((Annotation) prop).getValue());
+ if (prop instanceof ParcelableSpan) {
+ ParcelableSpan ps = (ParcelableSpan)prop;
+ p.writeInt(ps.getSpanTypeId());
+ ps.writeToParcel(p, parcelableFlags);
writeWhere(p, sp, o);
}
}
@@ -707,8 +619,7 @@ public class TextUtils {
}
public static final Parcelable.Creator<CharSequence> CHAR_SEQUENCE_CREATOR
- = new Parcelable.Creator<CharSequence>()
- {
+ = new Parcelable.Creator<CharSequence>() {
/**
* Read and return a new CharSequence, possibly with styles,
* from the parcel.
@@ -729,89 +640,75 @@ public class TextUtils {
switch (kind) {
case ALIGNMENT_SPAN:
- readSpan(p, sp, new AlignmentSpan.Standard(
- Layout.Alignment.valueOf(p.readString())));
+ readSpan(p, sp, new AlignmentSpan.Standard(p));
break;
case FOREGROUND_COLOR_SPAN:
- readSpan(p, sp, new ForegroundColorSpan(p.readInt()));
+ readSpan(p, sp, new ForegroundColorSpan(p));
break;
case RELATIVE_SIZE_SPAN:
- readSpan(p, sp, new RelativeSizeSpan(p.readFloat()));
+ readSpan(p, sp, new RelativeSizeSpan(p));
break;
case SCALE_X_SPAN:
- readSpan(p, sp, new ScaleXSpan(p.readFloat()));
+ readSpan(p, sp, new ScaleXSpan(p));
break;
case STRIKETHROUGH_SPAN:
- readSpan(p, sp, new StrikethroughSpan());
+ readSpan(p, sp, new StrikethroughSpan(p));
break;
case UNDERLINE_SPAN:
- readSpan(p, sp, new UnderlineSpan());
+ readSpan(p, sp, new UnderlineSpan(p));
break;
case STYLE_SPAN:
- readSpan(p, sp, new StyleSpan(p.readInt()));
+ readSpan(p, sp, new StyleSpan(p));
break;
case BULLET_SPAN:
- readSpan(p, sp, new BulletSpan());
+ readSpan(p, sp, new BulletSpan(p));
break;
case QUOTE_SPAN:
- readSpan(p, sp, new QuoteSpan(p.readInt()));
+ readSpan(p, sp, new QuoteSpan(p));
break;
case LEADING_MARGIN_SPAN:
- readSpan(p, sp, new LeadingMarginSpan.Standard(p.readInt(),
- p.readInt()));
+ readSpan(p, sp, new LeadingMarginSpan.Standard(p));
break;
case URL_SPAN:
- readSpan(p, sp, new URLSpan(p.readString()));
+ readSpan(p, sp, new URLSpan(p));
break;
case BACKGROUND_COLOR_SPAN:
- readSpan(p, sp, new BackgroundColorSpan(p.readInt()));
+ readSpan(p, sp, new BackgroundColorSpan(p));
break;
case TYPEFACE_SPAN:
- readSpan(p, sp, new TypefaceSpan(p.readString()));
+ readSpan(p, sp, new TypefaceSpan(p));
break;
case SUPERSCRIPT_SPAN:
- readSpan(p, sp, new SuperscriptSpan());
+ readSpan(p, sp, new SuperscriptSpan(p));
break;
case SUBSCRIPT_SPAN:
- readSpan(p, sp, new SubscriptSpan());
+ readSpan(p, sp, new SubscriptSpan(p));
break;
case ABSOLUTE_SIZE_SPAN:
- readSpan(p, sp, new AbsoluteSizeSpan(p.readInt()));
+ readSpan(p, sp, new AbsoluteSizeSpan(p));
break;
case TEXT_APPEARANCE_SPAN:
- readSpan(p, sp, new TextAppearanceSpan(
- p.readInt() != 0
- ? p.readString()
- : null,
- p.readInt(), // style
- p.readInt(), // size
- p.readInt() != 0
- ? ColorStateList.CREATOR.createFromParcel(p)
- : null,
- p.readInt() != 0
- ? ColorStateList.CREATOR.createFromParcel(p)
- : null));
+ readSpan(p, sp, new TextAppearanceSpan(p));
break;
case ANNOTATION:
- readSpan(p, sp,
- new Annotation(p.readString(), p.readString()));
+ readSpan(p, sp, new Annotation(p));
break;
default:
diff --git a/core/java/android/text/TextWatcher.java b/core/java/android/text/TextWatcher.java
index 7456b28..bad09f2 100644
--- a/core/java/android/text/TextWatcher.java
+++ b/core/java/android/text/TextWatcher.java
@@ -20,7 +20,7 @@ package android.text;
* When an object of a type is attached to an Editable, its methods will
* be called when the text is changed.
*/
-public interface TextWatcher {
+public interface TextWatcher extends NoCopySpan {
/**
* This method is called to notify you that, within <code>s</code>,
* the <code>count</code> characters beginning at <code>start</code>
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 73adedf..0dc96c3 100644
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -27,6 +27,7 @@ import com.android.internal.R;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
+import java.util.Locale;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
@@ -188,6 +189,12 @@ public class DateFormat {
*/
public static final char YEAR = 'y';
+
+ private static final Object sLocaleLock = new Object();
+ private static Locale sIs24HourLocale;
+ private static boolean sIs24Hour;
+
+
/**
* Returns true if user preference is set to 24-hour format.
* @param context the context to use for the content resolver
@@ -198,20 +205,34 @@ public class DateFormat {
Settings.System.TIME_12_24);
if (value == null) {
+ Locale locale = context.getResources().getConfiguration().locale;
+
+ synchronized (sLocaleLock) {
+ if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) {
+ return sIs24Hour;
+ }
+ }
+
java.text.DateFormat natural =
java.text.DateFormat.getTimeInstance(
- java.text.DateFormat.LONG,
- context.getResources().getConfiguration().locale);
+ java.text.DateFormat.LONG, locale);
if (natural instanceof SimpleDateFormat) {
SimpleDateFormat sdf = (SimpleDateFormat) natural;
String pattern = sdf.toPattern();
if (pattern.indexOf('H') >= 0) {
- return true;
+ value = "24";
} else {
- return false;
+ value = "12";
}
+ } else {
+ value = "12";
+ }
+
+ synchronized (sLocaleLock) {
+ sIs24HourLocale = locale;
+ sIs24Hour = !value.equals("12");
}
}
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index feae6cf..8a7cdd9 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -595,6 +595,17 @@ public class DateUtils
* @param elapsedSeconds the elapsed time in seconds.
*/
public static String formatElapsedTime(long elapsedSeconds) {
+ return formatElapsedTime(null, elapsedSeconds);
+ }
+
+ /**
+ * Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
+ * for display on the call-in-progress screen.
+ *
+ * @param recycle {@link StringBuilder} to recycle, if possible
+ * @param elapsedSeconds the elapsed time in seconds.
+ */
+ public static String formatElapsedTime(StringBuilder recycle, long elapsedSeconds) {
initFormatStrings();
long hours = 0;
@@ -613,18 +624,24 @@ public class DateUtils
String result;
if (hours > 0) {
- return formatElapsedTime(sElapsedFormatHMMSS, hours, minutes, seconds);
+ return formatElapsedTime(recycle, sElapsedFormatHMMSS, hours, minutes, seconds);
} else {
- return formatElapsedTime(sElapsedFormatMMSS, minutes, seconds);
+ return formatElapsedTime(recycle, sElapsedFormatMMSS, minutes, seconds);
}
}
/**
* Fast formatting of h:mm:ss
*/
- private static String formatElapsedTime(String format, long hours, long minutes, long seconds) {
+ private static String formatElapsedTime(StringBuilder recycle, String format, long hours,
+ long minutes, long seconds) {
if (FAST_FORMAT_HMMSS.equals(format)) {
- StringBuffer sb = new StringBuffer(16);
+ StringBuilder sb = recycle;
+ if (sb == null) {
+ sb = new StringBuilder(8);
+ } else {
+ sb.setLength(0);
+ }
sb.append(hours);
sb.append(TIME_SEPARATOR);
if (minutes < 10) {
@@ -649,9 +666,15 @@ public class DateUtils
/**
* Fast formatting of m:ss
*/
- private static String formatElapsedTime(String format, long minutes, long seconds) {
+ private static String formatElapsedTime(StringBuilder recycle, String format, long minutes,
+ long seconds) {
if (FAST_FORMAT_MMSS.equals(format)) {
- StringBuffer sb = new StringBuffer(16);
+ StringBuilder sb = recycle;
+ if (sb == null) {
+ sb = new StringBuilder(8);
+ } else {
+ sb.setLength(0);
+ }
if (minutes < 10) {
sb.append(TIME_PADDING);
} else {
@@ -1028,8 +1051,9 @@ public class DateUtils
* If FORMAT_NO_YEAR is set, then the year is not shown.
* If neither FORMAT_SHOW_YEAR nor FORMAT_NO_YEAR are set, then the year
* is shown only if it is different from the current year, or if the start
- * and end dates fall on different years.
- *
+ * and end dates fall on different years. If both are set,
+ * FORMAT_SHOW_YEAR takes precedence.
+ *
* <p>
* Normally the date is shown unless the start and end day are the same.
* If FORMAT_SHOW_DATE is set, then the date is always shown, even for
@@ -1120,24 +1144,28 @@ public class DateUtils
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;
-
- Time startDate;
+
+ // If we're getting called with a single instant in time (from
+ // e.g. formatDateTime(), below), then we can skip a lot of
+ // computation below that'd otherwise be thrown out.
+ boolean isInstant = (startMillis == endMillis);
+
+ Time startDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+ startDate.set(startMillis);
+
Time endDate;
-
- if (useUTC) {
- startDate = new Time(Time.TIMEZONE_UTC);
- endDate = new Time(Time.TIMEZONE_UTC);
+ int dayDistance;
+ if (isInstant) {
+ endDate = startDate;
+ dayDistance = 0;
} else {
- startDate = new Time();
- endDate = new Time();
+ endDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+ endDate.set(endMillis);
+ int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
+ int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
+ dayDistance = endJulianDay - startJulianDay;
}
-
- startDate.set(startMillis);
- endDate.set(endMillis);
- int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
- int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
- int dayDistance = endJulianDay - startJulianDay;
-
+
// If the end date ends at 12am at the beginning of a day,
// then modify it to make it look like it ends at midnight on
// the previous day. This will allow us to display "8pm - midnight",
@@ -1152,20 +1180,21 @@ public class DateUtils
// and an end date of Nov 12 at 00:00.
// If the start and end time are the same, then skip this and don't
// adjust the date.
- if ((endDate.hour | endDate.minute | endDate.second) == 0
- && (!showTime || dayDistance <= 1) && (startMillis != endMillis)) {
+ if (!isInstant
+ && (endDate.hour | endDate.minute | endDate.second) == 0
+ && (!showTime || dayDistance <= 1)) {
endDate.monthDay -= 1;
endDate.normalize(true /* ignore isDst */);
}
-
+
int startDay = startDate.monthDay;
int startMonthNum = startDate.month;
int startYear = startDate.year;
-
+
int endDay = endDate.monthDay;
int endMonthNum = endDate.month;
int endYear = endDate.year;
-
+
String startWeekDayString = "";
String endWeekDayString = "";
if (showWeekDay) {
@@ -1176,9 +1205,9 @@ public class DateUtils
weekDayFormat = WEEKDAY_FORMAT;
}
startWeekDayString = startDate.format(weekDayFormat);
- endWeekDayString = endDate.format(weekDayFormat);
+ endWeekDayString = isInstant ? startWeekDayString : endDate.format(weekDayFormat);
}
-
+
String startTimeString = "";
String endTimeString = "";
if (showTime) {
@@ -1204,7 +1233,7 @@ public class DateUtils
boolean capNoon = (flags & FORMAT_CAP_NOON) != 0;
boolean noMidnight = (flags & FORMAT_NO_MIDNIGHT) != 0;
boolean capMidnight = (flags & FORMAT_CAP_MIDNIGHT) != 0;
-
+
boolean startOnTheHour = startDate.minute == 0 && startDate.second == 0;
boolean endOnTheHour = endDate.minute == 0 && endDate.second == 0;
if (abbrevTime && startOnTheHour) {
@@ -1220,20 +1249,41 @@ public class DateUtils
startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
}
}
- if (abbrevTime && endOnTheHour) {
- if (capAMPM) {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
+
+ // Don't waste time on setting endTimeFormat when
+ // we're dealing with an instant, where we'll never
+ // need the end point. (It's the same as the start
+ // point)
+ if (!isInstant) {
+ if (abbrevTime && endOnTheHour) {
+ if (capAMPM) {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
+ }
} else {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
+ if (capAMPM) {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
+ }
}
- } else {
- if (capAMPM) {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
- } else {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
+
+ if (endDate.hour == 12 && endOnTheHour && !noNoon) {
+ if (capNoon) {
+ endTimeFormat = res.getString(com.android.internal.R.string.Noon);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.noon);
+ }
+ } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
+ if (capMidnight) {
+ endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.midnight);
+ }
}
}
-
+
if (startDate.hour == 12 && startOnTheHour && !noNoon) {
if (capNoon) {
startTimeFormat = res.getString(com.android.internal.R.string.Noon);
@@ -1243,37 +1293,32 @@ public class DateUtils
// Don't show the start time starting at midnight. Show
// 12am instead.
}
-
- if (endDate.hour == 12 && endOnTheHour && !noNoon) {
- if (capNoon) {
- endTimeFormat = res.getString(com.android.internal.R.string.Noon);
- } else {
- endTimeFormat = res.getString(com.android.internal.R.string.noon);
- }
- } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
- if (capMidnight) {
- endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
- } else {
- endTimeFormat = res.getString(com.android.internal.R.string.midnight);
- }
- }
}
+
startTimeString = startDate.format(startTimeFormat);
- endTimeString = endDate.format(endTimeFormat);
+ endTimeString = isInstant ? startTimeString : endDate.format(endTimeFormat);
}
-
- // Get the current year
- long millis = System.currentTimeMillis();
- Time time = new Time();
- time.set(millis);
- int currentYear = time.year;
-
+
// Show the year if the user specified FORMAT_SHOW_YEAR or if
// the starting and end years are different from each other
// or from the current year. But don't show the year if the
- // user specified FORMAT_NO_YEAR;
- showYear = showYear || (!noYear && (startYear != endYear || startYear != currentYear));
-
+ // user specified FORMAT_NO_YEAR.
+ if (showYear) {
+ // No code... just a comment for clarity. Keep showYear
+ // on, as they enabled it with FORMAT_SHOW_YEAR. This
+ // takes precedence over them setting FORMAT_NO_YEAR.
+ } else if (noYear) {
+ // They explicitly didn't want a year.
+ showYear = false;
+ } else if (startYear != endYear) {
+ showYear = true;
+ } else {
+ // Show the year if it's not equal to the current year.
+ Time currentTime = new Time();
+ currentTime.setToNow();
+ showYear = startYear != currentTime.year;
+ }
+
String defaultDateFormat, fullFormat, dateRange;
if (numericDate) {
defaultDateFormat = res.getString(com.android.internal.R.string.numeric_date);
@@ -1306,7 +1351,7 @@ public class DateUtils
}
}
}
-
+
if (showWeekDay) {
if (showTime) {
fullFormat = res.getString(com.android.internal.R.string.wday1_date1_time1_wday2_date2_time2);
@@ -1320,20 +1365,20 @@ public class DateUtils
fullFormat = res.getString(com.android.internal.R.string.date1_date2);
}
}
-
+
if (noMonthDay && startMonthNum == endMonthNum) {
// Example: "January, 2008"
String startDateString = startDate.format(defaultDateFormat);
return startDateString;
}
-
+
if (startYear != endYear || noMonthDay) {
// Different year or we are not showing the month day number.
// Example: "December 31, 2007 - January 1, 2008"
// Or: "January - February, 2008"
String startDateString = startDate.format(defaultDateFormat);
String endDateString = endDate.format(defaultDateFormat);
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat,
@@ -1341,7 +1386,7 @@ public class DateUtils
endWeekDayString, endDateString, endTimeString);
return dateRange;
}
-
+
// Get the month, day, and year strings for the start and end dates
String monthFormat;
if (numericDate) {
@@ -1354,16 +1399,17 @@ public class DateUtils
String startMonthString = startDate.format(monthFormat);
String startMonthDayString = startDate.format(MONTH_DAY_FORMAT);
String startYearString = startDate.format(YEAR_FORMAT);
- String endMonthString = endDate.format(monthFormat);
- String endMonthDayString = endDate.format(MONTH_DAY_FORMAT);
- String endYearString = endDate.format(YEAR_FORMAT);
-
+
+ String endMonthString = isInstant ? null : endDate.format(monthFormat);
+ String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT);
+ String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT);
+
if (startMonthNum != endMonthNum) {
// Same year, different month.
// Example: "October 28 - November 3"
// or: "Wed, Oct 31 - Sat, Nov 3, 2007"
// or: "Oct 31, 8am - Sat, Nov 3, 2007, 5pm"
-
+
int index = 0;
if (showWeekDay) index = 1;
if (showYear) index += 2;
@@ -1371,7 +1417,7 @@ public class DateUtils
if (numericDate) index += 8;
int resId = sameYearTable[index];
fullFormat = res.getString(resId);
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat,
@@ -1381,7 +1427,7 @@ public class DateUtils
endYearString, endTimeString);
return dateRange;
}
-
+
if (startDay != endDay) {
// Same month, different day.
int index = 0;
@@ -1391,7 +1437,7 @@ public class DateUtils
if (numericDate) index += 8;
int resId = sameMonthTable[index];
fullFormat = res.getString(resId);
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat,
@@ -1401,19 +1447,19 @@ public class DateUtils
endYearString, endTimeString);
return dateRange;
}
-
+
// Same start and end day
boolean showDate = (flags & FORMAT_SHOW_DATE) != 0;
-
+
// If nothing was specified, then show the date.
if (!showTime && !showDate && !showWeekDay) showDate = true;
-
+
// Compute the time string (example: "10:00 - 11:00 am")
String timeString = "";
if (showTime) {
// If the start and end time are the same, then just show the
// start time.
- if (startMillis == endMillis) {
+ if (isInstant) {
// Same start and end time.
// Example: "10:15 AM"
timeString = startTimeString;
@@ -1423,7 +1469,7 @@ public class DateUtils
timeString = String.format(timeFormat, startTimeString, endTimeString);
}
}
-
+
// Figure out which full format to use.
fullFormat = "";
String dateString = "";
@@ -1457,7 +1503,7 @@ public class DateUtils
} else if (showTime) {
return timeString;
}
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat, timeString, startWeekDayString, dateString);
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
index 5bf9b20..daa99c2 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -394,6 +394,7 @@ public class Time {
*
* @param s the string to parse
* @return true if the resulting time value is in UTC time
+ * @throws android.util.TimeFormatException if s cannot be parsed.
*/
public boolean parse(String s) {
if (nativeParse(s)) {
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 7457439..17c7a6c 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -16,6 +16,7 @@
package android.text.method;
+import android.util.Log;
import android.view.KeyEvent;
import android.text.*;
import android.widget.TextView;
@@ -131,6 +132,16 @@ implements MovementMethod
}
public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
+ if (executeDown(widget, buffer, keyCode)) {
+ MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
+ MetaKeyKeyListener.resetLockedMeta(buffer);
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
boolean handled = false;
switch (keyCode) {
@@ -170,6 +181,20 @@ implements MovementMethod
return false;
}
+ public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
+ int code = event.getKeyCode();
+ if (code != KeyEvent.KEYCODE_UNKNOWN
+ && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+ int repeat = event.getRepeatCount();
+ boolean handled = false;
+ while ((--repeat) > 0) {
+ handled |= executeDown(view, text, code);
+ }
+ return handled;
+ }
+ return false;
+ }
+
public boolean onTrackballEvent(TextView widget, Spannable text,
MotionEvent event) {
return false;
@@ -179,7 +204,7 @@ implements MovementMethod
MotionEvent event) {
boolean handled = Touch.onTouchEvent(widget, buffer, event);
- if (widget.isFocused()) {
+ if (widget.isFocused() && !widget.didTouchFocusSelect()) {
if (event.getAction() == MotionEvent.ACTION_UP) {
int x = (int) event.getX();
int y = (int) event.getY();
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index a875368..6df6a3a 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -18,7 +18,6 @@ package android.text.method;
import android.view.KeyEvent;
import android.view.View;
-import android.text.InputType;
import android.text.*;
import android.text.method.TextKeyListener.Capitalize;
import android.widget.TextView;
@@ -26,7 +25,7 @@ import android.widget.TextView;
public abstract class BaseKeyListener
extends MetaKeyKeyListener
implements KeyListener {
- /* package */ static final Object OLD_SEL_START = new Object();
+ /* package */ static final Object OLD_SEL_START = new NoCopySpan.Concrete();
/**
* Performs the action that happens when you press the DEL key in
@@ -127,5 +126,35 @@ implements KeyListener {
return super.onKeyDown(view, content, keyCode, event);
}
+
+ /**
+ * Base implementation handles ACTION_MULTIPLE KEYCODE_UNKNOWN by inserting
+ * the event's text into the content.
+ */
+ public boolean onKeyOther(View view, Editable content, KeyEvent event) {
+ if (event.getAction() != KeyEvent.ACTION_MULTIPLE
+ || event.getKeyCode() != KeyEvent.KEYCODE_UNKNOWN) {
+ // Not something we are interested in.
+ return false;
+ }
+
+ int selStart, selEnd;
+
+ {
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ selStart = Math.min(a, b);
+ selEnd = Math.max(a, b);
+ }
+
+ CharSequence text = event.getCharacters();
+ if (text == null) {
+ return false;
+ }
+
+ content.replace(selStart, selEnd, text);
+ return true;
+ }
}
diff --git a/core/java/android/text/method/KeyListener.java b/core/java/android/text/method/KeyListener.java
index 4ae6191..8594852 100644
--- a/core/java/android/text/method/KeyListener.java
+++ b/core/java/android/text/method/KeyListener.java
@@ -66,6 +66,13 @@ public interface KeyListener {
int keyCode, KeyEvent event);
/**
+ * If the key listener wants to other kinds of key events, return true,
+ * otherwise return false and the caller (i.e. the widget host)
+ * will handle the key.
+ */
+ public boolean onKeyOther(View view, Editable text, KeyEvent event);
+
+ /**
* Remove the given shift states from the edited text.
*/
public void clearMetaKeyState(View view, Editable content, int states);
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index 92ac531..22e9cc6 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -252,5 +252,5 @@ extends ScrollingMovementMethod
}
private static LinkMovementMethod sInstance;
- private static Object FROM_BELOW = new Object();
+ private static Object FROM_BELOW = new NoCopySpan.Concrete();
}
diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
index d5a473b..61ec67f 100644
--- a/core/java/android/text/method/MetaKeyKeyListener.java
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -71,10 +71,10 @@ public abstract class MetaKeyKeyListener {
| 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();
+ private static final Object CAP = new NoCopySpan.Concrete();
+ private static final Object ALT = new NoCopySpan.Concrete();
+ private static final Object SYM = new NoCopySpan.Concrete();
+ private static final Object SELECTING = new NoCopySpan.Concrete();
/**
* Resets all meta state to inactive.
@@ -159,13 +159,21 @@ public abstract class MetaKeyKeyListener {
/**
* Returns true if this object is one that this class would use to
- * keep track of meta state in the specified text.
+ * keep track of any meta state in the specified text.
*/
public static boolean isMetaTracker(CharSequence text, Object what) {
return what == CAP || what == ALT || what == SYM ||
what == SELECTING;
}
+ /**
+ * Returns true if this object is one that this class would use to
+ * keep track of the selecting meta state in the specified text.
+ */
+ public static boolean isSelectingMetaTracker(CharSequence text, Object what) {
+ return what == SELECTING;
+ }
+
private static void adjust(Spannable content, Object what) {
int current = content.getSpanFlags(what);
@@ -283,10 +291,14 @@ public abstract class MetaKeyKeyListener {
}
public void clearMetaKeyState(View view, Editable content, int states) {
- 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);
+ clearMetaKeyState(content, states);
+ }
+
+ public static void clearMetaKeyState(Editable content, int states) {
+ if ((states&META_SHIFT_ON) != 0) content.removeSpan(CAP);
+ if ((states&META_ALT_ON) != 0) content.removeSpan(ALT);
+ if ((states&META_SYM_ON) != 0) content.removeSpan(SYM);
+ if ((states&META_SELECTING) != 0) content.removeSpan(SELECTING);
}
/**
diff --git a/core/java/android/text/method/MovementMethod.java b/core/java/android/text/method/MovementMethod.java
index 9e37e59..29f67a1 100644
--- a/core/java/android/text/method/MovementMethod.java
+++ b/core/java/android/text/method/MovementMethod.java
@@ -26,6 +26,14 @@ public interface MovementMethod
public void initialize(TextView widget, Spannable text);
public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event);
public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event);
+
+ /**
+ * If the key listener wants to other kinds of key events, return true,
+ * otherwise return false and the caller (i.e. the widget host)
+ * will handle the key.
+ */
+ public boolean onKeyOther(TextView view, Spannable text, KeyEvent event);
+
public void onTakeFocus(TextView widget, Spannable text, int direction);
public boolean onTrackballEvent(TextView widget, Spannable text,
MotionEvent event);
diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java
index 348b658..9270ca5 100644
--- a/core/java/android/text/method/NumberKeyListener.java
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -24,7 +24,6 @@ import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
-import android.util.SparseIntArray;
/**
* For numeric text entry
@@ -102,6 +101,11 @@ public abstract class NumberKeyListener extends BaseKeyListener
selEnd = Math.max(a, b);
}
+ if (selStart < 0 || selEnd < 0) {
+ selStart = selEnd = 0;
+ Selection.setSelection(content, 0);
+ }
+
int i = event != null ? lookup(event, content) : 0;
int repeatCount = event != null ? event.getRepeatCount() : 0;
if (repeatCount == 0) {
diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java
index edaa836..fad4f64 100644
--- a/core/java/android/text/method/PasswordTransformationMethod.java
+++ b/core/java/android/text/method/PasswordTransformationMethod.java
@@ -22,6 +22,7 @@ import android.graphics.Rect;
import android.view.View;
import android.text.Editable;
import android.text.GetChars;
+import android.text.NoCopySpan;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.Selection;
@@ -104,8 +105,10 @@ implements TransformationMethod, TextWatcher
sp.removeSpan(old[i]);
}
- sp.setSpan(new Visible(sp, this), start, start + count,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ if (count == 1) {
+ sp.setSpan(new Visible(sp, this), start, start + count,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
}
}
}
@@ -249,7 +252,8 @@ implements TransformationMethod, TextWatcher
* Used to stash a reference back to the View in the Editable so we
* can use it to check the settings.
*/
- private static class ViewReference extends WeakReference<View> {
+ private static class ViewReference extends WeakReference<View>
+ implements NoCopySpan {
public ViewReference(View v) {
super(v);
}
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index 863b2e2..21bc2a6 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -16,18 +16,12 @@
package android.text.method;
-import android.os.Message;
-import android.os.Handler;
import android.text.*;
import android.text.method.TextKeyListener.Capitalize;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
-import android.widget.TextView;
-
-import java.util.HashMap;
/**
* This is the standard key listener for alphabetic input on qwerty
@@ -302,21 +296,30 @@ public class QwertyKeyListener extends BaseKeyListener {
String old = new String(repl[0].mText);
content.removeSpan(repl[0]);
- content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
- en, en, Spannable.SPAN_POINT_POINT);
- content.replace(st, en, old);
- en = content.getSpanStart(TextKeyListener.INHIBIT_REPLACEMENT);
- if (en - 1 >= 0) {
+ // only cancel the autocomplete if the cursor is at the end of
+ // the replaced span (or after it, because the user is
+ // backspacing over the space after the word, not the word
+ // itself).
+ if (selStart >= en) {
content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
- en - 1, en,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ en, en, Spannable.SPAN_POINT_POINT);
+ content.replace(st, en, old);
+
+ en = content.getSpanStart(TextKeyListener.INHIBIT_REPLACEMENT);
+ if (en - 1 >= 0) {
+ content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
+ en - 1, en,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ content.removeSpan(TextKeyListener.INHIBIT_REPLACEMENT);
+ }
+ adjustMetaAfterKeypress(content);
} else {
- content.removeSpan(TextKeyListener.INHIBIT_REPLACEMENT);
+ adjustMetaAfterKeypress(content);
+ return super.onKeyDown(view, content, keyCode, event);
}
- adjustMetaAfterKeypress(content);
-
return true;
}
}
@@ -442,7 +445,7 @@ public class QwertyKeyListener extends BaseKeyListener {
return Character.toUpperCase(src.charAt(0)) + src.substring(1);
}
- /* package */ static class Replaced
+ /* package */ static class Replaced implements NoCopySpan
{
public Replaced(char[] text) {
mText = text;
diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java
index db470be..563ceed 100644
--- a/core/java/android/text/method/ScrollingMovementMethod.java
+++ b/core/java/android/text/method/ScrollingMovementMethod.java
@@ -144,6 +144,10 @@ implements MovementMethod
}
public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
+ return executeDown(widget, buffer, keyCode);
+ }
+
+ private boolean executeDown(TextView widget, Spannable buffer, int keyCode) {
boolean handled = false;
switch (keyCode) {
@@ -171,6 +175,26 @@ implements MovementMethod
return false;
}
+ public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
+ int code = event.getKeyCode();
+ if (code != KeyEvent.KEYCODE_UNKNOWN
+ && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+ int repeat = event.getRepeatCount();
+ boolean first = true;
+ boolean handled = false;
+ while ((--repeat) > 0) {
+ if (first && executeDown(view, text, code)) {
+ handled = true;
+ MetaKeyKeyListener.adjustMetaAfterKeypress(text);
+ MetaKeyKeyListener.resetLockedMeta(text);
+ }
+ first = false;
+ }
+ return handled;
+ }
+ return false;
+ }
+
public boolean onTrackballEvent(TextView widget, Spannable text,
MotionEvent event) {
return false;
diff --git a/core/java/android/text/method/SingleLineTransformationMethod.java b/core/java/android/text/method/SingleLineTransformationMethod.java
index a4fcf15..6a05fe4 100644
--- a/core/java/android/text/method/SingleLineTransformationMethod.java
+++ b/core/java/android/text/method/SingleLineTransformationMethod.java
@@ -27,22 +27,24 @@ import android.view.View;
/**
* This transformation method causes any newline characters (\n) to be
- * displayed as spaces instead of causing line breaks.
+ * displayed as spaces instead of causing line breaks, and causes
+ * carriage return characters (\r) to have no appearance.
*/
public class SingleLineTransformationMethod
extends ReplacementTransformationMethod {
- private static char[] ORIGINAL = new char[] { '\n' };
- private static char[] REPLACEMENT = new char[] { ' ' };
+ private static char[] ORIGINAL = new char[] { '\n', '\r' };
+ private static char[] REPLACEMENT = new char[] { ' ', '\uFEFF' };
/**
- * The character to be replaced is \n.
+ * The characters to be replaced are \n and \r.
*/
protected char[] getOriginal() {
return ORIGINAL;
}
/**
- * The character \n is replaced with is space.
+ * The character \n is replaced with is space;
+ * the character \r is replaced with is FEFF (zero width space).
*/
protected char[] getReplacement() {
return REPLACEMENT;
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
index b1c380a..5be2a48 100644
--- a/core/java/android/text/method/TextKeyListener.java
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -38,10 +38,10 @@ public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
private static TextKeyListener[] sInstance =
new TextKeyListener[Capitalize.values().length * 2];
- /* package */ static final Object ACTIVE = new Object();
- /* package */ static final Object CAPPED = new Object();
- /* package */ static final Object INHIBIT_REPLACEMENT = new Object();
- /* package */ static final Object LAST_TYPED = new Object();
+ /* package */ static final Object ACTIVE = new NoCopySpan.Concrete();
+ /* package */ static final Object CAPPED = new NoCopySpan.Concrete();
+ /* package */ static final Object INHIBIT_REPLACEMENT = new NoCopySpan.Concrete();
+ /* package */ static final Object LAST_TYPED = new NoCopySpan.Concrete();
private Capitalize mAutoCap;
private boolean mAutoText;
@@ -140,6 +140,13 @@ public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
return im.onKeyUp(view, content, keyCode, event);
}
+ @Override
+ public boolean onKeyOther(View view, Editable content, KeyEvent event) {
+ KeyListener im = getKeyListener(event);
+
+ return im.onKeyOther(view, content, event);
+ }
+
/**
* Clear all the input state (autotext, autocap, multitap, undo)
* from the specified Editable, going beyond Editable.clear(), which
@@ -205,6 +212,10 @@ public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
return false;
}
+ public boolean onKeyOther(View view, Editable content, KeyEvent event) {
+ return false;
+ }
+
public void clearMetaKeyState(View view, Editable content, int states) {
}
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 8b097c5..65036ad 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -17,6 +17,7 @@
package android.text.method;
import android.text.Layout;
+import android.text.NoCopySpan;
import android.text.Layout.Alignment;
import android.text.Spannable;
import android.view.MotionEvent;
@@ -103,7 +104,7 @@ public class Touch {
if (ds.length > 0) {
if (ds[0].mFarEnough == false) {
- int slop = ViewConfiguration.getTouchSlop();
+ int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
if (Math.abs(event.getX() - ds[0].mX) >= slop ||
Math.abs(event.getY() - ds[0].mY) >= slop) {
@@ -141,7 +142,7 @@ public class Touch {
return false;
}
- private static class DragState {
+ private static class DragState implements NoCopySpan {
public float mX;
public float mY;
public boolean mFarEnough;
diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java
index 8f6ed5a..484f8ce 100644
--- a/core/java/android/text/style/AbsoluteSizeSpan.java
+++ b/core/java/android/text/style/AbsoluteSizeSpan.java
@@ -16,17 +16,35 @@
package android.text.style;
-import android.graphics.Paint;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
-public class AbsoluteSizeSpan extends MetricAffectingSpan {
+public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableSpan {
- private int mSize;
+ private final int mSize;
public AbsoluteSizeSpan(int size) {
mSize = size;
}
+ public AbsoluteSizeSpan(Parcel src) {
+ mSize = src.readInt();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.ABSOLUTE_SIZE_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSize);
+ }
+
public int getSize() {
return mSize;
}
diff --git a/core/java/android/text/style/AlignmentSpan.java b/core/java/android/text/style/AlignmentSpan.java
index d51edcc..b8a37da 100644
--- a/core/java/android/text/style/AlignmentSpan.java
+++ b/core/java/android/text/style/AlignmentSpan.java
@@ -16,24 +16,40 @@
package android.text.style;
+import android.os.Parcel;
import android.text.Layout;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
-public interface AlignmentSpan
-extends ParagraphStyle
-{
+public interface AlignmentSpan extends ParagraphStyle {
public Layout.Alignment getAlignment();
public static class Standard
- implements AlignmentSpan
- {
+ implements AlignmentSpan, ParcelableSpan {
public Standard(Layout.Alignment align) {
mAlignment = align;
}
+ public Standard(Parcel src) {
+ mAlignment = Layout.Alignment.valueOf(src.readString());
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.ALIGNMENT_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mAlignment.name());
+ }
+
public Layout.Alignment getAlignment() {
return mAlignment;
}
- private Layout.Alignment mAlignment;
+ private final Layout.Alignment mAlignment;
}
}
diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java
index 27eda69..580a369 100644
--- a/core/java/android/text/style/BackgroundColorSpan.java
+++ b/core/java/android/text/style/BackgroundColorSpan.java
@@ -16,16 +16,36 @@
package android.text.style;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
-public class BackgroundColorSpan extends CharacterStyle implements UpdateAppearance {
+public class BackgroundColorSpan extends CharacterStyle
+ implements UpdateAppearance, ParcelableSpan {
- private int mColor;
+ private final int mColor;
public BackgroundColorSpan(int color) {
mColor = color;
}
+ public BackgroundColorSpan(Parcel src) {
+ mColor = src.readInt();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.BACKGROUND_COLOR_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mColor);
+ }
+
public int getBackgroundColor() {
return mColor;
}
diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java
index 70c4d33..655bd81 100644
--- a/core/java/android/text/style/BulletSpan.java
+++ b/core/java/android/text/style/BulletSpan.java
@@ -18,17 +18,30 @@ package android.text.style;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.os.Parcel;
import android.text.Layout;
+import android.text.ParcelableSpan;
import android.text.Spanned;
+import android.text.TextUtils;
-public class BulletSpan implements LeadingMarginSpan {
+public class BulletSpan implements LeadingMarginSpan, ParcelableSpan {
+ private final int mGapWidth;
+ private final boolean mWantColor;
+ private final int mColor;
+
+ private static final int BULLET_RADIUS = 3;
+ public static final int STANDARD_GAP_WIDTH = 2;
public BulletSpan() {
mGapWidth = STANDARD_GAP_WIDTH;
+ mWantColor = false;
+ mColor = 0;
}
public BulletSpan(int gapWidth) {
mGapWidth = gapWidth;
+ mWantColor = false;
+ mColor = 0;
}
public BulletSpan(int gapWidth, int color) {
@@ -37,6 +50,26 @@ public class BulletSpan implements LeadingMarginSpan {
mColor = color;
}
+ public BulletSpan(Parcel src) {
+ mGapWidth = src.readInt();
+ mWantColor = src.readInt() != 0;
+ mColor = src.readInt();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.BULLET_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mGapWidth);
+ dest.writeInt(mWantColor ? 1 : 0);
+ dest.writeInt(mColor);
+ }
+
public int getLeadingMargin(boolean first) {
return 2 * BULLET_RADIUS + mGapWidth;
}
@@ -66,11 +99,4 @@ public class BulletSpan implements LeadingMarginSpan {
p.setStyle(style);
}
}
-
- private int mGapWidth;
- private boolean mWantColor;
- private int mColor;
-
- private static final int BULLET_RADIUS = 3;
- public static final int STANDARD_GAP_WIDTH = 2;
}
diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java
index 99b3381..476124d 100644
--- a/core/java/android/text/style/ForegroundColorSpan.java
+++ b/core/java/android/text/style/ForegroundColorSpan.java
@@ -16,16 +16,36 @@
package android.text.style;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
-public class ForegroundColorSpan extends CharacterStyle implements UpdateAppearance {
+public class ForegroundColorSpan extends CharacterStyle
+ implements UpdateAppearance, ParcelableSpan {
- private int mColor;
+ private final int mColor;
public ForegroundColorSpan(int color) {
mColor = color;
}
+ public ForegroundColorSpan(Parcel src) {
+ mColor = src.readInt();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.FOREGROUND_COLOR_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mColor);
+ }
+
public int getForegroundColor() {
return mColor;
}
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index 2eebc0d..29c0c76 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -44,8 +44,9 @@ public class ImageSpan extends DynamicDrawableSpan {
public ImageSpan(Bitmap b, int verticalAlignment) {
super(verticalAlignment);
mDrawable = new BitmapDrawable(b);
- mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(),
- mDrawable.getIntrinsicHeight());
+ int width = mDrawable.getIntrinsicWidth();
+ int height = mDrawable.getIntrinsicHeight();
+ mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0);
}
public ImageSpan(Drawable d) {
@@ -87,6 +88,7 @@ public class ImageSpan extends DynamicDrawableSpan {
super(verticalAlignment);
mContext = context;
mContentUri = uri;
+ mSource = uri.toString();
}
public ImageSpan(Context context, int resourceId) {
@@ -116,6 +118,8 @@ public class ImageSpan extends DynamicDrawableSpan {
mContentUri);
bitmap = BitmapFactory.decodeStream(is);
drawable = new BitmapDrawable(bitmap);
+ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight());
is.close();
} catch (Exception e) {
Log.e("sms", "Failed to loaded content " + mContentUri, e);
diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
index 85a27dc..8e212e3 100644
--- a/core/java/android/text/style/LeadingMarginSpan.java
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -18,7 +18,10 @@ package android.text.style;
import android.graphics.Paint;
import android.graphics.Canvas;
+import android.os.Parcel;
import android.text.Layout;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
public interface LeadingMarginSpan
extends ParagraphStyle
@@ -30,9 +33,9 @@ extends ParagraphStyle
CharSequence text, int start, int end,
boolean first, Layout layout);
- public static class Standard
- implements LeadingMarginSpan
- {
+ public static class Standard implements LeadingMarginSpan, ParcelableSpan {
+ private final int mFirst, mRest;
+
public Standard(int first, int rest) {
mFirst = first;
mRest = rest;
@@ -42,6 +45,24 @@ extends ParagraphStyle
this(every, every);
}
+ public Standard(Parcel src) {
+ mFirst = src.readInt();
+ mRest = src.readInt();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.LEADING_MARGIN_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFirst);
+ dest.writeInt(mRest);
+ }
+
public int getLeadingMargin(boolean first) {
return first ? mFirst : mRest;
}
@@ -53,7 +74,5 @@ extends ParagraphStyle
boolean first, Layout layout) {
;
}
-
- private int mFirst, mRest;
}
}
diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java
index 3f4a32f..29dd273 100644
--- a/core/java/android/text/style/QuoteSpan.java
+++ b/core/java/android/text/style/QuoteSpan.java
@@ -18,26 +18,43 @@ package android.text.style;
import android.graphics.Paint;
import android.graphics.Canvas;
-import android.graphics.RectF;
+import android.os.Parcel;
import android.text.Layout;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
-public class QuoteSpan
-implements LeadingMarginSpan
-{
+public class QuoteSpan implements LeadingMarginSpan, ParcelableSpan {
private static final int STRIPE_WIDTH = 2;
private static final int GAP_WIDTH = 2;
- private int mColor = 0xff0000ff;
+ private final int mColor;
public QuoteSpan() {
super();
+ mColor = 0xff0000ff;
}
public QuoteSpan(int color) {
- this();
+ super();
mColor = color;
}
+ public QuoteSpan(Parcel src) {
+ mColor = src.readInt();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.QUOTE_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mColor);
+ }
+
public int getColor() {
return mColor;
}
diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java
index a8ad076..9717362 100644
--- a/core/java/android/text/style/RelativeSizeSpan.java
+++ b/core/java/android/text/style/RelativeSizeSpan.java
@@ -16,17 +16,35 @@
package android.text.style;
-import android.graphics.Paint;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
-public class RelativeSizeSpan extends MetricAffectingSpan {
+public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableSpan {
- private float mProportion;
+ private final float mProportion;
public RelativeSizeSpan(float proportion) {
mProportion = proportion;
}
+ public RelativeSizeSpan(Parcel src) {
+ mProportion = src.readFloat();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.RELATIVE_SIZE_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mProportion);
+ }
+
public float getSizeChange() {
return mProportion;
}
diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java
index ac9e35d..655064b 100644
--- a/core/java/android/text/style/ScaleXSpan.java
+++ b/core/java/android/text/style/ScaleXSpan.java
@@ -16,17 +16,35 @@
package android.text.style;
-import android.graphics.Paint;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
-public class ScaleXSpan extends MetricAffectingSpan {
+public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan {
- private float mProportion;
+ private final float mProportion;
public ScaleXSpan(float proportion) {
mProportion = proportion;
}
+ public ScaleXSpan(Parcel src) {
+ mProportion = src.readFloat();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.SCALE_X_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mProportion);
+ }
+
public float getScaleX() {
return mProportion;
}
diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java
index dd430e5..b51363a 100644
--- a/core/java/android/text/style/StrikethroughSpan.java
+++ b/core/java/android/text/style/StrikethroughSpan.java
@@ -16,9 +16,29 @@
package android.text.style;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
-public class StrikethroughSpan extends CharacterStyle implements UpdateAppearance {
+public class StrikethroughSpan extends CharacterStyle
+ implements UpdateAppearance, ParcelableSpan {
+ public StrikethroughSpan() {
+ }
+
+ public StrikethroughSpan(Parcel src) {
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.STRIKETHROUGH_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ }
@Override
public void updateDrawState(TextPaint ds) {
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index cc8b06c..8e6147c 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -18,7 +18,10 @@ package android.text.style;
import android.graphics.Paint;
import android.graphics.Typeface;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
/**
*
@@ -28,9 +31,9 @@ import android.text.TextPaint;
* you get bold italic. You can't turn off a style from the base style.
*
*/
-public class StyleSpan extends MetricAffectingSpan {
+public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan {
- private int mStyle;
+ private final int mStyle;
/**
*
@@ -42,6 +45,22 @@ public class StyleSpan extends MetricAffectingSpan {
mStyle = style;
}
+ public StyleSpan(Parcel src) {
+ mStyle = src.readInt();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.STYLE_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mStyle);
+ }
+
/**
* Returns the style constant defined in {@link android.graphics.Typeface}.
*/
diff --git a/core/java/android/text/style/SubscriptSpan.java b/core/java/android/text/style/SubscriptSpan.java
index 78d6ba9..de1d8b2 100644
--- a/core/java/android/text/style/SubscriptSpan.java
+++ b/core/java/android/text/style/SubscriptSpan.java
@@ -16,9 +16,29 @@
package android.text.style;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class SubscriptSpan extends MetricAffectingSpan implements ParcelableSpan {
+ public SubscriptSpan() {
+ }
+
+ public SubscriptSpan(Parcel src) {
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.SUBSCRIPT_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ }
-public class SubscriptSpan extends MetricAffectingSpan {
@Override
public void updateDrawState(TextPaint tp) {
tp.baselineShift -= (int) (tp.ascent() / 2);
diff --git a/core/java/android/text/style/SuperscriptSpan.java b/core/java/android/text/style/SuperscriptSpan.java
index 79be4de..285fe84 100644
--- a/core/java/android/text/style/SuperscriptSpan.java
+++ b/core/java/android/text/style/SuperscriptSpan.java
@@ -16,9 +16,29 @@
package android.text.style;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
+
+public class SuperscriptSpan extends MetricAffectingSpan implements ParcelableSpan {
+ public SuperscriptSpan() {
+ }
+
+ public SuperscriptSpan(Parcel src) {
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.SUPERSCRIPT_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ }
-public class SuperscriptSpan extends MetricAffectingSpan {
@Override
public void updateDrawState(TextPaint tp) {
tp.baselineShift += (int) (tp.ascent() / 2);
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index c4ec976..de929e3 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -19,20 +19,22 @@ package android.text.style;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
-import android.graphics.Paint;
import android.graphics.Typeface;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
/**
* Sets the text color, size, style, and typeface to match a TextAppearance
* resource.
*/
-public class TextAppearanceSpan extends MetricAffectingSpan {
- private String mTypeface;
- private int mStyle;
- private int mTextSize;
- private ColorStateList mTextColor;
- private ColorStateList mTextColorLink;
+public class TextAppearanceSpan extends MetricAffectingSpan implements ParcelableSpan {
+ private final String mTypeface;
+ private final int mStyle;
+ private final int mTextSize;
+ private final ColorStateList mTextColor;
+ private final ColorStateList mTextColorLink;
/**
* Uses the specified TextAppearance resource to determine the
@@ -53,11 +55,13 @@ public class TextAppearanceSpan extends MetricAffectingSpan {
*/
public TextAppearanceSpan(Context context, int appearance,
int colorList) {
+ ColorStateList textColor;
+
TypedArray a =
context.obtainStyledAttributes(appearance,
com.android.internal.R.styleable.TextAppearance);
- mTextColor = a.getColorStateList(com.android.internal.R.styleable.
+ textColor = a.getColorStateList(com.android.internal.R.styleable.
TextAppearance_textColor);
mTextColorLink = a.getColorStateList(com.android.internal.R.styleable.
TextAppearance_textColorLink);
@@ -79,6 +83,10 @@ public class TextAppearanceSpan extends MetricAffectingSpan {
case 3:
mTypeface = "monospace";
break;
+
+ default:
+ mTypeface = null;
+ break;
}
a.recycle();
@@ -87,9 +95,11 @@ public class TextAppearanceSpan extends MetricAffectingSpan {
a = context.obtainStyledAttributes(com.android.internal.R.style.Theme,
com.android.internal.R.styleable.Theme);
- mTextColor = a.getColorStateList(colorList);
+ textColor = a.getColorStateList(colorList);
a.recycle();
}
+
+ mTextColor = textColor;
}
/**
@@ -105,6 +115,48 @@ public class TextAppearanceSpan extends MetricAffectingSpan {
mTextColorLink = linkColor;
}
+ public TextAppearanceSpan(Parcel src) {
+ mTypeface = src.readString();
+ mStyle = src.readInt();
+ mTextSize = src.readInt();
+ if (src.readInt() != 0) {
+ mTextColor = ColorStateList.CREATOR.createFromParcel(src);
+ } else {
+ mTextColor = null;
+ }
+ if (src.readInt() != 0) {
+ mTextColorLink = ColorStateList.CREATOR.createFromParcel(src);
+ } else {
+ mTextColorLink = null;
+ }
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.TEXT_APPEARANCE_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mTypeface);
+ dest.writeInt(mStyle);
+ dest.writeInt(mTextSize);
+ if (mTextColor != null) {
+ dest.writeInt(1);
+ mTextColor.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mTextColorLink != null) {
+ dest.writeInt(1);
+ mTextColorLink.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
/**
* Returns the typeface family specified by this span, or <code>null</code>
* if it does not specify one.
diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java
index 7519ac2..f194060 100644
--- a/core/java/android/text/style/TypefaceSpan.java
+++ b/core/java/android/text/style/TypefaceSpan.java
@@ -18,13 +18,16 @@ package android.text.style;
import android.graphics.Paint;
import android.graphics.Typeface;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
/**
* Changes the typeface family of the text to which the span is attached.
*/
-public class TypefaceSpan extends MetricAffectingSpan {
- private String mFamily;
+public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan {
+ private final String mFamily;
/**
* @param family The font family for this typeface. Examples include
@@ -34,6 +37,22 @@ public class TypefaceSpan extends MetricAffectingSpan {
mFamily = family;
}
+ public TypefaceSpan(Parcel src) {
+ mFamily = src.readString();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.TYPEFACE_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mFamily);
+ }
+
/**
* Returns the font family name.
*/
diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java
index 79809b5..d29bfb6 100644
--- a/core/java/android/text/style/URLSpan.java
+++ b/core/java/android/text/style/URLSpan.java
@@ -16,19 +16,39 @@
package android.text.style;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.text.TextPaint;
+import android.os.Parcel;
+import android.provider.Browser;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
import android.view.View;
-public class URLSpan extends ClickableSpan {
+public class URLSpan extends ClickableSpan implements ParcelableSpan {
- private String mURL;
+ private final String mURL;
public URLSpan(String url) {
mURL = url;
}
+ public URLSpan(Parcel src) {
+ mURL = src.readString();
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.URL_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mURL);
+ }
+
public String getURL() {
return mURL;
}
@@ -36,8 +56,9 @@ public class URLSpan extends ClickableSpan {
@Override
public void onClick(View widget) {
Uri uri = Uri.parse(getURL());
+ Context context = widget.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.addCategory(Intent.CATEGORY_BROWSABLE);
- widget.getContext().startActivity(intent);
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
+ context.startActivity(intent);
}
}
diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java
index ca6f10c..b0cb0e8 100644
--- a/core/java/android/text/style/UnderlineSpan.java
+++ b/core/java/android/text/style/UnderlineSpan.java
@@ -16,9 +16,29 @@
package android.text.style;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
import android.text.TextPaint;
+import android.text.TextUtils;
-public class UnderlineSpan extends CharacterStyle implements UpdateAppearance {
+public class UnderlineSpan extends CharacterStyle
+ implements UpdateAppearance, ParcelableSpan {
+ public UnderlineSpan() {
+ }
+
+ public UnderlineSpan(Parcel src) {
+ }
+
+ public int getSpanTypeId() {
+ return TextUtils.UNDERLINE_SPAN;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ }
@Override
public void updateDrawState(TextPaint ds) {
diff --git a/core/java/android/text/util/Regex.java b/core/java/android/text/util/Regex.java
index 4c128ad..a349b82 100644
--- a/core/java/android/text/util/Regex.java
+++ b/core/java/android/text/util/Regex.java
@@ -66,9 +66,9 @@ public class Regex {
public static final Pattern WEB_URL_PATTERN
= Pattern.compile(
"((?:(http|https|Http|Https):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
- + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2}))+(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
- + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2}))+)?\\@)?)?"
- + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]*\\.)+" // named host
+ + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+ + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+ + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}\\.)+" // named host
+ "(?:" // plus top level domain
+ "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ "|(?:biz|b[abdefghijmnorstvwyz])"
@@ -122,12 +122,12 @@ public class Regex {
public static final Pattern EMAIL_ADDRESS_PATTERN
= Pattern.compile(
- "[a-zA-Z0-9\\+\\.\\_\\%\\-]+" +
+ "[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" +
"\\@" +
- "[a-zA-Z0-9][a-zA-Z0-9\\-]*" +
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
"(" +
"\\." +
- "[a-zA-Z0-9][a-zA-Z0-9\\-]*" +
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
")+"
);
diff --git a/core/java/android/text/util/Rfc822Validator.java b/core/java/android/text/util/Rfc822Validator.java
index 9f03bb0..6a6bf69 100644
--- a/core/java/android/text/util/Rfc822Validator.java
+++ b/core/java/android/text/util/Rfc822Validator.java
@@ -16,6 +16,7 @@
package android.text.util;
+import android.text.TextUtils;
import android.widget.AutoCompleteTextView;
import java.util.regex.Pattern;
@@ -67,7 +68,7 @@ public class Rfc822Validator implements AutoCompleteTextView.Validator {
/**
* @return a string in which all the characters that are illegal for the username
- * part of the email address have been removed.
+ * or the domain name part of the email address have been removed.
*/
private String removeIllegalCharacters(String s) {
StringBuilder result = new StringBuilder();
@@ -101,6 +102,9 @@ public class Rfc822Validator implements AutoCompleteTextView.Validator {
* {@inheritDoc}
*/
public CharSequence fixText(CharSequence cs) {
+ // Return an empty string if the email address only contains spaces, \n or \t
+ if (TextUtils.getTrimmedLength(cs) == 0) return "";
+
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs);
StringBuilder sb = new StringBuilder();
@@ -111,10 +115,10 @@ public class Rfc822Validator implements AutoCompleteTextView.Validator {
// If there is no @, just append the domain of the account
tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain);
} else {
- // Otherwise, remove everything right of the '@' and append the domain
- // ("a@b" becomes "a@gmail.com").
+ // Otherwise, remove the illegal characters on both sides of the '@'
String fix = removeIllegalCharacters(text.substring(0, index));
- tokens[i].setAddress(fix + "@" + mDomain);
+ String domain = removeIllegalCharacters(text.substring(index + 1));
+ tokens[i].setAddress(fix + "@" + (domain.length() != 0 ? domain : mDomain));
}
sb.append(tokens[i].toString());
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 8fc3602..9de4cbe 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -16,6 +16,8 @@
package android.util;
+import android.os.*;
+
/**
* A structure describing general information about a display, such as its
@@ -23,6 +25,16 @@ package android.util;
*/
public class DisplayMetrics {
/**
+ * The reference density used throughout the system.
+ *
+ * @hide Pending API council approval
+ */
+ public static final int DEFAULT_DENSITY = 160;
+
+ private static final int sLcdDensity = SystemProperties.getInt("ro.sf.lcd_density",
+ DEFAULT_DENSITY);
+
+ /**
* The absolute width of the display in pixels.
*/
public int widthPixels;
@@ -43,7 +55,9 @@ public class DisplayMetrics {
* example, a 240x320 screen will have a density of 1 even if its width is
* 1.8", 1.3", etc. However, if the screen resolution is increased to
* 320x480 but the screen size remained 1.5"x2" then the density would be
- * increased (probably to 1.5).
+ * increased (probably to 1.5).
+ *
+ * @see #DEFAULT_DENSITY
*/
public float density;
/**
@@ -60,7 +74,7 @@ public class DisplayMetrics {
* The exact physical pixels per inch of the screen in the Y dimension.
*/
public float ydpi;
-
+
public DisplayMetrics() {
}
@@ -76,10 +90,9 @@ public class DisplayMetrics {
public void setToDefaults() {
widthPixels = 0;
heightPixels = 0;
- density = 1;
- scaledDensity = 1;
- xdpi = 160;
- ydpi = 160;
+ density = sLcdDensity / (float) DEFAULT_DENSITY;
+ scaledDensity = density;
+ xdpi = sLcdDensity;
+ ydpi = sLcdDensity;
}
}
-
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index 610cfd4..9ab3b53 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -73,13 +73,20 @@ public class SparseIntArray {
int i = binarySearch(mKeys, 0, mSize, key);
if (i >= 0) {
- System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1));
- System.arraycopy(mValues, i + 1, mValues, i, mSize - (i + 1));
- mSize--;
+ removeAt(i);
}
}
/**
+ * Removes the mapping at the given index.
+ */
+ public void removeAt(int index) {
+ System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+ System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+ mSize--;
+ }
+
+ /**
* Adds a mapping from the specified key to the specified value,
* replacing the previous mapping from the specified key if there
* was one.
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index a4ee35a..d4ba9e2 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -16,9 +16,6 @@
package android.util;
-import android.util.Config;
-import android.util.Log;
-
/**
* Container for a dynamically typed data value. Primarily used with
* {@link android.content.res.Resources} for holding resource values.
@@ -141,6 +138,16 @@ public class TypedValue {
/* ------------------------------------------------------------ */
+ /**
+ * If {@link #density} is equal to this value, then the density should be
+ * treated as the system's default density value: {@link DisplayMetrics#DEFAULT_DENSITY}.
+ *
+ * @hide Pending API council approval
+ */
+ public static final int DENSITY_DEFAULT = 0;
+
+ /* ------------------------------------------------------------ */
+
/** The type held by this value, as defined by the constants here.
* This tells you how to interpret the other fields in the object. */
public int type;
@@ -161,7 +168,14 @@ public class TypedValue {
/** If Value came from a resource, these are the configurations for which
* its contents can change. */
public int changingConfigurations = -1;
-
+
+ /**
+ * If the Value came from a resource, this holds the corresponding pixel density.
+ *
+ * @hide Pending API council approval
+ * */
+ public int density;
+
/* ------------------------------------------------------------ */
/** Return the data for this value as a float. Only use for values
@@ -454,6 +468,7 @@ public class TypedValue {
data = other.data;
assetCookie = other.assetCookie;
resourceId = other.resourceId;
+ density = other.density;
}
public String toString()
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 4048763..15fb839 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -397,7 +397,7 @@ public class FocusFinder {
int numTouchables = touchables.size();
- int edgeSlop = ViewConfiguration.getEdgeSlop();
+ int edgeSlop = ViewConfiguration.get(root.mContext).getScaledEdgeSlop();
Rect closestBounds = new Rect();
Rect touchableBounds = mOtherRect;
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index fc9af05..f7ac522 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -18,6 +18,7 @@ package android.view;
import android.os.Handler;
import android.os.Message;
+import android.content.Context;
/**
* Detects various gestures and events using the supplied {@link MotionEvent}s.
@@ -34,7 +35,6 @@ import android.os.Message;
* </ul>
*/
public class GestureDetector {
-
/**
* The listener that is used to notify when gestures occur.
* If you want to listen for all the different gestures then implement
@@ -113,12 +113,48 @@ public class GestureDetector {
}
/**
- * A convenience class to extend when you only want to listen for a
- * subset of all the gestures. This implements all methods in the
- * {@link OnGestureListener} but does nothing and return {@code false}
- * for all applicable methods.
+ * The listener that is used to notify when a double-tap or a confirmed
+ * single-tap occur.
*/
- public static class SimpleOnGestureListener implements OnGestureListener {
+ public interface OnDoubleTapListener {
+ /**
+ * Notified when a single-tap occurs.
+ * <p>
+ * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
+ * will only be called after the detector is confident that the user's
+ * first tap is not followed by a second tap leading to a double-tap
+ * gesture.
+ *
+ * @param e The down motion event of the single-tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onSingleTapConfirmed(MotionEvent e);
+
+ /**
+ * Notified when a double-tap occurs.
+ *
+ * @param e The down motion event of the first tap of the double-tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTap(MotionEvent e);
+
+ /**
+ * Notified when an event within a double-tap gesture occurs, including
+ * the down, move, and up events.
+ *
+ * @param e The motion event that occurred during the double-tap gesture.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTapEvent(MotionEvent e);
+ }
+
+ /**
+ * A convenience class to extend when you only want to listen for a subset
+ * of all the gestures. This implements all methods in the
+ * {@link OnGestureListener} and {@link OnDoubleTapListener} but does
+ * nothing and return {@code false} for all applicable methods.
+ */
+ public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@@ -142,23 +178,55 @@ public class GestureDetector {
public boolean onDown(MotionEvent e) {
return false;
}
+
+ public boolean onDoubleTap(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return false;
+ }
}
- private static final int TOUCH_SLOP_SQUARE = ViewConfiguration.getTouchSlop()
- * ViewConfiguration.getTouchSlop();
+ // TODO: ViewConfiguration
+ private int mBiggerTouchSlopSquare = 20 * 20;
+
+ private int mTouchSlopSquare;
+ private int mDoubleTapSlopSquare;
+ private int mMinimumFlingVelocity;
+
+ private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+ private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
+ // TODO make new double-tap timeout, and define its events (i.e. either time
+ // between down-down or time between up-down)
+ private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
// constants for Message.what used by GestureHandler below
private static final int SHOW_PRESS = 1;
private static final int LONG_PRESS = 2;
+ private static final int TAP = 3;
private final Handler mHandler;
private final OnGestureListener mListener;
+ private OnDoubleTapListener mDoubleTapListener;
+ private boolean mStillDown;
private boolean mInLongPress;
private boolean mAlwaysInTapRegion;
+ private boolean mAlwaysInBiggerTapRegion;
private MotionEvent mCurrentDownEvent;
- private MotionEvent mCurrentUpEvent;
+ private MotionEvent mPreviousUpEvent;
+
+ /**
+ * True when the user is still touching for the second tap (down, move, and
+ * up events). Can only be true if there is a double tap listener attached.
+ */
+ private boolean mIsDoubleTapping;
private float mLastMotionY;
private float mLastMotionX;
@@ -189,9 +257,16 @@ public class GestureDetector {
case LONG_PRESS:
dispatchLongPress();
break;
+
+ case TAP:
+ // If the user's finger is still down, do not count it as a tap
+ if (mDoubleTapListener != null && !mStillDown) {
+ mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+ }
+ break;
default:
- throw new RuntimeException("Unknown message " + msg); //never
+ throw new RuntimeException("Unknown message " + msg); //never
}
}
}
@@ -203,16 +278,17 @@ public class GestureDetector {
*
* @param listener the listener invoked for all the callbacks, this must
* not be null.
- * @param handler the handler to use, this must
- * not be null.
+ * @param handler the handler to use
*
* @throws NullPointerException if either {@code listener} or
* {@code handler} is null.
+ *
+ * @deprecated Use {@link #GestureDetector(android.content.Context,
+ * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
*/
+ @Deprecated
public GestureDetector(OnGestureListener listener, Handler handler) {
- mHandler = new GestureHandler(handler);
- mListener = listener;
- init();
+ this(null, listener, handler);
}
/**
@@ -222,19 +298,90 @@ public class GestureDetector {
*
* @param listener the listener invoked for all the callbacks, this must
* not be null.
+ *
* @throws NullPointerException if {@code listener} is null.
+ *
+ * @deprecated Use {@link #GestureDetector(android.content.Context,
+ * android.view.GestureDetector.OnGestureListener)} instead.
*/
+ @Deprecated
public GestureDetector(OnGestureListener listener) {
- mHandler = new GestureHandler();
+ this(null, listener, null);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * You may only use this constructor from a UI thread (this is the usual situation).
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public GestureDetector(Context context, OnGestureListener listener) {
+ this(context, listener, null);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * You may only use this constructor from a UI thread (this is the usual situation).
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
+ if (handler != null) {
+ mHandler = new GestureHandler(handler);
+ } else {
+ mHandler = new GestureHandler();
+ }
mListener = listener;
- init();
+ if (listener instanceof OnDoubleTapListener) {
+ setOnDoubleTapListener((OnDoubleTapListener) listener);
+ }
+ init(context);
}
- private void init() {
+ private void init(Context context) {
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
+
+ // Fallback to support pre-donuts releases
+ int touchSlop, doubleTapSlop;
+ if (context == null) {
+ //noinspection deprecation
+ touchSlop = ViewConfiguration.getTouchSlop();
+ doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
+ //noinspection deprecation
+ mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
+ } else {
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ touchSlop = configuration.getScaledTouchSlop();
+ doubleTapSlop = configuration.getScaledDoubleTapSlop();
+ mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+ }
+ mTouchSlopSquare = touchSlop * touchSlop;
+ mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
+ }
+
+ /**
+ * Sets the listener which will be called for double-tap and related
+ * gestures.
+ *
+ * @param onDoubleTapListener the listener invoked for all the callbacks, or
+ * null to stop listening for double-tap gestures.
+ */
+ public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
+ mDoubleTapListener = onDoubleTapListener;
}
/**
@@ -266,9 +413,6 @@ public class GestureDetector {
* else false.
*/
public boolean onTouchEvent(MotionEvent ev) {
- final long tapTime = ViewConfiguration.getTapTimeout();
- final long longpressTime = ViewConfiguration.getLongPressTimeout();
- final int touchSlop = ViewConfiguration.getTouchSlop();
final int action = ev.getAction();
final float y = ev.getY();
final float x = ev.getX();
@@ -282,19 +426,38 @@ public class GestureDetector {
switch (action) {
case MotionEvent.ACTION_DOWN:
+ if (mDoubleTapListener != null) {
+ boolean hadTapMessage = mHandler.hasMessages(TAP);
+ if (hadTapMessage) mHandler.removeMessages(TAP);
+ if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
+ isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
+ // This is a second tap
+ mIsDoubleTapping = true;
+ // Give a callback with the first tap of the double-tap
+ handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
+ // Give a callback with down event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+ } else {
+ // This is a first tap
+ mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
+ }
+ }
+
mLastMotionX = x;
mLastMotionY = y;
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
+ mAlwaysInBiggerTapRegion = true;
+ mStillDown = true;
mInLongPress = false;
-
+
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
- + tapTime + longpressTime);
+ + TAP_TIMEOUT + LONGPRESS_TIMEOUT);
}
- mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + tapTime);
- handled = mListener.onDown(ev);
+ mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
+ handled |= mListener.onDown(ev);
break;
case MotionEvent.ACTION_MOVE:
@@ -303,18 +466,25 @@ public class GestureDetector {
}
final float scrollX = mLastMotionX - x;
final float scrollY = mLastMotionY - y;
- if (mAlwaysInTapRegion) {
+ if (mIsDoubleTapping) {
+ // Give the move events of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+ } else if (mAlwaysInTapRegion) {
final int deltaX = (int) (x - mCurrentDownEvent.getX());
final int deltaY = (int) (y - mCurrentDownEvent.getY());
int distance = (deltaX * deltaX) + (deltaY * deltaY);
- if (distance > TOUCH_SLOP_SQUARE) {
+ if (distance > mTouchSlopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastMotionX = x;
mLastMotionY = y;
mAlwaysInTapRegion = false;
+ mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
+ if (distance > mBiggerTouchSlopSquare) {
+ mAlwaysInBiggerTapRegion = false;
+ }
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastMotionX = x;
@@ -323,12 +493,15 @@ public class GestureDetector {
break;
case MotionEvent.ACTION_UP:
- mCurrentUpEvent = MotionEvent.obtain(ev);
- if (mInLongPress) {
+ mStillDown = false;
+ MotionEvent currentUpEvent = MotionEvent.obtain(ev);
+ if (mIsDoubleTapping) {
+ // Finally, give the up event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
+ } else if (mInLongPress) {
+ mHandler.removeMessages(TAP);
mInLongPress = false;
- break;
- }
- if (mAlwaysInTapRegion) {
+ } else if (mAlwaysInTapRegion) {
handled = mListener.onSingleTapUp(ev);
} else {
@@ -338,21 +511,26 @@ public class GestureDetector {
final float velocityY = velocityTracker.getYVelocity();
final float velocityX = velocityTracker.getXVelocity();
- if ((Math.abs(velocityY) > ViewConfiguration.getMinimumFlingVelocity())
- || (Math.abs(velocityX) > ViewConfiguration.getMinimumFlingVelocity())){
- handled = mListener.onFling(mCurrentDownEvent, mCurrentUpEvent, velocityX, velocityY);
+ if ((Math.abs(velocityY) > mMinimumFlingVelocity)
+ || (Math.abs(velocityX) > mMinimumFlingVelocity)){
+ handled = mListener.onFling(mCurrentDownEvent, currentUpEvent, velocityX, velocityY);
}
}
+ mPreviousUpEvent = MotionEvent.obtain(ev);
mVelocityTracker.recycle();
mVelocityTracker = null;
+ mIsDoubleTapping = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
case MotionEvent.ACTION_CANCEL:
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
mVelocityTracker.recycle();
mVelocityTracker = null;
+ mIsDoubleTapping = false;
+ mStillDown = false;
if (mInLongPress) {
mInLongPress = false;
break;
@@ -361,7 +539,23 @@ public class GestureDetector {
return handled;
}
+ private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
+ MotionEvent secondDown) {
+ if (!mAlwaysInBiggerTapRegion) {
+ return false;
+ }
+
+ if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
+ return false;
+ }
+
+ int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
+ int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
+ return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
+ }
+
private void dispatchLongPress() {
+ mHandler.removeMessages(TAP);
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
new file mode 100644
index 0000000..841066c
--- /dev/null
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Constants to be used to perform haptic feedback effects via
+ * {@link View#performHapticFeedback(int)}
+ */
+public class HapticFeedbackConstants {
+
+ private HapticFeedbackConstants() {}
+
+ public static final int LONG_PRESS = 0;
+
+ /**
+ * Flag for {@link View#performHapticFeedback(int, int)
+ * View.performHapticFeedback(int, int)}: Ignore the setting in the
+ * view for whether to perform haptic feedback, do it always.
+ */
+ public static final int FLAG_IGNORE_VIEW_SETTING = 0x0001;
+
+ /**
+ * Flag for {@link View#performHapticFeedback(int, int)
+ * View.performHapticFeedback(int, int)}: Ignore the global setting
+ * for whether to perform haptic feedback, do it always.
+ */
+ public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002;
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 40251db..a856b24 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -104,6 +104,9 @@ interface IWindowManager
int getKeycodeState(int sw);
int getKeycodeStateForDevice(int devid, int sw);
+ // Report whether the hardware supports the given keys; returns true if successful
+ boolean hasKeys(in int[] keycodes, inout boolean[] keyExists);
+
// For testing
void setInTouchMode(boolean showFocus);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 7276f17..1156856 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -106,5 +106,6 @@ interface IWindowSession {
void setInTouchMode(boolean showFocus);
boolean getInTouchMode();
+
+ boolean performHapticFeedback(IWindow window, int effectId, boolean always);
}
-
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 0347d50..25958aa 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -18,6 +18,8 @@ package android.view;
import android.text.method.MetaKeyKeyListener;
import android.util.SparseIntArray;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.SparseArray;
@@ -350,6 +352,28 @@ public class KeyCharacterMap
return getKeyboardType_native(mPointer);
}
+ /**
+ * Queries the framework about whether any physical keys exist on the
+ * device that are capable of producing the given key codes.
+ */
+ public static boolean deviceHasKey(int keyCode) {
+ int[] codeArray = new int[1];
+ codeArray[0] = keyCode;
+ boolean[] ret = deviceHasKeys(codeArray);
+ return ret[0];
+ }
+
+ public static boolean[] deviceHasKeys(int[] keyCodes) {
+ boolean[] ret = new boolean[keyCodes.length];
+ IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ try {
+ wm.hasKeys(keyCodes, ret);
+ } catch (RemoteException e) {
+ // no fallback; just return the empty array
+ }
+ return ret;
+ }
+
private int mPointer;
private int mKeyboardDevice;
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 1575aad..430cc71 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -92,7 +92,7 @@ public class KeyEvent implements Parcelable {
public static final int KEYCODE_SYM = 63;
public static final int KEYCODE_EXPLORER = 64;
public static final int KEYCODE_ENVELOPE = 65;
- public static final int KEYCODE_ENTER = 66;
+ public static final int KEYCODE_ENTER = 66;
public static final int KEYCODE_DEL = 67;
public static final int KEYCODE_GRAVE = 68;
public static final int KEYCODE_MINUS = 69;
@@ -117,7 +117,8 @@ public class KeyEvent implements Parcelable {
public static final int KEYCODE_PREVIOUSSONG = 88;
public static final int KEYCODE_REWIND = 89;
public static final int KEYCODE_FORWARD = 90;
- private static final int LAST_KEYCODE = KEYCODE_FORWARD;
+ public static final int KEYCODE_MUTE = 91;
+ private static final int LAST_KEYCODE = KEYCODE_MUTE;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
@@ -144,8 +145,12 @@ public class KeyEvent implements Parcelable {
public static final int ACTION_UP = 1;
/**
* {@link #getAction} value: multiple duplicate key events have
- * occurred in a row. The {#link {@link #getRepeatCount()} method returns
- * the number of duplicates.
+ * occurred in a row, or a complex string is being delivered. If the
+ * key code is not {#link {@link #KEYCODE_UNKNOWN} then the
+ * {#link {@link #getRepeatCount()} method returns the number of times
+ * the given key code should be executed.
+ * Otherwise, if the key code {@link #KEYCODE_UNKNOWN}, then
+ * this is a sequence of characters as returned by {@link #getCharacters}.
*/
public static final int ACTION_MULTIPLE = 2;
@@ -224,6 +229,12 @@ public class KeyEvent implements Parcelable {
public static final int FLAG_SOFT_KEYBOARD = 0x2;
/**
+ * This mask is set if we don't want the key event to cause us to leave
+ * touch mode.
+ */
+ public static final int FLAG_KEEP_TOUCH_MODE = 0x4;
+
+ /**
* Returns the maximum keycode.
*/
public static int getMaxKeyCode() {
@@ -248,6 +259,7 @@ public class KeyEvent implements Parcelable {
private int mFlags;
private long mDownTime;
private long mEventTime;
+ private String mCharacters;
public interface Callback {
/**
@@ -406,6 +418,28 @@ public class KeyEvent implements Parcelable {
}
/**
+ * Create a new key event for a string of characters. The key code,
+ * action, and repeat could will automatically be set to
+ * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, and 0 for you.
+ *
+ * @param time The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event occured.
+ * @param characters The string of characters.
+ * @param device The device ID that generated the key event.
+ * @param flags The flags for this key event
+ */
+ public KeyEvent(long time, String characters, int device, int flags) {
+ mDownTime = time;
+ mEventTime = time;
+ mCharacters = characters;
+ mAction = ACTION_MULTIPLE;
+ mKeyCode = KEYCODE_UNKNOWN;
+ mRepeatCount = 0;
+ mDeviceId = device;
+ mFlags = flags;
+ }
+
+ /**
* Copy an existing key event, modifying its time and repeat count.
*
* @param origEvent The existing event to be copied.
@@ -423,6 +457,7 @@ public class KeyEvent implements Parcelable {
mDeviceId = origEvent.mDeviceId;
mScancode = origEvent.mScancode;
mFlags = origEvent.mFlags;
+ mCharacters = origEvent.mCharacters;
}
/**
@@ -441,6 +476,8 @@ public class KeyEvent implements Parcelable {
mDeviceId = origEvent.mDeviceId;
mScancode = origEvent.mScancode;
mFlags = origEvent.mFlags;
+ // Don't copy mCharacters, since one way or the other we'll lose it
+ // when changing the action.
}
/**
@@ -472,6 +509,7 @@ public class KeyEvent implements Parcelable {
case KEYCODE_ENDCALL:
case KEYCODE_VOLUME_UP:
case KEYCODE_VOLUME_DOWN:
+ case KEYCODE_MUTE:
case KEYCODE_POWER:
case KEYCODE_HEADSETHOOK:
case KEYCODE_PLAYPAUSE:
@@ -580,7 +618,7 @@ public class KeyEvent implements Parcelable {
/**
* Retrieve the key code of the key event. This is the physical key that
- * was pressed -- not the Unicode character.
+ * was pressed, <em>not</em> the Unicode character.
*
* @return The key code of the event.
*/
@@ -589,6 +627,18 @@ public class KeyEvent implements Parcelable {
}
/**
+ * For the special case of a {@link #ACTION_MULTIPLE} event with key
+ * code of {@link #KEYCODE_UNKNOWN}, this is a raw string of characters
+ * associated with the event. In all other cases it is null.
+ *
+ * @return Returns a String of 1 or more characters associated with
+ * the event.
+ */
+ public final String getCharacters() {
+ return mCharacters;
+ }
+
+ /**
* Retrieve the hardware key id of this key event. These values are not
* reliable and vary from device to device.
*
@@ -772,16 +822,18 @@ public class KeyEvent implements Parcelable {
if (receiver.onKeyMultiple(code, count, this)) {
return true;
}
- mAction = ACTION_DOWN;
- mRepeatCount = 0;
- boolean handled = receiver.onKeyDown(code, this);
- if (handled) {
- mAction = ACTION_UP;
- receiver.onKeyUp(code, this);
+ if (code != KeyEvent.KEYCODE_UNKNOWN) {
+ mAction = ACTION_DOWN;
+ mRepeatCount = 0;
+ boolean handled = receiver.onKeyDown(code, this);
+ if (handled) {
+ mAction = ACTION_UP;
+ receiver.onKeyUp(code, this);
+ }
+ mAction = ACTION_MULTIPLE;
+ mRepeatCount = count;
+ return handled;
}
- mAction = ACTION_MULTIPLE;
- mRepeatCount = count;
- return handled;
}
return false;
}
diff --git a/core/java/android/view/OrientationEventListener.java b/core/java/android/view/OrientationEventListener.java
new file mode 100755
index 0000000..391ba1e
--- /dev/null
+++ b/core/java/android/view/OrientationEventListener.java
@@ -0,0 +1,174 @@
+/*
+ * 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.view;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * Helper class for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ */
+public abstract class OrientationEventListener {
+ private static final String TAG = "OrientationEventListener";
+ private static final boolean DEBUG = false;
+ private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private int mOrientation = ORIENTATION_UNKNOWN;
+ private SensorManager mSensorManager;
+ private boolean mEnabled = false;
+ private int mRate;
+ private Sensor mSensor;
+ private SensorEventListener mSensorEventListener;
+ private OrientationListener mOldListener;
+
+ /**
+ * Returned from onOrientationChanged when the device orientation cannot be determined
+ * (typically when the device is in a close to flat position).
+ *
+ * @see #onOrientationChanged
+ */
+ public static final int ORIENTATION_UNKNOWN = -1;
+
+ /**
+ * Creates a new OrientationEventListener.
+ *
+ * @param context for the OrientationEventListener.
+ */
+ public OrientationEventListener(Context context) {
+ this(context, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ /**
+ * Creates a new OrientationEventListener.
+ *
+ * @param context for the OrientationEventListener.
+ * @param rate at which sensor events are processed (see also
+ * {@link android.hardware.SensorManager SensorManager}). Use the default
+ * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
+ * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+ */
+ public OrientationEventListener(Context context, int rate) {
+ mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mRate = rate;
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (mSensor != null) {
+ // Create listener only if sensors do exist
+ mSensorEventListener = new SensorEventListenerImpl();
+ }
+ }
+
+ void registerListener(OrientationListener lis) {
+ mOldListener = lis;
+ }
+
+ /**
+ * Enables the OrientationEventListener so it will monitor the sensor and call
+ * {@link #onOrientationChanged} when the device orientation changes.
+ */
+ public void enable() {
+ if (mSensor == null) {
+ Log.w(TAG, "Cannot detect sensors. Not enabled");
+ return;
+ }
+ if (mEnabled == false) {
+ if (localLOGV) Log.d(TAG, "OrientationEventListener enabled");
+ mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
+ mEnabled = true;
+ }
+ }
+
+ /**
+ * Disables the OrientationEventListener.
+ */
+ public void disable() {
+ if (mSensor == null) {
+ Log.w(TAG, "Cannot detect sensors. Invalid disable");
+ return;
+ }
+ if (mEnabled == true) {
+ if (localLOGV) Log.d(TAG, "OrientationEventListener disabled");
+ mSensorManager.unregisterListener(mSensorEventListener);
+ mEnabled = false;
+ }
+ }
+
+ class SensorEventListenerImpl implements SensorEventListener {
+ private static final int _DATA_X = 0;
+ private static final int _DATA_Y = 1;
+ private static final int _DATA_Z = 2;
+
+ public void onSensorChanged(SensorEvent event) {
+ float[] values = event.values;
+ int orientation = ORIENTATION_UNKNOWN;
+ float X = -values[_DATA_X];
+ float Y = -values[_DATA_Y];
+ float Z = -values[_DATA_Z];
+ float magnitude = X*X + Y*Y;
+ // Don't trust the angle if the magnitude is small compared to the y value
+ if (magnitude * 4 >= Z*Z) {
+ float OneEightyOverPi = 57.29577957855f;
+ float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
+ orientation = 90 - (int)Math.round(angle);
+ // normalize to 0 - 359 range
+ while (orientation >= 360) {
+ orientation -= 360;
+ }
+ while (orientation < 0) {
+ orientation += 360;
+ }
+ }
+ if (mOldListener != null) {
+ mOldListener.onSensorChanged(Sensor.TYPE_ACCELEROMETER, event.values);
+ }
+ if (orientation != mOrientation) {
+ mOrientation = orientation;
+ onOrientationChanged(orientation);
+ }
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+ }
+
+ /*
+ * Returns true if sensor is enabled and false otherwise
+ */
+ public boolean canDetectOrientation() {
+ return mSensor != null;
+ }
+
+ /**
+ * Called when the orientation of the device has changed.
+ * orientation parameter is in degrees, ranging from 0 to 359.
+ * orientation is 0 degrees when the device is oriented in its natural position,
+ * 90 degrees when its left side is at the top, 180 degrees when it is upside down,
+ * and 270 degrees when its right side is to the top.
+ * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat
+ * and the orientation cannot be determined.
+ *
+ * @param orientation The new orientation of the device.
+ *
+ * @see #ORIENTATION_UNKNOWN
+ */
+ abstract public void onOrientationChanged(int orientation);
+}
diff --git a/core/java/android/view/OrientationListener.java b/core/java/android/view/OrientationListener.java
index 974c2e8..ce8074e 100644
--- a/core/java/android/view/OrientationListener.java
+++ b/core/java/android/view/OrientationListener.java
@@ -18,23 +18,16 @@ package android.view;
import android.content.Context;
import android.hardware.SensorListener;
-import android.hardware.SensorManager;
-import android.util.Config;
-import android.util.Log;
/**
* Helper class for receiving notifications from the SensorManager when
* the orientation of the device has changed.
+ * @deprecated use {@link android.view.OrientationEventListener} instead.
+ * This class internally uses the OrientationEventListener.
*/
+@Deprecated
public abstract class OrientationListener implements SensorListener {
-
- private static final String TAG = "OrientationListener";
- private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
- private SensorManager mSensorManager;
- private int mOrientation = ORIENTATION_UNKNOWN;
- private boolean mEnabled = false;
- private int mRate;
+ private OrientationEventListener mOrientationEventLis;
/**
* Returned from onOrientationChanged when the device orientation cannot be determined
@@ -42,7 +35,7 @@ public abstract class OrientationListener implements SensorListener {
*
* @see #onOrientationChanged
*/
- public static final int ORIENTATION_UNKNOWN = -1;
+ public static final int ORIENTATION_UNKNOWN = OrientationEventListener.ORIENTATION_UNKNOWN;
/**
* Creates a new OrientationListener.
@@ -50,8 +43,7 @@ public abstract class OrientationListener implements SensorListener {
* @param context for the OrientationListener.
*/
public OrientationListener(Context context) {
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- mRate = SensorManager.SENSOR_DELAY_NORMAL;
+ mOrientationEventLis = new OrientationEventListenerInternal(context);
}
/**
@@ -64,78 +56,55 @@ public abstract class OrientationListener implements SensorListener {
* SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
*/
public OrientationListener(Context context, int rate) {
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- mRate = rate;
+ mOrientationEventLis = new OrientationEventListenerInternal(context, rate);
}
-
+
+ class OrientationEventListenerInternal extends OrientationEventListener {
+ OrientationEventListenerInternal(Context context) {
+ super(context);
+ }
+
+ OrientationEventListenerInternal(Context context, int rate) {
+ super(context, rate);
+ // register so that onSensorChanged gets invoked
+ registerListener(OrientationListener.this);
+ }
+
+ public void onOrientationChanged(int orientation) {
+ OrientationListener.this.onOrientationChanged(orientation);
+ }
+ }
+
/**
* Enables the OrientationListener so it will monitor the sensor and call
* {@link #onOrientationChanged} when the device orientation changes.
*/
public void enable() {
- if (mEnabled == false) {
- if (localLOGV) Log.d(TAG, "OrientationListener enabled");
- mSensorManager.registerListener(this, SensorManager.SENSOR_ACCELEROMETER, mRate);
- mEnabled = true;
- }
+ mOrientationEventLis.enable();
}
/**
* Disables the OrientationListener.
*/
public void disable() {
- if (mEnabled == true) {
- if (localLOGV) Log.d(TAG, "OrientationListener disabled");
- mSensorManager.unregisterListener(this);
- mEnabled = false;
- }
+ mOrientationEventLis.disable();
}
-
- /**
- *
- */
+
+ public void onAccuracyChanged(int sensor, int accuracy) {
+ }
+
public void onSensorChanged(int sensor, float[] values) {
- int orientation = ORIENTATION_UNKNOWN;
- float X = values[SensorManager.RAW_DATA_X];
- float Y = values[SensorManager.RAW_DATA_Y];
- float Z = values[SensorManager.RAW_DATA_Z];
- float magnitude = X*X + Y*Y;
- // Don't trust the angle if the magnitude is small compared to the y value
- if (magnitude * 4 >= Z*Z) {
- float OneEightyOverPi = 57.29577957855f;
- float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
- orientation = 90 - (int)Math.round(angle);
- // normalize to 0 - 359 range
- while (orientation >= 360) {
- orientation -= 360;
- }
- while (orientation < 0) {
- orientation += 360;
- }
- }
-
- if (orientation != mOrientation) {
- mOrientation = orientation;
- onOrientationChanged(orientation);
- }
+ // just ignore the call here onOrientationChanged is invoked anyway
}
- public void onAccuracyChanged(int sensor, int accuracy) {
- // TODO Auto-generated method stub
- }
/**
- * Called when the orientation of the device has changed.
- * orientation parameter is in degrees, ranging from 0 to 359.
- * orientation is 0 degrees when the device is oriented in its natural position,
- * 90 degrees when its left side is at the top, 180 degrees when it is upside down,
- * and 270 degrees when its right side is to the top.
- * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat
- * and the orientation cannot be determined.
- *
+ * Look at {@link android.view.OrientationEventListener#onOrientationChanged}
+ * for method description and usage
* @param orientation The new orientation of the device.
*
* @see #ORIENTATION_UNKNOWN
*/
abstract public void onOrientationChanged(int orientation);
+
}
diff --git a/core/java/android/view/RemotableViewMethod.java b/core/java/android/view/RemotableViewMethod.java
new file mode 100644
index 0000000..4318290
--- /dev/null
+++ b/core/java/android/view/RemotableViewMethod.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @hide
+ * This annotation indicates that a method on a subclass of View
+ * is alllowed to be used with the {@link android.widget.RemoteViews} mechanism.
+ */
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RemotableViewMethod {
+}
+
+
+
diff --git a/core/java/android/view/SurfaceHolder.java b/core/java/android/view/SurfaceHolder.java
index 21a72e7..3d0dda3 100644
--- a/core/java/android/view/SurfaceHolder.java
+++ b/core/java/android/view/SurfaceHolder.java
@@ -240,7 +240,7 @@ public interface SurfaceHolder {
* in particular there is no guarantee that the content of the Surface
* will remain unchanged when lockCanvas() is called again.
*
- * @see android.view.SurfaceHolder.lockCanvas
+ * @see #lockCanvas()
*
* @param canvas The Canvas previously returned by lockCanvas().
*/
diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java
index 057df92..27b49db 100644
--- a/core/java/android/view/TouchDelegate.java
+++ b/core/java/android/view/TouchDelegate.java
@@ -77,7 +77,9 @@ public class TouchDelegate {
* actual extent.
*/
public static final int TO_RIGHT = 8;
-
+
+ private int mSlop;
+
/**
* Constructor
*
@@ -87,10 +89,10 @@ public class TouchDelegate {
*/
public TouchDelegate(Rect bounds, View delegateView) {
mBounds = bounds;
-
- int slop = ViewConfiguration.getTouchSlop();
+
+ mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
mSlopBounds = new Rect(bounds);
- mSlopBounds.inset(-slop, -slop);
+ mSlopBounds.inset(-mSlop, -mSlop);
mDelegateView = delegateView;
}
@@ -141,7 +143,7 @@ public class TouchDelegate {
} else {
// Offset event coordinates to be outside the target view (in case it does
// something like tracking pressed state)
- int slop = ViewConfiguration.getTouchSlop();
+ int slop = mSlop;
event.setLocation(-(slop * 2), -(slop * 2));
}
handled = delegateView.dispatchTouchEvent(event);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 85f482c..c3e00c4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -57,15 +57,35 @@ import com.android.internal.view.menu.MenuBuilder;
import java.util.ArrayList;
import java.util.Arrays;
+import java.lang.ref.SoftReference;
/**
* <p>
- * The <code>View</code> class represents the basic UI building block. A view
+ * This class represents the basic building block for user interface components. A View
* occupies a rectangular area on the screen and is responsible for drawing and
- * event handling. <code>View</code> is the base class for <em>widgets</em>,
- * used to create interactive graphical user interfaces.
+ * event handling. View is the base class for <em>widgets</em>, which are
+ * used to create interactive UI components (buttons, text fields, etc.). The
+ * {@link android.view.ViewGroup} subclass is the base class for <em>layouts</em>, which
+ * are invisible containers that hold other Views (or other ViewGroups) and define
+ * their layout properties.
* </p>
*
+ * <div class="special">
+ * <p>For an introduction to using this class to develop your
+ * application's user interface, read the Developer Guide documentation on
+ * <strong><a href="{@docRoot}guide/topics/ui/index.html">User Interface</a></strong>. Special topics
+ * include:
+ * <br/><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Declaring Layout</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/menus.html">Creating Menus</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/layout-objects.html">Common Layout Objects</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/ui-events.html">Handling UI Events</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/themes.html">Applying Styles and Themes</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/custom-components.html">Building Custom Components</a>
+ * <br/><a href="{@docRoot}guide/topics/ui/how-android-draws.html">How Android Draws Views</a>.
+ * </p>
+ * </div>
+ *
* <a name="Using"></a>
* <h3>Using Views</h3>
* <p>
@@ -817,6 +837,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public static final int SOUND_EFFECTS_ENABLED = 0x08000000;
/**
+ * View flag indicating whether this view should have haptic feedback
+ * enabled for events such as long presses.
+ */
+ public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000;
+
+ /**
* Use with {@link #focusSearch}. Move focus to the previous selectable
* item.
*/
@@ -1308,6 +1334,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
static final int HAS_BOUNDS = 0x00000010;
/** {@hide} */
static final int DRAWN = 0x00000020;
+ /**
+ * When this flag is set, this view is running an animation on behalf of its
+ * children and should therefore not cancel invalidate requests, even if they
+ * lie outside of this view's bounds.
+ *
+ * {@hide}
+ */
+ static final int DRAW_ANIMATION = 0x00000040;
/** {@hide} */
static final int SKIP_DRAW = 0x00000080;
/** {@hide} */
@@ -1353,8 +1387,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
static final int SCROLL_CONTAINER_ADDED = 0x00100000;
- // Note: flag 0x00000040 is available
-
/**
* The parent this view is attached to.
* {@hide}
@@ -1479,8 +1511,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
@ViewDebug.ExportedProperty
int mUserPaddingBottom;
- private int mOldWidthMeasureSpec = Integer.MIN_VALUE;
- private int mOldHeightMeasureSpec = Integer.MIN_VALUE;
+ /**
+ * @hide
+ */
+ int mOldWidthMeasureSpec = Integer.MIN_VALUE;
+ /**
+ * @hide
+ */
+ int mOldHeightMeasureSpec = Integer.MIN_VALUE;
private Resources mResources = null;
@@ -1532,7 +1570,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
private int[] mDrawableState = null;
- private Bitmap mDrawingCache;
+ private SoftReference<Bitmap> mDrawingCache;
/**
* When this view has focus and the next focus is {@link #FOCUS_LEFT},
@@ -1559,6 +1597,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
private int mNextFocusDownId = View.NO_ID;
private CheckForLongPress mPendingCheckForLongPress;
+ private UnsetPressedState mUnsetPressedState;
/**
* Whether the long press's action has been invoked. The tap's action is invoked on the
@@ -1611,6 +1650,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public View(Context context) {
mContext = context;
mResources = context != null ? context.getResources() : null;
+ mViewFlags = SOUND_EFFECTS_ENABLED|HAPTIC_FEEDBACK_ENABLED;
++sInstanceCount;
}
@@ -1677,9 +1717,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
- viewFlagValues |= SOUND_EFFECTS_ENABLED;
- viewFlagMasks |= SOUND_EFFECTS_ENABLED;
-
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
@@ -1775,6 +1812,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
viewFlagValues &= ~SOUND_EFFECTS_ENABLED;
viewFlagMasks |= SOUND_EFFECTS_ENABLED;
}
+ case com.android.internal.R.styleable.View_hapticFeedbackEnabled:
+ if (!a.getBoolean(attr, true)) {
+ viewFlagValues &= ~HAPTIC_FEEDBACK_ENABLED;
+ viewFlagMasks |= HAPTIC_FEEDBACK_ENABLED;
+ }
case R.styleable.View_scrollbars:
final int scrollbars = a.getInt(attr, SCROLLBARS_NONE);
if (scrollbars != SCROLLBARS_NONE) {
@@ -1898,7 +1940,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
initScrollCache();
mScrollCache.fadingEdgeLength = a.getDimensionPixelSize(
- R.styleable.View_fadingEdgeLength, ViewConfiguration.getFadingEdgeLength());
+ R.styleable.View_fadingEdgeLength,
+ ViewConfiguration.get(mContext).getScaledFadingEdgeLength());
}
/**
@@ -2013,36 +2056,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mScrollCache.scrollBar = new ScrollBarDrawable();
}
- mScrollCache.scrollBarSize = a.getDimensionPixelSize(
+ final ScrollabilityCache scrollabilityCache = mScrollCache;
+
+ scrollabilityCache.scrollBarSize = a.getDimensionPixelSize(
com.android.internal.R.styleable.View_scrollbarSize,
- ViewConfiguration.getScrollBarSize());
+ ViewConfiguration.get(mContext).getScaledScrollBarSize());
Drawable track = a.getDrawable(R.styleable.View_scrollbarTrackHorizontal);
- mScrollCache.scrollBar.setHorizontalTrackDrawable(track);
+ scrollabilityCache.scrollBar.setHorizontalTrackDrawable(track);
Drawable thumb = a.getDrawable(R.styleable.View_scrollbarThumbHorizontal);
if (thumb != null) {
- mScrollCache.scrollBar.setHorizontalThumbDrawable(thumb);
+ scrollabilityCache.scrollBar.setHorizontalThumbDrawable(thumb);
}
boolean alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawHorizontalTrack,
false);
if (alwaysDraw) {
- mScrollCache.scrollBar.setAlwaysDrawHorizontalTrack(true);
+ scrollabilityCache.scrollBar.setAlwaysDrawHorizontalTrack(true);
}
track = a.getDrawable(R.styleable.View_scrollbarTrackVertical);
- mScrollCache.scrollBar.setVerticalTrackDrawable(track);
+ scrollabilityCache.scrollBar.setVerticalTrackDrawable(track);
thumb = a.getDrawable(R.styleable.View_scrollbarThumbVertical);
if (thumb != null) {
- mScrollCache.scrollBar.setVerticalThumbDrawable(thumb);
+ scrollabilityCache.scrollBar.setVerticalThumbDrawable(thumb);
}
alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawVerticalTrack,
false);
if (alwaysDraw) {
- mScrollCache.scrollBar.setAlwaysDrawVerticalTrack(true);
+ scrollabilityCache.scrollBar.setAlwaysDrawVerticalTrack(true);
}
// Re-apply user/background padding so that scrollbar(s) get added
@@ -2056,7 +2101,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
private void initScrollCache() {
if (mScrollCache == null) {
- mScrollCache = new ScrollabilityCache();
+ mScrollCache = new ScrollabilityCache(ViewConfiguration.get(mContext));
}
}
@@ -2153,6 +2198,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (!handled) {
handled = showContextMenu();
}
+ if (handled) {
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
return handled;
}
@@ -2619,9 +2667,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @attr ref android.R.styleable#View_visibility
*/
@ViewDebug.ExportedProperty(mapping = {
- @ViewDebug.IntToString(from = 0, to = "VISIBLE"),
- @ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
- @ViewDebug.IntToString(from = 8, to = "GONE")
+ @ViewDebug.IntToString(from = VISIBLE, to = "VISIBLE"),
+ @ViewDebug.IntToString(from = INVISIBLE, to = "INVISIBLE"),
+ @ViewDebug.IntToString(from = GONE, to = "GONE")
})
public int getVisibility() {
return mViewFlags & VISIBILITY_MASK;
@@ -2633,8 +2681,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
* @attr ref android.R.styleable#View_visibility
*/
+ @RemotableViewMethod
public void setVisibility(int visibility) {
setFlags(visibility, VISIBILITY_MASK);
+ if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false);
}
/**
@@ -2712,7 +2762,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Set whether this view should have sound effects enabled for events such as
* clicking and touching.
*
- * You may wish to disable sound effects for a view if you already play sounds,
+ * <p>You may wish to disable sound effects for a view if you already play sounds,
* for instance, a dial key that plays dtmf tones.
*
* @param soundEffectsEnabled whether sound effects are enabled for this view.
@@ -2738,6 +2788,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
+ * Set whether this view should have haptic feedback for events such as
+ * long presses.
+ *
+ * <p>You may wish to disable haptic feedback if your view already controls
+ * its own haptic feedback.
+ *
+ * @param hapticFeedbackEnabled whether haptic feedback enabled for this view.
+ * @see #isHapticFeedbackEnabled()
+ * @see #performHapticFeedback(int)
+ * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+ */
+ public void setHapticFeedbackEnabled(boolean hapticFeedbackEnabled) {
+ setFlags(hapticFeedbackEnabled ? HAPTIC_FEEDBACK_ENABLED: 0, HAPTIC_FEEDBACK_ENABLED);
+ }
+
+ /**
+ * @return whether this view should have haptic feedback enabled for events
+ * long presses.
+ *
+ * @see #setHapticFeedbackEnabled(boolean)
+ * @see #performHapticFeedback(int)
+ * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isHapticFeedbackEnabled() {
+ return HAPTIC_FEEDBACK_ENABLED == (mViewFlags & HAPTIC_FEEDBACK_ENABLED);
+ }
+
+ /**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
@@ -3195,13 +3274,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
+ * This is called when a container is going to temporarily detach a child
+ * that currently has focus, with
+ * {@link ViewGroup#detachViewFromParent(View) ViewGroup.detachViewFromParent}.
+ * It will either be followed by {@link #onFinishTemporaryDetach()} or
+ * {@link #onDetachedFromWindow()} when the container is done. Generally
+ * this is currently only done ListView for a view with focus.
+ */
+ public void onStartTemporaryDetach() {
+ }
+
+ /**
+ * Called after {@link #onStartTemporaryDetach} when the container is done
+ * changing the view.
+ */
+ public void onFinishTemporaryDetach() {
+ }
+
+ /**
* capture information of this view for later analysis: developement only
* check dynamic switch to make sure we only dump view
* when ViewDebug.SYSTEM_PROPERTY_CAPTURE_VIEW) is set
*/
private static void captureViewInfo(String subTag, View v) {
- if (v == null ||
- SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_VIEW, 0) == 0) {
+ if (v == null || SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_VIEW, 0) == 0) {
return;
}
ViewDebug.dumpCapturedView(subTag, v);
@@ -3410,6 +3506,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
void performCollectViewAttributes(int visibility) {
+ //noinspection PointlessBitwiseExpression
if (((visibility | mViewFlags) & (VISIBILITY_MASK | KEEP_SCREEN_ON))
== (VISIBLE | KEEP_SCREEN_ON)) {
mAttachInfo.mKeepScreenOn = true;
@@ -3571,8 +3668,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* would make sense to automatically display a soft input window for
* it. Subclasses should override this if they implement
* {@link #onCreateInputConnection(EditorInfo)} to return true if
- * a call on that method would return a non-null InputConnection. The
- * default implementation always returns false.
+ * a call on that method would return a non-null InputConnection, and
+ * they are really a first-class editor that the user would normally
+ * start typing on when the go into a window containing your view.
+ *
+ * <p>The default implementation always returns false. This does
+ * <em>not</em> mean that its {@link #onCreateInputConnection(EditorInfo)}
+ * will not be called or the user can not otherwise perform edits on your
+ * view; it is just a hint to the system that this is not the primary
+ * purpose of this view.
*
* @return Returns true if this view is a text editor, else false.
*/
@@ -3597,6 +3701,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
+ * Called by the {@link android.view.inputmethod.InputMethodManager}
+ * when a view who is not the current
+ * input connection target is trying to make a call on the manager. The
+ * default implementation returns false; you can override this to return
+ * true for certain views if you are performing InputConnection proxying
+ * to them.
+ * @param view The View that is making the InputMethodManager call.
+ * @return Return true to allow the call, false to reject.
+ */
+ public boolean checkInputConnectionProxy(View view) {
+ return false;
+ }
+
+ /**
* Show the context menu for this view. It is not safe to hold on to the
* menu after returning from this method.
*
@@ -3708,10 +3826,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
}
- final UnsetPressedState unsetPressedState = new UnsetPressedState();
- if (!post(unsetPressedState)) {
+ if (mUnsetPressedState == null) {
+ mUnsetPressedState = new UnsetPressedState();
+ }
+
+ if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
- unsetPressedState.run();
+ mUnsetPressedState.run();
}
}
break;
@@ -3734,7 +3855,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
- int slop = ViewConfiguration.getTouchSlop();
+ int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
@@ -3874,25 +3995,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
- mDrawingCache = null;
+ destroyDrawingCache();
}
if ((changed & DRAWING_CACHE_ENABLED) != 0) {
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
- mDrawingCache = null;
+ destroyDrawingCache();
mPrivateFlags &= ~DRAWING_CACHE_VALID;
}
if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
- mDrawingCache = null;
+ destroyDrawingCache();
mPrivateFlags &= ~DRAWING_CACHE_VALID;
}
@@ -3941,6 +4053,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
mBackgroundSizeChanged = true;
+
+ final AttachInfo ai = mAttachInfo;
+ if (ai != null) {
+ ai.mViewScrollChanged = true;
+ }
}
/**
@@ -4413,14 +4530,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @see #invalidate()
*/
public void postInvalidate() {
- // We try only with the AttachInfo because there's no point in invalidating
- // if we are not attached to our window
- if (mAttachInfo != null) {
- Message msg = Message.obtain();
- msg.what = AttachInfo.INVALIDATE_MSG;
- msg.obj = this;
- mAttachInfo.mHandler.sendMessage(msg);
- }
+ postInvalidateDelayed(0);
}
/**
@@ -4436,16 +4546,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @see #invalidate(Rect)
*/
public void postInvalidate(int left, int top, int right, int bottom) {
- // We try only with the AttachInfo because there's no point in invalidating
- // if we are not attached to our window
- if (mAttachInfo != null) {
- Message msg = Message.obtain();
- msg.what = AttachInfo.INVALIDATE_RECT_MSG;
- msg.obj = this;
- msg.arg1 = (left << 16) | (top & 0xFFFF);
- msg.arg2 = (right << 16) | (bottom & 0xFFFF);
- mAttachInfo.mHandler.sendMessage(msg);
- }
+ postInvalidateDelayed(0, left, top, right, bottom);
}
/**
@@ -4477,16 +4578,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @param right The right coordinate of the rectangle to invalidate.
* @param bottom The bottom coordinate of the rectangle to invalidate.
*/
- public void postInvalidateDelayed(long delayMilliseconds, int left, int top
- , int right, int bottom) {
+ public void postInvalidateDelayed(long delayMilliseconds, int left, int top,
+ int right, int bottom) {
+
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
if (mAttachInfo != null) {
- Message msg = Message.obtain();
+ final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire();
+ info.target = this;
+ info.left = left;
+ info.top = top;
+ info.right = right;
+ info.bottom = bottom;
+
+ final Message msg = Message.obtain();
msg.what = AttachInfo.INVALIDATE_RECT_MSG;
- msg.obj = this;
- msg.arg1 = (left << 16) | (top & 0xFFFF);
- msg.arg2 = (right << 16) | (bottom & 0xFFFF);
+ msg.obj = info;
mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
}
@@ -4865,7 +4972,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
final boolean drawHorizontalScrollBar =
(viewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
final boolean drawVerticalScrollBar =
- (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL;
+ (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL
+ && !isVerticalScrollBarHidden();
if (drawVerticalScrollBar || drawHorizontalScrollBar) {
final int width = mRight - mLeft;
@@ -4887,6 +4995,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
}
}
+
+ /**
+ * Override this if the vertical scrollbar needs to be hidden in a subclass, like when
+ * FastScroller is visible.
+ * @return whether to temporarily hide the vertical scrollbar
+ * @hide
+ */
+ protected boolean isVerticalScrollBarHidden() {
+ return false;
+ }
/**
* <p>Draw the horizontal scrollbar if
@@ -4924,7 +5042,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
(viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL ?
getVerticalScrollbarWidth() : 0;
- scrollBar.setBounds(scrollX + (mPaddingLeft & inside) + getScrollBarPaddingLeft(), top,
+ scrollBar.setBounds(scrollX + (mPaddingLeft & inside), top,
scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap, top + size);
scrollBar.setParameters(
computeHorizontalScrollRange(),
@@ -5022,6 +5140,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
+ destroyDrawingCache();
}
/**
@@ -5332,11 +5451,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
return null;
}
- if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED &&
- ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null)) {
+ if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
buildDrawingCache();
}
- return mDrawingCache;
+ return mDrawingCache == null ? null : mDrawingCache.get();
}
/**
@@ -5351,7 +5469,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
public void destroyDrawingCache() {
if (mDrawingCache != null) {
- mDrawingCache.recycle();
+ final Bitmap bitmap = mDrawingCache.get();
+ if (bitmap != null) bitmap.recycle();
mDrawingCache = null;
}
}
@@ -5391,7 +5510,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @see #destroyDrawingCache()
*/
public void buildDrawingCache() {
- if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null) {
+ if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null ||
+ mDrawingCache.get() == null) {
+
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE);
}
@@ -5408,16 +5529,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (width <= 0 || height <= 0 ||
(width * height * (opaque ? 2 : 4) >= // Projected bitmap size in bytes
- ViewConfiguration.getMaximumDrawingCacheSize())) {
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
- mDrawingCache = null;
+ ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) {
+ destroyDrawingCache();
return;
}
boolean clear = true;
- Bitmap bitmap = mDrawingCache;
+ Bitmap bitmap = mDrawingCache == null ? null : mDrawingCache.get();
if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
@@ -5442,12 +5560,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
// Try to cleanup memory
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- }
+ if (bitmap != null) bitmap.recycle();
try {
- mDrawingCache = bitmap = Bitmap.createBitmap(width, height, quality);
+ bitmap = Bitmap.createBitmap(width, height, quality);
+ mDrawingCache = new SoftReference<Bitmap>(bitmap);
} catch (OutOfMemoryError e) {
// If there is not enough memory to create the bitmap cache, just
// ignore the issue as bitmap caches are not required to draw the
@@ -5485,9 +5602,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
final int restoreCount = canvas.save();
canvas.translate(-mScrollX, -mScrollY);
+ mPrivateFlags |= DRAWN;
+
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
- mPrivateFlags |= DRAWN;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
@@ -5616,6 +5734,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
+ mPrivateFlags |= DRAWN;
+
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
@@ -5656,7 +5776,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
- mPrivateFlags |= DRAWN;
onDraw(canvas);
// Step 4, draw the children
@@ -5760,7 +5879,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
// Step 3, draw the content
- mPrivateFlags |= DRAWN;
onDraw(canvas);
// Step 4, draw the children
@@ -6411,32 +6529,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
return mBGDrawable;
}
- private int getScrollBarPaddingLeft() {
- // TODO: Deal with RTL languages
- return 0;
- }
-
- /*
- * Returns the pixels occupied by the vertical scrollbar, if not overlaid
- */
- private int getScrollBarPaddingRight() {
- // TODO: Deal with RTL languages
- if ((mViewFlags & SCROLLBARS_VERTICAL) == 0) {
- return 0;
- }
- return (mViewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getVerticalScrollbarWidth();
- }
-
- /*
- * Returns the pixels occupied by the horizontal scrollbar, if not overlaid
- */
- private int getScrollBarPaddingBottom() {
- if ((mViewFlags & SCROLLBARS_HORIZONTAL) == 0) {
- return 0;
- }
- return (mViewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getHorizontalScrollbarHeight();
- }
-
/**
* Sets the padding. The view may add on the space required to display
* the scrollbars, depending on the style and visibility of the scrollbars.
@@ -6460,7 +6552,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mUserPaddingRight = right;
mUserPaddingBottom = bottom;
- if (mPaddingLeft != left + getScrollBarPaddingLeft()) {
+ final int viewFlags = mViewFlags;
+
+ // Common case is there are no scroll bars.
+ if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) {
+ // TODO: Deal with RTL languages to adjust left padding instead of right.
+ if ((viewFlags & SCROLLBARS_VERTICAL) != 0) {
+ right += (viewFlags & SCROLLBARS_INSET_MASK) == 0
+ ? 0 : getVerticalScrollbarWidth();
+ }
+ if ((viewFlags & SCROLLBARS_HORIZONTAL) == 0) {
+ bottom += (viewFlags & SCROLLBARS_INSET_MASK) == 0
+ ? 0 : getHorizontalScrollbarHeight();
+ }
+ }
+
+ if (mPaddingLeft != left) {
changed = true;
mPaddingLeft = left;
}
@@ -6468,13 +6575,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
changed = true;
mPaddingTop = top;
}
- if (mPaddingRight != right + getScrollBarPaddingRight()) {
+ if (mPaddingRight != right) {
changed = true;
- mPaddingRight = right + getScrollBarPaddingRight();
+ mPaddingRight = right;
}
- if (mPaddingBottom != bottom + getScrollBarPaddingBottom()) {
+ if (mPaddingBottom != bottom) {
changed = true;
- mPaddingBottom = bottom + getScrollBarPaddingBottom();
+ mPaddingBottom = bottom;
}
if (changed) {
@@ -7275,20 +7382,57 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Play a sound effect for this view.
*
- * The framework will play sound effects for some built in actions, such as
+ * <p>The framework will play sound effects for some built in actions, such as
* clicking, but you may wish to play these effects in your widget,
* for instance, for internal navigation.
*
- * The sound effect will only be played if sound effects are enabled by the user, and
+ * <p>The sound effect will only be played if sound effects are enabled by the user, and
* {@link #isSoundEffectsEnabled()} is true.
*
* @param soundConstant One of the constants defined in {@link SoundEffectConstants}
*/
- protected void playSoundEffect(int soundConstant) {
- if (mAttachInfo == null || mAttachInfo.mSoundEffectPlayer == null || !isSoundEffectsEnabled()) {
+ public void playSoundEffect(int soundConstant) {
+ if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
return;
}
- mAttachInfo.mSoundEffectPlayer.playSoundEffect(soundConstant);
+ mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
+ }
+
+ /**
+ * Provide haptic feedback to the user for this view.
+ *
+ * <p>The framework will provide haptic feedback for some built in actions,
+ * such as long presses, but you may wish to provide feedback for your
+ * own widget.
+ *
+ * <p>The feedback will only be performed if
+ * {@link #isHapticFeedbackEnabled()} is true.
+ *
+ * @param feedbackConstant One of the constants defined in
+ * {@link HapticFeedbackConstants}
+ */
+ public boolean performHapticFeedback(int feedbackConstant) {
+ return performHapticFeedback(feedbackConstant, 0);
+ }
+
+ /**
+ * Like {@link #performHapticFeedback(int)}, with additional options.
+ *
+ * @param feedbackConstant One of the constants defined in
+ * {@link HapticFeedbackConstants}
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ */
+ public boolean performHapticFeedback(int feedbackConstant, int flags) {
+ if (mAttachInfo == null) {
+ return false;
+ }
+ if ((flags&HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
+ && !isHapticFeedbackEnabled()) {
+ return false;
+ }
+ return mAttachInfo.mRootCallbacks.performHapticFeedback(
+ feedbackConstant,
+ (flags&HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
}
/**
@@ -7667,8 +7811,70 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
static class AttachInfo {
- interface SoundEffectPlayer {
+ interface Callbacks {
void playSoundEffect(int effectId);
+ boolean performHapticFeedback(int effectId, boolean always);
+ }
+
+ /**
+ * InvalidateInfo is used to post invalidate(int, int, int, int) messages
+ * to a Handler. This class contains the target (View) to invalidate and
+ * the coordinates of the dirty rectangle.
+ *
+ * For performance purposes, this class also implements a pool of up to
+ * POOL_LIMIT objects that get reused. This reduces memory allocations
+ * whenever possible.
+ *
+ * The pool is implemented as a linked list of InvalidateInfo object with
+ * the root pointing to the next available InvalidateInfo. If the root
+ * is null (i.e. when all instances from the pool have been acquired),
+ * then a new InvalidateInfo is created and returned to the caller.
+ *
+ * An InvalidateInfo is sent back to the pool by calling its release()
+ * method. If the pool is full the object is simply discarded.
+ *
+ * This implementation follows the object pool pattern used in the
+ * MotionEvent class.
+ */
+ static class InvalidateInfo {
+ private static final int POOL_LIMIT = 10;
+ private static final Object sLock = new Object();
+
+ private static int sAcquiredCount = 0;
+ private static InvalidateInfo sRoot;
+
+ private InvalidateInfo next;
+
+ View target;
+
+ int left;
+ int top;
+ int right;
+ int bottom;
+
+ static InvalidateInfo acquire() {
+ synchronized (sLock) {
+ if (sRoot == null) {
+ return new InvalidateInfo();
+ }
+
+ InvalidateInfo info = sRoot;
+ sRoot = info.next;
+ sAcquiredCount--;
+
+ return info;
+ }
+ }
+
+ void release() {
+ synchronized (sLock) {
+ if (sAcquiredCount < POOL_LIMIT) {
+ sAcquiredCount++;
+ next = sRoot;
+ sRoot = this;
+ }
+ }
+ }
}
final IWindowSession mSession;
@@ -7677,7 +7883,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
final IBinder mWindowToken;
- final SoundEffectPlayer mSoundEffectPlayer;
+ final Callbacks mRootCallbacks;
/**
* The top view of the hierarchy.
@@ -7771,6 +7977,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
boolean mViewVisibilityChanged;
/**
+ * Set to true if a view has been scrolled.
+ */
+ boolean mViewScrollChanged;
+
+ /**
* Global to the view hierarchy used as a temporary for dealing with
* x/y points in the transparent region computations.
*/
@@ -7824,12 +8035,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @param handler the events handler the view must use
*/
AttachInfo(IWindowSession session, IWindow window,
- Handler handler, SoundEffectPlayer effectPlayer) {
+ Handler handler, Callbacks effectPlayer) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
mHandler = handler;
- mSoundEffectPlayer = effectPlayer;
+ mRootCallbacks = effectPlayer;
}
}
@@ -7839,18 +8050,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* instances of View.</p>
*/
private static class ScrollabilityCache {
- public int fadingEdgeLength = ViewConfiguration.getFadingEdgeLength();
+ public int fadingEdgeLength;
- public int scrollBarSize = ViewConfiguration.getScrollBarSize();
+ public int scrollBarSize;
public ScrollBarDrawable scrollBar;
public final Paint paint;
public final Matrix matrix;
public Shader shader;
- private int mLastColor = 0;
+ private int mLastColor;
+
+ public ScrollabilityCache(ViewConfiguration configuration) {
+ fadingEdgeLength = configuration.getScaledFadingEdgeLength();
+ scrollBarSize = configuration.getScaledScrollBarSize();
- public ScrollabilityCache() {
paint = new Paint();
matrix = new Matrix();
// use use a height of 1, and then wack the matrix each time we
@@ -7869,7 +8083,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
shader = new LinearGradient(0, 0, 0, 1, color, 0, Shader.TileMode.CLAMP);
paint.setShader(shader);
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
+ // Restore the default transfer mode (src_over)
+ paint.setXfermode(null);
}
}
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index b7110ce..d3f48c6 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -16,17 +16,19 @@
package android.view;
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.util.SparseArray;
+
/**
* Contains methods to standard constants used in the UI for timeouts, sizes, and distances.
- *
*/
public class ViewConfiguration {
-
/**
* Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
* pixels
*/
- private static final int SCROLL_BAR_SIZE = 6;
+ private static final int SCROLL_BAR_SIZE = 10;
/**
* Defines the length of the fading edges in pixels
@@ -54,7 +56,7 @@ public class ViewConfiguration {
/**
* Defines the duration in milliseconds we will wait to see if a touch event
- * is a top or a scroll. If the user does not move within this interval, it is
+ * is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
*/
private static final int TAP_TIMEOUT = 100;
@@ -65,6 +67,13 @@ public class ViewConfiguration {
* considered to be a tap.
*/
private static final int JUMP_TAP_TIMEOUT = 500;
+
+ /**
+ * Defines the duration in milliseconds between the first tap's up event and
+ * the second tap's down event for an interaction to be considered a
+ * double-tap.
+ */
+ private static final int DOUBLE_TAP_TIMEOUT = 300;
/**
* Defines the duration in milliseconds we want to display zoom controls in response
@@ -80,7 +89,12 @@ public class ViewConfiguration {
/**
* Distance a touch can wander before we think the user is scrolling in pixels
*/
- private static final int TOUCH_SLOP = 12;
+ private static final int TOUCH_SLOP = 25;
+
+ /**
+ * Distance between the first touch and second touch to still be considered a double tap
+ */
+ private static final int DOUBLE_TAP_SLOP = 100;
/**
* Distance a touch needs to be outside of a window's bounds for it to
@@ -97,30 +111,126 @@ public class ViewConfiguration {
* The maximum size of View's drawing cache, expressed in bytes. This size
* should be at least equal to the size of the screen in ARGB888 format.
*/
- private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4; // One HVGA screen, ARGB8888
+ @Deprecated
+ private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4; // HVGA screen, ARGB8888
/**
* The coefficient of friction applied to flings/scrolls.
*/
private static float SCROLL_FRICTION = 0.015f;
+ private final int mEdgeSlop;
+ private final int mFadingEdgeLength;
+ private final int mMinimumFlingVelocity;
+ private final int mScrollbarSize;
+ private final int mTouchSlop;
+ private final int mDoubleTapSlop;
+ private final int mWindowTouchSlop;
+ private final int mMaximumDrawingCacheSize;
+
+ private static final SparseArray<ViewConfiguration> sConfigurations =
+ new SparseArray<ViewConfiguration>(2);
+
+ /**
+ * @deprecated Use {@link android.view.ViewConfiguration#get(android.content.Context)} instead.
+ */
+ @Deprecated
+ public ViewConfiguration() {
+ mEdgeSlop = EDGE_SLOP;
+ mFadingEdgeLength = FADING_EDGE_LENGTH;
+ mMinimumFlingVelocity = MINIMUM_FLING_VELOCITY;
+ mScrollbarSize = SCROLL_BAR_SIZE;
+ mTouchSlop = TOUCH_SLOP;
+ mDoubleTapSlop = DOUBLE_TAP_SLOP;
+ mWindowTouchSlop = WINDOW_TOUCH_SLOP;
+ //noinspection deprecation
+ mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
+ }
+
+ /**
+ * Creates a new configuration for the specified context. The configuration depends on
+ * various parameters of the context, like the dimension of the display or the density
+ * of the display.
+ *
+ * @param context The application context used to initialize this view configuration.
+ *
+ * @see #get(android.content.Context)
+ * @see android.util.DisplayMetrics
+ */
+ private ViewConfiguration(Context context) {
+ final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ final float density = metrics.density;
+
+ mEdgeSlop = (int) (density * EDGE_SLOP + 0.5f);
+ mFadingEdgeLength = (int) (density * FADING_EDGE_LENGTH + 0.5f);
+ mMinimumFlingVelocity = (int) (density * MINIMUM_FLING_VELOCITY + 0.5f);
+ mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
+ mTouchSlop = (int) (density * TOUCH_SLOP + 0.5f);
+ mDoubleTapSlop = (int) (density * DOUBLE_TAP_SLOP + 0.5f);
+ mWindowTouchSlop = (int) (density * WINDOW_TOUCH_SLOP + 0.5f);
+
+ // Size of the screen in bytes, in ARGB_8888 format
+ mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels;
+ }
+
+ /**
+ * Returns a configuration for the specified context. The configuration depends on
+ * various parameters of the context, like the dimension of the display or the
+ * density of the display.
+ *
+ * @param context The application context used to initialize the view configuration.
+ */
+ public static ViewConfiguration get(Context context) {
+ final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+ final int density = (int) (100.0f * metrics.density);
+
+ ViewConfiguration configuration = sConfigurations.get(density);
+ if (configuration == null) {
+ configuration = new ViewConfiguration(context);
+ sConfigurations.put(density, configuration);
+ }
+
+ return configuration;
+ }
+
/**
* @return The width of the horizontal scrollbar and the height of the vertical
* scrollbar in pixels
+ *
+ * @deprecated Use {@link #getScaledScrollBarSize()} instead.
*/
+ @Deprecated
public static int getScrollBarSize() {
return SCROLL_BAR_SIZE;
}
/**
- * @return Defines the length of the fading edges in pixels
+ * @return The width of the horizontal scrollbar and the height of the vertical
+ * scrollbar in pixels
*/
+ public int getScaledScrollBarSize() {
+ return mScrollbarSize;
+ }
+
+ /**
+ * @return the length of the fading edges in pixels
+ *
+ * @deprecated Use {@link #getScaledFadingEdgeLength()} instead.
+ */
+ @Deprecated
public static int getFadingEdgeLength() {
return FADING_EDGE_LENGTH;
}
-
+
/**
- * @return Defines the duration in milliseconds of the pressed state in child
+ * @return the length of the fading edges in pixels
+ */
+ public int getScaledFadingEdgeLength() {
+ return mFadingEdgeLength;
+ }
+
+ /**
+ * @return the duration in milliseconds of the pressed state in child
* components.
*/
public static int getPressedStateDuration() {
@@ -128,7 +238,7 @@ public class ViewConfiguration {
}
/**
- * @return Defines the duration in milliseconds before a press turns into
+ * @return the duration in milliseconds before a press turns into
* a long press
*/
public static int getLongPressTimeout() {
@@ -136,8 +246,8 @@ public class ViewConfiguration {
}
/**
- * @return Defines the duration in milliseconds we will wait to see if a touch event
- * is a top or a scroll. If the user does not move within this interval, it is
+ * @return the duration in milliseconds we will wait to see if a touch event
+ * is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
*/
public static int getTapTimeout() {
@@ -145,7 +255,7 @@ public class ViewConfiguration {
}
/**
- * @return Defines the duration in milliseconds we will wait to see if a touch event
+ * @return the duration in milliseconds we will wait to see if a touch event
* is a jump tap. If the user does not move within this interval, it is
* considered to be a tap.
*/
@@ -154,46 +264,133 @@ public class ViewConfiguration {
}
/**
+ * @return the duration in milliseconds between the first tap's up event and
+ * the second tap's down event for an interaction to be considered a
+ * double-tap.
+ * @hide pending API council
+ */
+ public static int getDoubleTapTimeout() {
+ return DOUBLE_TAP_TIMEOUT;
+ }
+
+ /**
* @return Inset in pixels to look for touchable content when the user touches the edge of the
* screen
+ *
+ * @deprecated Use {@link #getScaledEdgeSlop()} instead.
*/
+ @Deprecated
public static int getEdgeSlop() {
return EDGE_SLOP;
}
-
+
+ /**
+ * @return Inset in pixels to look for touchable content when the user touches the edge of the
+ * screen
+ */
+ public int getScaledEdgeSlop() {
+ return mEdgeSlop;
+ }
+
/**
* @return Distance a touch can wander before we think the user is scrolling in pixels
+ *
+ * @deprecated Use {@link #getScaledTouchSlop()} instead.
*/
+ @Deprecated
public static int getTouchSlop() {
return TOUCH_SLOP;
}
+
+ /**
+ * @return Distance a touch can wander before we think the user is scrolling in pixels
+ */
+ public int getScaledTouchSlop() {
+ return mTouchSlop;
+ }
+
+ /**
+ * @return Distance between the first touch and second touch to still be
+ * considered a double tap
+ * @deprecated Use {@link #getScaledDoubleTapSlop()} instead.
+ * @hide The only client of this should be GestureDetector, which needs this
+ * for clients that still use its deprecated constructor.
+ */
+ @Deprecated
+ public static int getDoubleTapSlop() {
+ return DOUBLE_TAP_SLOP;
+ }
/**
+ * @return Distance between the first touch and second touch to still be
+ * considered a double tap
+ * @hide pending API council
+ */
+ public int getScaledDoubleTapSlop() {
+ return mDoubleTapSlop;
+ }
+
+ /**
* @return Distance a touch must be outside the bounds of a window for it
* to be counted as outside the window for purposes of dismissing that
* window.
+ *
+ * @deprecated Use {@link #getScaledWindowTouchSlop()} instead.
*/
+ @Deprecated
public static int getWindowTouchSlop() {
return WINDOW_TOUCH_SLOP;
}
+
+ /**
+ * @return Distance a touch must be outside the bounds of a window for it
+ * to be counted as outside the window for purposes of dismissing that
+ * window.
+ */
+ public int getScaledWindowTouchSlop() {
+ return mWindowTouchSlop;
+ }
/**
- * Minimum velocity to initiate a fling, as measured in pixels per second
+ * @return Minimum velocity to initiate a fling, as measured in pixels per second.
+ *
+ * @deprecated Use {@link #getScaledMinimumFlingVelocity()} instead.
*/
- public static int getMinimumFlingVelocity() {
- return MINIMUM_FLING_VELOCITY;
+ @Deprecated
+ public static int getMinimumFlingVelocity() {
+ return MINIMUM_FLING_VELOCITY;
+ }
+
+ /**
+ * @return Minimum velocity to initiate a fling, as measured in pixels per second.
+ */
+ public int getScaledMinimumFlingVelocity() {
+ return mMinimumFlingVelocity;
}
/**
* The maximum drawing cache size expressed in bytes.
*
* @return the maximum size of View's drawing cache expressed in bytes
+ *
+ * @deprecated Use {@link #getScaledMaximumDrawingCacheSize()} instead.
*/
+ @Deprecated
public static int getMaximumDrawingCacheSize() {
+ //noinspection deprecation
return MAXIMUM_DRAWING_CACHE_SIZE;
}
/**
+ * The maximum drawing cache size expressed in bytes.
+ *
+ * @return the maximum size of View's drawing cache expressed in bytes
+ */
+ public int getScaledMaximumDrawingCacheSize() {
+ return mMaximumDrawingCacheSize;
+ }
+
+ /**
* The amount of time that the zoom controls should be
* displayed on the screen expressed in milliseconds.
*
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 883c7bd..6ea7a82 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -17,9 +17,12 @@
package android.view;
import android.util.Log;
+import android.util.DisplayMetrics;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.os.Environment;
+import android.os.Debug;
import java.io.File;
import java.io.BufferedWriter;
@@ -43,6 +46,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.AccessibleObject;
/**
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -73,7 +77,7 @@ public class ViewDebug {
* when it is set, we log key events, touch/motion and trackball events
*/
static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
-
+
/**
* This annotation can be used to mark fields and methods to be dumped by
* the view server. Only non-void methods with no arguments can be annotated
@@ -113,6 +117,27 @@ public class ViewDebug {
IntToString[] mapping() default { };
/**
+ * A mapping can be defined to map array indices to specific strings.
+ * A mapping can be used to see human readable values for the indices
+ * of an array:
+ *
+ * <pre>
+ * @ViewDebug.ExportedProperty(mapping = {
+ * @ViewDebug.IntToString(from = 0, to = "INVALID"),
+ * @ViewDebug.IntToString(from = 1, to = "FIRST"),
+ * @ViewDebug.IntToString(from = 2, to = "SECOND")
+ * })
+ * private int[] mElements;
+ * <pre>
+ *
+ * @return An array of int to String mappings
+ *
+ * @see android.view.ViewDebug.IntToString
+ * @see #mapping()
+ */
+ IntToString[] indexMapping() default { };
+
+ /**
* When deep export is turned on, this property is not dumped. Instead, the
* properties contained in this property are dumped. Each child property
* is prefixed with the name of this property.
@@ -187,10 +212,12 @@ public class ViewDebug {
private static final String REMOTE_COMMAND_DUMP = "DUMP";
private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
+ private static final String REMOTE_PROFILE = "PROFILE";
private static HashMap<Class<?>, Field[]> sFieldsForClasses;
private static HashMap<Class<?>, Method[]> sMethodsForClasses;
-
+ private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
+
/**
* Defines the type of hierarhcy trace to output to the hierarchy traces file.
*/
@@ -347,6 +374,7 @@ public class ViewDebug {
}
File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
+ //noinspection ResultOfMethodCallIgnored
recyclerDump.mkdirs();
recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler");
@@ -450,6 +478,7 @@ public class ViewDebug {
}
File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
+ //noinspection ResultOfMethodCallIgnored
hierarchyDump.mkdirs();
hierarchyDump = new File(hierarchyDump, prefix + ".traces");
@@ -497,6 +526,7 @@ public class ViewDebug {
sHierarchyTraces = null;
File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
+ //noinspection ResultOfMethodCallIgnored
hierarchyDump.mkdirs();
hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree");
@@ -538,32 +568,41 @@ public class ViewDebug {
invalidate(view, params[0]);
} else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
requestLayout(view, params[0]);
+ } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
+ profile(view, clientStream, params[0]);
}
}
}
- private static View findViewByHashCode(View root, String parameter) {
- final String[] ids = parameter.split("@");
- final String className = ids[0];
- final int hashCode = Integer.parseInt(ids[1], 16);
+ private static View findView(View root, String parameter) {
+ // Look by type/hashcode
+ if (parameter.indexOf('@') != -1) {
+ final String[] ids = parameter.split("@");
+ final String className = ids[0];
+ final int hashCode = Integer.parseInt(ids[1], 16);
- View view = root.getRootView();
- if (view instanceof ViewGroup) {
- return findView((ViewGroup) view, className, hashCode);
+ View view = root.getRootView();
+ if (view instanceof ViewGroup) {
+ return findView((ViewGroup) view, className, hashCode);
+ }
+ } else {
+ // Look by id
+ final int id = root.getResources().getIdentifier(parameter, null, null);
+ return root.getRootView().findViewById(id);
}
return null;
}
private static void invalidate(View root, String parameter) {
- final View view = findViewByHashCode(root, parameter);
+ final View view = findView(root, parameter);
if (view != null) {
view.postInvalidate();
}
}
private static void requestLayout(View root, String parameter) {
- final View view = findViewByHashCode(root, parameter);
+ final View view = findView(root, parameter);
if (view != null) {
root.post(new Runnable() {
public void run() {
@@ -573,19 +612,139 @@ public class ViewDebug {
}
}
- private static void capture(View root, final OutputStream clientStream, String parameter)
+ private static void profile(View root, OutputStream clientStream, String parameter)
throws IOException {
+ final View view = findView(root, parameter);
+ BufferedWriter out = null;
+ try {
+ out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+
+ if (view != null) {
+ final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() {
+ public Void[] pre() {
+ forceLayout(view);
+ return null;
+ }
+
+ private void forceLayout(View view) {
+ view.forceLayout();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ forceLayout(group.getChildAt(i));
+ }
+ }
+ }
+
+ public void run(Void... data) {
+ view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+ }
+
+ public void post(Void... data) {
+ }
+ });
+
+ final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() {
+ public Void[] pre() {
+ return null;
+ }
+
+ public void run(Void... data) {
+ view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
+ }
+
+ public void post(Void... data) {
+ }
+ });
+
+ final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() {
+ public Object[] pre() {
+ final DisplayMetrics metrics = view.getResources().getDisplayMetrics();
+ final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels,
+ metrics.heightPixels, Bitmap.Config.RGB_565);
+ final Canvas canvas = new Canvas(bitmap);
+ return new Object[] { bitmap, canvas };
+ }
+
+ public void run(Object... data) {
+ view.draw((Canvas) data[1]);
+ }
+
+ public void post(Object... data) {
+ ((Bitmap) data[0]).recycle();
+ }
+ });
+
+ out.write(String.valueOf(durationMeasure));
+ out.write(' ');
+ out.write(String.valueOf(durationLayout));
+ out.write(' ');
+ out.write(String.valueOf(durationDraw));
+ out.newLine();
+ } else {
+ out.write("-1 -1 -1");
+ out.newLine();
+ }
+ } catch (Exception e) {
+ android.util.Log.w("View", "Problem profiling the view:", e);
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
+ interface ViewOperation<T> {
+ T[] pre();
+ void run(T... data);
+ void post(T... data);
+ }
+
+ private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
final CountDownLatch latch = new CountDownLatch(1);
- final View captureView = findViewByHashCode(root, parameter);
+ final long[] duration = new long[1];
+
+ view.post(new Runnable() {
+ public void run() {
+ try {
+ T[] data = operation.pre();
+ long start = Debug.threadCpuTimeNanos();
+ operation.run(data);
+ duration[0] = Debug.threadCpuTimeNanos() - start;
+ operation.post(data);
+ } finally {
+ latch.countDown();
+ }
+ }
+ });
+
+ try {
+ latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.w("View", "Could not complete the profiling of the view " + view);
+ Thread.currentThread().interrupt();
+ return -1;
+ }
+
+ return duration[0];
+ }
+
+ private static void capture(View root, final OutputStream clientStream, String parameter)
+ throws IOException {
+
+ final View captureView = findView(root, parameter);
if (captureView != null) {
+ final CountDownLatch latch = new CountDownLatch(1);
final Bitmap[] cache = new Bitmap[1];
final boolean hasCache = captureView.isDrawingCacheEnabled();
final boolean willNotCache = captureView.willNotCacheDrawing();
if (willNotCache) {
+ // TODO: Should happen on the UI thread
captureView.setWillNotCacheDrawing(false);
}
@@ -619,12 +778,15 @@ public class ViewDebug {
}
}
} catch (InterruptedException e) {
- Log.w("View", "Could not complete the capture of the view " + captureView);
+ Log.w("View", "Could not complete the capture of the view " + captureView);
+ Thread.currentThread().interrupt();
} finally {
if (willNotCache) {
+ // TODO: Should happen on the UI thread
captureView.setWillNotCacheDrawing(true);
}
if (!hasCache) {
+ // TODO: Should happen on the UI thread
captureView.destroyDrawingCache();
}
}
@@ -642,6 +804,8 @@ public class ViewDebug {
}
out.write("DONE.");
out.newLine();
+ } catch (Exception e) {
+ android.util.Log.w("View", "Problem dumping the view:", e);
} finally {
if (out != null) {
out.close();
@@ -713,7 +877,12 @@ public class ViewDebug {
if (sFieldsForClasses == null) {
sFieldsForClasses = new HashMap<Class<?>, Field[]>();
}
+ if (sAnnotations == null) {
+ sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
+ }
+
final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
+ final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
Field[] fields = map.get(klass);
if (fields != null) {
@@ -729,6 +898,7 @@ public class ViewDebug {
if (field.isAnnotationPresent(ExportedProperty.class)) {
field.setAccessible(true);
foundFields.add(field);
+ annotations.put(field, field.getAnnotation(ExportedProperty.class));
}
}
@@ -740,9 +910,14 @@ public class ViewDebug {
private static Method[] getExportedPropertyMethods(Class<?> klass) {
if (sMethodsForClasses == null) {
- sMethodsForClasses = new HashMap<Class<?>, Method[]>();
+ sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
+ }
+ if (sAnnotations == null) {
+ sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
}
+
final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
+ final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
Method[] methods = map.get(klass);
if (methods != null) {
@@ -756,10 +931,11 @@ public class ViewDebug {
for (int i = 0; i < count; i++) {
final Method method = methods[i];
if (method.getParameterTypes().length == 0 &&
- method.isAnnotationPresent(ExportedProperty.class) &&
- method.getReturnType() != Void.class) {
+ method.isAnnotationPresent(ExportedProperty.class) &&
+ method.getReturnType() != Void.class) {
method.setAccessible(true);
foundMethods.add(method);
+ annotations.put(method, method.getAnnotation(ExportedProperty.class));
}
}
@@ -799,20 +975,10 @@ public class ViewDebug {
final Class<?> returnType = method.getReturnType();
if (returnType == int.class) {
- ExportedProperty property = method.getAnnotation(ExportedProperty.class);
+ final ExportedProperty property = sAnnotations.get(method);
if (property.resolveId() && view instanceof View) {
- final Resources resources = ((View) view).getContext().getResources();
final int id = (Integer) methodValue;
- if (id >= 0) {
- try {
- methodValue = resources.getResourceTypeName(id) + '/' +
- resources.getResourceEntryName(id);
- } catch (Resources.NotFoundException e) {
- methodValue = "UNKNOWN";
- }
- } else {
- methodValue = "NO_ID";
- }
+ methodValue = resolveId(view, id);
} else {
final IntToString[] mapping = property.mapping();
if (mapping.length > 0) {
@@ -833,28 +999,22 @@ public class ViewDebug {
}
}
}
+ } else if (returnType == int[].class) {
+ final ExportedProperty property = sAnnotations.get(method);
+ final int[] array = (int[]) methodValue;
+ final String valuePrefix = prefix + method.getName() + '_';
+ final String suffix = "()";
+
+ exportUnrolledArray(view, out, property, array, valuePrefix, suffix);
} else if (!returnType.isPrimitive()) {
- ExportedProperty property = method.getAnnotation(ExportedProperty.class);
+ final ExportedProperty property = sAnnotations.get(method);
if (property.deepExport()) {
dumpViewProperties(methodValue, out, prefix + property.prefix());
continue;
}
}
- out.write(prefix);
- out.write(method.getName());
- out.write("()=");
-
- if (methodValue != null) {
- final String value = methodValue.toString().replace("\n", "\\n");
- out.write(String.valueOf(value.length()));
- out.write(",");
- out.write(value);
- } else {
- out.write("4,null");
- }
-
- out.write(' ');
+ writeEntry(out, prefix, method.getName(), "()", methodValue);
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
@@ -875,20 +1035,10 @@ public class ViewDebug {
final Class<?> type = field.getType();
if (type == int.class) {
- ExportedProperty property = field.getAnnotation(ExportedProperty.class);
+ final ExportedProperty property = sAnnotations.get(field);
if (property.resolveId() && view instanceof View) {
- final Resources resources = ((View) view).getContext().getResources();
final int id = field.getInt(view);
- if (id >= 0) {
- try {
- fieldValue = resources.getResourceTypeName(id) + '/' +
- resources.getResourceEntryName(id);
- } catch (Resources.NotFoundException e) {
- fieldValue = "UNKNOWN";
- }
- } else {
- fieldValue = "NO_ID";
- }
+ fieldValue = resolveId(view, id);
} else {
final IntToString[] mapping = property.mapping();
if (mapping.length > 0) {
@@ -907,8 +1057,18 @@ public class ViewDebug {
}
}
}
+ } else if (type == int[].class) {
+ final ExportedProperty property = sAnnotations.get(field);
+ final int[] array = (int[]) field.get(view);
+ final String valuePrefix = prefix + field.getName() + '_';
+ final String suffix = "";
+
+ exportUnrolledArray(view, out, property, array, valuePrefix, suffix);
+
+ // We exit here!
+ return;
} else if (!type.isPrimitive()) {
- ExportedProperty property = field.getAnnotation(ExportedProperty.class);
+ final ExportedProperty property = sAnnotations.get(field);
if (property.deepExport()) {
dumpViewProperties(field.get(view), out, prefix + property.prefix());
continue;
@@ -917,24 +1077,100 @@ public class ViewDebug {
if (fieldValue == null) {
fieldValue = field.get(view);
- }
+ }
- out.write(prefix);
- out.write(field.getName());
- out.write('=');
+ writeEntry(out, prefix, field.getName(), "", fieldValue);
+ } catch (IllegalAccessException e) {
+ }
+ }
+ }
- if (fieldValue != null) {
- final String value = fieldValue.toString().replace("\n", "\\n");
- out.write(String.valueOf(value.length()));
- out.write(",");
- out.write(value);
- } else {
- out.write("4,null");
+ private static void writeEntry(BufferedWriter out, String prefix, String name,
+ String suffix, Object value) throws IOException {
+
+ out.write(prefix);
+ out.write(name);
+ out.write(suffix);
+ out.write("=");
+ writeValue(out, value);
+ out.write(' ');
+ }
+
+ private static void exportUnrolledArray(Object view, BufferedWriter out,
+ ExportedProperty property, int[] array, String prefix, String suffix)
+ throws IOException {
+
+ final IntToString[] indexMapping = property.indexMapping();
+ final boolean hasIndexMapping = indexMapping.length > 0;
+
+ final IntToString[] mapping = property.mapping();
+ final boolean hasMapping = mapping.length > 0;
+
+ final boolean resolveId = property.resolveId() && view instanceof View;
+ final int valuesCount = array.length;
+
+ for (int j = 0; j < valuesCount; j++) {
+ String name;
+ String value;
+
+ final int intValue = array[j];
+
+ name = String.valueOf(j);
+ if (hasIndexMapping) {
+ int mappingCount = indexMapping.length;
+ for (int k = 0; k < mappingCount; k++) {
+ final IntToString mapped = indexMapping[k];
+ if (mapped.from() == j) {
+ name = mapped.to();
+ break;
+ }
}
+ }
- out.write(' ');
- } catch (IllegalAccessException e) {
+ value = String.valueOf(intValue);
+ if (hasMapping) {
+ int mappingCount = mapping.length;
+ for (int k = 0; k < mappingCount; k++) {
+ final IntToString mapped = mapping[k];
+ if (mapped.from() == intValue) {
+ value = mapped.to();
+ break;
+ }
+ }
+ }
+
+ if (resolveId) {
+ value = (String) resolveId(view, intValue);
}
+
+ writeEntry(out, prefix, name, suffix, value);
+ }
+ }
+
+ private static Object resolveId(Object view, int id) {
+ Object fieldValue;
+ final Resources resources = ((View) view).getContext().getResources();
+ if (id >= 0) {
+ try {
+ fieldValue = resources.getResourceTypeName(id) + '/' +
+ resources.getResourceEntryName(id);
+ } catch (Resources.NotFoundException e) {
+ fieldValue = "id/0x" + Integer.toHexString(id);
+ }
+ } else {
+ fieldValue = "NO_ID";
+ }
+ return fieldValue;
+ }
+
+ private static void writeValue(BufferedWriter out, Object value) throws IOException {
+ if (value != null) {
+ String output = value.toString().replace("\n", "\\n");
+ out.write(String.valueOf(output.length()));
+ out.write(",");
+ out.write(output);
+ } else {
+ out.write("4,null");
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e26a19e..dc7b299 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -25,7 +25,9 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Region;
+import android.graphics.RectF;
import android.os.Parcelable;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
@@ -74,6 +76,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// The current transformation to apply on the child being drawn
private Transformation mChildTransformation;
+ private RectF mInvalidateRegion;
// Target of Motion events
private View mMotionTarget;
@@ -1021,6 +1024,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
void dispatchDetachedFromWindow() {
+ // If we still have a motion target, we are still in the process of
+ // dispatching motion events to a child; we need to get rid of that
+ // child to avoid dispatching events to it after the window is torn
+ // down. To make sure we keep the child in a consistent state, we
+ // first send it an ACTION_CANCEL motion event.
+ if (mMotionTarget != null) {
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ mMotionTarget.dispatchTouchEvent(event);
+ event.recycle();
+ mMotionTarget = null;
+ }
+
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
@@ -1199,6 +1216,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
+ // We will draw our child's animation, let's reset the flag
+ mPrivateFlags &= ~DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
@@ -1327,9 +1346,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final Animation a = child.getAnimation();
boolean concatMatrix = false;
+ final int childWidth = cr - cl;
+ final int childHeight = cb - ct;
+
if (a != null) {
- if (!a.isInitialized()) {
- a.initialize(cr - cl, cb - ct, getWidth(), getHeight());
+ if (mInvalidateRegion == null) {
+ mInvalidateRegion = new RectF();
+ }
+ final RectF region = mInvalidateRegion;
+
+ final boolean initialized = a.isInitialized();
+ if (!initialized) {
+ a.initialize(childWidth, childHeight, getWidth(), getHeight());
+ a.initializeInvalidateRegion(0, 0, childWidth, childHeight);
child.onAnimationStart();
}
@@ -1347,10 +1376,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
FLAG_OPTIMIZE_INVALIDATE) {
mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
} else if ((flags & FLAG_INVALIDATE_REQUIRED) == 0) {
+ // The child need to draw an animation, potentially offscreen, so
+ // make sure we do not cancel invalidate requests
+ mPrivateFlags |= DRAW_ANIMATION;
invalidate(cl, ct, cr, cb);
}
} else {
- mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+ a.getInvalidateRegion(0, 0, childWidth, childHeight, region, transformToApply);
+
+ // The child need to draw an animation, potentially offscreen, so
+ // make sure we do not cancel invalidate requests
+ mPrivateFlags |= DRAW_ANIMATION;
+
+ final int left = cl + (int) region.left;
+ final int top = ct + (int) region.top;
+ invalidate(left, top, left + (int) region.width(), top + (int) region.height());
}
}
} else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
@@ -1367,7 +1407,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
- if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW)) {
+ if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW) &&
+ (child.mPrivateFlags & DRAW_ANIMATION) == 0) {
return more;
}
@@ -1429,16 +1470,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (hasNoCache) {
- canvas.clipRect(sx, sy, sx + cr - cl, sy + cb - ct);
+ canvas.clipRect(sx, sy, sx + childWidth, sy + childHeight);
} else {
- canvas.clipRect(0, 0, cr - cl, cb - ct);
+ canvas.clipRect(0, 0, childWidth, childHeight);
}
}
+ // Clear the flag as early as possible to allow draw() implementations
+ // to call invalidate() successfully when doing animations
+ child.mPrivateFlags |= DRAWN;
+
if (hasNoCache) {
// Fast path for layouts with no backgrounds
if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
- child.mPrivateFlags |= DRAWN;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
@@ -1455,7 +1499,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
cachePaint.setAlpha(255);
mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE;
}
- child.mPrivateFlags |= DRAWN;
if (ViewRoot.PROFILE_DRAWING) {
EventLog.writeEvent(60003, hashCode());
}
@@ -1922,8 +1965,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
LayoutAnimationController.AnimationParameters animationParams =
params.layoutAnimationParameters;
if (animationParams == null) {
- animationParams =
- new LayoutAnimationController.AnimationParameters();
+ animationParams = new LayoutAnimationController.AnimationParameters();
params.layoutAnimationParameters = animationParams;
}
@@ -2278,8 +2320,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
+
+ // If the child is drawing an animation, we want to copy this flag onto
+ // ourselves and the parent to make sure the invalidate request goes
+ // through
+ final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;
do {
+ if (drawAnimation && parent instanceof View) {
+ ((View) parent).mPrivateFlags |= DRAW_ANIMATION;
+ }
parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);
}
@@ -2307,7 +2357,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int left = mLeft;
final int top = mTop;
- if (dirty.intersect(0, 0, mRight - left, mBottom - top)) {
+ if (dirty.intersect(0, 0, mRight - left, mBottom - top) ||
+ (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = left;
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 9e0289a..dd2b154 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -33,6 +33,7 @@ import android.util.Config;
import android.util.Log;
import android.util.EventLog;
import android.util.SparseArray;
+import android.util.DisplayMetrics;
import android.view.View.MeasureSpec;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
@@ -61,7 +62,7 @@ import static javax.microedition.khronos.opengles.GL10.*;
*/
@SuppressWarnings({"EmptyCatchBlock"})
public final class ViewRoot extends Handler implements ViewParent,
- View.AttachInfo.SoundEffectPlayer {
+ View.AttachInfo.Callbacks {
private static final String TAG = "ViewRoot";
private static final boolean DBG = false;
@SuppressWarnings({"ConstantConditionalExpression"})
@@ -142,6 +143,7 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean mFullRedrawNeeded;
boolean mNewSurfaceNeeded;
boolean mHasHadWindowFocus;
+ boolean mLastWasImTarget;
boolean mWindowAttributesChanged = false;
@@ -177,12 +179,14 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean mUseGL;
boolean mGlWanted;
+ final ViewConfiguration mViewConfiguration;
+
/**
* see {@link #playSoundEffect(int)}
*/
AudioManager mAudioManager;
-
+ private final float mDensity;
public ViewRoot(Context context) {
super();
@@ -215,7 +219,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mVisRect = new Rect();
mVisPoint = new Point();
mWinFrame = new Rect();
- mWindow = new W(this);
+ mWindow = new W(this, context);
mInputMethodCallback = new InputMethodCallback(this);
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
@@ -224,6 +228,8 @@ public final class ViewRoot extends Handler implements ViewParent,
mSurface = new Surface();
mAdded = false;
mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
+ mViewConfiguration = ViewConfiguration.get(context);
+ mDensity = context.getResources().getDisplayMetrics().density;
}
@Override
@@ -993,6 +999,21 @@ public final class ViewRoot extends Handler implements ViewParent,
mNewSurfaceNeeded = false;
mViewVisibility = viewVisibility;
+ if (mAttachInfo.mHasWindowFocus) {
+ final boolean imTarget = WindowManager.LayoutParams
+ .mayUseInputMethod(mWindowAttributes.flags);
+ if (imTarget != mLastWasImTarget) {
+ mLastWasImTarget = imTarget;
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && imTarget) {
+ imm.startGettingWindowFocus(mView);
+ imm.onWindowFocus(mView, mView.findFocus(),
+ mWindowAttributes.softInputMode,
+ !mHasHadWindowFocus, mWindowAttributes.flags);
+ }
+ }
+ }
+
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
if (!cancelDraw && !newSurface) {
@@ -1075,6 +1096,11 @@ public final class ViewRoot extends Handler implements ViewParent,
}
scrollToRectOrFocus(null, false);
+
+ if (mAttachInfo.mViewScrollChanged) {
+ mAttachInfo.mViewScrollChanged = false;
+ mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
+ }
int yoff;
final boolean scrolling = mScroller != null
@@ -1088,7 +1114,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mCurScrollY = yoff;
fullRedrawNeeded = true;
}
-
+
Rect dirty = mDirty;
if (mUseGL) {
if (!dirty.isEmpty()) {
@@ -1101,6 +1127,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
canvas.translate(0, -yoff);
+ mView.mPrivateFlags |= View.DRAWN;
mView.draw(canvas);
canvas.translate(0, yoff);
@@ -1123,7 +1150,6 @@ public final class ViewRoot extends Handler implements ViewParent,
return;
}
-
if (fullRedrawNeeded)
dirty.union(0, 0, mWidth, mHeight);
@@ -1135,20 +1161,22 @@ public final class ViewRoot extends Handler implements ViewParent,
+ surface + " surface.isValid()=" + surface.isValid());
}
- if (!dirty.isEmpty()) {
- Canvas canvas;
- try {
- canvas = surface.lockCanvas(dirty);
- } catch (Surface.OutOfResourcesException e) {
- Log.e("ViewRoot", "OutOfResourcesException locking surface", e);
- // TODO: we should ask the window manager to do something!
- // for now we just do nothing
- return;
- }
+ Canvas canvas;
+ try {
+ canvas = surface.lockCanvas(dirty);
+ // TODO: Do this in native
+ canvas.setDensityScale(mDensity);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.e("ViewRoot", "OutOfResourcesException locking surface", e);
+ // TODO: we should ask the window manager to do something!
+ // for now we just do nothing
+ return;
+ }
- long startTime;
+ try {
+ if (!dirty.isEmpty()) {
+ long startTime;
- try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
@@ -1164,7 +1192,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
if (!canvas.isOpaque()) {
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
} else if (yoff != 0) {
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
@@ -1175,6 +1203,7 @@ public final class ViewRoot extends Handler implements ViewParent,
dirty.setEmpty();
mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
canvas.translate(0, -yoff);
+ mView.mPrivateFlags |= View.DRAWN;
mView.draw(canvas);
canvas.translate(0, yoff);
@@ -1186,17 +1215,17 @@ public final class ViewRoot extends Handler implements ViewParent,
sDrawTime = now;
}
- } finally {
- surface.unlockCanvasAndPost(canvas);
- }
-
- if (PROFILE_DRAWING) {
- EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
+ if (PROFILE_DRAWING) {
+ EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
+ }
}
+
+ } finally {
+ surface.unlockCanvasAndPost(canvas);
+ }
- if (LOCAL_LOGV) {
- Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
- }
+ if (LOCAL_LOGV) {
+ Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
}
if (scrolling) {
@@ -1378,14 +1407,22 @@ public final class ViewRoot extends Handler implements ViewParent,
void dispatchDetachedFromWindow() {
if (Config.LOGV) Log.v("ViewRoot", "Detaching in " + this + " of " + mSurface);
+
if (mView != null) {
mView.dispatchDetachedFromWindow();
}
+
mView = null;
mAttachInfo.mRootView = null;
+
if (mUseGL) {
destroyGL();
}
+
+ try {
+ sWindowSession.remove(mWindow);
+ } catch (RemoteException e) {
+ }
}
/**
@@ -1414,6 +1451,7 @@ public final class ViewRoot extends Handler implements ViewParent,
public final static int FINISHED_EVENT = 1010;
public final static int DISPATCH_KEY_FROM_IME = 1011;
public final static int FINISH_INPUT_CONNECTION = 1012;
+ public final static int CHECK_FOCUS = 1013;
@Override
public void handleMessage(Message msg) {
@@ -1422,11 +1460,9 @@ public final class ViewRoot extends Handler implements ViewParent,
((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);
+ final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
+ info.target.invalidate(info.left, info.top, info.right, info.bottom);
+ info.release();
break;
case DO_TRAVERSAL:
if (mProfile) {
@@ -1478,7 +1514,7 @@ public final class ViewRoot extends Handler implements ViewParent,
event.offsetLocation(0, mCurScrollY);
handled = mView.dispatchTouchEvent(event);
if (!handled && isDown) {
- int edgeSlop = ViewConfiguration.getEdgeSlop();
+ int edgeSlop = mViewConfiguration.getScaledEdgeSlop();
final int edgeFlags = event.getEdgeFlags();
int direction = View.FOCUS_UP;
@@ -1587,16 +1623,23 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
}
+
+ mLastWasImTarget = WindowManager.LayoutParams
+ .mayUseInputMethod(mWindowAttributes.flags);
+
+ InputMethodManager imm = InputMethodManager.peekInstance();
if (mView != null) {
+ if (hasWindowFocus && imm != null && mLastWasImTarget) {
+ imm.startGettingWindowFocus(mView);
+ }
mView.dispatchWindowFocusChanged(hasWindowFocus);
}
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
if (hasWindowFocus) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.onWindowFocus(mView.findFocus(),
+ if (imm != null && mLastWasImTarget) {
+ imm.onWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
@@ -1626,6 +1669,12 @@ public final class ViewRoot extends Handler implements ViewParent,
imm.reportFinishInputConnection((InputConnection)msg.obj);
}
} break;
+ case CHECK_FOCUS: {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.checkFocus();
+ }
+ } break;
}
}
@@ -1787,6 +1836,11 @@ public final class ViewRoot extends Handler implements ViewParent,
if (event != null) {
event.recycle();
}
+ // If we reach this, we delivered a trackball event to mView and
+ // mView consumed it. Because we will not translate the trackball
+ // event into a key event, touch mode will not exit, so we exit
+ // touch mode here.
+ ensureTouchMode(false);
//noinspection ReturnInsideFinallyBlock
return;
}
@@ -1941,6 +1995,9 @@ public final class ViewRoot extends Handler implements ViewParent,
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return false;
}
+ if ((event.getFlags()&KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) {
+ return false;
+ }
// only relevant if we are in touch mode
if (!mAttachInfo.mInTouchMode) {
@@ -2042,8 +2099,10 @@ public final class ViewRoot extends Handler implements ViewParent,
}
private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
- boolean handled = false;
- handled = mView.dispatchKeyEventPreIme(event);
+ // If mView is null, we just consume the key event because it doesn't
+ // make sense to do anything else with it.
+ boolean handled = mView != null
+ ? mView.dispatchKeyEventPreIme(event) : true;
if (handled) {
if (sendDone) {
if (LOCAL_LOGV) Log.v(
@@ -2058,10 +2117,9 @@ public final class ViewRoot extends Handler implements ViewParent,
// If it is possible for this window to interact with the input
// method window, then we want to first dispatch our key events
// to the input method.
- if (WindowManager.LayoutParams.mayUseInputMethod(
- mWindowAttributes.flags)) {
+ if (mLastWasImTarget) {
InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null && mView != null && imm.isActive()) {
+ if (imm != null && mView != null) {
int seq = enqueuePendingEvent(event, sendDone);
if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
+ seq + " event=" + event);
@@ -2089,6 +2147,10 @@ public final class ViewRoot extends Handler implements ViewParent,
sWindowSession.finishKey(mWindow);
} catch (RemoteException e) {
}
+ } else {
+ Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq
+ + " handled=" + handled + " ev=" + event
+ + ") neither delivering nor finishing key");
}
}
}
@@ -2108,7 +2170,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
boolean keyHandled = mView.dispatchKeyEvent(event);
- if ((!keyHandled && isDown) || (action == KeyEvent.ACTION_MULTIPLE)) {
+ if (!keyHandled && isDown) {
int direction = 0;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
@@ -2208,6 +2270,17 @@ public final class ViewRoot extends Handler implements ViewParent,
/**
* {@inheritDoc}
*/
+ public boolean performHapticFeedback(int effectId, boolean always) {
+ try {
+ return sWindowSession.performHapticFeedback(mWindow, effectId, always);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public View focusSearch(View focused, int direction) {
checkThread();
if (!(mView instanceof ViewGroup)) {
@@ -2248,10 +2321,6 @@ public final class ViewRoot extends Handler implements ViewParent,
}
if (mAdded) {
mAdded = false;
- try {
- sWindowSession.remove(mWindow);
- } catch (RemoteException e) {
- }
if (immediate) {
dispatchDetachedFromWindow();
} else if (mView != null) {
@@ -2384,11 +2453,71 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
+ static class EventCompletion extends Handler {
+ final IWindow mWindow;
+ final KeyEvent mKeyEvent;
+ final boolean mIsPointer;
+ final MotionEvent mMotionEvent;
+
+ EventCompletion(Looper looper, IWindow window, KeyEvent key,
+ boolean isPointer, MotionEvent motion) {
+ super(looper);
+ mWindow = window;
+ mKeyEvent = key;
+ mIsPointer = isPointer;
+ mMotionEvent = motion;
+ sendEmptyMessage(0);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mKeyEvent != null) {
+ try {
+ sWindowSession.finishKey(mWindow);
+ } catch (RemoteException e) {
+ }
+ } else if (mIsPointer) {
+ boolean didFinish;
+ MotionEvent event = mMotionEvent;
+ if (event == null) {
+ try {
+ event = sWindowSession.getPendingPointerMove(mWindow);
+ } catch (RemoteException e) {
+ }
+ didFinish = true;
+ } else {
+ didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE;
+ }
+ if (!didFinish) {
+ try {
+ sWindowSession.finishKey(mWindow);
+ } catch (RemoteException e) {
+ }
+ }
+ } else {
+ MotionEvent event = mMotionEvent;
+ if (event == null) {
+ try {
+ event = sWindowSession.getPendingTrackballMove(mWindow);
+ } catch (RemoteException e) {
+ }
+ } else {
+ try {
+ sWindowSession.finishKey(mWindow);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+ }
+
static class W extends IWindow.Stub {
- private WeakReference<ViewRoot> mViewRoot;
+ private final WeakReference<ViewRoot> mViewRoot;
+ private final Looper mMainLooper;
- public W(ViewRoot viewRoot) {
+ public W(ViewRoot viewRoot, Context context) {
mViewRoot = new WeakReference<ViewRoot>(viewRoot);
+ mMainLooper = context.getMainLooper();
}
public void resized(int w, int h, Rect coveredInsets,
@@ -2404,6 +2533,9 @@ public final class ViewRoot extends Handler implements ViewParent,
final ViewRoot viewRoot = mViewRoot.get();
if (viewRoot != null) {
viewRoot.dispatchKey(event);
+ } else {
+ Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!");
+ new EventCompletion(mMainLooper, this, event, false, null);
}
}
@@ -2411,6 +2543,8 @@ public final class ViewRoot extends Handler implements ViewParent,
final ViewRoot viewRoot = mViewRoot.get();
if (viewRoot != null) {
viewRoot.dispatchPointer(event, eventTime);
+ } else {
+ new EventCompletion(mMainLooper, this, null, true, event);
}
}
@@ -2418,6 +2552,8 @@ public final class ViewRoot extends Handler implements ViewParent,
final ViewRoot viewRoot = mViewRoot.get();
if (viewRoot != null) {
viewRoot.dispatchTrackball(event, eventTime);
+ } else {
+ new EventCompletion(mMainLooper, this, null, false, event);
}
}
@@ -2502,7 +2638,7 @@ public final class ViewRoot extends Handler implements ViewParent,
* for us to consider the user to be doing fast trackball movements,
* and thus apply an acceleration.
*/
- static final long FAST_MOVE_TIME = 100;
+ static final long FAST_MOVE_TIME = 150;
/**
* Scaling factor to the time (in milliseconds) between events to how
@@ -2510,7 +2646,7 @@ public final class ViewRoot extends Handler implements ViewParent,
* is < FAST_MOVE_TIME this multiplies the acceleration; when >
* FAST_MOVE_TIME it divides it.
*/
- static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/50);
+ static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
float position;
float absPosition;
@@ -2748,7 +2884,6 @@ public final class ViewRoot extends Handler implements ViewParent,
synchronized (mActions) {
final ArrayList<HandlerAction> actions = mActions;
- final int count = actions.size();
while (actions.remove(handlerAction)) {
// Keep going
@@ -2776,7 +2911,20 @@ public final class ViewRoot extends Handler implements ViewParent,
@Override
public boolean equals(Object o) {
- return action.equals(o);
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ HandlerAction that = (HandlerAction) o;
+
+ return !(action != null ? !action.equals(that.action) : that.action != null);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = action != null ? action.hashCode() : 0;
+ result = 31 * result + (int) (delay ^ (delay >>> 32));
+ return result;
}
}
}
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 05f5fa2..47b52e4 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -35,6 +35,7 @@ public final class ViewTreeObserver {
private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
private ArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
private ArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
+ private ArrayList<OnScrollChangedListener> mOnScrollChangedListeners;
private boolean mAlive = true;
@@ -99,6 +100,20 @@ public final class ViewTreeObserver {
}
/**
+ * Interface definition for a callback to be invoked when
+ * something in the view tree has been scrolled.
+ *
+ * @hide pending API council approval
+ */
+ public interface OnScrollChangedListener {
+ /**
+ * Callback method to be invoked when something in the view tree
+ * has been scrolled.
+ */
+ public void onScrollChanged();
+ }
+
+ /**
* Parameters used with OnComputeInternalInsetsListener.
* {@hide pending API Council approval}
*/
@@ -361,6 +376,44 @@ public final class ViewTreeObserver {
}
/**
+ * Register a callback to be invoked when a view has been scrolled.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @hide pending API council approval
+ */
+ public void addOnScrollChangedListener(OnScrollChangedListener listener) {
+ checkIsAlive();
+
+ if (mOnScrollChangedListeners == null) {
+ mOnScrollChangedListeners = new ArrayList<OnScrollChangedListener>();
+ }
+
+ mOnScrollChangedListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed scroll-changed callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnScrollChangedListener(OnScrollChangedListener)
+ *
+ * @hide pending API council approval
+ */
+ public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
+ checkIsAlive();
+ if (mOnScrollChangedListeners == null) {
+ return;
+ }
+ mOnScrollChangedListeners.remove(victim);
+ }
+
+ /**
* Register a callback to be invoked when the invoked when the touch mode changes.
*
* @param listener The callback to add
@@ -525,6 +578,19 @@ public final class ViewTreeObserver {
}
/**
+ * Notifies registered listeners that something has scrolled.
+ */
+ final void dispatchOnScrollChanged() {
+ final ArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
+
+ if (listeners != null) {
+ for (OnScrollChangedListener scl : mOnScrollChangedListeners) {
+ scl.onScrollChanged();
+ }
+ }
+ }
+
+ /**
* Returns whether there are listeners for computing internal insets.
*/
final boolean hasComputeInternalInsetsListeners() {
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index f4d0fde..a573983 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -16,18 +16,17 @@
package android.view;
-import android.media.ToneGenerator;
-import android.media.AudioManager;
-import android.media.AudioService;
-import android.media.AudioSystem;
+import android.bluetooth.HeadsetBase;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.AudioService;
+import android.media.AudioSystem;
+import android.media.ToneGenerator;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
-import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
import android.widget.ImageView;
@@ -39,7 +38,7 @@ import android.widget.Toast;
* Handle the volume up and down keys.
*
* This code really should be moved elsewhere.
- *
+ *
* @hide
*/
public class VolumePanel extends Handler
@@ -54,7 +53,7 @@ public class VolumePanel extends Handler
* PhoneWindow will implement this part.
*/
public static final int PLAY_SOUND_DELAY = 300;
-
+
/**
* The delay before vibrating. This small period exists so if the user is
* moving to silent mode, it will not emit a short vibrate (it normally
@@ -64,28 +63,30 @@ public class VolumePanel extends Handler
public static final int VIBRATE_DELAY = 300;
private static final int VIBRATE_DURATION = 300;
- private static final int BEEP_DURATION = 150;
+ private static final int BEEP_DURATION = 150;
private static final int MAX_VOLUME = 100;
private static final int FREE_DELAY = 10000;
-
+
private static final int MSG_VOLUME_CHANGED = 0;
private static final int MSG_FREE_RESOURCES = 1;
private static final int MSG_PLAY_SOUND = 2;
private static final int MSG_STOP_SOUNDS = 3;
private static final int MSG_VIBRATE = 4;
-
+
private static final int RINGTONE_VOLUME_TEXT = com.android.internal.R.string.volume_ringtone;
private static final int MUSIC_VOLUME_TEXT = com.android.internal.R.string.volume_music;
private static final int INCALL_VOLUME_TEXT = com.android.internal.R.string.volume_call;
private static final int ALARM_VOLUME_TEXT = com.android.internal.R.string.volume_alarm;
private static final int UNKNOWN_VOLUME_TEXT = com.android.internal.R.string.volume_unknown;
private static final int NOTIFICATION_VOLUME_TEXT =
- com.android.internal.R.string.volume_notification;
-
+ com.android.internal.R.string.volume_notification;
+ private static final int BLUETOOTH_INCALL_VOLUME_TEXT =
+ com.android.internal.R.string.volume_bluetooth_call;
+
protected Context mContext;
private AudioManager mAudioManager;
protected AudioService mAudioService;
-
+
private final Toast mToast;
private final View mView;
private final TextView mMessage;
@@ -117,13 +118,13 @@ public class VolumePanel extends Handler
mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
mVibrator = new Vibrator();
}
-
+
public void postVolumeChanged(int streamType, int flags) {
if (hasMessages(MSG_VOLUME_CHANGED)) return;
removeMessages(MSG_FREE_RESOURCES);
obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
}
-
+
/**
* Override this if you have other work to do when the volume changes (for
* example, vibrating, playing a sound, etc.). Make sure to call through to
@@ -132,31 +133,31 @@ public class VolumePanel extends Handler
protected void onVolumeChanged(int streamType, int flags) {
if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")");
-
+
if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
onShowVolumeChanged(streamType, flags);
}
-
+
if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0) {
removeMessages(MSG_PLAY_SOUND);
sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
}
-
+
if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
removeMessages(MSG_PLAY_SOUND);
removeMessages(MSG_VIBRATE);
onStopSounds();
}
-
+
removeMessages(MSG_FREE_RESOURCES);
- sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
+ sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
}
protected void onShowVolumeChanged(int streamType, int flags) {
int index = mAudioService.getStreamVolume(streamType);
int message = UNKNOWN_VOLUME_TEXT;
int additionalMessage = 0;
-
+
if (LOGD) {
Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
+ ", flags: " + flags + "), index: " + index);
@@ -166,13 +167,13 @@ public class VolumePanel extends Handler
int max = mAudioService.getStreamMaxVolume(streamType);
switch (streamType) {
-
+
case AudioManager.STREAM_RING: {
message = RINGTONE_VOLUME_TEXT;
setRingerIcon(index);
break;
}
-
+
case AudioManager.STREAM_MUSIC: {
message = MUSIC_VOLUME_TEXT;
if (mAudioManager.isBluetoothA2dpOn()) {
@@ -184,7 +185,7 @@ public class VolumePanel extends Handler
}
break;
}
-
+
case AudioManager.STREAM_VOICE_CALL: {
/*
* For in-call voice call volume, there is no inaudible volume.
@@ -194,13 +195,7 @@ public class VolumePanel extends Handler
index++;
max++;
message = INCALL_VOLUME_TEXT;
- if (mAudioManager.isBluetoothScoOn()) {
- additionalMessage =
- com.android.internal.R.string.volume_call_hint_playing_through_bluetooth;
- setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_in_call);
- } else {
- setSmallIcon(index);
- }
+ setSmallIcon(index);
break;
}
@@ -209,12 +204,25 @@ public class VolumePanel extends Handler
setSmallIcon(index);
break;
}
-
+
case AudioManager.STREAM_NOTIFICATION: {
message = NOTIFICATION_VOLUME_TEXT;
setSmallIcon(index);
break;
}
+
+ case AudioManager.STREAM_BLUETOOTH_SCO: {
+ /*
+ * For in-call voice call volume, there is no inaudible volume.
+ * Rescale the UI control so the progress bar doesn't go all
+ * the way to zero and don't show the mute icon.
+ */
+ index++;
+ max++;
+ message = BLUETOOTH_INCALL_VOLUME_TEXT;
+ setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_in_call);
+ break;
+ }
}
String messageString = Resources.getSystem().getString(message);
@@ -228,25 +236,25 @@ public class VolumePanel extends Handler
mAdditionalMessage.setVisibility(View.VISIBLE);
mAdditionalMessage.setText(Resources.getSystem().getString(additionalMessage));
}
-
+
if (max != mLevel.getMax()) {
mLevel.setMax(max);
}
mLevel.setProgress(index);
-
+
mToast.setView(mView);
mToast.setDuration(Toast.LENGTH_SHORT);
mToast.setGravity(Gravity.TOP, 0, 0);
mToast.show();
-
+
// Do a little vibrate if applicable (only when going into vibrate mode)
- if ((flags & AudioManager.FLAG_VIBRATE) != 0 &&
+ if ((flags & AudioManager.FLAG_VIBRATE) != 0 &&
mAudioService.isStreamAffectedByRingerMode(streamType) &&
mAudioService.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE &&
mAudioService.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) {
sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
}
-
+
}
protected void onPlaySound(int streamType, int flags) {
@@ -256,7 +264,7 @@ public class VolumePanel extends Handler
// Force stop right now
onStopSounds();
}
-
+
synchronized (this) {
ToneGenerator toneGen = getOrCreateToneGenerator(streamType);
toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
@@ -266,7 +274,7 @@ public class VolumePanel extends Handler
}
protected void onStopSounds() {
-
+
synchronized (this) {
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int i = numStreamTypes - 1; i >= 0; i--) {
@@ -277,17 +285,17 @@ public class VolumePanel extends Handler
}
}
}
-
+
protected void onVibrate() {
-
+
// Make sure we ended up in vibrate ringer mode
if (mAudioService.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) {
return;
}
-
+
mVibrator.vibrate(VIBRATE_DURATION);
}
-
+
/**
* Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
*/
@@ -303,13 +311,13 @@ public class VolumePanel extends Handler
/**
* Makes the small icon visible, and hides the large icon.
- *
+ *
* @param index The volume index, where 0 means muted.
*/
private void setSmallIcon(int index) {
mLargeStreamIcon.setVisibility(View.GONE);
mSmallStreamIcon.setVisibility(View.VISIBLE);
-
+
mSmallStreamIcon.setImageResource(index == 0
? com.android.internal.R.drawable.ic_volume_off_small
: com.android.internal.R.drawable.ic_volume_small);
@@ -317,7 +325,7 @@ public class VolumePanel extends Handler
/**
* Makes the large image view visible with the given icon.
- *
+ *
* @param resId The icon to display.
*/
private void setLargeIcon(int resId) {
@@ -329,7 +337,7 @@ public class VolumePanel extends Handler
/**
* Makes the ringer icon visible with an icon that is chosen
* based on the current ringer mode.
- *
+ *
* @param index
*/
private void setRingerIcon(int index) {
@@ -350,13 +358,13 @@ public class VolumePanel extends Handler
}
mLargeStreamIcon.setImageResource(icon);
}
-
+
protected void onFreeResources() {
// We'll keep the views, just ditch the cached drawable and hence
// bitmaps
mSmallStreamIcon.setImageDrawable(null);
mLargeStreamIcon.setImageDrawable(null);
-
+
synchronized (this) {
for (int i = mToneGenerators.length - 1; i >= 0; i--) {
if (mToneGenerators[i] != null) {
@@ -366,26 +374,26 @@ public class VolumePanel extends Handler
}
}
}
-
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
-
+
case MSG_VOLUME_CHANGED: {
onVolumeChanged(msg.arg1, msg.arg2);
break;
}
-
+
case MSG_FREE_RESOURCES: {
onFreeResources();
break;
}
-
+
case MSG_STOP_SOUNDS: {
onStopSounds();
break;
}
-
+
case MSG_PLAY_SOUND: {
onPlaySound(msg.arg1, msg.arg2);
break;
@@ -395,8 +403,8 @@ public class VolumePanel extends Handler
onVibrate();
break;
}
-
+
}
}
-
+
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index a68436b..428de67 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -525,6 +525,21 @@ public abstract class Window {
}
/**
+ * Specify custom animations to use for the window, as per
+ * {@link WindowManager.LayoutParams#windowAnimations
+ * WindowManager.LayoutParams.windowAnimations}. Providing anything besides
+ * 0 here will override the animations the window would
+ * normally retrieve from its theme.
+ */
+ public void setWindowAnimations(int resId) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.windowAnimations = resId;
+ if (mCallback != null) {
+ mCallback.onWindowAttributesChanged(attrs);
+ }
+ }
+
+ /**
* Specify an explicit soft input mode to use for the window, as per
* {@link WindowManager.LayoutParams#softInputMode
* WindowManager.LayoutParams.softInputMode}. Providing anything besides
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 7e47ad1..b87cc42 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -16,6 +16,7 @@
package android.view;
+import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.os.Parcel;
@@ -126,8 +127,6 @@ public interface WindowManager extends ViewManager {
* @see #TYPE_APPLICATION_MEDIA
* @see #TYPE_APPLICATION_SUB_PANEL
* @see #TYPE_APPLICATION_ATTACHED_DIALOG
- * @see #TYPE_INPUT_METHOD
- * @see #TYPE_INPUT_METHOD_DIALOG
* @see #TYPE_STATUS_BAR
* @see #TYPE_SEARCH_BAR
* @see #TYPE_PHONE
@@ -514,26 +513,33 @@ public interface WindowManager extends ViewManager {
/**
* Visibility state for {@link #softInputMode}: please hide any soft input
- * area.
+ * area when normally appropriate (when the user is navigating
+ * forward to your window).
*/
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
/**
+ * Visibility state for {@link #softInputMode}: please always hide any
+ * soft input area when this window receives focus.
+ */
+ public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
+
+ /**
* Visibility state for {@link #softInputMode}: please show the soft
* input area when normally appropriate (when the user is navigating
* forward to your window).
*/
- public static final int SOFT_INPUT_STATE_VISIBLE = 3;
+ public static final int SOFT_INPUT_STATE_VISIBLE = 4;
/**
* Visibility state for {@link #softInputMode}: please always make the
* soft input area visible when this window receives input focus.
*/
- public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 4;
+ public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
/**
* Mask for {@link #softInputMode} of the bits that determine the
- * way that the window should be adjusted to accomodate the soft
+ * way that the window should be adjusted to accommodate the soft
* input window.
*/
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
@@ -635,6 +641,14 @@ public interface WindowManager extends ViewManager {
public float dimAmount = 1.0f;
/**
+ * This can be used to override the user's preferred brightness of
+ * the screen. A value of less than 0, the default, means to use the
+ * preferred screen brightness. 0 to 1 adjusts the brightness from
+ * dark to full bright.
+ */
+ public float screenBrightness = -1.0f;
+
+ /**
* Identifier for this window. This will usually be filled in for
* you.
*/
@@ -645,6 +659,17 @@ public interface WindowManager extends ViewManager {
*/
public String packageName = null;
+ /**
+ * Specific orientation value for a window.
+ * May be any of the same values allowed
+ * for {@link android.content.pm.ActivityInfo#screenOrientation}.
+ * If not set, a default value of
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}
+ * will be used.
+ */
+ public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+
public LayoutParams() {
super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
type = TYPE_APPLICATION;
@@ -719,9 +744,11 @@ public interface WindowManager extends ViewManager {
out.writeInt(windowAnimations);
out.writeFloat(alpha);
out.writeFloat(dimAmount);
+ out.writeFloat(screenBrightness);
out.writeStrongBinder(token);
out.writeString(packageName);
TextUtils.writeToParcel(mTitle, out, parcelableFlags);
+ out.writeInt(screenOrientation);
}
public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -752,9 +779,11 @@ public interface WindowManager extends ViewManager {
windowAnimations = in.readInt();
alpha = in.readFloat();
dimAmount = in.readFloat();
+ screenBrightness = in.readFloat();
token = in.readStrongBinder();
packageName = in.readString();
mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ screenOrientation = in.readInt();
}
public static final int LAYOUT_CHANGED = 1<<0;
@@ -767,6 +796,8 @@ public interface WindowManager extends ViewManager {
public static final int ALPHA_CHANGED = 1<<7;
public static final int MEMORY_TYPE_CHANGED = 1<<8;
public static final int SOFT_INPUT_MODE_CHANGED = 1<<9;
+ public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
+ public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
public final int copyFrom(LayoutParams o) {
int changes = 0;
@@ -861,7 +892,15 @@ public interface WindowManager extends ViewManager {
dimAmount = o.dimAmount;
changes |= DIM_AMOUNT_CHANGED;
}
+ if (screenBrightness != o.screenBrightness) {
+ screenBrightness = o.screenBrightness;
+ changes |= SCREEN_BRIGHTNESS_CHANGED;
+ }
+ if (screenOrientation != o.screenOrientation) {
+ screenOrientation = o.screenOrientation;
+ changes |= SCREEN_ORIENTATION_CHANGED;
+ }
return changes;
}
@@ -907,6 +946,10 @@ public interface WindowManager extends ViewManager {
sb.append(" wanim=0x");
sb.append(Integer.toHexString(windowAnimations));
}
+ if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+ sb.append(" or=");
+ sb.append(screenOrientation);
+ }
sb.append('}');
return sb.toString();
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 6af4915..220869c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -54,7 +54,7 @@ import android.view.animation.Animation;
* window manager is a very low-level system service, there are few other
* system services you can call with this lock held. It is explicitly okay to
* make calls into the package manager and power manager; it is explicitly not
- * okay to make calls into the activity manager. Note that
+ * okay to make calls into the activity manager or most other services. Note that
* {@link android.content.Context#checkPermission(String, int, int)} and
* variations require calling into the activity manager.
* <dt> Li <dd> Called with the input thread lock held. This lock can be
@@ -129,14 +129,22 @@ public interface WindowManagerPolicy {
Rect contentFrame, Rect visibleFrame);
/**
- * Retrieve the current frame of the window. Must be called with the
- * window manager lock held.
+ * Retrieve the current frame of the window that has been assigned by
+ * the window manager. Must be called with the window manager lock held.
*
* @return Rect The rectangle holding the window frame.
*/
public Rect getFrameLw();
/**
+ * Retrieve the current frame of the window that is actually shown.
+ * Must be called with the window manager lock held.
+ *
+ * @return Rect The rectangle holding the shown window frame.
+ */
+ public Rect getShownFrameLw();
+
+ /**
* Retrieve the frame of the display that this window was last
* laid out in. Must be called with the
* window manager lock held.
@@ -273,9 +281,12 @@ public interface WindowManagerPolicy {
* false, this is based on the currently requested
* frame, which any current animation will be moving
* towards.
+ * @param onlyOpaque If true, this will only pass if the window is
+ * also opaque.
* @return Returns true if the window is both full screen and opaque
*/
- public boolean fillsScreenLw(int width, int height, boolean shownFrame);
+ public boolean fillsScreenLw(int width, int height, boolean shownFrame,
+ boolean onlyOpaque);
/**
* Returns true if this window has been shown on screen at some time in
@@ -289,16 +300,18 @@ public interface WindowManagerPolicy {
* Can be called by the policy to force a window to be hidden,
* regardless of whether the client or window manager would like
* it shown. Must be called with the window manager lock held.
+ * Returns true if {@link #showLw} was last called for the window.
*/
- public void hideLw(boolean doAnimation);
+ public boolean hideLw(boolean doAnimation);
/**
* Can be called to undo the effect of {@link #hideLw}, allowing a
* window to be shown as long as the window manager and client would
* also like it to be shown. Must be called with the window manager
* lock held.
+ * Returns true if {@link #hideLw} was last called for the window.
*/
- public void showLw(boolean doAnimation);
+ public boolean showLw(boolean doAnimation);
}
/** No transition happening. */
@@ -735,10 +748,17 @@ public interface WindowManagerPolicy {
* ActivityInfo.SCREEN_ORIENTATION_PORTRAIT}), return a surface
* rotation.
*/
- public int rotationForOrientation(int orientation);
+ public int rotationForOrientationLw(int orientation, int lastRotation,
+ boolean displayEnabled);
+
+ /**
+ * Called when the system is mostly done booting to dentermine whether
+ * the system should go into safe mode.
+ */
+ public boolean detectSafeMode();
/**
- * Called when the system is mostly done booting
+ * Called when the system is mostly done booting.
*/
public void systemReady();
@@ -761,5 +781,16 @@ public interface WindowManagerPolicy {
*/
public boolean isCheekPressedAgainstScreen(MotionEvent ev);
- public void setCurrentOrientation(int newOrientation);
+ public void setCurrentOrientationLw(int newOrientation);
+
+ /**
+ * Call from application to perform haptic feedback on its window.
+ */
+ public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always);
+
+ /**
+ * Called when we have stopped keeping the screen on because a window
+ * requesting this is no longer visible.
+ */
+ public void screenOnStoppedLw();
}
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
new file mode 100755
index 0000000..f1f5f70
--- /dev/null
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -0,0 +1,196 @@
+/*
+ * 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.view;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * A special helper class used by the WindowManager
+ * for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ * @hide
+ */
+public abstract class WindowOrientationListener {
+ private static final String TAG = "WindowOrientationListener";
+ private static final boolean DEBUG = false;
+ private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private SensorManager mSensorManager;
+ private boolean mEnabled = false;
+ private int mRate;
+ private Sensor mSensor;
+ private SensorEventListener mSensorEventListener;
+ private int mSensorRotation = -1;
+
+ /**
+ * Creates a new WindowOrientationListener.
+ *
+ * @param context for the WindowOrientationListener.
+ */
+ public WindowOrientationListener(Context context) {
+ this(context, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ /**
+ * Creates a new WindowOrientationListener.
+ *
+ * @param context for the WindowOrientationListener.
+ * @param rate at which sensor events are processed (see also
+ * {@link android.hardware.SensorManager SensorManager}). Use the default
+ * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
+ * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+ */
+ public WindowOrientationListener(Context context, int rate) {
+ mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mRate = rate;
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (mSensor != null) {
+ // Create listener only if sensors do exist
+ mSensorEventListener = new SensorEventListenerImpl();
+ }
+ }
+
+ /**
+ * Enables the WindowOrientationListener so it will monitor the sensor and call
+ * {@link #onOrientationChanged} when the device orientation changes.
+ */
+ public void enable() {
+ if (mSensor == null) {
+ Log.w(TAG, "Cannot detect sensors. Not enabled");
+ return;
+ }
+ if (mEnabled == false) {
+ if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled");
+ mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
+ mEnabled = true;
+ }
+ }
+
+ /**
+ * Disables the WindowOrientationListener.
+ */
+ public void disable() {
+ if (mSensor == null) {
+ Log.w(TAG, "Cannot detect sensors. Invalid disable");
+ return;
+ }
+ if (mEnabled == true) {
+ if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled");
+ mSensorManager.unregisterListener(mSensorEventListener);
+ mEnabled = false;
+ }
+ }
+
+ class SensorEventListenerImpl implements SensorEventListener {
+ private static final int _DATA_X = 0;
+ private static final int _DATA_Y = 1;
+ private static final int _DATA_Z = 2;
+ // Angle around x-axis thats considered almost perfect vertical to hold
+ // the device
+ private static final int PIVOT = 30;
+ // Angle around x-asis that's considered almost too vertical. Beyond
+ // this angle will not result in any orientation changes. f phone faces uses,
+ // the device is leaning backward.
+ private static final int PIVOT_UPPER = 65;
+ // Angle about x-axis that's considered negative vertical. Beyond this
+ // angle will not result in any orientation changes. If phone faces uses,
+ // the device is leaning forward.
+ private static final int PIVOT_LOWER = 0;
+ // Upper threshold limit for switching from portrait to landscape
+ private static final int PL_UPPER = 280;
+ // Lower threshold limit for switching from landscape to portrait
+ private static final int LP_LOWER = 320;
+ // Lower threshold limt for switching from portrait to landscape
+ private static final int PL_LOWER = 240;
+ // Upper threshold limit for switching from landscape to portrait
+ private static final int LP_UPPER = 360;
+
+ // Internal value used for calculating linear variant
+ private static final float PL_LINEAR_FACTOR =
+ ((float)(PL_UPPER-PL_LOWER))/((float)(PIVOT_UPPER-PIVOT_LOWER));
+ // Internal value used for calculating linear variant
+ private static final float LP_LINEAR_FACTOR =
+ ((float)(LP_UPPER - LP_LOWER))/((float)(PIVOT_UPPER-PIVOT_LOWER));
+
+ public void onSensorChanged(SensorEvent event) {
+ float[] values = event.values;
+ float X = values[_DATA_X];
+ float Y = values[_DATA_Y];
+ float Z = values[_DATA_Z];
+ float OneEightyOverPi = 57.29577957855f;
+ float gravity = (float) Math.sqrt(X*X+Y*Y+Z*Z);
+ float zyangle = Math.abs((float)Math.asin(Z/gravity)*OneEightyOverPi);
+ int rotation = mSensorRotation;
+ if ((zyangle <= PIVOT_UPPER) && (zyangle >= PIVOT_LOWER)) {
+ // Check orientation only if the phone is flat enough
+ // Don't trust the angle if the magnitude is small compared to the y value
+ float angle = (float)Math.atan2(Y, -X) * OneEightyOverPi;
+ int orientation = 90 - (int)Math.round(angle);
+ // normalize to 0 - 359 range
+ while (orientation >= 360) {
+ orientation -= 360;
+ }
+ while (orientation < 0) {
+ orientation += 360;
+ }
+
+ float delta = (float)Math.abs(zyangle - PIVOT);
+ if (((orientation >= 0) && (orientation <= LP_UPPER)) ||
+ (orientation >= PL_LOWER)) {
+ float threshold;
+ if (mSensorRotation == Surface.ROTATION_90) {
+ threshold = LP_LOWER + (LP_LINEAR_FACTOR * delta) ;
+ } else {
+ threshold = PL_UPPER - (PL_LINEAR_FACTOR * delta);
+ }
+ rotation = (orientation >= PL_LOWER &&
+ orientation <= threshold) ? Surface.ROTATION_90 : Surface.ROTATION_0;
+ }
+
+ }
+ if (rotation != mSensorRotation) {
+ mSensorRotation = rotation;
+ onOrientationChanged(mSensorRotation);
+ }
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+ }
+
+ /*
+ * Returns true if sensor is enabled and false otherwise
+ */
+ public boolean canDetectOrientation() {
+ return mSensor != null;
+ }
+
+ /**
+ * Called when the rotation view of the device has changed.
+ * Can be either Surface.ROTATION_90 or Surface.ROTATION_0.
+ * @param rotation The new orientation of the device.
+ *
+ * @see #ORIENTATION_UNKNOWN
+ */
+ abstract public void onOrientationChanged(int rotation);
+}
diff --git a/core/java/android/view/animation/AlphaAnimation.java b/core/java/android/view/animation/AlphaAnimation.java
index 16a10a4..651fe45 100644
--- a/core/java/android/view/animation/AlphaAnimation.java
+++ b/core/java/android/view/animation/AlphaAnimation.java
@@ -31,7 +31,7 @@ public class AlphaAnimation extends Animation {
private float mToAlpha;
/**
- * Constructor used whan an AlphaAnimation is loaded from a resource.
+ * Constructor used when an AlphaAnimation is loaded from a resource.
*
* @param context Application context to use
* @param attrs Attribute set from which to read values
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 9264398..a662760 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -20,13 +20,14 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
+import android.graphics.RectF;
/**
* Abstraction for an Animation that can be applied to Views, Surfaces, or
* other objects. See the {@link android.view.animation animation package
* description file}.
*/
-public abstract class Animation {
+public abstract class Animation implements Cloneable {
/**
* Repeat the animation indefinitely.
*/
@@ -174,8 +175,13 @@ public abstract class Animation {
*/
private int mZAdjustment;
- // Indicates what was the last value returned by getTransformation()
private boolean mMore = true;
+ private boolean mOneMoreTime = true;
+
+ RectF mPreviousRegion = new RectF();
+ RectF mRegion = new RectF();
+ Transformation mTransformation = new Transformation();
+ Transformation mPreviousTransformation = new Transformation();
/**
* Creates a new animation with a duration of 0ms, the default interpolator, with
@@ -217,16 +223,29 @@ public abstract class Animation {
a.recycle();
}
+ @Override
+ protected Animation clone() throws CloneNotSupportedException {
+ final Animation animation = (Animation) super.clone();
+ animation.mPreviousRegion = new RectF();
+ animation.mRegion = new RectF();
+ animation.mTransformation = new Transformation();
+ animation.mPreviousTransformation = new Transformation();
+ return animation;
+ }
+
/**
* Reset the initialization state of this animation.
*
* @see #initialize(int, int, int, int)
*/
public void reset() {
+ mPreviousRegion.setEmpty();
+ mPreviousTransformation.clear();
mInitialized = false;
mCycleFlip = false;
mRepeated = 0;
mMore = true;
+ mOneMoreTime = true;
}
/**
@@ -255,10 +274,8 @@ public abstract class Animation {
* @param parentHeight Height of the animated object's parent
*/
public void initialize(int width, int height, int parentWidth, int parentHeight) {
+ reset();
mInitialized = true;
- mCycleFlip = false;
- mRepeated = 0;
- mMore = true;
}
/**
@@ -323,6 +340,7 @@ public abstract class Animation {
* to run.
*/
public void restrictDuration(long durationMillis) {
+ // If we start after the duration, then we just won't run.
if (mStartOffset > durationMillis) {
mStartOffset = durationMillis;
mDuration = 0;
@@ -332,11 +350,26 @@ public abstract class Animation {
long dur = mDuration + mStartOffset;
if (dur > durationMillis) {
- mDuration = dur = durationMillis-mStartOffset;
+ mDuration = durationMillis-mStartOffset;
+ dur = durationMillis;
+ }
+ // If the duration is 0 or less, then we won't run.
+ if (mDuration <= 0) {
+ mDuration = 0;
+ mRepeatCount = 0;
+ return;
}
+ // Reduce the number of repeats to keep below the maximum duration.
+ // The comparison between mRepeatCount and duration is to catch
+ // overflows after multiplying them.
if (mRepeatCount < 0 || mRepeatCount > durationMillis
|| (dur*mRepeatCount) > durationMillis) {
- mRepeatCount = (int)(durationMillis/dur);
+ // Figure out how many times to do the animation. Subtract 1 since
+ // repeat count is the number of times to repeat so 0 runs once.
+ mRepeatCount = (int)(durationMillis/dur) - 1;
+ if (mRepeatCount < 0) {
+ mRepeatCount = 0;
+ }
}
}
@@ -399,7 +432,7 @@ public abstract class Animation {
* Sets how many times the animation should be repeated. If the repeat
* count is 0, the animation is never repeated. If the repeat count is
* greater than 0 or {@link #INFINITE}, the repeat mode will be taken
- * into account. The repeat count if 0 by default.
+ * into account. The repeat count is 0 by default.
*
* @param repeatCount the number of times the animation should be repeated
* @attr ref android.R.styleable#Animation_repeatCount
@@ -707,6 +740,11 @@ public abstract class Animation {
}
}
+ if (!mMore && mOneMoreTime) {
+ mOneMoreTime = false;
+ return true;
+ }
+
return mMore;
}
@@ -765,7 +803,59 @@ public abstract class Animation {
return value;
}
}
-
+
+ /**
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ * @param invalidate
+ * @param transformation
+ *
+ * @hide
+ */
+ public void getInvalidateRegion(int left, int top, int right, int bottom,
+ RectF invalidate, Transformation transformation) {
+
+ final RectF tempRegion = mRegion;
+ final RectF previousRegion = mPreviousRegion;
+
+ invalidate.set(left, top, right, bottom);
+ transformation.getMatrix().mapRect(invalidate);
+ // Enlarge the invalidate region to account for rounding errors
+ invalidate.inset(-1.0f, -1.0f);
+ tempRegion.set(invalidate);
+ invalidate.union(previousRegion);
+
+ previousRegion.set(tempRegion);
+
+ final Transformation tempTransformation = mTransformation;
+ final Transformation previousTransformation = mPreviousTransformation;
+
+ tempTransformation.set(transformation);
+ transformation.set(previousTransformation);
+ previousTransformation.set(tempTransformation);
+ }
+
+ /**
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ *
+ * @hide
+ */
+ public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
+ final RectF region = mPreviousRegion;
+ region.set(left, top, right, bottom);
+ // Enlarge the invalidate region to account for rounding errors
+ region.inset(-1.0f, -1.0f);
+ if (mFillBefore) {
+ final Transformation previousTransformation = mPreviousTransformation;
+ applyTransformation(0.0f, previousTransformation);
+ }
+ }
+
/**
* Utility class to parse a string description of a size.
*/
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 688da70..98b2594 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -19,6 +19,7 @@ package android.view.animation;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.graphics.RectF;
import java.util.ArrayList;
import java.util.List;
@@ -39,6 +40,7 @@ public class AnimationSet extends Animation {
private static final int PROPERTY_SHARE_INTERPOLATOR_MASK = 0x10;
private static final int PROPERTY_DURATION_MASK = 0x20;
private static final int PROPERTY_MORPH_MATRIX_MASK = 0x40;
+ private static final int PROPERTY_CHANGE_BOUNDS_MASK = 0x80;
private int mFlags = 0;
@@ -51,7 +53,7 @@ public class AnimationSet extends Animation {
private long[] mStoredOffsets;
/**
- * Constructor used whan an AnimationSet is loaded from a resource.
+ * Constructor used when an AnimationSet is loaded from a resource.
*
* @param context Application context to use
* @param attrs Attribute set from which to read values
@@ -82,6 +84,22 @@ public class AnimationSet extends Animation {
init();
}
+ @Override
+ protected AnimationSet clone() throws CloneNotSupportedException {
+ final AnimationSet animation = (AnimationSet) super.clone();
+ animation.mTempTransformation = new Transformation();
+ animation.mAnimations = new ArrayList<Animation>();
+
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+
+ for (int i = 0; i < count; i++) {
+ animation.mAnimations.add(animations.get(i).clone());
+ }
+
+ return animation;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
@@ -145,6 +163,11 @@ public class AnimationSet extends Animation {
mFlags |= PROPERTY_MORPH_MATRIX_MASK;
}
+ boolean changeBounds = (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == 0;
+ if (changeBounds && a.willChangeTransformationMatrix()) {
+ mFlags |= PROPERTY_CHANGE_BOUNDS_MASK;
+ }
+
if (mAnimations.size() == 1) {
mDuration = a.getStartOffset() + a.getDuration();
mLastEnd = mStartOffset + mDuration;
@@ -239,7 +262,32 @@ public class AnimationSet extends Animation {
}
return duration;
}
-
+
+ /**
+ * @hide
+ */
+ public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
+ final RectF region = mPreviousRegion;
+ region.set(left, top, right, bottom);
+ region.inset(-1.0f, -1.0f);
+
+ if (mFillBefore) {
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+ final Transformation temp = mTempTransformation;
+
+ final Transformation previousTransformation = mPreviousTransformation;
+
+ for (int i = count - 1; i >= 0; --i) {
+ final Animation a = animations.get(i);
+
+ temp.clear();
+ a.applyTransformation(0.0f, temp);
+ previousTransformation.compose(temp);
+ }
+ }
+ }
+
/**
* The transformation of an animation set is the concatenation of all of its
* component animations.
@@ -313,7 +361,7 @@ public class AnimationSet extends Animation {
== PROPERTY_SHARE_INTERPOLATOR_MASK;
boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK)
== PROPERTY_START_OFFSET_MASK;
-
+
if (shareInterpolator) {
ensureInterpolator();
}
@@ -327,7 +375,17 @@ public class AnimationSet extends Animation {
final int repeatMode = mRepeatMode;
final Interpolator interpolator = mInterpolator;
final long startOffset = mStartOffset;
-
+
+
+ long[] storedOffsets = mStoredOffsets;
+ if (startOffsetSet) {
+ if (storedOffsets == null || storedOffsets.length != count) {
+ storedOffsets = mStoredOffsets = new long[count];
+ }
+ } else if (storedOffsets != null) {
+ storedOffsets = mStoredOffsets = null;
+ }
+
for (int i = 0; i < count; i++) {
Animation a = children.get(i);
if (durationSet) {
@@ -346,42 +404,35 @@ public class AnimationSet extends Animation {
a.setInterpolator(interpolator);
}
if (startOffsetSet) {
- a.setStartOffset(startOffset);
+ long offset = a.getStartOffset();
+ a.setStartOffset(offset + startOffset);
+ storedOffsets[i] = offset;
}
a.initialize(width, height, parentWidth, parentHeight);
}
}
- /**
- * @hide
- * @param startOffset the startOffset to add to the children's startOffset
- */
- void saveChildrenStartOffset(long startOffset) {
- final ArrayList<Animation> children = mAnimations;
- final int count = children.size();
- long[] storedOffsets = mStoredOffsets = new long[count];
-
- for (int i = 0; i < count; i++) {
- Animation animation = children.get(i);
- long offset = animation.getStartOffset();
- animation.setStartOffset(offset + startOffset);
- storedOffsets[i] = offset;
- }
+ @Override
+ public void reset() {
+ super.reset();
+ restoreChildrenStartOffset();
}
/**
* @hide
*/
void restoreChildrenStartOffset() {
+ final long[] offsets = mStoredOffsets;
+ if (offsets == null) return;
+
final ArrayList<Animation> children = mAnimations;
final int count = children.size();
- final long[] offsets = mStoredOffsets;
for (int i = 0; i < count; i++) {
children.get(i).setStartOffset(offsets[i]);
}
}
-
+
/**
* @return All the child animations in this AnimationSet. Note that
* this may include other AnimationSets, which are not expanded.
@@ -394,4 +445,9 @@ public class AnimationSet extends Animation {
public boolean willChangeTransformationMatrix() {
return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK;
}
+
+ @Override
+ public boolean willChangeBounds() {
+ return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK;
+ }
}
diff --git a/core/java/android/view/animation/LayoutAnimationController.java b/core/java/android/view/animation/LayoutAnimationController.java
index 9cfa8d7..882e738 100644
--- a/core/java/android/view/animation/LayoutAnimationController.java
+++ b/core/java/android/view/animation/LayoutAnimationController.java
@@ -318,9 +318,16 @@ public class LayoutAnimationController {
* @see #getDelayForView(android.view.View)
*/
public final Animation getAnimationForView(View view) {
- final long delay = getDelayForView(view);
+ final long delay = getDelayForView(view) + mAnimation.getStartOffset();
mMaxDelay = Math.max(mMaxDelay, delay);
- return new DelayedAnimation(delay, mAnimation);
+
+ try {
+ final Animation animation = mAnimation.clone();
+ animation.setStartOffset(delay);
+ return animation;
+ } catch (CloneNotSupportedException e) {
+ return null;
+ }
}
/**
@@ -425,149 +432,4 @@ public class LayoutAnimationController {
*/
public int index;
}
-
- /**
- * Encapsulates an animation and delays its start offset by a specified
- * amount. This allows to reuse the same base animation for various views
- * and get the effect of running multiple instances of the animation at
- * different times.
- */
- private static class DelayedAnimation extends Animation {
- private final long mDelay;
- private final Animation mAnimation;
-
- /**
- * Creates a new delayed animation that will delay the controller's
- * animation by the specified delay in milliseconds.
- *
- * @param delay the delay in milliseconds by which to offset the
- * @param animation the animation to delay
- */
- private DelayedAnimation(long delay, Animation animation) {
- mDelay = delay;
- mAnimation = animation;
- }
-
- @Override
- public boolean isInitialized() {
- return mAnimation.isInitialized();
- }
-
- @Override
- public void initialize(int width, int height, int parentWidth, int parentHeight) {
- mAnimation.initialize(width, height, parentWidth, parentHeight);
- }
-
- @Override
- public void reset() {
- mAnimation.reset();
- }
-
- @Override
- public boolean getTransformation(long currentTime, Transformation outTransformation) {
- final long oldOffset = mAnimation.getStartOffset();
- final boolean isSet = mAnimation instanceof AnimationSet;
- if (isSet) {
- AnimationSet set = ((AnimationSet) mAnimation);
- set.saveChildrenStartOffset(mDelay);
- }
- mAnimation.setStartOffset(oldOffset + mDelay);
-
- boolean result = mAnimation.getTransformation(currentTime,
- outTransformation);
-
- if (isSet) {
- AnimationSet set = ((AnimationSet) mAnimation);
- set.restoreChildrenStartOffset();
- }
- mAnimation.setStartOffset(oldOffset);
-
- return result;
- }
-
- @Override
- public void setStartTime(long startTimeMillis) {
- mAnimation.setStartTime(startTimeMillis);
- }
-
- @Override
- public long getStartTime() {
- return mAnimation.getStartTime();
- }
-
- @Override
- public void setInterpolator(Interpolator i) {
- mAnimation.setInterpolator(i);
- }
-
- @Override
- public void setStartOffset(long startOffset) {
- mAnimation.setStartOffset(startOffset);
- }
-
- @Override
- public void setDuration(long durationMillis) {
- mAnimation.setDuration(durationMillis);
- }
-
- @Override
- public void scaleCurrentDuration(float scale) {
- mAnimation.scaleCurrentDuration(scale);
- }
-
- @Override
- public void setRepeatMode(int repeatMode) {
- mAnimation.setRepeatMode(repeatMode);
- }
-
- @Override
- public void setFillBefore(boolean fillBefore) {
- mAnimation.setFillBefore(fillBefore);
- }
-
- @Override
- public void setFillAfter(boolean fillAfter) {
- mAnimation.setFillAfter(fillAfter);
- }
-
- @Override
- public Interpolator getInterpolator() {
- return mAnimation.getInterpolator();
- }
-
- @Override
- public long getDuration() {
- return mAnimation.getDuration();
- }
-
- @Override
- public long getStartOffset() {
- return mAnimation.getStartOffset() + mDelay;
- }
-
- @Override
- public int getRepeatMode() {
- return mAnimation.getRepeatMode();
- }
-
- @Override
- public boolean getFillBefore() {
- return mAnimation.getFillBefore();
- }
-
- @Override
- public boolean getFillAfter() {
- return mAnimation.getFillAfter();
- }
-
- @Override
- public boolean willChangeTransformationMatrix() {
- return mAnimation.willChangeTransformationMatrix();
- }
-
- @Override
- public boolean willChangeBounds() {
- return mAnimation.willChangeBounds();
- }
- }
}
diff --git a/core/java/android/view/animation/RotateAnimation.java b/core/java/android/view/animation/RotateAnimation.java
index 2f51b91..284ccce 100644
--- a/core/java/android/view/animation/RotateAnimation.java
+++ b/core/java/android/view/animation/RotateAnimation.java
@@ -40,7 +40,7 @@ public class RotateAnimation extends Animation {
private float mPivotY;
/**
- * Constructor used whan an RotateAnimation is loaded from a resource.
+ * Constructor used when a RotateAnimation is loaded from a resource.
*
* @param context Application context to use
* @param attrs Attribute set from which to read values
diff --git a/core/java/android/view/animation/ScaleAnimation.java b/core/java/android/view/animation/ScaleAnimation.java
index 122ed6d..1a56c8b 100644
--- a/core/java/android/view/animation/ScaleAnimation.java
+++ b/core/java/android/view/animation/ScaleAnimation.java
@@ -40,7 +40,7 @@ public class ScaleAnimation extends Animation {
private float mPivotY;
/**
- * Constructor used whan an ScaleAnimation is loaded from a resource.
+ * Constructor used when a ScaleAnimation is loaded from a resource.
*
* @param context Application context to use
* @param attrs Attribute set from which to read values
diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java
index bb13972..a785d43 100644
--- a/core/java/android/view/animation/TranslateAnimation.java
+++ b/core/java/android/view/animation/TranslateAnimation.java
@@ -45,7 +45,7 @@ public class TranslateAnimation extends Animation {
private float mToYDelta;
/**
- * Constructor used when an TranslateAnimation is loaded from a resource.
+ * Constructor used when a TranslateAnimation is loaded from a resource.
*
* @param context Application context to use
* @param attrs Attribute set from which to read values
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index a6ce293..deca910 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -17,34 +17,386 @@
package android.view.inputmethod;
import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
import android.os.Handler;
-import android.os.Message;
+import android.os.SystemClock;
+import android.text.Editable;
+import android.text.NoCopySpan;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.method.MetaKeyKeyListener;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRoot;
+class ComposingText implements NoCopySpan {
+}
+
/**
* Base class for implementors of the InputConnection interface, taking care
- * of implementing common system-oriented parts of the functionality.
+ * of most of the common behavior for providing a connection to an Editable.
+ * Implementors of this class will want to be sure to implement
+ * {@link #getEditable} to provide access to their own editable object.
*/
-public abstract class BaseInputConnection implements InputConnection {
+public class BaseInputConnection implements InputConnection {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "BaseInputConnection";
+ static final Object COMPOSING = new ComposingText();
+
final InputMethodManager mIMM;
final Handler mH;
final View mTargetView;
+ final boolean mDummyMode;
- BaseInputConnection(InputMethodManager mgr) {
+ private Object[] mDefaultComposingSpans;
+
+ Editable mEditable;
+ KeyCharacterMap mKeyCharacterMap;
+
+ BaseInputConnection(InputMethodManager mgr, boolean dummyMode) {
mIMM = mgr;
mTargetView = null;
mH = null;
+ mDummyMode = dummyMode;
}
- public BaseInputConnection(View targetView) {
+ public BaseInputConnection(View targetView, boolean dummyMode) {
mIMM = (InputMethodManager)targetView.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
mH = targetView.getHandler();
mTargetView = targetView;
+ mDummyMode = dummyMode;
}
+ 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);
+ }
+
+ /**
+ * Return the target of edit operations. The default implementation
+ * returns its own fake editable that is just used for composing text;
+ * subclasses that are real text editors should override this and
+ * supply their own.
+ */
+ public Editable getEditable() {
+ if (mEditable == null) {
+ mEditable = Editable.Factory.getInstance().newEditable("");
+ Selection.setSelection(mEditable, 0);
+ }
+ return mEditable;
+ }
+
+ /**
+ * Default implementation does nothing.
+ */
+ public boolean beginBatchEdit() {
+ return false;
+ }
+
+ /**
+ * Default implementation does nothing.
+ */
+ public boolean endBatchEdit() {
+ return false;
+ }
+
+ /**
+ * Default implementation uses
+ * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
+ * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
+ */
+ public boolean clearMetaKeyStates(int states) {
+ final Editable content = getEditable();
+ if (content == null) return false;
+ MetaKeyKeyListener.clearMetaKeyState(content, states);
+ return true;
+ }
+
+ /**
+ * Default implementation does nothing.
+ */
+ public boolean commitCompletion(CompletionInfo text) {
+ return false;
+ }
+
+ /**
+ * Default implementation replaces any existing composing text with
+ * the given text. In addition, only if dummy mode, a key event is
+ * sent for the new text and the current editable buffer cleared.
+ */
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ if (DEBUG) Log.v(TAG, "commitText " + text);
+ replaceText(text, newCursorPosition, false);
+ sendCurrentText();
+ return true;
+ }
+
+ /**
+ * The default implementation performs the deletion around the current
+ * selection position of the editable text.
+ */
+ public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength
+ + " / " + rightLength);
+ final Editable content = getEditable();
+ if (content == null) return false;
+
+ beginBatchEdit();
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ // ignore the composing text.
+ int ca = getComposingSpanStart(content);
+ int cb = getComposingSpanEnd(content);
+ if (cb < ca) {
+ int tmp = ca;
+ ca = cb;
+ cb = tmp;
+ }
+ if (ca != -1 && cb != -1) {
+ if (ca < a) a = ca;
+ if (cb > b) b = cb;
+ }
+
+ int deleted = 0;
+
+ if (leftLength > 0) {
+ int start = a - leftLength;
+ if (start < 0) start = 0;
+ content.delete(start, a);
+ deleted = a - start;
+ }
+
+ if (rightLength > 0) {
+ b = b - deleted;
+
+ int end = b + rightLength;
+ if (end > content.length()) end = content.length();
+
+ content.delete(b, end);
+ }
+
+ endBatchEdit();
+
+ return true;
+ }
+
+ /**
+ * The default implementation removes the composing state from the
+ * current editable text. In addition, only if dummy mode, a key event is
+ * sent for the new text and the current editable buffer cleared.
+ */
+ public boolean finishComposingText() {
+ if (DEBUG) Log.v(TAG, "finishComposingText");
+ final Editable content = getEditable();
+ if (content != null) {
+ beginBatchEdit();
+ removeComposingSpans(content);
+ endBatchEdit();
+ sendCurrentText();
+ }
+ return true;
+ }
+
+ /**
+ * The default implementation uses TextUtils.getCapsMode to get the
+ * cursor caps mode for the current selection position in the editable
+ * text, unless in dummy mode in which case 0 is always returned.
+ */
+ public int getCursorCapsMode(int reqModes) {
+ if (mDummyMode) return 0;
+
+ final Editable content = getEditable();
+ if (content == null) return 0;
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ return TextUtils.getCapsMode(content, a, reqModes);
+ }
+
+ /**
+ * The default implementation always returns null.
+ */
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ return null;
+ }
+
+ /**
+ * The default implementation returns the given amount of text from the
+ * current cursor position in the buffer.
+ */
+ public CharSequence getTextBeforeCursor(int length, int flags) {
+ final Editable content = getEditable();
+ if (content == null) return null;
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ if (length > a) {
+ length = a;
+ }
+
+ if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+ return content.subSequence(a - length, a);
+ }
+ return TextUtils.substring(content, a - length, a);
+ }
+
+ /**
+ * The default implementation returns the given amount of text from the
+ * current cursor position in the buffer.
+ */
+ public CharSequence getTextAfterCursor(int length, int flags) {
+ final Editable content = getEditable();
+ if (content == null) return null;
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ if (b + length > content.length()) {
+ length = content.length() - b;
+ }
+
+
+ if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+ return content.subSequence(b, b + length);
+ }
+ return TextUtils.substring(content, b, b + length);
+ }
+
+ /**
+ * The default implementation does nothing.
+ */
+ public boolean performEditorAction(int actionCode) {
+ return false;
+ }
+
+ /**
+ * The default implementation does nothing.
+ */
+ public boolean performContextMenuAction(int id) {
+ return false;
+ }
+
+ /**
+ * The default implementation does nothing.
+ */
+ public boolean performPrivateCommand(String action, Bundle data) {
+ return false;
+ }
+
+ /**
+ * The default implementation places the given text into the editable,
+ * replacing any existing composing text. The new text is marked as
+ * in a composing state with the composing style.
+ */
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ if (DEBUG) Log.v(TAG, "setComposingText " + text);
+ replaceText(text, newCursorPosition, true);
+ return true;
+ }
+
+ /**
+ * The default implementation changes the selection position in the
+ * current editable text.
+ */
+ public boolean setSelection(int start, int end) {
+ if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
+ final Editable content = getEditable();
+ if (content == null) return false;
+ int len = content.length();
+ if (start > len || end > len) {
+ // If the given selection is out of bounds, just ignore it.
+ // Most likely the text was changed out from under the IME,
+ // the the IME is going to have to update all of its state
+ // anyway.
+ return true;
+ }
+ if (start == end && MetaKeyKeyListener.getMetaState(content,
+ MetaKeyKeyListener.META_SELECTING) != 0) {
+ // If we are in selection mode, then we want to extend the
+ // selection instead of replacing it.
+ Selection.extendSelection(content, start);
+ } else {
+ Selection.setSelection(content, start, end);
+ }
+ return true;
+ }
+
/**
* Provides standard implementation for sending a key event to the window
* attached to the input connection's view.
@@ -66,20 +418,161 @@ public abstract class BaseInputConnection implements InputConnection {
}
/**
- * Provides standard implementation for hiding the status icon associated
- * with the current input method.
+ * Updates InputMethodManager with the current fullscreen mode.
*/
- public boolean hideStatusIcon() {
- mIMM.updateStatusIcon(0, null);
+ public boolean reportFullscreenMode(boolean enabled) {
+ mIMM.setFullscreenMode(enabled);
return true;
}
- /**
- * Provides standard implementation for showing the status icon associated
- * with the current input method.
- */
- public boolean showStatusIcon(String packageName, int resId) {
- mIMM.updateStatusIcon(resId, packageName);
- return true;
+ private void sendCurrentText() {
+ if (!mDummyMode) {
+ return;
+ }
+
+ Editable content = getEditable();
+ if (content != null) {
+ final int N = content.length();
+ if (N == 0) {
+ return;
+ }
+ if (N == 1) {
+ // If it's 1 character, we have a chance of being
+ // able to generate normal key events...
+ if (mKeyCharacterMap == null) {
+ mKeyCharacterMap = KeyCharacterMap.load(
+ KeyCharacterMap.BUILT_IN_KEYBOARD);
+ }
+ char[] chars = new char[1];
+ content.getChars(0, 1, chars, 0);
+ KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
+ if (events != null) {
+ for (int i=0; i<events.length; i++) {
+ if (DEBUG) Log.v(TAG, "Sending: " + events[i]);
+ sendKeyEvent(events[i]);
+ }
+ content.clear();
+ return;
+ }
+ }
+
+ // Otherwise, revert to the special key event containing
+ // the actual characters.
+ KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(),
+ content.toString(), KeyCharacterMap.BUILT_IN_KEYBOARD, 0);
+ sendKeyEvent(event);
+ content.clear();
+ }
+ }
+
+ private void replaceText(CharSequence text, int newCursorPosition,
+ boolean composing) {
+ final Editable content = getEditable();
+ if (content == null) {
+ return;
+ }
+
+ beginBatchEdit();
+
+ // delete composing text set previously.
+ int a = getComposingSpanStart(content);
+ int b = getComposingSpanEnd(content);
+
+ if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
+
+ if (b < a) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ if (a != -1 && b != -1) {
+ removeComposingSpans(content);
+ } else {
+ a = Selection.getSelectionStart(content);
+ b = Selection.getSelectionEnd(content);
+ if (a >=0 && b>= 0 && a != b) {
+ if (b < a) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+ }
+ }
+
+ if (composing) {
+ Spannable sp = null;
+ if (!(text instanceof Spannable)) {
+ sp = new SpannableStringBuilder(text);
+ text = sp;
+ if (mDefaultComposingSpans == null) {
+ Context context;
+ if (mTargetView != null) {
+ context = mTargetView.getContext();
+ } else if (mIMM.mServedView != null) {
+ context = mIMM.mServedView.getContext();
+ } else {
+ context = null;
+ }
+ if (context != null) {
+ TypedArray ta = context.getTheme()
+ .obtainStyledAttributes(new int[] {
+ com.android.internal.R.attr.candidatesTextStyleSpans
+ });
+ CharSequence style = ta.getText(0);
+ ta.recycle();
+ if (style != null && style instanceof Spanned) {
+ mDefaultComposingSpans = ((Spanned)style).getSpans(
+ 0, style.length(), Object.class);
+ }
+ }
+ }
+ if (mDefaultComposingSpans != null) {
+ for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
+ sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ } else {
+ sp = (Spannable)text;
+ }
+ setComposingSpans(sp);
+ }
+
+ if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
+ + text + "\", composing=" + composing
+ + ", type=" + text.getClass().getCanonicalName());
+
+ if (DEBUG) {
+ LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
+ lp.println("Current text:");
+ TextUtils.dumpSpans(content, lp, " ");
+ lp.println("Composing text:");
+ TextUtils.dumpSpans(text, lp, " ");
+ }
+
+ // Position the cursor appropriately, so that after replacing the
+ // desired range of text it will be located in the correct spot.
+ // This allows us to deal with filters performing edits on the text
+ // we are providing here.
+ if (newCursorPosition > 0) {
+ newCursorPosition += b - 1;
+ } else {
+ newCursorPosition += a;
+ }
+ if (newCursorPosition < 0) newCursorPosition = 0;
+ if (newCursorPosition > content.length())
+ newCursorPosition = content.length();
+ Selection.setSelection(content, newCursorPosition);
+
+ content.replace(a, b, text);
+
+ if (DEBUG) {
+ LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
+ lp.println("Final text:");
+ TextUtils.dumpSpans(content, lp, " ");
+ }
+
+ endBatchEdit();
}
}
diff --git a/core/java/android/view/inputmethod/DefaultInputMethod.java b/core/java/android/view/inputmethod/DefaultInputMethod.java
deleted file mode 100644
index 073b01c..0000000
--- a/core/java/android/view/inputmethod/DefaultInputMethod.java
+++ /dev/null
@@ -1,239 +0,0 @@
-package android.view.inputmethod;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethod;
-import com.android.internal.view.IInputMethodCallback;
-import com.android.internal.view.IInputMethodSession;
-import com.android.internal.view.InputConnectionWrapper;
-
-/**
- * This is the default input method that runs in the same context of the
- * application that requests text input. It does nothing but returns false for
- * any key events, so that all key events will be processed by the key listener
- * of the focused text box.
- * {@hide}
- */
-public class DefaultInputMethod implements InputMethod, InputMethodSession {
- private static IInputMethod sInstance = new SimpleInputMethod(
- new DefaultInputMethod());
-
- private static InputMethodInfo sProperty = new InputMethodInfo(
- "android.text.inputmethod", DefaultInputMethod.class.getName(),
- "Default", "android.text.inputmethod.defaultImeSettings");
-
- private InputConnection mInputConnection;
-
- public static IInputMethod getInstance() {
- return sInstance;
- }
-
- public static InputMethodInfo getMetaInfo() {
- return sProperty;
- }
-
- public void bindInput(InputBinding binding) {
- mInputConnection = binding.getConnection();
- }
-
- public void unbindInput() {
- }
-
- public void createSession(SessionCallback callback) {
- callback.sessionCreated(this);
- }
-
- public void setSessionEnabled(InputMethodSession session, boolean enabled) {
- }
-
- public void revokeSession(InputMethodSession session) {
- }
-
- public void finishInput() {
- mInputConnection.hideStatusIcon();
- }
-
- public void displayCompletions(CompletionInfo[] completions) {
- }
-
- public void updateExtractedText(int token, ExtractedText text) {
- }
-
- public void updateSelection(int oldSelStart, int oldSelEnd,
- int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
- }
-
- public void updateCursor(Rect newCursor) {
- }
-
- public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback) {
- callback.finishedEvent(seq, false);
- }
-
- public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback) {
- callback.finishedEvent(seq, false);
- }
-
- public void restartInput(EditorInfo attribute) {
- }
-
- public void attachToken(IBinder token) {
- }
-
- public void startInput(EditorInfo attribute) {
- mInputConnection
- .showStatusIcon("android", com.android.internal.R.drawable.ime_qwerty);
- }
-
- public void appPrivateCommand(String action, Bundle data) {
- }
-
- public void hideSoftInput() {
- }
-
- public void showSoftInput(int flags) {
- }
-}
-
-// ----------------------------------------------------------------------
-
-class SimpleInputMethod extends IInputMethod.Stub {
- final InputMethod mInputMethod;
-
- static class Session extends IInputMethodSession.Stub {
- final InputMethodSession mSession;
-
- Session(InputMethodSession session) {
- mSession = session;
- }
-
- public void finishInput() {
- mSession.finishInput();
- }
-
- public void updateSelection(int oldSelStart, int oldSelEnd,
- int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
- mSession.updateSelection(oldSelStart, oldSelEnd,
- newSelStart, newSelEnd, candidatesStart, candidatesEnd);
- }
-
- public void updateCursor(Rect newCursor) {
- mSession.updateCursor(newCursor);
- }
-
- static class InputMethodEventCallbackWrapper implements InputMethodSession.EventCallback {
- final IInputMethodCallback mCb;
- InputMethodEventCallbackWrapper(IInputMethodCallback cb) {
- mCb = cb;
- }
- public void finishedEvent(int seq, boolean handled) {
- try {
- mCb.finishedEvent(seq, handled);
- } catch (RemoteException e) {
- }
- }
- }
-
- public void dispatchKeyEvent(int seq, KeyEvent event, IInputMethodCallback callback) {
- mSession.dispatchKeyEvent(seq, event,
- new InputMethodEventCallbackWrapper(callback));
- }
-
- public void dispatchTrackballEvent(int seq, MotionEvent event, IInputMethodCallback callback) {
- mSession.dispatchTrackballEvent(seq, event,
- new InputMethodEventCallbackWrapper(callback));
- }
-
- public void displayCompletions(CompletionInfo[] completions) {
- mSession.displayCompletions(completions);
- }
-
- public void updateExtractedText(int token, ExtractedText text) {
- mSession.updateExtractedText(token, text);
- }
-
- public void appPrivateCommand(String action, Bundle data) {
- mSession.appPrivateCommand(action, data);
- }
- }
-
- public SimpleInputMethod(InputMethod inputMethod) {
- mInputMethod = inputMethod;
- }
-
- public InputMethod getInternalInputMethod() {
- return mInputMethod;
- }
-
- public void attachToken(IBinder token) {
- mInputMethod.attachToken(token);
- }
-
- public void bindInput(InputBinding binding) {
- InputConnectionWrapper ic = new InputConnectionWrapper(
- IInputContext.Stub.asInterface(binding.getConnectionToken()));
- InputBinding nu = new InputBinding(ic, binding);
- mInputMethod.bindInput(nu);
- }
-
- public void unbindInput() {
- mInputMethod.unbindInput();
- }
-
- public void restartInput(EditorInfo attribute) {
- mInputMethod.restartInput(attribute);
- }
-
- public void startInput(EditorInfo attribute) {
- mInputMethod.startInput(attribute);
- }
-
- static class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
- final IInputMethodCallback mCb;
- InputMethodSessionCallbackWrapper(IInputMethodCallback cb) {
- mCb = cb;
- }
-
- public void sessionCreated(InputMethodSession session) {
- try {
- mCb.sessionCreated(new Session(session));
- } catch (RemoteException e) {
- }
- }
- }
-
- public void createSession(IInputMethodCallback callback) throws RemoteException {
- mInputMethod.createSession(new InputMethodSessionCallbackWrapper(callback));
- }
-
- public void setSessionEnabled(IInputMethodSession session, boolean enabled) throws RemoteException {
- try {
- InputMethodSession ls = ((Session)session).mSession;
- mInputMethod.setSessionEnabled(ls, enabled);
- } catch (ClassCastException e) {
- Log.w("SimpleInputMethod", "Incoming session not of correct type: " + session, e);
- }
- }
-
- public void revokeSession(IInputMethodSession session) throws RemoteException {
- try {
- InputMethodSession ls = ((Session)session).mSession;
- mInputMethod.revokeSession(ls);
- } catch (ClassCastException e) {
- Log.w("SimpleInputMethod", "Incoming session not of correct type: " + session, e);
- }
- }
-
- public void showSoftInput(boolean blah) {
- }
-
- public void hideSoftInput() {
- }
-}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c050691..b00e565 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -5,6 +5,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputType;
import android.text.TextUtils;
+import android.util.Printer;
/**
* An EditorInfo describes several attributes of a text editing object
@@ -21,20 +22,109 @@ public class EditorInfo implements InputType, Parcelable {
* @see #TYPE_MASK_VARIATION
* @see #TYPE_MASK_FLAGS
*/
- public int inputType = TYPE_CLASS_TEXT;
+ public int inputType = TYPE_NULL;
/**
- * A string supplying additional information about the content type that
- * is private to a particular IME implementation. The string must be
+ * Set of bits in {@link #imeOptions} that provide alternative actions
+ * associated with the "enter" key. This both helps the IME provide
+ * better feedback about what the enter key will do, and also allows it
+ * to provide alternative mechanisms for providing that command.
+ */
+ public static final int IME_MASK_ACTION = 0x000000ff;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: no specific action has been
+ * associated with this editor, let the editor come up with its own if
+ * it can.
+ */
+ public static final int IME_ACTION_UNSPECIFIED = 0x00000000;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: there is no available action.
+ */
+ public static final int IME_ACTION_NONE = 0x00000001;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "go"
+ * operation to take the user to the target of the text they typed.
+ * Typically used, for example, when entering a URL.
+ */
+ public static final int IME_ACTION_GO = 0x00000002;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "search"
+ * operation, taking the user to the results of searching for the text
+ * the have typed (in whatever context is appropriate).
+ */
+ public static final int IME_ACTION_SEARCH = 0x00000003;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "send"
+ * operation, delivering the text to its target. This is typically used
+ * when composing a message.
+ */
+ public static final int IME_ACTION_SEND = 0x00000004;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "next"
+ * operation, taking the user to the next field that will accept text.
+ */
+ public static final int IME_ACTION_NEXT = 0x00000005;
+
+ /**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "done"
+ * operation, typically meaning the IME will be closed.
+ */
+ public static final int IME_ACTION_DONE = 0x00000006;
+
+ /**
+ * Flag of {@link #imeOptions}: used in conjunction with
+ * {@link #IME_MASK_ACTION}, this indicates that the action should not
+ * be available in-line as the same as a "enter" key. Typically this is
+ * because the action has such a significant impact or is not recoverable
+ * enough that accidentally hitting it should be avoided, such as sending
+ * a message.
+ */
+ public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000;
+
+ /**
+ * Generic unspecified type for {@link #imeOptions}.
+ */
+ public static final int IME_NULL = 0x00000000;
+
+ /**
+ * Extended type information for the editor, to help the IME better
+ * integrate with it.
+ */
+ public int imeOptions = IME_NULL;
+
+ /**
+ * A string supplying additional information options that are
+ * private to a particular IME implementation. The string must be
* scoped to a package owned by the implementation, to ensure there are
* no conflicts between implementations, but other than that you can put
* whatever you want in it to communicate with the IME. For example,
* you could have a string that supplies an argument like
* <code>"com.example.myapp.SpecialMode=3"</code>. This field is can be
- * filled in from the {@link android.R.attr#editorPrivateContentType}
+ * filled in from the {@link android.R.attr#privateImeOptions}
* attribute of a TextView.
*/
- public String privateContentType = null;
+ public String privateImeOptions = null;
+
+ /**
+ * In some cases an IME may be able to display an arbitrary label for
+ * a command the user can perform, which you can specify here. You can
+ * not count on this being used.
+ */
+ public CharSequence actionLabel = null;
+
+ /**
+ * If {@link #actionLabel} has been given, this is the id for that command
+ * when the user presses its button that is delivered back with
+ * {@link InputConnection#performEditorAction(int)
+ * InputConnection.performEditorAction()}.
+ */
+ public int actionId = 0;
/**
* The text offset of the start of the selection at the time editing
@@ -71,6 +161,26 @@ public class EditorInfo implements InputType, Parcelable {
public CharSequence label;
/**
+ * Name of the package that owns this editor.
+ */
+ public String packageName;
+
+ /**
+ * Identifier for the editor's field. This is optional, and may be
+ * 0. By default it is filled in with the result of
+ * {@link android.view.View#getId() View.getId()} on the View that
+ * is being edited.
+ */
+ public int fieldId;
+
+ /**
+ * Additional name for the editor's field. This can supply additional
+ * name information for the field. By default it is null. The actual
+ * contents have no meaning.
+ */
+ public String fieldName;
+
+ /**
* Any extra data to supply to the input method. This is for extended
* communication with specific input methods; the name fields in the
* bundle should be scoped (such as "com.mydomain.im.SOME_FIELD") so
@@ -81,6 +191,27 @@ public class EditorInfo implements InputType, Parcelable {
public Bundle extras;
/**
+ * Write debug output of this object.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "inputType=0x" + Integer.toHexString(inputType)
+ + " imeOptions=0x" + Integer.toHexString(imeOptions)
+ + " privateImeOptions=" + privateImeOptions);
+ pw.println(prefix + "actionLabel=" + actionLabel
+ + " actionId=" + actionId);
+ pw.println(prefix + "initialSelStart=" + initialSelStart
+ + " initialSelEnd=" + initialSelEnd
+ + " initialCapsMode=0x"
+ + Integer.toHexString(initialCapsMode));
+ pw.println(prefix + "hintText=" + hintText
+ + " label=" + label);
+ pw.println(prefix + "packageName=" + packageName
+ + " fieldId=" + fieldId
+ + " fieldName=" + fieldName);
+ pw.println(prefix + "extras=" + extras);
+ }
+
+ /**
* Used to package this object into a {@link Parcel}.
*
* @param dest The {@link Parcel} to be written.
@@ -88,12 +219,18 @@ public class EditorInfo implements InputType, Parcelable {
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(inputType);
- dest.writeString(privateContentType);
+ dest.writeInt(imeOptions);
+ dest.writeString(privateImeOptions);
+ TextUtils.writeToParcel(actionLabel, dest, flags);
+ dest.writeInt(actionId);
dest.writeInt(initialSelStart);
dest.writeInt(initialSelEnd);
dest.writeInt(initialCapsMode);
TextUtils.writeToParcel(hintText, dest, flags);
TextUtils.writeToParcel(label, dest, flags);
+ dest.writeString(packageName);
+ dest.writeInt(fieldId);
+ dest.writeString(fieldName);
dest.writeBundle(extras);
}
@@ -104,12 +241,18 @@ public class EditorInfo implements InputType, Parcelable {
public EditorInfo createFromParcel(Parcel source) {
EditorInfo res = new EditorInfo();
res.inputType = source.readInt();
- res.privateContentType = source.readString();
+ res.imeOptions = source.readInt();
+ res.privateImeOptions = source.readString();
+ res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.actionId = source.readInt();
res.initialSelStart = source.readInt();
res.initialSelEnd = source.readInt();
res.initialCapsMode = source.readInt();
res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.packageName = source.readString();
+ res.fieldId = source.readInt();
+ res.fieldName = source.readString();
res.extras = source.readBundle();
return res;
}
diff --git a/core/java/android/view/inputmethod/ExtractedText.java b/core/java/android/view/inputmethod/ExtractedText.java
index 0ca3c79..c2851d6 100644
--- a/core/java/android/view/inputmethod/ExtractedText.java
+++ b/core/java/android/view/inputmethod/ExtractedText.java
@@ -19,6 +19,22 @@ public class ExtractedText implements Parcelable {
public int startOffset;
/**
+ * If the content is a report of a partial text change, this is the
+ * offset where the change starts and it runs until
+ * {@link #partialEndOffset}. If the content is the full text, this
+ * field is -1.
+ */
+ public int partialStartOffset;
+
+ /**
+ * If the content is a report of a partial text change, this is the offset
+ * where the change ends. Note that the actual text may be larger or
+ * smaller than the difference between this and {@link #partialEndOffset},
+ * meaning a reduction or increase, respectively, in the total text.
+ */
+ public int partialEndOffset;
+
+ /**
* The offset where the selection currently starts within the extracted
* text. The real selection start position is at
* <var>startOffset</var>+<var>selectionStart</var>.
@@ -39,6 +55,11 @@ public class ExtractedText implements Parcelable {
public static final int FLAG_SINGLE_LINE = 0x0001;
/**
+ * Bit for {@link #flags}: set if the editor is currently in selection mode.
+ */
+ public static final int FLAG_SELECTING = 0x0002;
+
+ /**
* Additional bit flags of information about the edited text.
*/
public int flags;
@@ -52,9 +73,11 @@ public class ExtractedText implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
TextUtils.writeToParcel(text, dest, flags);
dest.writeInt(startOffset);
+ dest.writeInt(partialStartOffset);
+ dest.writeInt(partialEndOffset);
dest.writeInt(selectionStart);
dest.writeInt(selectionEnd);
- dest.writeInt(flags);
+ dest.writeInt(this.flags);
}
/**
@@ -65,6 +88,8 @@ public class ExtractedText implements Parcelable {
ExtractedText res = new ExtractedText();
res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
res.startOffset = source.readInt();
+ res.partialStartOffset = source.readInt();
+ res.partialEndOffset = source.readInt();
res.selectionStart = source.readInt();
res.selectionEnd = source.readInt();
res.flags = source.readInt();
diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.java b/core/java/android/view/inputmethod/ExtractedTextRequest.java
index d962329..e84b094 100644
--- a/core/java/android/view/inputmethod/ExtractedTextRequest.java
+++ b/core/java/android/view/inputmethod/ExtractedTextRequest.java
@@ -16,6 +16,13 @@ public class ExtractedTextRequest implements Parcelable {
public int token;
/**
+ * Additional request flags, having the same possible values as the
+ * flags parameter of {@link InputConnection#getTextBeforeCursor
+ * InputConnection.getTextBeforeCursor()}.
+ */
+ public int flags;
+
+ /**
* Hint for the maximum number of lines to return.
*/
public int hintMaxLines;
@@ -33,6 +40,7 @@ public class ExtractedTextRequest implements Parcelable {
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(token);
+ dest.writeInt(this.flags);
dest.writeInt(hintMaxLines);
dest.writeInt(hintMaxChars);
}
@@ -45,6 +53,7 @@ public class ExtractedTextRequest implements Parcelable {
public ExtractedTextRequest createFromParcel(Parcel source) {
ExtractedTextRequest res = new ExtractedTextRequest();
res.token = source.readInt();
+ res.flags = source.readInt();
res.hintMaxLines = source.readInt();
res.hintMaxChars = source.readInt();
return res;
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index bd7b050..8b6831e 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -17,7 +17,6 @@
package android.view.inputmethod;
import android.os.Bundle;
-import android.text.Spanned;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -32,6 +31,21 @@ import android.view.KeyEvent;
*/
public interface InputConnection {
/**
+ * Flag for use with {@link #getTextAfterCursor} and
+ * {@link #getTextBeforeCursor} to have style information returned along
+ * with the text. If not set, you will receive only the raw text. If
+ * set, you may receive a complex CharSequence of both text and style
+ * spans.
+ */
+ static final int GET_TEXT_WITH_STYLES = 0x0001;
+
+ /**
+ * Flag for use with {@link #getExtractedText} to indicate you would
+ * like to receive updates when the extracted text changes.
+ */
+ public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
+
+ /**
* Get <var>n</var> characters of text before the current cursor position.
*
* <p>This method may fail either if the input connection has become invalid
@@ -40,11 +54,13 @@ public interface InputConnection {
* In either case, a null is returned.
*
* @param n The expected length of the text.
+ * @param flags Supplies additional options controlling how the text is
+ * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
*
* @return Returns the text before the cursor position; the length of the
* returned text might be less than <var>n</var>.
*/
- public CharSequence getTextBeforeCursor(int n);
+ public CharSequence getTextBeforeCursor(int n, int flags);
/**
* Get <var>n</var> characters of text after the current cursor position.
@@ -55,11 +71,13 @@ public interface InputConnection {
* In either case, a null is returned.
*
* @param n The expected length of the text.
+ * @param flags Supplies additional options controlling how the text is
+ * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
*
* @return Returns the text after the cursor position; the length of the
* returned text might be less than <var>n</var>.
*/
- public CharSequence getTextAfterCursor(int n);
+ public CharSequence getTextAfterCursor(int n, int flags);
/**
* Retrieve the current capitalization mode in effect at the current
@@ -82,8 +100,6 @@ public interface InputConnection {
*/
public int getCursorCapsMode(int reqModes);
- public static final int EXTRACTED_TEXT_MONITOR = 0x0001;
-
/**
* Retrieve the current text in the input connection's editor, and monitor
* for any changes to it. This function returns with the current text,
@@ -97,7 +113,7 @@ public interface InputConnection {
*
* @param request Description of how the text should be returned.
* @param flags Additional options to control the client, either 0 or
- * {@link #EXTRACTED_TEXT_MONITOR}.
+ * {@link #GET_EXTRACTED_TEXT_MONITOR}.
*
* @return Returns an ExtractedText object describing the state of the
* text view and containing the extracted text itself.
@@ -118,7 +134,7 @@ public interface InputConnection {
* @return Returns true on success, false if the input connection is no longer
* valid.
*/
- boolean deleteSurroundingText(int leftLength, int rightLength);
+ public boolean deleteSurroundingText(int leftLength, int rightLength);
/**
* Set composing text around the current cursor position with the given text,
@@ -131,8 +147,14 @@ public interface InputConnection {
* object to the text. {#link android.text.SpannableString} and
* {#link android.text.SpannableStringBuilder} are two
* implementations of the interface {#link android.text.Spanned}.
- * @param newCursorPosition The new cursor position within the
- * <var>text</var>.
+ * @param newCursorPosition The new cursor position around the text. If
+ * > 0, this is relative to the end of the text - 1; if <= 0, this
+ * is relative to the start of the text. So a value of 1 will
+ * always advance you to the position after the full text being
+ * inserted. Note that this means you can't position the cursor
+ * within the text, because the editor can make modifications to
+ * the text you are providing so it is not possible to correctly
+ * specify locations there.
*
* @return Returns true on success, false if the input connection is no longer
* valid.
@@ -141,7 +163,7 @@ public interface InputConnection {
/**
* Have the text editor finish whatever composing text is currently
- * active. This simple leaves the text as-is, removing any special
+ * active. This simply leaves the text as-is, removing any special
* composing styling or other state that was around it. The cursor
* position remains unchanged.
*/
@@ -153,8 +175,14 @@ public interface InputConnection {
* automatically.
*
* @param text The committed text.
- * @param newCursorPosition The new cursor position within the
- * <var>text</var>.
+ * @param newCursorPosition The new cursor position around the text. If
+ * > 0, this is relative to the end of the text - 1; if <= 0, this
+ * is relative to the start of the text. So a value of 1 will
+ * always advance you to the position after the full text being
+ * inserted. Note that this means you can't position the cursor
+ * within the text, because the editor can make modifications to
+ * the text you are providing so it is not possible to correctly
+ * specify locations there.
*
*
* @return Returns true on success, false if the input connection is no longer
@@ -177,6 +205,36 @@ public interface InputConnection {
public boolean commitCompletion(CompletionInfo text);
/**
+ * Set the selection of the text editor. To set the cursor position,
+ * start and end should have the same value.
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean setSelection(int start, int end);
+
+ /**
+ * Have the editor perform an action it has said it can do.
+ *
+ * @param editorAction This must be one of the action constants for
+ * {@link EditorInfo#imeOptions EditorInfo.editorType}, such as
+ * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}.
+ *
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean performEditorAction(int editorAction);
+
+ /**
+ * Perform a context menu action on the field. The given id may be one of:
+ * {@link android.R.id#selectAll},
+ * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+ * {@link android.R.id#cut}, {@link android.R.id#copy},
+ * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
+ * or {@link android.R.id#switchInputMethod}
+ */
+ public boolean performContextMenuAction(int id);
+
+ /**
* Tell the editor that you are starting a batch of editor operations.
* The editor will try to avoid sending you updates about its state
* until {@link #endBatchEdit} is called.
@@ -234,6 +292,13 @@ public interface InputConnection {
public boolean clearMetaKeyStates(int states);
/**
+ * Called by the IME to tell the client when it switches between fullscreen
+ * and normal modes. This will normally be called for you by the standard
+ * implementation of {@link android.inputmethodservice.InputMethodService}.
+ */
+ public boolean reportFullscreenMode(boolean enabled);
+
+ /**
* API to send private commands from an input method to its connected
* editor. This can be used to provide domain-specific features that are
* only known between certain input methods and their clients. Note that
@@ -251,23 +316,4 @@ public interface InputConnection {
* valid.
*/
public boolean performPrivateCommand(String action, Bundle data);
-
- /**
- * Show an icon in the status bar.
- *
- * @param packageName The package holding the icon resource to be shown.
- * @param resId The resource id of the icon to show.
- *
- * @return Returns true on success, false if the input connection is no longer
- * valid.
- */
- public boolean showStatusIcon(String packageName, int resId);
-
- /**
- * Hide the icon shown in the status bar.
- *
- * @return Returns true on success, false if the input connection is no longer
- * valid.
- */
- public boolean hideStatusIcon();
}
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index f65b2a1..e3d5e62 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -20,88 +20,86 @@ import android.os.Bundle;
import android.view.KeyEvent;
/**
- * Wrapper around InputConnection interface, calling through to another
- * implementation of it.
+ * <p>Wrapper class for proxying calls to another InputConnection. Subclass
+ * and have fun!
*/
public class InputConnectionWrapper implements InputConnection {
- InputConnection mBase;
+ private final InputConnection mTarget;
- /**
- * Create a new wrapper around an existing InputConnection implementation.
- */
- public InputConnectionWrapper(InputConnection base) {
- mBase = base;
+ public InputConnectionWrapper(InputConnection target) {
+ mTarget = target;
}
- /**
- * Return the base InputConnection that this class is wrapping.
- */
- InputConnection getBase() {
- return mBase;
+ public CharSequence getTextBeforeCursor(int n, int flags) {
+ return mTarget.getTextBeforeCursor(n, flags);
}
- public CharSequence getTextBeforeCursor(int n) {
- return mBase.getTextBeforeCursor(n);
- }
-
- public CharSequence getTextAfterCursor(int n) {
- return mBase.getTextAfterCursor(n);
+ public CharSequence getTextAfterCursor(int n, int flags) {
+ return mTarget.getTextAfterCursor(n, flags);
}
public int getCursorCapsMode(int reqModes) {
- return mBase.getCursorCapsMode(reqModes);
+ return mTarget.getCursorCapsMode(reqModes);
}
public ExtractedText getExtractedText(ExtractedTextRequest request,
int flags) {
- return mBase.getExtractedText(request, flags);
+ return mTarget.getExtractedText(request, flags);
}
public boolean deleteSurroundingText(int leftLength, int rightLength) {
- return mBase.deleteSurroundingText(leftLength, rightLength);
+ return mTarget.deleteSurroundingText(leftLength, rightLength);
}
public boolean setComposingText(CharSequence text, int newCursorPosition) {
- return mBase.setComposingText(text, newCursorPosition);
+ return mTarget.setComposingText(text, newCursorPosition);
}
public boolean finishComposingText() {
- return mBase.finishComposingText();
+ return mTarget.finishComposingText();
}
public boolean commitText(CharSequence text, int newCursorPosition) {
- return mBase.commitText(text, newCursorPosition);
+ return mTarget.commitText(text, newCursorPosition);
}
public boolean commitCompletion(CompletionInfo text) {
- return mBase.commitCompletion(text);
+ return mTarget.commitCompletion(text);
+ }
+
+ public boolean setSelection(int start, int end) {
+ return mTarget.setSelection(start, end);
+ }
+
+ public boolean performEditorAction(int editorAction) {
+ return mTarget.performEditorAction(editorAction);
+ }
+
+ public boolean performContextMenuAction(int id) {
+ return mTarget.performContextMenuAction(id);
}
public boolean beginBatchEdit() {
- return mBase.beginBatchEdit();
+ return mTarget.beginBatchEdit();
}
public boolean endBatchEdit() {
- return mBase.endBatchEdit();
+ return mTarget.endBatchEdit();
}
public boolean sendKeyEvent(KeyEvent event) {
- return mBase.sendKeyEvent(event);
+ return mTarget.sendKeyEvent(event);
}
public boolean clearMetaKeyStates(int states) {
- return mBase.clearMetaKeyStates(states);
- }
-
- public boolean performPrivateCommand(String action, Bundle data) {
- return mBase.performPrivateCommand(action, data);
+ return mTarget.clearMetaKeyStates(states);
}
- public boolean showStatusIcon(String packageName, int resId) {
- return mBase.showStatusIcon(packageName, resId);
+ public boolean reportFullscreenMode(boolean enabled) {
+ return mTarget.reportFullscreenMode(enabled);
}
- public boolean hideStatusIcon() {
- return mBase.hideStatusIcon();
+ public boolean performPrivateCommand(String action, Bundle data) {
+ return mTarget.performPrivateCommand(action, data);
}
}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index c0e6590..a5e0e94 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -18,6 +18,7 @@ package android.view.inputmethod;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.os.ResultReceiver;
/**
* The InputMethod interface represents an input method which can generate key
@@ -113,12 +114,15 @@ public interface InputMethod {
* is ready for this input method to process received events and send result
* text back to the application.
*
- * @param attribute The attribute of the text box (typically, a EditText)
+ * @param inputConnection Optional specific input connection for
+ * communicating with the text box; if null, you should use the generic
+ * bound input connection.
+ * @param info Information about the text box (typically, an EditText)
* that requests input.
*
* @see EditorInfo
*/
- public void startInput(EditorInfo attribute);
+ public void startInput(InputConnection inputConnection, EditorInfo info);
/**
* This method is called when the state of this input method needs to be
@@ -128,12 +132,15 @@ public interface InputMethod {
* Typically, this method is called when the input focus is moved from one
* text box to another.
*
+ * @param inputConnection Optional specific input connection for
+ * communicating with the text box; if null, you should use the generic
+ * bound input connection.
* @param attribute The attribute of the text box (typically, a EditText)
* that requests input.
*
* @see EditorInfo
*/
- public void restartInput(EditorInfo attribute);
+ public void restartInput(InputConnection inputConnection, EditorInfo attribute);
/**
* Create a new {@link InputMethodSession} that can be handed to client
@@ -165,7 +172,7 @@ public interface InputMethod {
public void revokeSession(InputMethodSession session);
/**
- * Flag for {@link #showSoftInput(int)}: this show has been explicitly
+ * Flag for {@link #showSoftInput}: this show has been explicitly
* requested by the user. If not set, the system has decided it may be
* a good idea to show the input method based on a navigation operation
* in the UI.
@@ -173,15 +180,38 @@ public interface InputMethod {
public static final int SHOW_EXPLICIT = 0x00001;
/**
+ * Flag for {@link #showSoftInput}: this show has been forced to
+ * happen by the user. If set, the input method should remain visible
+ * until deliberated dismissed by the user in its UI.
+ */
+ public static final int SHOW_FORCED = 0x00002;
+
+ /**
* Request that any soft input part of the input method be shown to the user.
*
- * @param flags Provide additional information about the show request.
+ * @param flags Provides additional information about the show request.
* Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
+ * @param resultReceiver The client requesting the show may wish to
+ * be told the impact of their request, which should be supplied here.
+ * The result code should be
+ * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+ * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+ * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+ * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
*/
- public void showSoftInput(int flags);
+ public void showSoftInput(int flags, ResultReceiver resultReceiver);
/**
* Request that any soft input part of the input method be hidden from the user.
+ * @param flags Provides additional information about the show request.
+ * Currently always 0.
+ * @param resultReceiver The client requesting the show may wish to
+ * be told the impact of their request, which should be supplied here.
+ * The result code should be
+ * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+ * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+ * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+ * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
*/
- public void hideSoftInput();
+ public void hideSoftInput(int flags, ResultReceiver resultReceiver);
}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index e8f4b54..b4c5b72 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -174,6 +174,14 @@ public final class InputMethodInfo implements Parcelable {
}
/**
+ * Return the raw information about the Service implementing this
+ * input method. Do not modify the returned object.
+ */
+ public ServiceInfo getServiceInfo() {
+ return mService.serviceInfo;
+ }
+
+ /**
* Return the component of the service that implements this input
* method.
*/
@@ -225,15 +233,6 @@ public final class InputMethodInfo implements Parcelable {
return mIsDefaultResId;
}
- /**
- * Returns true if this input method is one of the components that is
- * built in to the system.
- */
- public boolean isBuiltin() {
- return mService.serviceInfo.packageName.equals(
- InputMethodManager.BUILDIN_INPUTMETHOD_PACKAGE);
- }
-
public void dump(Printer pw, String prefix) {
pw.println(prefix + "mId=" + mId
+ " mSettingsActivityName=" + mSettingsActivityName);
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a9a9594..4de9eef 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -24,13 +24,17 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewRoot;
+import com.android.internal.os.HandlerCaller;
import com.android.internal.view.IInputConnectionWrapper;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodCallback;
@@ -39,7 +43,11 @@ import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.InputBindResult;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Central system API to the overall input method framework (IMF) architecture,
@@ -183,12 +191,6 @@ public final class InputMethodManager {
static final boolean DEBUG = false;
static final String TAG = "InputMethodManager";
- /**
- * The package name of the build-in input method.
- * {@hide}
- */
- public static final String BUILDIN_INPUTMETHOD_PACKAGE = "android.text.inputmethod";
-
static final Object mInstanceSync = new Object();
static InputMethodManager mInstance;
@@ -199,8 +201,7 @@ public final class InputMethodManager {
// global lock.
final H mH;
- // The currently active input connection.
- final MutableInputConnectionWrapper mInputConnectionWrapper;
+ // Our generic input connection if the current target does not have its own.
final IInputContext mIInputContext;
/**
@@ -209,22 +210,38 @@ public final class InputMethodManager {
boolean mActive = false;
/**
- * The current base input connection, used when mActive is true.
+ * Set whenever this client becomes inactive, to know we need to reset
+ * state with the IME then next time we receive focus.
*/
- InputConnection mCurrentInputConnection;
-
+ boolean mHasBeenInactive = true;
+
+ /**
+ * As reported by IME through InputConnection.
+ */
+ boolean mFullscreenMode;
+
// -----------------------------------------------------------
/**
+ * This is the root view of the overall window that currently has input
+ * method focus.
+ */
+ View mCurRootView;
+ /**
* This is the view that should currently be served by an input method,
* regardless of the state of setting that up.
*/
View mServedView;
/**
- * For evaluating the state after a focus change, this is the view that
- * had focus.
+ * This is then next view that will be served by the input method, when
+ * we get around to updating things.
*/
- View mLastServedView;
+ View mNextServedView;
+ /**
+ * True if we should restart input in the next served view, even if the
+ * view hasn't actually changed from the current serve view.
+ */
+ boolean mNextServedNeedsStart;
/**
* This is set when we are in the process of connecting, to determine
* when we have actually finished.
@@ -270,7 +287,10 @@ public final class InputMethodManager {
// -----------------------------------------------------------
- static final int MSG_CHECK_FOCUS = 1;
+ static final int MSG_DUMP = 1;
+ static final int MSG_BIND = 2;
+ static final int MSG_UNBIND = 3;
+ static final int MSG_SET_ACTIVE = 4;
class H extends Handler {
H(Looper looper) {
@@ -280,185 +300,140 @@ public final class InputMethodManager {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_CHECK_FOCUS:
- checkFocus();
+ case MSG_DUMP: {
+ HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ try {
+ doDump((FileDescriptor)args.arg1,
+ (PrintWriter)args.arg2, (String[])args.arg3);
+ } catch (RuntimeException e) {
+ ((PrintWriter)args.arg2).println("Exception: " + e);
+ }
+ synchronized (args.arg4) {
+ ((CountDownLatch)args.arg4).countDown();
+ }
return;
+ }
+ case MSG_BIND: {
+ final InputBindResult res = (InputBindResult)msg.obj;
+ synchronized (mH) {
+ if (mBindSequence < 0 || mBindSequence != res.sequence) {
+ Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
+ + ", given seq=" + res.sequence);
+ return;
+ }
+
+ mCurMethod = res.method;
+ mCurId = res.id;
+ mBindSequence = res.sequence;
+ }
+ startInputInner();
+ return;
+ }
+ case MSG_UNBIND: {
+ final int sequence = msg.arg1;
+ synchronized (mH) {
+ if (mBindSequence == sequence) {
+ if (false) {
+ // XXX the server has already unbound!
+ if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
+ try {
+ mCurMethod.finishInput();
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+ clearBindingLocked();
+
+ // If we were actively using the last input method, then
+ // we would like to re-connect to the next input method.
+ if (mServedView != null && mServedView.isFocused()) {
+ mServedConnecting = true;
+ }
+ }
+ startInputInner();
+ }
+ return;
+ }
+ case MSG_SET_ACTIVE: {
+ final boolean active = msg.arg1 != 0;
+ synchronized (mH) {
+ mActive = active;
+ mFullscreenMode = false;
+ if (!active) {
+ // Some other client has starting using the IME, so note
+ // that this happened and make sure our own editor's
+ // state is reset.
+ mHasBeenInactive = true;
+ try {
+ // Note that finishComposingText() is allowed to run
+ // even when we are not active.
+ mIInputContext.finishComposingText();
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ return;
+ }
}
}
}
- static class NoOpInputConnection implements InputConnection {
-
- public boolean clearMetaKeyStates(int states) {
- return false;
- }
-
- public boolean beginBatchEdit() {
- return false;
- }
-
- public boolean endBatchEdit() {
- return false;
- }
-
- public boolean commitCompletion(CompletionInfo text) {
- return false;
+ class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
+ public ControlledInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
+ super(mainLooper, conn);
}
- public boolean commitText(CharSequence text, int newCursorPosition) {
- return false;
- }
-
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- return false;
- }
-
- public int getCursorCapsMode(int reqModes) {
- return 0;
- }
-
- public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
- return null;
- }
-
- public CharSequence getTextAfterCursor(int n) {
- return null;
- }
-
- public CharSequence getTextBeforeCursor(int n) {
- return null;
- }
-
- public boolean hideStatusIcon() {
- return false;
- }
-
- public boolean performPrivateCommand(String action, Bundle data) {
- return false;
- }
-
- public boolean sendKeyEvent(KeyEvent event) {
- return false;
- }
-
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
- return false;
- }
-
- public boolean finishComposingText() {
- return false;
- }
-
- public boolean showStatusIcon(String packageName, int resId) {
- return false;
+ public boolean isActive() {
+ return mActive;
}
}
- final NoOpInputConnection mNoOpInputConnection = new NoOpInputConnection();
-
final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
- public void setUsingInputMethod(boolean state) {
+ @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ // No need to check for dump permission, since we only give this
+ // interface to the system.
+ CountDownLatch latch = new CountDownLatch(1);
+ HandlerCaller.SomeArgs sargs = new HandlerCaller.SomeArgs();
+ sargs.arg1 = fd;
+ sargs.arg2 = fout;
+ sargs.arg3 = args;
+ sargs.arg4 = latch;
+ mH.sendMessage(mH.obtainMessage(MSG_DUMP, sargs));
+ try {
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ fout.println("Timeout waiting for dump");
+ }
+ } catch (InterruptedException e) {
+ fout.println("Interrupted waiting for dump");
+ }
+ }
+
+ public void setUsingInputMethod(boolean state) {
}
public void onBindMethod(InputBindResult res) {
- synchronized (mH) {
- if (mBindSequence < 0 || mBindSequence != res.sequence) {
- Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
- + ", given seq=" + res.sequence);
- return;
- }
-
- mCurMethod = res.method;
- mCurId = res.id;
- mBindSequence = res.sequence;
- }
- startInputInner();
+ mH.sendMessage(mH.obtainMessage(MSG_BIND, res));
}
public void onUnbindMethod(int sequence) {
- synchronized (mH) {
- if (mBindSequence == sequence) {
- if (false) {
- // XXX the server has already unbound!
- if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
- try {
- mCurMethod.finishInput();
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
- }
- }
- }
- clearBindingLocked();
-
- // If we were actively using the last input method, then
- // we would like to re-connect to the next input method.
- if (mServedView != null && mServedView.isFocused()) {
- mServedConnecting = true;
- }
- }
- startInputInner();
- }
+ mH.sendMessage(mH.obtainMessage(MSG_UNBIND, sequence, 0));
}
public void setActive(boolean active) {
- mActive = active;
- mInputConnectionWrapper.setBaseInputConnection(active
- ? mCurrentInputConnection : mNoOpInputConnection);
+ mH.sendMessage(mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, 0));
}
};
- final InputConnection mDummyInputConnection = new BaseInputConnection(this) {
- public boolean beginBatchEdit() {
- return false;
- }
- public boolean endBatchEdit() {
- return false;
- }
- public boolean commitText(CharSequence text, int newCursorPosition) {
- return false;
- }
- public boolean commitCompletion(CompletionInfo text) {
- return false;
- }
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- return false;
- }
- public ExtractedText getExtractedText(ExtractedTextRequest request,
- int flags) {
- return null;
- }
- public CharSequence getTextAfterCursor(int n) {
- return null;
- }
- public CharSequence getTextBeforeCursor(int n) {
- return null;
- }
- public int getCursorCapsMode(int reqModes) {
- return 0;
- }
- public boolean clearMetaKeyStates(int states) {
- return false;
- }
- public boolean performPrivateCommand(String action, Bundle data) {
- return false;
- }
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
- return false;
- }
- public boolean finishComposingText() {
- return false;
- }
- };
+ final InputConnection mDummyInputConnection = new BaseInputConnection(this, true);
InputMethodManager(IInputMethodManager service, Looper looper) {
mService = service;
mMainLooper = looper;
mH = new H(looper);
- mInputConnectionWrapper = new MutableInputConnectionWrapper(mNoOpInputConnection);
- mIInputContext = new IInputConnectionWrapper(looper,
- mInputConnectionWrapper);
- setCurrentInputConnection(mDummyInputConnection);
+ mIInputContext = new ControlledInputConnectionWrapper(looper,
+ mDummyInputConnection);
if (mInstance == null) {
mInstance = this;
@@ -517,21 +492,47 @@ public final class InputMethodManager {
}
}
- public void updateStatusIcon(int iconId, String iconPackage) {
+ public void showStatusIcon(IBinder imeToken, String packageName, int iconId) {
+ try {
+ mService.updateStatusIcon(imeToken, packageName, iconId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void hideStatusIcon(IBinder imeToken) {
try {
- mService.updateStatusIcon(iconId, iconPackage);
+ mService.updateStatusIcon(imeToken, null, 0);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
+ /** @hide */
+ public void setFullscreenMode(boolean fullScreen) {
+ mFullscreenMode = fullScreen;
+ }
+
+ /**
+ * Allows you to discover whether the attached input method is running
+ * in fullscreen mode. Return true if it is fullscreen, entirely covering
+ * your UI, else returns false.
+ */
+ public boolean isFullscreenMode() {
+ return mFullscreenMode;
+ }
+
/**
* Return true if the given view is the currently active view for the
* input method.
*/
public boolean isActive(View view) {
+ checkFocus();
synchronized (mH) {
- return mServedView == view && mCurrentTextBoxAttribute != null;
+ return (mServedView == view
+ || (mServedView != null
+ && mServedView.checkInputConnectionProxy(view)))
+ && mCurrentTextBoxAttribute != null;
}
}
@@ -539,6 +540,7 @@ public final class InputMethodManager {
* Return true if any view is currently active in the input method.
*/
public boolean isActive() {
+ checkFocus();
synchronized (mH) {
return mServedView != null && mCurrentTextBoxAttribute != null;
}
@@ -549,6 +551,7 @@ public final class InputMethodManager {
* If false, it has no input connection, so can only handle raw key events.
*/
public boolean isAcceptingText() {
+ checkFocus();
return mServedInputConnection != null;
}
@@ -563,31 +566,21 @@ public final class InputMethodManager {
}
/**
- * Record the desired input connection, but only set it if mActive is true.
- */
- void setCurrentInputConnection(InputConnection connection) {
- mCurrentInputConnection = connection;
- mInputConnectionWrapper.setBaseInputConnection(mActive
- ? connection : mNoOpInputConnection);
- }
-
- /**
* Reset all of the state associated with a served view being connected
* to an input method
*/
void clearConnectionLocked() {
mCurrentTextBoxAttribute = null;
mServedInputConnection = null;
- setCurrentInputConnection(mDummyInputConnection);
}
/**
* Disconnect any existing input connection, clearing the served view.
*/
void finishInputLocked() {
+ mNextServedView = null;
if (mServedView != null) {
if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView);
- updateStatusIcon(0, null);
if (mCurrentTextBoxAttribute != null) {
try {
@@ -628,8 +621,10 @@ public final class InputMethodManager {
}
public void displayCompletions(View view, CompletionInfo[] completions) {
+ checkFocus();
synchronized (mH) {
- if (mServedView != view) {
+ if (mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view))) {
return;
}
@@ -644,8 +639,10 @@ public final class InputMethodManager {
}
public void updateExtractedText(View view, int token, ExtractedText text) {
+ checkFocus();
synchronized (mH) {
- if (mServedView != view) {
+ if (mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view))) {
return;
}
@@ -659,13 +656,66 @@ public final class InputMethodManager {
}
/**
- * Flag for {@link #showSoftInput} to indicate that the this is an implicit
+ * Flag for {@link #showSoftInput} to indicate that this is an implicit
* request to show the input window, not as the result of a direct request
* by the user. The window may not be shown in this case.
*/
public static final int SHOW_IMPLICIT = 0x0001;
/**
+ * Flag for {@link #showSoftInput} to indicate that the user has forced
+ * the input method open (such as by long-pressing menu) so it should
+ * not be closed until they explicitly do so.
+ */
+ public static final int SHOW_FORCED = 0x0002;
+
+ /**
+ * Synonym for {@link #showSoftInput(View, int, ResultReceiver)} without
+ * a result receiver: explicitly request that the current input method's
+ * soft input area be shown to the user, if needed.
+ *
+ * @param view The currently focused view, which would like to receive
+ * soft keyboard input.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #SHOW_IMPLICIT} bit set.
+ */
+ public boolean showSoftInput(View view, int flags) {
+ return showSoftInput(view, flags, null);
+ }
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window was unchanged and remains shown.
+ */
+ public static final int RESULT_UNCHANGED_SHOWN = 0;
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window was unchanged and remains hidden.
+ */
+ public static final int RESULT_UNCHANGED_HIDDEN = 1;
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window changed from hidden to shown.
+ */
+ public static final int RESULT_SHOWN = 2;
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window changed from shown to hidden.
+ */
+ public static final int RESULT_HIDDEN = 3;
+
+ /**
* Explicitly request that the current input method's soft input area be
* shown to the user, if needed. Call this if the user interacts with
* your view in such a way that they have expressed they would like to
@@ -675,17 +725,35 @@ public final class InputMethodManager {
* soft keyboard input.
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #SHOW_IMPLICIT} bit set.
+ * @param resultReceiver If non-null, this will be called by the IME when
+ * it has processed your request to tell you what it has done. The result
+ * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
+ * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
+ * {@link #RESULT_HIDDEN}.
*/
- public void showSoftInput(View view, int flags) {
+ public boolean showSoftInput(View view, int flags,
+ ResultReceiver resultReceiver) {
+ checkFocus();
synchronized (mH) {
- if (mServedView != view) {
- return;
+ if (mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view))) {
+ return false;
}
try {
- mService.showSoftInput(mClient, flags);
+ return mService.showSoftInput(mClient, flags, resultReceiver);
} catch (RemoteException e) {
}
+
+ return false;
+ }
+ }
+
+ /** @hide */
+ public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
+ try {
+ mService.showSoftInput(mClient, flags, resultReceiver);
+ } catch (RemoteException e) {
}
}
@@ -697,6 +765,27 @@ public final class InputMethodManager {
public static final int HIDE_IMPLICIT_ONLY = 0x0001;
/**
+ * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
+ * input window should normally be hidden, unless it was originally
+ * shown with {@link #SHOW_FORCED}.
+ */
+ public static final int HIDE_NOT_ALWAYS = 0x0002;
+
+ /**
+ * Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)
+ * without a result: request to hide the soft input window from the
+ * context of the window that is currently accepting input.
+ *
+ * @param windowToken The token of the window that is making the request,
+ * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+ */
+ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
+ return hideSoftInputFromWindow(windowToken, flags, null);
+ }
+
+ /**
* Request to hide the soft input window from the context of the window
* that is currently accepting input. This should be called as a result
* of the user doing some actually than fairly explicitly requests to
@@ -706,20 +795,77 @@ public final class InputMethodManager {
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+ * @param resultReceiver If non-null, this will be called by the IME when
+ * it has processed your request to tell you what it has done. The result
+ * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
+ * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
+ * {@link #RESULT_HIDDEN}.
*/
- public void hideSoftInputFromWindow(IBinder windowToken, int flags) {
+ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+ ResultReceiver resultReceiver) {
+ checkFocus();
synchronized (mH) {
if (mServedView == null || mServedView.getWindowToken() != windowToken) {
- return;
+ return false;
}
try {
- mService.hideSoftInput(mClient, flags);
+ return mService.hideSoftInput(mClient, flags, resultReceiver);
} catch (RemoteException e) {
}
+ return false;
}
}
+
+ /**
+ * This method toggles the input method window display.
+ * If the input window is already displayed, it gets hidden.
+ * If not the input window will be displayed.
+ * @param windowToken The token of the window that is making the request,
+ * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+ * @param showFlags Provides additional operating flags. May be
+ * 0 or have the {@link #SHOW_IMPLICIT},
+ * {@link #SHOW_FORCED} bit set.
+ * @param hideFlags Provides additional operating flags. May be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+ * {@link #HIDE_NOT_ALWAYS} bit set.
+ **/
+ public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
+ synchronized (mH) {
+ if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+ return;
+ }
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.toggleSoftInput(showFlags, hideFlags);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ /*
+ * This method toggles the input method window display.
+ * If the input window is already displayed, it gets hidden.
+ * If not the input window will be displayed.
+ * @param showFlags Provides additional operating flags. May be
+ * 0 or have the {@link #SHOW_IMPLICIT},
+ * {@link #SHOW_FORCED} bit set.
+ * @param hideFlags Provides additional operating flags. May be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+ * {@link #HIDE_NOT_ALWAYS} bit set.
+ * @hide
+ */
+ public void toggleSoftInput(int showFlags, int hideFlags) {
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.toggleSoftInput(showFlags, hideFlags);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
/**
* If the input method is currently connected to the given view,
* restart it with its new contents. You should call this when the text
@@ -729,8 +875,10 @@ public final class InputMethodManager {
* @param view The view whose text has changed.
*/
public void restartInput(View view) {
+ checkFocus();
synchronized (mH) {
- if (mServedView != view) {
+ if (mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view))) {
return;
}
@@ -773,12 +921,15 @@ public final class InputMethodManager {
startInputInner();
}
});
+ return;
}
// Okay we are now ready to call into the served view and have it
// do its stuff.
// Life is good: let's hook everything up!
EditorInfo tba = new EditorInfo();
+ tba.packageName = view.getContext().getPackageName();
+ tba.fieldId = view.getId();
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
@@ -801,22 +952,23 @@ public final class InputMethodManager {
mCurrentTextBoxAttribute = tba;
mServedConnecting = false;
mServedInputConnection = ic;
+ IInputContext servedContext;
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
- setCurrentInputConnection(ic);
+ servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic);
} else {
- setCurrentInputConnection(mDummyInputConnection);
+ servedContext = null;
}
try {
if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
+ ic + " tba=" + tba + " initial=" + initial);
- InputBindResult res = mService.startInput(mClient, tba, initial,
- mCurMethod == null);
+ InputBindResult res = mService.startInput(mClient,
+ servedContext, tba, initial, mCurMethod == null);
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res != null) {
if (res.id != null) {
@@ -846,6 +998,7 @@ public final class InputMethodManager {
* @hide
*/
public void windowDismissed(IBinder appWindowToken) {
+ checkFocus();
synchronized (mH) {
if (mServedView != null &&
mServedView.getWindowToken() == appWindowToken) {
@@ -860,17 +1013,22 @@ public final class InputMethodManager {
*/
public void focusIn(View view) {
synchronized (mH) {
- if (DEBUG) Log.v(TAG, "focusIn: " + view);
- // Okay we have a new view that is being served.
- if (mServedView != view) {
- mCurrentTextBoxAttribute = null;
- }
- mServedView = view;
- mCompletions = null;
- mServedConnecting = true;
+ focusInLocked(view);
}
+ }
+
+ void focusInLocked(View view) {
+ if (DEBUG) Log.v(TAG, "focusIn: " + view);
- startInputInner();
+ if (mCurRootView != view.getRootView()) {
+ // This is a request from a window that isn't in the window with
+ // IME focus, so ignore it.
+ if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");
+ return;
+ }
+
+ mNextServedView = view;
+ scheduleCheckFocusLocked(view);
}
/**
@@ -878,77 +1036,132 @@ 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) {
- ic = mServedInputConnection;
- if (view.hasWindowFocus()) {
- mLastServedView = view;
- mH.removeMessages(MSG_CHECK_FOCUS);
- mH.sendEmptyMessage(MSG_CHECK_FOCUS);
+ if (mServedView != view) {
+ // The following code would auto-hide the IME if we end up
+ // with no more views with focus. This can happen, however,
+ // whenever we go into touch mode, so it ends up hiding
+ // at times when we don't really want it to. For now it
+ // seems better to just turn it all off.
+ if (false && view.hasWindowFocus()) {
+ mNextServedView = null;
+ scheduleCheckFocusLocked(view);
}
}
}
-
- if (ic != null) {
- ic.finishComposingText();
- }
}
- void checkFocus() {
+ void scheduleCheckFocusLocked(View view) {
+ Handler vh = view.getHandler();
+ if (vh != null && !vh.hasMessages(ViewRoot.CHECK_FOCUS)) {
+ // This will result in a call to checkFocus() below.
+ vh.sendMessage(vh.obtainMessage(ViewRoot.CHECK_FOCUS));
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void checkFocus() {
+ // This is called a lot, so short-circuit before locking.
+ if (mServedView == mNextServedView && !mNextServedNeedsStart) {
+ return;
+ }
+
+ InputConnection ic = null;
synchronized (mH) {
+ if (mServedView == mNextServedView && !mNextServedNeedsStart) {
+ return;
+ }
if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
- + " last=" + mLastServedView);
- if (mServedView == mLastServedView) {
+ + " next=" + mNextServedView
+ + " restart=" + mNextServedNeedsStart);
+
+ mNextServedNeedsStart = false;
+ if (mNextServedView == null) {
finishInputLocked();
// In this case, we used to have a focused view on the window,
// but no longer do. We should make sure the input method is
// no longer shown, since it serves no purpose.
closeCurrentInput();
+ return;
}
- mLastServedView = null;
+
+ ic = mServedInputConnection;
+
+ mServedView = mNextServedView;
+ mCurrentTextBoxAttribute = null;
+ mCompletions = null;
+ mServedConnecting = true;
+ }
+
+ if (ic != null) {
+ ic.finishComposingText();
}
+
+ startInputInner();
}
void closeCurrentInput() {
try {
- mService.hideSoftInput(mClient, 0);
+ mService.hideSoftInput(mClient, HIDE_NOT_ALWAYS, null);
} catch (RemoteException e) {
}
}
/**
- * Called by ViewRoot the first time it gets window focus.
+ * Called by ViewRoot when its window gets input focus.
* @hide
*/
- public void onWindowFocus(View focusedView, int softInputMode,
+ public void onWindowFocus(View rootView, View focusedView, int softInputMode,
boolean first, int windowFlags) {
synchronized (mH) {
if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
+ " softInputMode=" + softInputMode
+ " first=" + first + " flags=#"
+ Integer.toHexString(windowFlags));
+ if (mHasBeenInactive) {
+ if (DEBUG) Log.v(TAG, "Has been inactive! Starting fresh");
+ mHasBeenInactive = false;
+ mNextServedNeedsStart = true;
+ }
+ focusInLocked(focusedView != null ? focusedView : rootView);
+ }
+
+ checkFocus();
+
+ synchronized (mH) {
try {
final boolean isTextEditor = focusedView != null &&
- focusedView.onCheckIsTextEditor();
- mService.windowGainedFocus(mClient, focusedView != null,
- isTextEditor, softInputMode, first, windowFlags);
+ focusedView.onCheckIsTextEditor();
+ mService.windowGainedFocus(mClient, rootView.getWindowToken(),
+ focusedView != null, isTextEditor, softInputMode, first,
+ windowFlags);
} catch (RemoteException e) {
}
}
}
+ /** @hide */
+ public void startGettingWindowFocus(View rootView) {
+ synchronized (mH) {
+ mCurRootView = rootView;
+ }
+ }
+
/**
* Report the current selection range.
*/
public void updateSelection(View view, int selStart, int selEnd,
int candidatesStart, int candidatesEnd) {
+ checkFocus();
synchronized (mH) {
- if (mServedView != view || mCurrentTextBoxAttribute == null
- || mCurMethod == null) {
+ if ((mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view)))
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
return;
}
@@ -984,9 +1197,11 @@ public final class InputMethodManager {
* Report the current cursor location in its window.
*/
public void updateCursor(View view, int left, int top, int right, int bottom) {
+ checkFocus();
synchronized (mH) {
- if (mServedView != view || mCurrentTextBoxAttribute == null
- || mCurMethod == null) {
+ if ((mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view)))
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
return;
}
@@ -1006,6 +1221,34 @@ public final class InputMethodManager {
}
/**
+ * Call {@link InputMethodSession#appPrivateCommand(String, Bundle)
+ * InputMethodSession.appPrivateCommand()} on the current Input Method.
+ * @param view Optional View that is sending the command, or null if
+ * you want to send the command regardless of the view that is attached
+ * to the input method.
+ * @param action Name of the command to be performed. This <em>must</em>
+ * be a scoped name, i.e. prefixed with a package name you own, so that
+ * different developers will not create conflicting commands.
+ * @param data Any data to include with the command.
+ */
+ public void sendAppPrivateCommand(View view, String action, Bundle data) {
+ checkFocus();
+ synchronized (mH) {
+ if ((mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view)))
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ return;
+ }
+ try {
+ if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
+ mCurMethod.appPrivateCommand(action, data);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+
+ /**
* Force switch to a new input method component. This can only be called
* from the currently active input method, as validated by the given token.
* @param token Supplies the identifying token given to an input method
@@ -1030,7 +1273,8 @@ public final class InputMethodManager {
* when it was started, which allows it to perform this operation on
* itself.
* @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+ * {@link #HIDE_NOT_ALWAYS} bit set.
*/
public void hideSoftInputFromInputMethod(IBinder token, int flags) {
try {
@@ -1041,6 +1285,27 @@ public final class InputMethodManager {
}
/**
+ * Show the input method's soft input area, so the user
+ * sees the input method window and can interact with it.
+ * This can only be called from the currently active input method,
+ * as validated by the given token.
+ *
+ * @param token Supplies the identifying token given to an input method
+ * when it was started, which allows it to perform this operation on
+ * itself.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #SHOW_IMPLICIT} or
+ * {@link #SHOW_FORCED} bit set.
+ */
+ public void showSoftInputFromInputMethod(IBinder token, int flags) {
+ try {
+ mService.showMySoftInput(token, flags);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* @hide
*/
public void dispatchKeyEvent(Context context, int seq, KeyEvent key,
@@ -1048,7 +1313,7 @@ public final class InputMethodManager {
synchronized (mH) {
if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
- if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
+ if (mCurMethod == null) {
try {
callback.finishedEvent(seq, false);
} catch (RemoteException e) {
@@ -1116,4 +1381,36 @@ public final class InputMethodManager {
}
}
}
+
+ void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ final Printer p = new PrintWriterPrinter(fout);
+ p.println("Input method client state for " + this + ":");
+
+ p.println(" mService=" + mService);
+ p.println(" mMainLooper=" + mMainLooper);
+ p.println(" mIInputContext=" + mIInputContext);
+ p.println(" mActive=" + mActive
+ + " mHasBeenInactive=" + mHasBeenInactive
+ + " mBindSequence=" + mBindSequence
+ + " mCurId=" + mCurId);
+ p.println(" mCurMethod=" + mCurMethod);
+ p.println(" mCurRootView=" + mCurRootView);
+ p.println(" mServedView=" + mServedView);
+ p.println(" mNextServedNeedsStart=" + mNextServedNeedsStart
+ + " mNextServedView=" + mNextServedView);
+ p.println(" mServedConnecting=" + mServedConnecting);
+ if (mCurrentTextBoxAttribute != null) {
+ p.println(" mCurrentTextBoxAttribute:");
+ mCurrentTextBoxAttribute.dump(p, " ");
+ } else {
+ p.println(" mCurrentTextBoxAttribute: null");
+ }
+ p.println(" mServedInputConnection=" + mServedInputConnection);
+ p.println(" mCompletions=" + mCompletions);
+ p.println(" mCursorRect=" + mCursorRect);
+ p.println(" mCursorSelStart=" + mCursorSelStart
+ + " mCursorSelEnd=" + mCursorSelEnd
+ + " mCursorCandStart=" + mCursorCandStart
+ + " mCursorCandEnd=" + mCursorCandEnd);
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index b5bbaff..bb03afa 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -139,4 +139,16 @@ public interface InputMethodSession {
* @param data Any data to include with the command.
*/
public void appPrivateCommand(String action, Bundle data);
+
+ /**
+ * Toggle the soft input window.
+ * Applications can toggle the state of the soft input window.
+ * @param showFlags Provides additional operating flags. May be
+ * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT},
+ * {@link InputMethodManager#SHOW_FORCED} bit set.
+ * @param hideFlags Provides additional operating flags. May be
+ * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY},
+ * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set.
+ */
+ public void toggleSoftInput(int showFlags, int hideFlags);
}
diff --git a/core/java/android/view/inputmethod/MutableInputConnectionWrapper.java b/core/java/android/view/inputmethod/MutableInputConnectionWrapper.java
deleted file mode 100644
index 025a059..0000000
--- a/core/java/android/view/inputmethod/MutableInputConnectionWrapper.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2007-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.view.inputmethod;
-
-
-/**
- * Special version of {@link InputConnectionWrapper} that allows the base
- * input connection to be modified after it is initially set.
- */
-public class MutableInputConnectionWrapper extends InputConnectionWrapper {
- public MutableInputConnectionWrapper(InputConnection base) {
- super(base);
- }
-
- /**
- * Change the base InputConnection for this wrapper. All calls will then be
- * delegated to the base input connection.
- *
- * @param base The new base InputConnection for this wrapper.
- */
- public void setBaseInputConnection(InputConnection base) {
- mBase = base;
- }
-}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 1dd37be..5401a6e 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -51,7 +51,7 @@ class BrowserFrame extends Handler {
private final Context mContext;
private final WebViewDatabase mDatabase;
private final WebViewCore mWebViewCore;
- private boolean mLoadInitFromJava;
+ /* package */ boolean mLoadInitFromJava;
private int mLoadType;
private boolean mFirstLayoutDone = true;
private boolean mCommitted = true;
@@ -201,10 +201,14 @@ class BrowserFrame extends Handler {
final String failingUrl) {
// As this is called for the main resource and loading will be stopped
// after, reset the state variables.
+ resetLoadingStates();
+ mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
+ }
+
+ private void resetLoadingStates() {
mCommitted = true;
mWebViewCore.mEndScaleZoom = mFirstLayoutDone == false;
mFirstLayoutDone = true;
- mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
}
/* package */boolean committed() {
@@ -290,6 +294,7 @@ class BrowserFrame extends Handler {
if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
if (isMainFrame) {
+ resetLoadingStates();
mCallbackProxy.switchOutDrawHistory();
mCallbackProxy.onPageFinished(url);
}
@@ -555,8 +560,11 @@ class BrowserFrame extends Handler {
method, isHighPriority);
loader.setHeaders(headers);
loader.setPostData(postData);
- loader.setCacheMode(cacheMode); // Set the load mode to the mode used
- // for the current page.
+ // Set the load mode to the mode used for the current page.
+ // If WebKit wants validation, go to network directly.
+ loader.setCacheMode(headers.containsKey("If-Modified-Since")
+ || headers.containsKey("If-None-Match") ?
+ WebSettings.LOAD_NO_CACHE : cacheMode);
// Set referrer to current URL?
if (!loader.executeLoad()) {
checker.responseAlert("startLoadingResource fail");
@@ -751,7 +759,14 @@ class BrowserFrame extends Handler {
/**
* Stop loading the current page.
*/
- public native void stopLoading();
+ public void stopLoading() {
+ if (mIsMainFrame) {
+ resetLoadingStates();
+ }
+ nativeStopLoading();
+ }
+
+ private native void nativeStopLoading();
/**
* Return true if the document has images.
diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java
index 16d663c..806b458 100644
--- a/core/java/android/webkit/ByteArrayBuilder.java
+++ b/core/java/android/webkit/ByteArrayBuilder.java
@@ -94,6 +94,14 @@ class ByteArrayBuilder {
return mChunks.isEmpty();
}
+ public synchronized void clear() {
+ Chunk c = getFirstChunk();
+ while (c != null) {
+ releaseChunk(c);
+ c = getFirstChunk();
+ }
+ }
+
private Chunk appendChunk(int length) {
if (length < mMinCapacity) {
length = mMinCapacity;
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index d12940d..dcf68cd 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -52,6 +52,7 @@ public final class CacheManager {
private static final String NO_STORE = "no-store";
private static final String NO_CACHE = "no-cache";
+ private static final String PRIVATE = "private";
private static final String MAX_AGE = "max-age";
private static long CACHE_THRESHOLD = 6 * 1024 * 1024;
@@ -612,7 +613,7 @@ public final class CacheManager {
// must be re-validated on every load. It does not mean that
// the content can not be cached. set to expire 0 means it
// can only be used in CACHE_MODE_CACHE_ONLY case
- if (NO_CACHE.equals(controls[i])) {
+ if (NO_CACHE.equals(controls[i]) || PRIVATE.equals(controls[i])) {
ret.expires = 0;
} else if (controls[i].startsWith(MAX_AGE)) {
int separator = controls[i].indexOf('=');
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 4f8e5e4..0f9f29c 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -29,6 +29,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
+import android.provider.Browser;
import android.util.Config;
import android.util.Log;
import android.view.KeyEvent;
@@ -175,6 +176,11 @@ class CallbackProxy extends Handler {
Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse(overrideUrl));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ // If another application is running a WebView and launches the
+ // Browser through this Intent, we want to reuse the same window if
+ // possible.
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID,
+ mContext.getPackageName());
try {
mContext.startActivity(intent);
override = true;
@@ -907,10 +913,12 @@ class CallbackProxy extends Handler {
}
public void onReceivedIcon(Bitmap icon) {
- if (Config.DEBUG && mBackForwardList.getCurrentItem() == null) {
- throw new AssertionError();
+ // The current item might be null if the icon was already stored in the
+ // database and this is a new WebView.
+ WebHistoryItem i = mBackForwardList.getCurrentItem();
+ if (i != null) {
+ i.setFavicon(icon);
}
- mBackForwardList.getCurrentItem().setFavicon(icon);
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 5a37f04..d90a2fd 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -171,6 +171,10 @@ public final class CookieManager {
boolean pathMatch(String urlPath) {
if (urlPath.startsWith(path)) {
int len = path.length();
+ if (len == 0) {
+ Log.w(LOGTAG, "Empty cookie path");
+ return false;
+ }
int urlLen = urlPath.length();
if (path.charAt(len-1) != PATH_DELIM && urlLen > len) {
// make sure /wee doesn't match /we
@@ -739,11 +743,16 @@ public final class CookieManager {
* Note: in the case of "foo=bluh, bar=bluh;path=/", we interpret
* it as one cookie instead of two cookies.
*/
+ int semicolonIndex = cookieString.indexOf(SEMICOLON, index);
int equalIndex = cookieString.indexOf(EQUAL, index);
if (equalIndex == -1) {
// bad format, force return
break;
}
+ if (semicolonIndex > -1 && semicolonIndex < equalIndex) {
+ // empty cookie, like "; path=/", return
+ break;
+ }
cookie = new Cookie(host, path);
cookie.name = cookieString.substring(index, equalIndex);
if (cookieString.charAt(equalIndex + 1) == QUOTATION) {
@@ -753,7 +762,7 @@ public final class CookieManager {
break;
}
}
- int semicolonIndex = cookieString.indexOf(SEMICOLON, index);
+ semicolonIndex = cookieString.indexOf(SEMICOLON, index);
if (semicolonIndex == -1) {
semicolonIndex = length;
}
@@ -864,7 +873,10 @@ public final class CookieManager {
"illegal format for max-age: " + value);
}
} else if (name.equals(PATH)) {
- cookie.path = value;
+ // only allow non-empty path value
+ if (value.length() > 0) {
+ cookie.path = value;
+ }
} else if (name.equals(DOMAIN)) {
int lastPeriod = value.lastIndexOf(PERIOD);
if (lastPeriod == 0) {
diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java
index 10343b2..54a4c1d 100644
--- a/core/java/android/webkit/FileLoader.java
+++ b/core/java/android/webkit/FileLoader.java
@@ -76,8 +76,12 @@ class FileLoader extends StreamLoader {
protected boolean setupStreamAndSendStatus() {
try {
if (mIsAsset) {
- mDataStream = mContext.getAssets().open(mPath,
- AssetManager.ACCESS_STREAMING);
+ try {
+ mDataStream = mContext.getAssets().open(mPath);
+ } catch (java.io.FileNotFoundException ex) {
+ // try the rest files included in the package
+ mDataStream = mContext.getAssets().openNonAsset(mPath);
+ }
} else {
if (!mAllowFileAccess) {
mHandler.error(EventHandler.FILE_ERROR,
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
index 7a3bbe6..42d03f0 100644
--- a/core/java/android/webkit/FrameLoader.java
+++ b/core/java/android/webkit/FrameLoader.java
@@ -21,7 +21,6 @@ import android.net.http.RequestHandle;
import android.util.Config;
import android.util.Log;
import android.webkit.CacheManager.CacheResult;
-import android.webkit.UrlInterceptRegistry;
import java.util.HashMap;
import java.util.Map;
@@ -220,6 +219,7 @@ class FrameLoader {
// Tell the Listener respond with the cache file
CacheLoader cacheLoader =
new CacheLoader(mListener, result);
+ mListener.setCacheLoader(cacheLoader);
cacheLoader.load();
}
@@ -233,12 +233,14 @@ class FrameLoader {
private boolean handleUrlIntercept() {
// Check if the URL can be served from UrlIntercept. If
// successful, return the data just like a cache hit.
- CacheResult result = UrlInterceptRegistry.getSurrogate(
+
+ PluginData data = UrlInterceptRegistry.getPluginData(
mListener.url(), mHeaders);
- if(result != null) {
- // Intercepted. The data is stored in result.stream. Setup
- // a load from the CacheResult.
- startCacheLoad(result);
+
+ if(data != null) {
+ PluginContentLoader loader =
+ new PluginContentLoader(mListener, data);
+ loader.load();
return true;
}
// Not intercepted. Carry on as normal.
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 3f2bbe5..f9fb0b0 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -33,6 +33,8 @@ import android.util.Config;
import android.util.Log;
import android.webkit.CacheManager.CacheResult;
+import com.android.internal.R;
+
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -56,6 +58,9 @@ class LoadListener extends Handler implements EventHandler {
private static final int MSG_CONTENT_ERROR = 130;
private static final int MSG_LOCATION_CHANGED = 140;
private static final int MSG_LOCATION_CHANGED_REQUEST = 150;
+ private static final int MSG_STATUS = 160;
+ private static final int MSG_SSL_CERTIFICATE = 170;
+ private static final int MSG_SSL_ERROR = 180;
// Standard HTTP status codes in a more representative format
private static final int HTTP_OK = 200;
@@ -166,9 +171,7 @@ class LoadListener extends Handler implements EventHandler {
* available. The headers are sent onto WebCore to see what we
* should do with them.
*/
- if (mNativeLoader != 0) {
- commitHeadersCheckRedirect();
- }
+ handleHeaders((Headers) msg.obj);
break;
case MSG_CONTENT_DATA:
@@ -177,7 +180,7 @@ class LoadListener extends Handler implements EventHandler {
* in it's data buffer. This data buffer could be filled from a
* file (this thread) or from http (Network thread).
*/
- if (mNativeLoader != 0) {
+ if (mNativeLoader != 0 && !ignoreCallbacks()) {
commitLoad();
}
break;
@@ -189,7 +192,7 @@ class LoadListener extends Handler implements EventHandler {
* error.
*
*/
- tearDown();
+ handleEndData();
break;
case MSG_CONTENT_ERROR:
@@ -197,8 +200,7 @@ class LoadListener extends Handler implements EventHandler {
* This message is sent when a load error has occured. The
* LoadListener will clean itself up.
*/
- notifyError();
- tearDown();
+ handleError(msg.arg1, (String) msg.obj);
break;
case MSG_LOCATION_CHANGED:
@@ -224,6 +226,33 @@ class LoadListener extends Handler implements EventHandler {
stopMsg, contMsg);
break;
+ case MSG_STATUS:
+ /*
+ * This message is sent from the network thread when the http
+ * stack has received the status response from the server.
+ */
+ HashMap status = (HashMap) msg.obj;
+ handleStatus(((Integer) status.get("major")).intValue(),
+ ((Integer) status.get("minor")).intValue(),
+ ((Integer) status.get("code")).intValue(),
+ (String) status.get("reason"));
+ break;
+
+ case MSG_SSL_CERTIFICATE:
+ /*
+ * This message is sent when the network thread receives a ssl
+ * certificate.
+ */
+ handleCertificate((SslCertificate) msg.obj);
+ break;
+
+ case MSG_SSL_ERROR:
+ /*
+ * This message is sent when the network thread encounters a
+ * ssl error.
+ */
+ handleSslError((SslError) msg.obj);
+ break;
}
}
@@ -257,6 +286,11 @@ class LoadListener extends Handler implements EventHandler {
*/
public void headers(Headers headers) {
if (Config.LOGV) Log.v(LOGTAG, "LoadListener.headers");
+ sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers));
+ }
+
+ // Does the header parsing work on the WebCore thread.
+ private void handleHeaders(Headers headers) {
if (mCancelled) return;
mHeaders = headers;
mMimeType = "";
@@ -342,6 +376,11 @@ class LoadListener extends Handler implements EventHandler {
}
}
}
+
+ // if there is buffered data, commit them in the end
+ boolean needToCommit = mAuthHeader != null && !mustAuthenticate
+ && mNativeLoader != 0 && !mDataBuilder.isEmpty();
+
// it is only here that we can reset the last mAuthHeader object
// (if existed) and start a new one!!!
mAuthHeader = null;
@@ -375,7 +414,11 @@ class LoadListener extends Handler implements EventHandler {
mCacheResult.encoding = mEncoding;
}
}
- sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS));
+ commitHeadersCheckRedirect();
+
+ if (needToCommit) {
+ commitLoad();
+ }
}
/**
@@ -404,11 +447,20 @@ class LoadListener extends Handler implements EventHandler {
+ " code: " + code
+ " reason: " + reasonPhrase);
}
+ HashMap status = new HashMap();
+ status.put("major", majorVersion);
+ status.put("minor", minorVersion);
+ status.put("code", code);
+ status.put("reason", reasonPhrase);
+ sendMessageInternal(obtainMessage(MSG_STATUS, status));
+ }
+ // Handle the status callback on the WebCore thread.
+ private void handleStatus(int major, int minor, int code, String reason) {
if (mCancelled) return;
mStatusCode = code;
- mStatusText = reasonPhrase;
+ mStatusText = reason;
mPermanent = false;
}
@@ -418,8 +470,15 @@ class LoadListener extends Handler implements EventHandler {
* connection. In this context, can be called multiple
* times if we have redirects
* @param certificate The SSL certifcate
+ * IMPORTANT: as this is called from network thread, can't call native
+ * directly
*/
public void certificate(SslCertificate certificate) {
+ sendMessageInternal(obtainMessage(MSG_SSL_CERTIFICATE, certificate));
+ }
+
+ // Handle the certificate on the WebCore thread.
+ private void handleCertificate(SslCertificate certificate) {
// if this is the top-most main-frame page loader
if (mIsMainPageLoader) {
// update the browser frame (ie, the main frame)
@@ -436,14 +495,20 @@ class LoadListener extends Handler implements EventHandler {
* directly
*/
public void error(int id, String description) {
- mErrorID = id;
- mErrorDescription = description;
- sendMessageInternal(obtainMessage(MSG_CONTENT_ERROR));
if (Config.LOGV) {
Log.v(LOGTAG, "LoadListener.error url:" +
url() + " id:" + id + " description:" + description);
}
+ sendMessageInternal(obtainMessage(MSG_CONTENT_ERROR, id, 0, description));
+ }
+
+ // Handle the error on the WebCore thread.
+ private void handleError(int id, String description) {
+ mErrorID = id;
+ mErrorDescription = description;
detachRequestHandle();
+ notifyError();
+ tearDown();
}
/**
@@ -453,11 +518,15 @@ class LoadListener extends Handler implements EventHandler {
* @param length The length of data.
* IMPORTANT: as this is called from network thread, can't call native
* directly
+ * XXX: Unlike the other network thread methods, this method can do the
+ * work of decoding the data and appending it to the data builder because
+ * mDataBuilder is a thread-safe structure.
*/
public void data(byte[] data, int length) {
if (Config.LOGV) {
Log.v(LOGTAG, "LoadListener.data(): url: " + url());
}
+
// Decode base64 data
// Note: It's fine that we only decode base64 here and not in the other
// data call because the only caller of the stream version is not
@@ -479,7 +548,7 @@ class LoadListener extends Handler implements EventHandler {
sendMessage = mDataBuilder.isEmpty();
mDataBuilder.append(data, 0, length);
}
- if (sendMessage && !ignoreCallbacks()) {
+ if (sendMessage) {
// Send a message whenever data comes in after a write to WebCore
sendMessageInternal(obtainMessage(MSG_CONTENT_DATA));
}
@@ -495,7 +564,11 @@ class LoadListener extends Handler implements EventHandler {
if (Config.LOGV) {
Log.v(LOGTAG, "LoadListener.endData(): url: " + url());
}
+ sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
+ }
+ // Handle the end of data.
+ private void handleEndData() {
if (mCancelled) return;
switch (mStatusCode) {
@@ -505,17 +578,13 @@ class LoadListener extends Handler implements EventHandler {
case HTTP_FOUND:
case HTTP_SEE_OTHER:
case HTTP_TEMPORARY_REDIRECT:
- if (mMethod == null && mRequestHandle == null) {
- Log.e(LOGTAG, "LoadListener.endData(): method is null!");
- Log.e(LOGTAG, "LoadListener.endData(): url = " + url());
- }
// 301, 302, 303, and 307 - redirect
if (mStatusCode == HTTP_TEMPORARY_REDIRECT) {
if (mRequestHandle != null &&
mRequestHandle.getMethod().equals("POST")) {
sendMessageInternal(obtainMessage(
MSG_LOCATION_CHANGED_REQUEST));
- } else if (mMethod != null && mMethod.equals("POST")) {
+ } else if (mMethod != null && mMethod.equals("POST")) {
sendMessageInternal(obtainMessage(
MSG_LOCATION_CHANGED_REQUEST));
} else {
@@ -558,9 +627,14 @@ class LoadListener extends Handler implements EventHandler {
default:
break;
}
-
- sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
detachRequestHandle();
+ tearDown();
+ }
+
+ /* This method is called from CacheLoader when the initial request is
+ * serviced by the Cache. */
+ /* package */ void setCacheLoader(CacheLoader c) {
+ mCacheLoader = c;
}
/**
@@ -574,9 +648,16 @@ class LoadListener extends Handler implements EventHandler {
CacheResult result = CacheManager.getCacheFile(url(),
headers);
+ // Go ahead and set the cache loader to null in case the result is
+ // null.
+ mCacheLoader = null;
+
if (result != null) {
- CacheLoader cacheLoader =
- new CacheLoader(this, result);
+ // The contents of the cache may need to be revalidated so just
+ // remember the cache loader in the case that the server responds
+ // positively to the cached content. This is also used to detect if
+ // a redirect came from the cache.
+ mCacheLoader = new CacheLoader(this, result);
// If I got a cachedUrl and the revalidation header was not
// added, then the cached content valid, we should use it.
@@ -589,14 +670,8 @@ class LoadListener extends Handler implements EventHandler {
"and usable: " + url());
}
// Load the cached file
- cacheLoader.load();
+ mCacheLoader.load();
return true;
- } else {
- // The contents of the cache need to be revalidated
- // so just provide the listener with the cache loader
- // in the case that the server response positively to
- // the cached content.
- setCacheLoader(cacheLoader);
}
}
return false;
@@ -615,7 +690,11 @@ class LoadListener extends Handler implements EventHandler {
" primary error: " + error.getPrimaryError() +
" certificate: " + error.getCertificate());
}
+ sendMessageInternal(obtainMessage(MSG_SSL_ERROR, error));
+ }
+ // Handle the ssl error on the WebCore thread.
+ private void handleSslError(SslError error) {
if (!mCancelled) {
mSslError = error;
Network.getInstance(mContext).handleSslErrorRequest(this);
@@ -705,14 +784,6 @@ class LoadListener extends Handler implements EventHandler {
}
/**
- * Set the CacheLoader for the case where we might want to load from cache
- * @param result
- */
- void setCacheLoader(CacheLoader result) {
- mCacheLoader = result;
- }
-
- /**
* This is called when a request can be satisfied by the cache, however,
* the cache result could be a redirect. In this case we need to issue
* the network request.
@@ -1002,6 +1073,11 @@ class LoadListener extends Handler implements EventHandler {
clearNativeLoader();
}
+ // This count is transferred from RequestHandle to LoadListener when
+ // loading from the cache so that we can detect redirect loops that switch
+ // between the network and the cache.
+ private int mCacheRedirectCount;
+
/*
* Perform the actual redirection. This involves setting up the new URL,
* informing WebCore and then telling the Network to start loading again.
@@ -1014,6 +1090,14 @@ class LoadListener extends Handler implements EventHandler {
return;
}
+ // Do the same check for a redirect loop that
+ // RequestHandle.setupRedirect does.
+ if (mCacheRedirectCount >= RequestHandle.MAX_REDIRECT_COUNT) {
+ handleError(EventHandler.ERROR_REDIRECT_LOOP, mContext.getString(
+ R.string.httpErrorRedirectLoop));
+ return;
+ }
+
String redirectTo = mHeaders.getLocation();
if (redirectTo != null) {
int nativeResponse = createNativeResponse();
@@ -1031,7 +1115,7 @@ class LoadListener extends Handler implements EventHandler {
return;
} else if (!URLUtil.isNetworkUrl(redirectTo)) {
final String text = mContext
- .getString(com.android.internal.R.string.open_permission_deny)
+ .getString(R.string.open_permission_deny)
+ "\n" + redirectTo;
nativeAddData(text.getBytes(), text.length());
nativeFinished();
@@ -1051,36 +1135,58 @@ class LoadListener extends Handler implements EventHandler {
mCacheResult = null;
}
+ // This will strip the anchor
setUrl(redirectTo);
// Redirect may be in the cache
if (mRequestHeaders == null) {
mRequestHeaders = new HashMap<String, String>();
}
+ boolean fromCache = false;
+ if (mCacheLoader != null) {
+ // This is a redirect from the cache loader. Increment the
+ // redirect count to avoid redirect loops.
+ mCacheRedirectCount++;
+ fromCache = true;
+ }
if (!checkCache(mRequestHeaders)) {
// mRequestHandle can be null when the request was satisfied
// by the cache, and the cache returned a redirect
if (mRequestHandle != null) {
- mRequestHandle.setupRedirect(redirectTo, mStatusCode,
+ mRequestHandle.setupRedirect(mUrl, mStatusCode,
mRequestHeaders);
} else {
- String method = mMethod;
-
- if (method == null) {
+ // If the original request came from the cache, there is no
+ // RequestHandle, we have to create a new one through
+ // Network.requestURL.
+ Network network = Network.getInstance(getContext());
+ if (!network.requestURL(mMethod, mRequestHeaders,
+ mPostData, this, mIsHighPriority)) {
+ // Signal a bad url error if we could not load the
+ // redirection.
+ handleError(EventHandler.ERROR_BAD_URL,
+ mContext.getString(R.string.httpErrorBadUrl));
return;
}
-
- Network network = Network.getInstance(getContext());
- network.requestURL(method, mRequestHeaders,
- mPostData, this, mIsHighPriority);
}
+ if (fromCache) {
+ // If we are coming from a cache load, we need to transfer
+ // the redirect count to the new (or old) RequestHandle to
+ // keep the redirect count in sync.
+ mRequestHandle.setRedirectCount(mCacheRedirectCount);
+ }
+ } else if (!fromCache) {
+ // Switching from network to cache means we need to grab the
+ // redirect count from the RequestHandle to keep the count in
+ // sync. Add 1 to account for the current redirect.
+ mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1;
}
+ // Clear the buffered data since the redirect is valid.
+ mDataBuilder.clear();
} else {
- // With a null redirect, commit the original headers, the buffered
- // data, and then finish the load.
commitHeaders();
commitLoad();
- nativeFinished();
+ tearDown();
}
if (Config.LOGV) {
@@ -1167,14 +1273,16 @@ class LoadListener extends Handler implements EventHandler {
quoted = !quoted;
} else {
if (!quoted) {
- if (header.startsWith(
- HttpAuthHeader.BASIC_TOKEN, i)) {
+ if (header.regionMatches(true, i,
+ HttpAuthHeader.BASIC_TOKEN, 0,
+ HttpAuthHeader.BASIC_TOKEN.length())) {
pos[posLen++] = i;
continue;
}
- if (header.startsWith(
- HttpAuthHeader.DIGEST_TOKEN, i)) {
+ if (header.regionMatches(true, i,
+ HttpAuthHeader.DIGEST_TOKEN, 0,
+ HttpAuthHeader.DIGEST_TOKEN.length())) {
pos[posLen++] = i;
continue;
}
@@ -1186,8 +1294,9 @@ class LoadListener extends Handler implements EventHandler {
if (posLen > 0) {
// consider all digest schemes first (if any)
for (int i = 0; i < posLen; i++) {
- if (header.startsWith(HttpAuthHeader.DIGEST_TOKEN,
- pos[i])) {
+ if (header.regionMatches(true, pos[i],
+ HttpAuthHeader.DIGEST_TOKEN, 0,
+ HttpAuthHeader.DIGEST_TOKEN.length())) {
String sub = header.substring(pos[i],
(i + 1 < posLen ? pos[i + 1] : headerLen));
@@ -1201,7 +1310,9 @@ class LoadListener extends Handler implements EventHandler {
// ...then consider all basic schemes (if any)
for (int i = 0; i < posLen; i++) {
- if (header.startsWith(HttpAuthHeader.BASIC_TOKEN, pos[i])) {
+ if (header.regionMatches(true, pos[i],
+ HttpAuthHeader.BASIC_TOKEN, 0,
+ HttpAuthHeader.BASIC_TOKEN.length())) {
String sub = header.substring(pos[i],
(i + 1 < posLen ? pos[i + 1] : headerLen));
@@ -1235,19 +1346,16 @@ class LoadListener extends Handler implements EventHandler {
*/
void setUrl(String url) {
if (url != null) {
- if (URLUtil.isDataUrl(url)) {
- // Don't strip anchor as that is a valid part of the URL
- mUrl = url;
- } else {
- mUrl = URLUtil.stripAnchor(url);
- }
mUri = null;
- if (URLUtil.isNetworkUrl(mUrl)) {
+ if (URLUtil.isNetworkUrl(url)) {
+ mUrl = URLUtil.stripAnchor(url);
try {
mUri = new WebAddress(mUrl);
} catch (ParseException e) {
e.printStackTrace();
}
+ } else {
+ mUrl = url;
}
}
}
@@ -1262,9 +1370,8 @@ class LoadListener extends Handler implements EventHandler {
// type (implying text/plain).
if (URLUtil.isDataUrl(mUrl) && mMimeType.length() != 0) {
cancel();
- final String text = mContext.getString(
- com.android.internal.R.string.httpErrorBadUrl);
- error(EventHandler.ERROR_BAD_URL, text);
+ final String text = mContext.getString(R.string.httpErrorBadUrl);
+ handleError(EventHandler.ERROR_BAD_URL, text);
} else {
// Note: This is ok because this is used only for the main content
// of frames. If no content-type was specified, it is fine to
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index 685e352..c9cc208 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -480,6 +480,7 @@ public /* package */ class MimeTypeMap {
sMimeTypeMap.loadEntry("video/mpeg", "mpg", false);
sMimeTypeMap.loadEntry("video/mpeg", "mpe", false);
sMimeTypeMap.loadEntry("video/mp4", "mp4", false);
+ sMimeTypeMap.loadEntry("video/mpeg", "VOB", false);
sMimeTypeMap.loadEntry("video/quicktime", "qt", false);
sMimeTypeMap.loadEntry("video/quicktime", "mov", false);
sMimeTypeMap.loadEntry("video/vnd.mpegurl", "mxu", false);
diff --git a/core/java/android/webkit/PluginContentLoader.java b/core/java/android/webkit/PluginContentLoader.java
new file mode 100644
index 0000000..2069599
--- /dev/null
+++ b/core/java/android/webkit/PluginContentLoader.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.net.http.Headers;
+
+import java.io.InputStream;
+import java.util.*;
+
+import org.apache.http.util.CharArrayBuffer;
+
+/**
+ * This class is a concrete implementation of StreamLoader that uses a
+ * PluginData object as the source for the stream.
+ */
+class PluginContentLoader extends StreamLoader {
+
+ private PluginData mData; // Content source
+
+ /**
+ * Constructs a PluginDataLoader for use when loading content from
+ * a plugin.
+ *
+ * @param loadListener LoadListener to pass the content to
+ * @param data PluginData used as the source for the content.
+ */
+ PluginContentLoader(LoadListener loadListener, PluginData data) {
+ super(loadListener);
+ mData = data;
+ }
+
+ @Override
+ protected boolean setupStreamAndSendStatus() {
+ mDataStream = mData.getInputStream();
+ mContentLength = mData.getContentLength();
+ mHandler.status(1, 1, mData.getStatusCode(), "OK");
+ return true;
+ }
+
+ @Override
+ protected void buildHeaders(Headers headers) {
+ // Crate a CharArrayBuffer with an arbitrary initial capacity.
+ CharArrayBuffer buffer = new CharArrayBuffer(100);
+ Iterator<Map.Entry<String, String[]>> responseHeadersIt =
+ mData.getHeaders().entrySet().iterator();
+ while (responseHeadersIt.hasNext()) {
+ Map.Entry<String, String[]> entry = responseHeadersIt.next();
+ // Headers.parseHeader() expects lowercase keys, so keys
+ // such as "Accept-Ranges" will fail to parse.
+ //
+ // UrlInterceptHandler instances supply a mapping of
+ // lowercase key to [ unmodified key, value ], so for
+ // Headers.parseHeader() to succeed, we need to construct
+ // a string using the key (i.e. entry.getKey()) and the
+ // element denoting the header value in the
+ // [ unmodified key, value ] pair (i.e. entry.getValue()[1).
+ //
+ // The reason why UrlInterceptHandler instances supply such a
+ // mapping in the first place is historical. Early versions of
+ // the Gears plugin used java.net.HttpURLConnection, which always
+ // returned headers names as capitalized strings. When these were
+ // fed back into webkit, they failed to parse.
+ //
+ // Mewanwhile, Gears was modified to use Apache HTTP library
+ // instead, so this design is now obsolete. Changing it however,
+ // would require changes to the Gears C++ codebase and QA-ing and
+ // submitting a new binary to the Android tree. Given the
+ // timelines for the next Android release, we will not do this
+ // for now.
+ //
+ // TODO: fix C++ Gears to remove the need for this
+ // design.
+ String keyValue = entry.getKey() + ": " + entry.getValue()[1];
+ buffer.ensureCapacity(keyValue.length());
+ buffer.append(keyValue);
+ // Parse it into the header container.
+ headers.parseHeader(buffer);
+ // Clear the buffer
+ buffer.clear();
+ }
+ }
+}
diff --git a/core/java/android/webkit/PluginData.java b/core/java/android/webkit/PluginData.java
new file mode 100644
index 0000000..2b539fe
--- /dev/null
+++ b/core/java/android/webkit/PluginData.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * This class encapsulates the content generated by a plugin. The
+ * data itself is meant to be loaded into webkit via the
+ * PluginContentLoader class, which needs to be able to construct an
+ * HTTP response. For this, it needs a stream with the response body,
+ * the length of the body, the response headers, and the response
+ * status code. The PluginData class is the container for all these
+ * parts.
+ *
+ */
+public final class PluginData {
+ /**
+ * The content stream.
+ */
+ private InputStream mStream;
+ /**
+ * The content length.
+ */
+ private long mContentLength;
+ /**
+ * The associated HTTP response headers stored as a map of
+ * lowercase header name to [ unmodified header name, header value].
+ * TODO: This design was always a hack. Remove (involves updating
+ * the Gears C++ side).
+ */
+ private Map<String, String[]> mHeaders;
+
+ /**
+ * The index of the header value in the above mapping.
+ */
+ private int mHeaderValueIndex;
+ /**
+ * The associated HTTP response code.
+ */
+ private int mStatusCode;
+
+ /**
+ * Creates a PluginData instance.
+ *
+ * @param stream The stream that supplies content for the plugin.
+ * @param length The length of the plugin content.
+ * @param headers The response headers. Map of
+ * lowercase header name to [ unmodified header name, header value]
+ * @param length The HTTP response status code.
+ */
+ public PluginData(
+ InputStream stream,
+ long length,
+ Map<String, String[]> headers,
+ int code) {
+ mStream = stream;
+ mContentLength = length;
+ mHeaders = headers;
+ mStatusCode = code;
+ }
+
+ /**
+ * Returns the input stream that contains the plugin content.
+ *
+ * @return An InputStream instance with the plugin content.
+ */
+ public InputStream getInputStream() {
+ return mStream;
+ }
+
+ /**
+ * Returns the length of the plugin content.
+ *
+ * @return the length of the plugin content.
+ */
+ public long getContentLength() {
+ return mContentLength;
+ }
+
+ /**
+ * Returns the HTTP response headers associated with the plugin
+ * content.
+ *
+ * @return A Map<String, String[]> containing all headers. The
+ * mapping is 'lowercase header name' to ['unmodified header
+ * name', header value].
+ */
+ public Map<String, String[]> getHeaders() {
+ return mHeaders;
+ }
+
+ /**
+ * Returns the HTTP status code for the response.
+ *
+ * @return The HTTP statue code, e.g 200.
+ */
+ public int getStatusCode() {
+ return mStatusCode;
+ }
+}
diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java
index b7b40b1..9de97c9 100644
--- a/core/java/android/webkit/TextDialog.java
+++ b/core/java/android/webkit/TextDialog.java
@@ -25,18 +25,13 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
-import android.os.Handler;
-import android.os.Message;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Selection;
import android.text.Spannable;
import android.text.TextPaint;
import android.text.TextUtils;
-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.inputmethod.InputMethodManager;
import android.view.KeyCharacterMap;
@@ -44,8 +39,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.View.MeasureSpec;
-import android.view.ViewConfiguration;
import android.widget.AbsoluteLayout.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
@@ -69,10 +62,8 @@ import java.util.ArrayList;
// on the enter key. The method for blocking unmatched key ups prevents
// the shift key from working properly.
private boolean mGotEnterDown;
- // Determines whether we allow calls to requestRectangleOnScreen to
- // propagate. We only want to scroll if the user is typing. If the
- // user is simply navigating through a textfield, we do not want to
- // scroll.
+ // mScrollToAccommodateCursor being set to false prevents us from scrolling
+ // the cursor on screen when using the trackball to select a textfield.
private boolean mScrollToAccommodateCursor;
private int mMaxLength;
// Keep track of the text before the change so we know whether we actually
@@ -86,22 +77,6 @@ import java.util.ArrayList;
// FIXME: This can be replaced with TextView.NO_FILTERS if that
// is made public/protected.
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
- // The time of the last enter down, so we know whether to perform a long
- // press.
- private long mDownTime;
-
- private boolean mTrackballDown = false;
- private static int LONGPRESS = 1;
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- if (msg.what == LONGPRESS) {
- if (mTrackballDown) {
- performLongClick();
- mTrackballDown = false;
- }
- }
- }
- };
/**
* Create a new TextDialog.
@@ -136,6 +111,7 @@ import java.util.ArrayList;
// that other applications that use embedded WebViews will properly
// display the text in textfields.
setTextColor(Color.BLACK);
+ setImeOptions(EditorInfo.IME_ACTION_NONE);
}
@Override
@@ -156,38 +132,33 @@ import java.util.ArrayList;
return true;
}
- // For single-line textfields, return key should not be handled
- // here. Instead, the WebView is passed the key up, so it may fire a
- // submit/onClick.
- // Center key should always be passed to a potential onClick
- if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)
- || KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+ if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)) {
if (isPopupShowing()) {
- super.dispatchKeyEvent(event);
- return true;
+ return super.dispatchKeyEvent(event);
}
- if (down) {
- if (event.getRepeatCount() == 0) {
- mGotEnterDown = true;
- mDownTime = event.getEventTime();
- // Send the keydown when the up comes, so that we have
- // a chance to handle a long press.
- } else if (mGotEnterDown && event.getEventTime() - mDownTime >
- ViewConfiguration.getLongPressTimeout()) {
- performLongClick();
- mGotEnterDown = false;
- }
- } else if (mGotEnterDown) {
- mGotEnterDown = false;
- if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
- mWebView.shortPressOnTextField();
- return true;
- }
+ if (!down) {
+ // Hide the keyboard, since the user has just submitted this
+ // form. The submission happens thanks to the two calls
+ // to sendDomEvent.
+ InputMethodManager.getInstance(mContext)
+ .hideSoftInputFromWindow(getWindowToken(), 0);
sendDomEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
sendDomEvent(event);
}
- return true;
+ return super.dispatchKeyEvent(event);
+ } else if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+ // Note that this handles center key and trackball.
+ if (isPopupShowing()) {
+ return super.dispatchKeyEvent(event);
+ }
+ // Center key should be passed to a potential onClick
+ if (!down) {
+ mWebView.shortPressOnTextField();
+ }
+ // Pass to super to handle longpress.
+ return super.dispatchKeyEvent(event);
}
+
// Ensure there is a layout so arrow keys are handled properly.
if (getLayout() == null) {
measure(mWidthSpec, mHeightSpec);
@@ -224,9 +195,8 @@ import java.util.ArrayList;
case KeyEvent.KEYCODE_DPAD_DOWN:
isArrowKey = true;
break;
- case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
- // For multi-line text boxes, newlines and dpad center will
+ // For multi-line text boxes, newlines will
// trigger onTextChanged for key down (which will send both
// key up and key down) but not key up.
mGotEnterDown = true;
@@ -268,7 +238,7 @@ import java.util.ArrayList;
// with WebCore's notion of the current selection, reset the selection
// to what it was before the key event.
Selection.setSelection(text, oldStart, oldEnd);
- // Ignore the key up event for newlines or dpad center. This prevents
+ // Ignore the key up event for newlines. This prevents
// multiple newlines in the native textarea.
if (mGotEnterDown && !down) {
return true;
@@ -290,6 +260,25 @@ import java.util.ArrayList;
}
/**
+ * Create a fake touch up event at (x,y) with respect to this TextDialog.
+ * This is used by WebView to act as though a touch event which happened
+ * before we placed the TextDialog actually hit it, so that it can place
+ * the cursor accordingly.
+ */
+ /* package */ void fakeTouchEvent(float x, float y) {
+ // We need to ensure that there is a Layout, since the Layout is used
+ // in determining where to place the cursor.
+ if (getLayout() == null) {
+ measure(mWidthSpec, mHeightSpec);
+ }
+ // Create a fake touch up, which is used to place the cursor.
+ MotionEvent ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
+ x, y, 0);
+ onTouchEvent(ev);
+ ev.recycle();
+ }
+
+ /**
* Determine whether this TextDialog currently represents the node
* represented by ptr.
* @param ptr Pointer to a node to compare to.
@@ -371,27 +360,8 @@ import java.util.ArrayList;
if (isPopupShowing()) {
return super.onTrackballEvent(event);
}
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- if (!mTrackballDown) {
- mTrackballDown = true;
- mHandler.sendEmptyMessageDelayed(LONGPRESS,
- ViewConfiguration.getLongPressTimeout());
- }
- return true;
- case MotionEvent.ACTION_UP:
- if (mTrackballDown) {
- mWebView.shortPressOnTextField();
- mTrackballDown = false;
- mHandler.removeMessages(LONGPRESS);
- }
- return true;
- case MotionEvent.ACTION_CANCEL:
- mTrackballDown = false;
- return true;
- case MotionEvent.ACTION_MOVE:
- // fall through
+ if (event.getAction() != MotionEvent.ACTION_MOVE) {
+ return false;
}
Spannable text = (Spannable) getText();
MovementMethod move = getMovementMethod();
@@ -405,6 +375,12 @@ import java.util.ArrayList;
//mWebView.setSelection(start, end);
return true;
}
+ // If the user is in a textfield, and the movement method is not
+ // handling the trackball events, it means they are at the end of the
+ // field and continuing to move the trackball. In this case, we should
+ // not scroll the cursor on screen bc the user may be attempting to
+ // scroll the page, possibly in the opposite direction of the cursor.
+ mScrollToAccommodateCursor = false;
return false;
}
@@ -416,9 +392,19 @@ import java.util.ArrayList;
// hide the soft keyboard when the edit text is out of focus
InputMethodManager.getInstance(mContext).hideSoftInputFromWindow(
getWindowToken(), 0);
- mHandler.removeMessages(LONGPRESS);
mWebView.removeView(this);
mWebView.requestFocus();
+ mScrollToAccommodateCursor = false;
+ }
+
+ /* package */ void enableScrollOnScreen(boolean enable) {
+ mScrollToAccommodateCursor = enable;
+ }
+
+ /* package */ void bringIntoView() {
+ if (getLayout() != null) {
+ bringPointIntoView(Selection.getSelectionEnd(getText()));
+ }
}
@Override
@@ -437,8 +423,15 @@ import java.util.ArrayList;
mWebView.passToJavaScript(getText().toString(), event);
}
+ /**
+ * Always use this instead of setAdapter, as this has features specific to
+ * the TextDialog.
+ */
public void setAdapterCustom(AutoCompleteAdapter adapter) {
- adapter.setTextView(this);
+ if (adapter != null) {
+ setInputType(EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+ adapter.setTextView(this);
+ }
super.setAdapter(adapter);
}
@@ -480,15 +473,10 @@ import java.util.ArrayList;
* @param inPassword True if the textfield is a password field.
*/
/* package */ void setInPassword(boolean inPassword) {
- PasswordTransformationMethod method;
if (inPassword) {
- method = PasswordTransformationMethod.getInstance();
- } else {
- method = null;
+ setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.
+ TYPE_TEXT_VARIATION_PASSWORD);
}
- setTransformationMethod(method);
- setInputType(inPassword ? EditorInfo.TYPE_TEXT_VARIATION_PASSWORD :
- EditorInfo.TYPE_CLASS_TEXT);
}
/* package */ void setMaxLength(int maxLength) {
@@ -539,7 +527,6 @@ import java.util.ArrayList;
// Set up a measure spec so a layout can always be recreated.
mWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
mHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
- mScrollToAccommodateCursor = false;
requestFocus();
}
@@ -547,19 +534,19 @@ import java.util.ArrayList;
* Set whether this is a single-line textfield or a multi-line textarea.
* Textfields scroll horizontally, and do not handle the enter key.
* Textareas behave oppositely.
+ * Do NOT call this after calling setInPassword(true). This will result in
+ * removing the password input type.
*/
public void setSingleLine(boolean single) {
- if (mSingle != single) {
- TextKeyListener.Capitalize cap;
- if (single) {
- cap = TextKeyListener.Capitalize.NONE;
- } else {
- cap = TextKeyListener.Capitalize.SENTENCES;
- }
- setKeyListener(TextKeyListener.getInstance(!single, cap));
- mSingle = single;
- setHorizontallyScrolling(single);
+ int inputType = EditorInfo.TYPE_CLASS_TEXT;
+ if (!single) {
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
+ | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
}
+ mSingle = single;
+ setHorizontallyScrolling(single);
+ setInputType(inputType);
}
/**
diff --git a/core/java/android/webkit/UrlInterceptHandler.java b/core/java/android/webkit/UrlInterceptHandler.java
index e1c9d61..9216413 100644
--- a/core/java/android/webkit/UrlInterceptHandler.java
+++ b/core/java/android/webkit/UrlInterceptHandler.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.webkit.CacheManager.CacheResult;
+import android.webkit.PluginData;
import java.util.Map;
public interface UrlInterceptHandler {
@@ -29,6 +30,20 @@ public interface UrlInterceptHandler {
* @param url URL string.
* @param headers The headers associated with the request. May be null.
* @return The CacheResult containing the surrogate response.
+ * @Deprecated Use PluginData getPluginData(String url,
+ * Map<String, String> headers); instead
*/
+ @Deprecated
public CacheResult service(String url, Map<String, String> headers);
+
+ /**
+ * Given an URL, returns the PluginData which contains the
+ * surrogate response for the request, or null if the handler is
+ * not interested.
+ *
+ * @param url URL string.
+ * @param headers The headers associated with the request. May be null.
+ * @return The PluginData containing the surrogate response.
+ */
+ public PluginData getPluginData(String url, Map<String, String> headers);
}
diff --git a/core/java/android/webkit/UrlInterceptRegistry.java b/core/java/android/webkit/UrlInterceptRegistry.java
index a218191..6051f29 100644
--- a/core/java/android/webkit/UrlInterceptRegistry.java
+++ b/core/java/android/webkit/UrlInterceptRegistry.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.webkit.CacheManager.CacheResult;
+import android.webkit.PluginData;
import android.webkit.UrlInterceptHandler;
import java.util.Iterator;
@@ -82,17 +83,21 @@ public final class UrlInterceptRegistry {
UrlInterceptHandler handler) {
return getHandlers().remove(handler);
}
-
+
/**
* Given an url, returns the CacheResult of the first
* UrlInterceptHandler interested, or null if none are.
- *
+ *
* @return A CacheResult containing surrogate content.
+ * @Deprecated Use PluginData getPluginData( String url,
+ * Map<String, String> headers) instead.
*/
+ @Deprecated
public static synchronized CacheResult getSurrogate(
String url, Map<String, String> headers) {
- if (urlInterceptDisabled())
+ if (urlInterceptDisabled()) {
return null;
+ }
Iterator iter = getHandlers().listIterator();
while (iter.hasNext()) {
UrlInterceptHandler handler = (UrlInterceptHandler) iter.next();
@@ -103,4 +108,27 @@ public final class UrlInterceptRegistry {
}
return null;
}
+
+ /**
+ * Given an url, returns the PluginData of the first
+ * UrlInterceptHandler interested, or null if none are or if
+ * intercepts are disabled.
+ *
+ * @return A PluginData instance containing surrogate content.
+ */
+ public static synchronized PluginData getPluginData(
+ String url, Map<String, String> headers) {
+ if (urlInterceptDisabled()) {
+ return null;
+ }
+ Iterator iter = getHandlers().listIterator();
+ while (iter.hasNext()) {
+ UrlInterceptHandler handler = (UrlInterceptHandler) iter.next();
+ PluginData data = handler.getPluginData(url, headers);
+ if (data != null) {
+ return data;
+ }
+ }
+ return null;
+ }
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 308b6aa..44f382c 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -20,6 +20,8 @@ import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
+import android.provider.Checkin;
+
import java.lang.SecurityException;
import android.content.pm.PackageManager;
@@ -136,7 +138,7 @@ public class WebSettings {
private int mDefaultFixedFontSize = 13;
private boolean mLoadsImagesAutomatically = true;
private boolean mBlockNetworkImage = false;
- private boolean mBlockNetworkLoads = false;
+ private boolean mBlockNetworkLoads;
private boolean mJavaScriptEnabled = false;
private boolean mPluginsEnabled = false;
private boolean mJavaScriptCanOpenWindowsAutomatically = false;
@@ -250,7 +252,9 @@ public class WebSettings {
mUserAgent = getCurrentUserAgent();
mUseDefaultUserAgent = true;
- verifyNetworkAccess();
+ mBlockNetworkLoads = mContext.checkPermission(
+ "android.permission.INTERNET", android.os.Process.myPid(),
+ android.os.Process.myUid()) != PackageManager.PERMISSION_GRANTED;
}
/**
@@ -320,10 +324,15 @@ public class WebSettings {
buffer.append("en");
}
- final String device = Build.DEVICE;
- if (device.length() > 0) {
+ final String model = Build.MODEL;
+ if (model.length() > 0) {
buffer.append("; ");
- buffer.append(device);
+ buffer.append(model);
+ }
+ final String id = Build.ID;
+ if (id.length() > 0) {
+ buffer.append(" Build/");
+ buffer.append(id);
}
final String base = mContext.getResources().getText(
com.android.internal.R.string.web_user_agent).toString();
@@ -407,6 +416,10 @@ public class WebSettings {
* @see WebSettings.TextSize
*/
public synchronized void setTextSize(TextSize t) {
+ if (WebView.mLogEvent && mTextSize != t ) {
+ Checkin.updateStats(mContext.getContentResolver(),
+ Checkin.Stats.Tag.BROWSER_TEXT_SIZE_CHANGE, 1, 0.0);
+ }
mTextSize = t;
postSync();
}
@@ -827,7 +840,7 @@ public class WebSettings {
private void verifyNetworkAccess() {
if (!mBlockNetworkLoads) {
if (mContext.checkPermission("android.permission.INTERNET",
- android.os.Process.myPid(), 0) !=
+ android.os.Process.myPid(), android.os.Process.myUid()) !=
PackageManager.PERMISSION_GRANTED) {
throw new SecurityException
("Permission denied - " +
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index c7ba498..d404040 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
-import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -38,13 +37,14 @@ import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.provider.Checkin;
import android.text.IClipboard;
import android.text.Selection;
import android.text.Spannable;
import android.util.AttributeSet;
import android.util.Config;
+import android.util.EventLog;
import android.util.Log;
-import android.view.animation.AlphaAnimation;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -64,10 +64,9 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
-import android.widget.RelativeLayout;
import android.widget.Scroller;
import android.widget.Toast;
-import android.widget.ZoomControls;
+import android.widget.ZoomButtonsController;
import android.widget.AdapterView.OnItemClickListener;
import java.io.File;
@@ -207,60 +206,6 @@ public class WebView extends AbsoluteLayout
static final boolean DEBUG = false;
static final boolean LOGV_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
- private class ExtendedZoomControls extends RelativeLayout {
- public ExtendedZoomControls(Context context, AttributeSet attrs) {
- super(context, attrs);
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(com.android.internal.R.layout.zoom_magnify, this
- , true);
- mZoomControls = (ZoomControls) findViewById
- (com.android.internal.R.id.zoomControls);
- mZoomMagnify = (ImageView) findViewById
- (com.android.internal.R.id.zoomMagnify);
- }
-
- public void show(boolean showZoom, boolean canZoomOut) {
- mZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE);
- mZoomMagnify.setVisibility(canZoomOut ? View.VISIBLE : View.GONE);
- fade(View.VISIBLE, 0.0f, 1.0f);
- }
-
- public void hide() {
- fade(View.GONE, 1.0f, 0.0f);
- }
-
- private void fade(int visibility, float startAlpha, float endAlpha) {
- AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);
- anim.setDuration(500);
- startAnimation(anim);
- setVisibility(visibility);
- }
-
- public void setIsZoomMagnifyEnabled(boolean isEnabled) {
- mZoomMagnify.setEnabled(isEnabled);
- }
-
- public boolean hasFocus() {
- return mZoomControls.hasFocus() || mZoomMagnify.hasFocus();
- }
-
- public void setOnZoomInClickListener(OnClickListener listener) {
- mZoomControls.setOnZoomInClickListener(listener);
- }
-
- public void setOnZoomOutClickListener(OnClickListener listener) {
- mZoomControls.setOnZoomOutClickListener(listener);
- }
-
- public void setOnZoomMagnifyClickListener(OnClickListener listener) {
- mZoomMagnify.setOnClickListener(listener);
- }
-
- ZoomControls mZoomControls;
- ImageView mZoomMagnify;
- }
-
/**
* Transportation object for returning WebView across thread boundaries.
*/
@@ -358,6 +303,11 @@ public class WebView extends AbsoluteLayout
// Whether to forward the touch events to WebCore
private boolean mForwardTouchEvents = false;
+ // Whether to prevent drag during touch. The initial value depends on
+ // mForwardTouchEvents. If WebCore wants touch events, we assume it will
+ // take control of touch events unless it says no for touch down event.
+ private boolean mPreventDrag;
+
// If updateTextEntry gets called while we are out of focus, use this
// variable to remember to do it next time we gain focus.
private boolean mNeedsUpdateTextEntry = false;
@@ -368,26 +318,20 @@ public class WebView extends AbsoluteLayout
/**
* Customizable constant
*/
- // pre-computed square of ViewConfiguration.getTouchSlop()
- private static final int TOUCH_SLOP_SQUARE =
- ViewConfiguration.getTouchSlop() * ViewConfiguration.getTouchSlop();
+ // pre-computed square of ViewConfiguration.getScaledTouchSlop()
+ private int mTouchSlopSquare;
+ // pre-computed density adjusted navigation slop
+ private int mNavSlop;
// This should be ViewConfiguration.getTapTimeout()
// But system time out is 100ms, which is too short for the browser.
// In the browser, if it switches out of tap too soon, jump tap won't work.
private static final int TAP_TIMEOUT = 200;
- // The duration in milliseconds we will wait to see if it is a double tap.
- // With a limited survey, the time between the first tap up and the second
- // tap down in the double tap case is around 70ms - 120ms.
- private static final int DOUBLE_TAP_TIMEOUT = 200;
// This should be ViewConfiguration.getLongPressTimeout()
// But system time out is 500ms, which is too short for the browser.
// With a short timeout, it's difficult to treat trigger a short press.
private static final int LONG_PRESS_TIMEOUT = 1000;
// needed to avoid flinging after a pause of no movement
private static final int MIN_FLING_TIME = 250;
- // The time that the Zoom Controls are visible before fading away
- private static final long ZOOM_CONTROLS_TIMEOUT =
- ViewConfiguration.getZoomControlsTimeout();
// The amount of content to overlap between two screens when going through
// pages with the space bar, in pixels.
private static final int PAGE_SCROLL_OVERLAP = 24;
@@ -422,10 +366,6 @@ public class WebView extends AbsoluteLayout
private boolean mWrapContent;
- // The View containing the zoom controls
- private ExtendedZoomControls mZoomControls;
- private Runnable mZoomControlRunnable;
-
// true if we should call webcore to draw the content, false means we have
// requested something but it isn't ready to draw yet.
private WebViewCore.FocusData mFocusData;
@@ -436,7 +376,6 @@ public class WebView extends AbsoluteLayout
private static final int NEVER_REMEMBER_PASSWORD = 2;
private static final int SWITCH_TO_SHORTPRESS = 3;
private static final int SWITCH_TO_LONGPRESS = 4;
- private static final int RELEASE_SINGLE_TAP = 5;
private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6;
private static final int SWITCH_TO_ENTER = 7;
private static final int RESUME_WEBCORE_UPDATE = 8;
@@ -460,19 +399,54 @@ public class WebView extends AbsoluteLayout
static final int LONG_PRESS_ENTER = 23;
static final int PREVENT_TOUCH_ID = 24;
static final int WEBCORE_NEED_TOUCH_EVENTS = 25;
+ // obj=Rect in doc coordinates
+ static final int INVAL_RECT_MSG_ID = 26;
+
+ static final String[] HandlerDebugString = {
+ "REMEMBER_PASSWORD", // = 1;
+ "NEVER_REMEMBER_PASSWORD", // = 2;
+ "SWITCH_TO_SHORTPRESS", // = 3;
+ "SWITCH_TO_LONGPRESS", // = 4;
+ "5",
+ "UPDATE_TEXT_ENTRY_ADAPTER", // = 6;
+ "SWITCH_TO_ENTER", // = 7;
+ "RESUME_WEBCORE_UPDATE", // = 8;
+ "9",
+ "SCROLL_TO_MSG_ID", // = 10;
+ "SCROLL_BY_MSG_ID", // = 11;
+ "SPAWN_SCROLL_TO_MSG_ID", // = 12;
+ "SYNC_SCROLL_TO_MSG_ID", // = 13;
+ "NEW_PICTURE_MSG_ID", // = 14;
+ "UPDATE_TEXT_ENTRY_MSG_ID", // = 15;
+ "WEBCORE_INITIALIZED_MSG_ID", // = 16;
+ "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17;
+ "DID_FIRST_LAYOUT_MSG_ID", // = 18;
+ "RECOMPUTE_FOCUS_MSG_ID", // = 19;
+ "NOTIFY_FOCUS_SET_MSG_ID", // = 20;
+ "MARK_NODE_INVALID_ID", // = 21;
+ "UPDATE_CLIPBOARD", // = 22;
+ "LONG_PRESS_ENTER", // = 23;
+ "PREVENT_TOUCH_ID", // = 24;
+ "WEBCORE_NEED_TOUCH_EVENTS", // = 25;
+ "INVAL_RECT_MSG_ID" // = 26;
+ };
// width which view is considered to be fully zoomed out
- static final int ZOOM_OUT_WIDTH = 1024;
+ static final int ZOOM_OUT_WIDTH = 1008;
- private static final float DEFAULT_MAX_ZOOM_SCALE = 4;
+ private static final float DEFAULT_MAX_ZOOM_SCALE = 4.0f;
private static final float DEFAULT_MIN_ZOOM_SCALE = 0.25f;
// scale limit, which can be set through viewport meta tag in the web page
private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
+ private boolean mMinZoomScaleFixed = false;
// initial scale in percent. 0 means using default.
private int mInitialScale = 0;
+ // set to true temporarily while the zoom control is being dragged
+ private boolean mPreviewZoomOnly = false;
+
// computed scale and inverse, from mZoomWidth.
private float mActualScale = 1;
private float mInvActualScale = 1;
@@ -496,6 +470,13 @@ public class WebView extends AbsoluteLayout
// Used to match key downs and key ups
private boolean mGotKeyDown;
+ /* package */ static boolean mLogEvent = true;
+ private static final int EVENT_LOG_ZOOM_LEVEL_CHANGE = 70101;
+ private static final int EVENT_LOG_DOUBLE_TAP_DURATION = 70102;
+
+ // for event log
+ private long mLastTouchUpTime = 0;
+
/**
* URI scheme for telephone number
*/
@@ -591,6 +572,41 @@ public class WebView extends AbsoluteLayout
}
}
+ private ZoomButtonsController mZoomButtonsController;
+ private ImageView mZoomOverviewButton;
+ private ImageView mZoomFitPageButton;
+
+ // These keep track of the center point of the zoom. They are used to
+ // determine the point around which we should zoom.
+ private float mZoomCenterX;
+ private float mZoomCenterY;
+
+ private ZoomButtonsController.OnZoomListener mZoomListener =
+ new ZoomButtonsController.OnZoomListener() {
+
+ public void onCenter(int x, int y) {
+ // Don't translate when the control is invoked, hence we do nothing
+ // in this callback
+ }
+
+ public void onVisibilityChanged(boolean visible) {
+ if (visible) {
+ switchOutDrawHistory();
+ updateZoomButtonsEnabled();
+ }
+ }
+
+ public void onZoom(boolean zoomIn) {
+ if (zoomIn) {
+ zoomIn();
+ } else {
+ zoomOut();
+ }
+
+ updateZoomButtonsEnabled();
+ }
+ };
+
/**
* Construct a new WebView with a Context object.
* @param context A Context object used to access application assets.
@@ -618,11 +634,6 @@ public class WebView extends AbsoluteLayout
super(context, attrs, defStyle);
init();
- TypedArray a = context.obtainStyledAttributes(
- com.android.internal.R.styleable.View);
- initializeScrollbars(a);
- a.recycle();
-
mCallbackProxy = new CallbackProxy(context, this);
mWebViewCore = new WebViewCore(context, this, mCallbackProxy);
mDatabase = WebViewDatabase.getInstance(context);
@@ -632,6 +643,49 @@ public class WebView extends AbsoluteLayout
mFocusData.mX = 0;
mFocusData.mY = 0;
mScroller = new Scroller(context);
+
+ initZoomController(context);
+ }
+
+ private void initZoomController(Context context) {
+ // Create the buttons controller
+ mZoomButtonsController = new ZoomButtonsController(this);
+ mZoomButtonsController.setOnZoomListener(mZoomListener);
+
+ // Create the accessory buttons
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ ViewGroup container = mZoomButtonsController.getContainer();
+ inflater.inflate(com.android.internal.R.layout.zoom_browser_accessory_buttons, container);
+ mZoomOverviewButton =
+ (ImageView) container.findViewById(com.android.internal.R.id.zoom_page_overview);
+ mZoomOverviewButton.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ mZoomButtonsController.setVisible(false);
+ zoomScrollOut();
+ if (mLogEvent) {
+ Checkin.updateStats(mContext.getContentResolver(),
+ Checkin.Stats.Tag.BROWSER_ZOOM_OVERVIEW, 1, 0.0);
+ }
+ }
+ });
+ mZoomFitPageButton =
+ (ImageView) container.findViewById(com.android.internal.R.id.zoom_fit_page);
+ mZoomFitPageButton.setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ zoomWithPreview(1f);
+ updateZoomButtonsEnabled();
+ }
+ });
+ }
+
+ private void updateZoomButtonsEnabled() {
+ mZoomButtonsController.setZoomInEnabled(mActualScale < mMaxZoomScale);
+ mZoomButtonsController.setZoomOutEnabled(mActualScale > mMinZoomScale);
+ mZoomFitPageButton.setEnabled(mActualScale != 1);
+ mZoomOverviewButton.setEnabled(canZoomScrollOut());
}
private void init() {
@@ -641,9 +695,13 @@ public class WebView extends AbsoluteLayout
setClickable(true);
setLongClickable(true);
- // should be conditional on if we're in the browser activity?
- setHorizontalScrollBarEnabled(true);
- setVerticalScrollBarEnabled(true);
+ final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ mTouchSlopSquare = slop * slop;
+ mMinLockSnapReverseDistance = slop;
+ // use one line height, 16 based on our current default font, for how
+ // far we allow a touch be away from the edge of a link
+ mNavSlop = (int) (16 * getContext().getResources()
+ .getDisplayMetrics().density);
}
/* package */ boolean onSavePassword(String schemePlusHost, String username,
@@ -748,7 +806,7 @@ public class WebView extends AbsoluteLayout
* to.
*/
private int getViewWidth() {
- if (mOverlayVerticalScrollbar) {
+ if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
return getWidth();
} else {
return getWidth() - getVerticalScrollbarWidth();
@@ -760,7 +818,7 @@ public class WebView extends AbsoluteLayout
* to.
*/
private int getViewHeight() {
- if (mOverlayHorizontalScrollbar) {
+ if (!isHorizontalScrollBarEnabled() || mOverlayHorizontalScrollbar) {
return getHeight();
} else {
return getHeight() - getHorizontalScrollbarHeight();
@@ -832,7 +890,6 @@ public class WebView extends AbsoluteLayout
*/
public void destroy() {
clearTextEntry();
- getViewTreeObserver().removeOnGlobalFocusChangeListener(this);
if (mWebViewCore != null) {
// Set the handlers to null before destroying WebViewCore so no
// more messages will be posted.
@@ -1265,7 +1322,7 @@ public class WebView extends AbsoluteLayout
nativeClearFocus(-1, -1);
if (top) {
// go to the top of the document
- return pinScrollTo(mScrollX, 0, true);
+ return pinScrollTo(mScrollX, 0, true, 0);
}
// Page up
int h = getHeight();
@@ -1276,7 +1333,7 @@ public class WebView extends AbsoluteLayout
y = -h / 2;
}
mUserScroll = true;
- return mScroller.isFinished() ? pinScrollBy(0, y, true)
+ return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
: extendScroll(y);
}
@@ -1291,7 +1348,7 @@ public class WebView extends AbsoluteLayout
}
nativeClearFocus(-1, -1);
if (bottom) {
- return pinScrollTo(mScrollX, mContentHeight, true);
+ return pinScrollTo(mScrollX, mContentHeight, true, 0);
}
// Page down.
int h = getHeight();
@@ -1302,7 +1359,7 @@ public class WebView extends AbsoluteLayout
y = h / 2;
}
mUserScroll = true;
- return mScroller.isFinished() ? pinScrollBy(0, y, true)
+ return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
: extendScroll(y);
}
@@ -1375,13 +1432,7 @@ public class WebView extends AbsoluteLayout
return;
}
clearTextEntry();
- ExtendedZoomControls zoomControls = (ExtendedZoomControls)
- getZoomControls();
- zoomControls.show(true, canZoomScrollOut());
- zoomControls.requestFocus();
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- mPrivateHandler.postDelayed(mZoomControlRunnable,
- ZOOM_CONTROLS_TIMEOUT);
+ mZoomButtonsController.setVisible(true);
}
/**
@@ -1493,7 +1544,8 @@ public class WebView extends AbsoluteLayout
private static int pinLoc(int x, int viewMax, int docMax) {
// Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
if (docMax < viewMax) { // the doc has room on the sides for "blank"
- x = -(viewMax - docMax) >> 1;
+ // pin the short document to the top/left of the screen
+ x = 0;
// Log.d(LOGTAG, "--- center " + x);
} else if (x < 0) {
x = 0;
@@ -1585,12 +1637,14 @@ public class WebView extends AbsoluteLayout
if (mDrawHistory) {
// If history Picture is drawn, don't update scroll. They will
// be updated when we get out of that mode.
- if (scale != mActualScale) {
+ if (scale != mActualScale && !mPreviewZoomOnly) {
mCallbackProxy.onScaleChanged(mActualScale, scale);
}
mActualScale = scale;
mInvActualScale = 1 / scale;
- sendViewSizeZoom();
+ if (!mPreviewZoomOnly) {
+ sendViewSizeZoom();
+ }
} else {
// update our scroll so we don't appear to jump
// i.e. keep the center of the doc in the center of the view
@@ -1598,11 +1652,11 @@ public class WebView extends AbsoluteLayout
int oldX = mScrollX;
int oldY = mScrollY;
float ratio = scale * mInvActualScale; // old inverse
- float sx = ratio * oldX + (ratio - 1) * getViewWidth() * 0.5f;
- float sy = ratio * oldY + (ratio - 1) * getViewHeight() * 0.5f;
+ float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
+ float sy = ratio * oldY + (ratio - 1) * mZoomCenterY;
// now update our new scale and inverse
- if (scale != mActualScale) {
+ if (scale != mActualScale && !mPreviewZoomOnly) {
mCallbackProxy.onScaleChanged(mActualScale, scale);
}
mActualScale = scale;
@@ -1614,8 +1668,10 @@ public class WebView extends AbsoluteLayout
mScrollX = pinLocX(Math.round(sx));
mScrollY = pinLocY(Math.round(sy));
- sendViewSizeZoom();
- sendOurVisibleRect();
+ if (!mPreviewZoomOnly) {
+ sendViewSizeZoom();
+ sendOurVisibleRect();
+ }
}
}
}
@@ -1817,8 +1873,8 @@ public class WebView extends AbsoluteLayout
*/
public void clearFormData() {
if (inEditingMode()) {
- ArrayAdapter<String> adapter = null;
- mTextEntry.setAdapter(adapter);
+ AutoCompleteAdapter adapter = null;
+ mTextEntry.setAdapterCustom(adapter);
}
}
@@ -1913,7 +1969,7 @@ public class WebView extends AbsoluteLayout
nativeSetFindIsDown();
// Now that the dialog has been removed, ensure that we scroll to a
// location that is not beyond the end of the page.
- pinScrollTo(mScrollX, mScrollY, false);
+ pinScrollTo(mScrollX, mScrollY, false, 0);
invalidate();
}
@@ -1956,13 +2012,13 @@ public class WebView extends AbsoluteLayout
// helper to pin the scrollBy parameters (already in view coordinates)
// returns true if the scroll was changed
- private boolean pinScrollBy(int dx, int dy, boolean animate) {
- return pinScrollTo(mScrollX + dx, mScrollY + dy, animate);
+ private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
+ return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration);
}
// helper to pin the scrollTo parameters (already in view coordinates)
// returns true if the scroll was changed
- private boolean pinScrollTo(int x, int y, boolean animate) {
+ private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
x = pinLocX(x);
y = pinLocY(y);
int dx = x - mScrollX;
@@ -1976,7 +2032,7 @@ public class WebView extends AbsoluteLayout
// Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
mScroller.startScroll(mScrollX, mScrollY, dx, dy,
- computeDuration(dx, dy));
+ animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
invalidate();
} else {
mScroller.abortAnimation(); // just in case
@@ -1987,7 +2043,7 @@ public class WebView extends AbsoluteLayout
// Scale from content to view coordinates, and pin.
// Also called by jni webview.cpp
- private void setContentScrollBy(int cx, int cy) {
+ private void setContentScrollBy(int cx, int cy, boolean animate) {
if (mDrawHistory) {
// disallow WebView to change the scroll position as History Picture
// is used in the view system.
@@ -2011,10 +2067,10 @@ public class WebView extends AbsoluteLayout
// vertical scroll?
// Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
if (cy == 0 && cx != 0) {
- pinScrollBy(cx, 0, true);
+ pinScrollBy(cx, 0, animate, 0);
}
} else {
- pinScrollBy(cx, cy, true);
+ pinScrollBy(cx, cy, animate, 0);
}
}
@@ -2034,7 +2090,7 @@ public class WebView extends AbsoluteLayout
int vy = contentToView(cy);
// Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" +
// vx + " " + vy + "]");
- pinScrollTo(vx, vy, false);
+ pinScrollTo(vx, vy, false, 0);
if (mScrollX != vx || mScrollY != vy) {
return true;
} else {
@@ -2051,7 +2107,7 @@ public class WebView extends AbsoluteLayout
}
int vx = contentToView(cx);
int vy = contentToView(cy);
- pinScrollTo(vx, vy, true);
+ pinScrollTo(vx, vy, true, 0);
}
/**
@@ -2140,8 +2196,19 @@ public class WebView extends AbsoluteLayout
/**
* Use this function to bind an object to Javascript so that the
* methods can be accessed from Javascript.
- * IMPORTANT, the object that is bound runs in another thread and
- * not in the thread that it was constructed in.
+ * <p><strong>IMPORTANT:</strong>
+ * <ul>
+ * <li> Using addJavascriptInterface() allows JavaScript to control your
+ * application. This can be a very useful feature or a dangerous security
+ * issue. When the HTML in the WebView is untrustworthy (for example, part
+ * or all of the HTML is provided by some person or process), then an
+ * attacker could inject HTML that will execute your code and possibly any
+ * code of the attacker's choosing.<br>
+ * Do not use addJavascriptInterface() unless all of the HTML in this
+ * WebView was written by you.</li>
+ * <li> The Java object that is bound runs in another thread and not in
+ * the thread that it was constructed in.</li>
+ * </ul></p>
* @param obj The class instance to bind to Javascript
* @param interfaceName The name to used to expose the class in Javascript
*/
@@ -2224,7 +2291,8 @@ public class WebView extends AbsoluteLayout
// state.
// If mNativeClass is 0, we should not reach here, so we do not
// need to check it again.
- nativeRecordButtons(mTouchMode == TOUCH_SHORTPRESS_START_MODE
+ nativeRecordButtons(hasFocus() && hasWindowFocus(),
+ mTouchMode == TOUCH_SHORTPRESS_START_MODE
|| mTrackballDown || mGotEnterDown, false);
drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing);
}
@@ -2273,10 +2341,12 @@ public class WebView extends AbsoluteLayout
invalidate();
} else {
zoomScale = mZoomScale;
+ // set mZoomScale to be 0 as we have done animation
+ mZoomScale = 0;
}
float scale = (mActualScale - zoomScale) * mInvActualScale;
- float tx = scale * ((getLeft() + getRight()) * 0.5f + mScrollX);
- float ty = scale * ((getTop() + getBottom()) * 0.5f + mScrollY);
+ float tx = scale * (mZoomCenterX + mScrollX);
+ float ty = scale * (mZoomCenterY + mScrollY);
// this block pins the translate to "legal" bounds. This makes the
// animation a bit non-obvious, but it means we won't pop when the
@@ -2566,11 +2636,8 @@ public class WebView extends AbsoluteLayout
private void startZoomScrollOut() {
setHorizontalScrollBarEnabled(false);
setVerticalScrollBarEnabled(false);
- if (mZoomControlRunnable != null) {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- }
- if (mZoomControls != null) {
- mZoomControls.hide();
+ if (mZoomButtonsController.isVisible()) {
+ mZoomButtonsController.setVisible(false);
}
int width = getViewWidth();
int height = getViewHeight();
@@ -2773,6 +2840,7 @@ public class WebView extends AbsoluteLayout
* @param end End of selection to delete.
*/
/* package */ void deleteSelection(int start, int end) {
+ mTextGeneration++;
mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end,
new WebViewCore.FocusData(mFocusData));
}
@@ -2793,12 +2861,20 @@ public class WebView extends AbsoluteLayout
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mTextEntry, 0);
+ mTextEntry.enableScrollOnScreen(true);
+ // Now we need to fake a touch event to place the cursor where the
+ // user touched.
+ AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams)
+ mTextEntry.getLayoutParams();
+ if (lp != null) {
+ // Take the last touch and adjust for the location of the
+ // TextDialog.
+ float x = mLastTouchX + (float) (mScrollX - lp.x);
+ float y = mLastTouchY + (float) (mScrollY - lp.y);
+ mTextEntry.fakeTouchEvent(x, y);
+ }
}
- // Used to register the global focus change listener one time to avoid
- // multiple references to WebView
- private boolean mGlobalFocusChangeListenerAdded;
-
private void updateTextEntry() {
if (mTextEntry == null) {
mTextEntry = new TextDialog(mContext, WebView.this);
@@ -2831,9 +2907,6 @@ public class WebView extends AbsoluteLayout
// Note that sendOurVisibleRect calls viewToContent, so the coordinates
// should be in content coordinates.
if (!Rect.intersects(node.mBounds, visibleRect)) {
- if (alreadyThere) {
- mTextEntry.remove();
- }
// Node is not on screen, so do not bother.
return;
}
@@ -2886,21 +2959,32 @@ public class WebView extends AbsoluteLayout
}
}
mTextEntry.setMaxLength(maxLength);
- ArrayAdapter<String> adapter = null;
- mTextEntry.setAdapter(adapter);
- mTextEntry.setInPassword(node.mIsPassword);
+ AutoCompleteAdapter adapter = null;
+ mTextEntry.setAdapterCustom(adapter);
mTextEntry.setSingleLine(node.mIsTextField);
+ mTextEntry.setInPassword(node.mIsPassword);
if (null == text) {
mTextEntry.setText("", 0, 0);
} else {
- mTextEntry.setText(text, 0, text.length());
+ // Change to true to enable the old style behavior, where
+ // entering a textfield/textarea always set the selection to the
+ // whole field. This was desirable for the case where the user
+ // intends to scroll past the field using the trackball.
+ // However, it causes a problem when replying to emails - the
+ // user expects the cursor to be at the beginning of the
+ // textarea. Testing out a new behavior, where textfields set
+ // selection at the end, and textareas at the beginning.
+ if (false) {
+ mTextEntry.setText(text, 0, text.length());
+ } else if (node.mIsTextField) {
+ int length = text.length();
+ mTextEntry.setText(text, length, length);
+ } else {
+ mTextEntry.setText(text, 0, 0);
+ }
}
mTextEntry.requestFocus();
}
- if (!mGlobalFocusChangeListenerAdded) {
- getViewTreeObserver().addOnGlobalFocusChangeListener(this);
- mGlobalFocusChangeListenerAdded = true;
- }
}
private class UpdateTextEntryAdapter implements Runnable {
@@ -2980,6 +3064,9 @@ public class WebView extends AbsoluteLayout
mSelectX = mScrollX + (int) mLastTouchX;
mSelectY = mScrollY + (int) mLastTouchY;
}
+ int contentX = viewToContent((int) mLastTouchX + mScrollX);
+ int contentY = viewToContent((int) mLastTouchY + mScrollY);
+ nativeClearFocus(contentX, contentY);
}
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
@@ -3001,7 +3088,9 @@ public class WebView extends AbsoluteLayout
mGotEnterDown = true;
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT);
- nativeRecordButtons(true, true);
+ // Already checked mNativeClass, so we do not need to check it
+ // again.
+ nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
return true;
}
// Bubble up the key event as WebView doesn't handle it
@@ -3031,8 +3120,8 @@ public class WebView extends AbsoluteLayout
(keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
break;
case KeyEvent.KEYCODE_9:
- debugDump();
- break;
+ nativeInstrumentReport();
+ return true;
}
}
@@ -3167,7 +3256,11 @@ public class WebView extends AbsoluteLayout
* @hide
*/
public void emulateShiftHeld() {
+ mExtendSelection = false;
mShiftIsPressed = true;
+ int contentX = viewToContent((int) mLastTouchX + mScrollX);
+ int contentY = viewToContent((int) mLastTouchY + mScrollY);
+ nativeClearFocus(contentX, contentY);
}
private boolean commitCopy() {
@@ -3182,6 +3275,7 @@ public class WebView extends AbsoluteLayout
mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
copiedSomething = true;
}
+ mExtendSelection = false;
}
mShiftIsPressed = false;
if (mTouchMode == TOUCH_SELECT_MODE) {
@@ -3210,29 +3304,31 @@ public class WebView extends AbsoluteLayout
ViewGroup p = (ViewGroup) parent;
p.setOnHierarchyChangeListener(null);
}
+
+ // Clean up the zoom controller
+ mZoomButtonsController.setVisible(false);
+ ZoomButtonsController.finishZoomTutorial(mContext, false);
}
// Implementation for OnHierarchyChangeListener
public void onChildViewAdded(View parent, View child) {}
- // When we are removed, remove this as a global focus change listener.
public void onChildViewRemoved(View p, View child) {
if (child == this) {
- p.getViewTreeObserver().removeOnGlobalFocusChangeListener(this);
- mGlobalFocusChangeListenerAdded = false;
+ if (inEditingMode()) {
+ clearTextEntry();
+ mNeedsUpdateTextEntry = true;
+ }
}
}
-
- // Use this to know when the textview has lost focus, and something other
- // than the webview has gained focus. Stop drawing the focus ring, remove
- // the TextView, and set a flag to put it back when we regain focus.
+
+ /**
+ * @deprecated WebView should not have implemented
+ * ViewTreeObserver.OnGlobalFocusChangeListener. This method
+ * does nothing now.
+ */
+ @Deprecated
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
- if (oldFocus == mTextEntry && newFocus != this) {
- mDrawFocusRing = false;
- mTextEntry.updateCachedTextfield();
- removeView(mTextEntry);
- mNeedsUpdateTextEntry = true;
- }
}
// To avoid drawing the focus ring, and remove the TextView when our window
@@ -3248,21 +3344,33 @@ public class WebView extends AbsoluteLayout
if (mNeedsUpdateTextEntry) {
updateTextEntry();
}
+ if (mNativeClass != 0) {
+ nativeRecordButtons(true, false, true);
+ }
} else {
// If our window gained focus, but we do not have it, do not
// draw the focus ring.
mDrawFocusRing = false;
+ // We do not call nativeRecordButtons here because we assume
+ // that when we lost focus, or window focus, it got called with
+ // false for the first parameter
}
} else {
- // If our window has lost focus, stop drawing the focus ring, and
- // remove the TextView if displayed, and flag it to be added when
- // we regain focus.
- mDrawFocusRing = false;
+ if (!mZoomButtonsController.isVisible()) {
+ /*
+ * The zoom controls come in their own window, so our window
+ * loses focus. Our policy is to not draw the focus ring if
+ * our window is not focused, but this is an exception since
+ * the user can still navigate the web page with the zoom
+ * controls showing.
+ */
+ // If our window has lost focus, stop drawing the focus ring
+ mDrawFocusRing = false;
+ }
mGotKeyDown = false;
mShiftIsPressed = false;
- if (inEditingMode()) {
- clearTextEntry();
- mNeedsUpdateTextEntry = true;
+ if (mNativeClass != 0) {
+ nativeRecordButtons(false, false, true);
}
}
invalidate();
@@ -3284,12 +3392,22 @@ public class WebView extends AbsoluteLayout
updateTextEntry();
mNeedsUpdateTextEntry = false;
}
+ if (mNativeClass != 0) {
+ nativeRecordButtons(true, false, true);
+ }
+ //} else {
+ // The WebView has gained focus while we do not have
+ // windowfocus. When our window lost focus, we should have
+ // called nativeRecordButtons(false...)
}
} else {
// When we lost focus, unless focus went to the TextView (which is
// true if we are in editing mode), stop drawing the focus ring.
if (!inEditingMode()) {
mDrawFocusRing = false;
+ if (mNativeClass != 0) {
+ nativeRecordButtons(false, false, true);
+ }
}
mGotKeyDown = false;
}
@@ -3300,6 +3418,15 @@ public class WebView extends AbsoluteLayout
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
+ // Center zooming to the center of the screen.
+ mZoomCenterX = getViewWidth() * .5f;
+ mZoomCenterY = getViewHeight() * .5f;
+
+ // update mMinZoomScale if the minimum zoom scale is not fixed
+ if (!mMinZoomScaleFixed) {
+ mMinZoomScale = (float) getViewWidth()
+ / Math.max(ZOOM_OUT_WIDTH, mContentWidth);
+ }
// we always force, in case our height changed, in which case we still
// want to send the notification over to webkit
@@ -3346,9 +3473,8 @@ public class WebView extends AbsoluteLayout
// 3. If there is a same direction back and forth, lock it.
// adjustable parameters
+ private int mMinLockSnapReverseDistance;
private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
- private static final int MIN_LOCK_SNAP_REVERSE_DISTANCE =
- ViewConfiguration.getTouchSlop();
private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
@Override
@@ -3387,11 +3513,14 @@ public class WebView extends AbsoluteLayout
WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
ted.mAction = action;
ted.mX = viewToContent((int) x + mScrollX);
- ted.mY = viewToContent((int) y + mScrollY);;
+ ted.mY = viewToContent((int) y + mScrollY);
mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
mLastSentTouchTime = eventTime;
}
+ int deltaX = (int) (mLastTouchX - x);
+ int deltaY = (int) (mLastTouchY - y);
+
switch (action) {
case MotionEvent.ACTION_DOWN: {
if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN
@@ -3418,7 +3547,13 @@ public class WebView extends AbsoluteLayout
mTouchSelection = mExtendSelection = true;
} else {
mTouchMode = TOUCH_INIT_MODE;
+ mPreventDrag = mForwardTouchEvents;
+ if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
+ EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION,
+ (eventTime - mLastTouchUpTime), eventTime);
+ }
}
+ // Trigger the link
if (mTouchMode == TOUCH_INIT_MODE) {
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
@@ -3445,9 +3580,6 @@ public class WebView extends AbsoluteLayout
}
mVelocityTracker.addMovement(ev);
- int deltaX = (int) (mLastTouchX - x);
- int deltaY = (int) (mLastTouchY - y);
-
if (mTouchMode != TOUCH_DRAG_MODE) {
if (mTouchMode == TOUCH_SELECT_MODE) {
mSelectX = mScrollX + (int) x;
@@ -3460,8 +3592,8 @@ public class WebView extends AbsoluteLayout
invalidate();
break;
}
- if ((deltaX * deltaX + deltaY * deltaY)
- < TOUCH_SLOP_SQUARE) {
+ if (mPreventDrag || (deltaX * deltaX + deltaY * deltaY)
+ < mTouchSlopSquare) {
break;
}
@@ -3496,6 +3628,12 @@ public class WebView extends AbsoluteLayout
mWebViewCore
.sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0);
}
+ if (getSettings().supportZoom()
+ && !mZoomButtonsController.isVisible()
+ && (canZoomScrollOut() ||
+ mMinZoomScale < mMaxZoomScale)) {
+ mZoomButtonsController.setVisible(true);
+ }
}
// do pan
@@ -3519,9 +3657,9 @@ public class WebView extends AbsoluteLayout
// reverse direction means lock in the snap mode
if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
((mSnapPositive &&
- deltaX < -MIN_LOCK_SNAP_REVERSE_DISTANCE)
+ deltaX < -mMinLockSnapReverseDistance)
|| (!mSnapPositive &&
- deltaX > MIN_LOCK_SNAP_REVERSE_DISTANCE))) {
+ deltaX > mMinLockSnapReverseDistance))) {
mSnapScrollMode = SNAP_X_LOCK;
}
} else {
@@ -3533,9 +3671,9 @@ public class WebView extends AbsoluteLayout
// reverse direction means lock in the snap mode
if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
((mSnapPositive &&
- deltaY < -MIN_LOCK_SNAP_REVERSE_DISTANCE)
+ deltaY < -mMinLockSnapReverseDistance)
|| (!mSnapPositive &&
- deltaY > MIN_LOCK_SNAP_REVERSE_DISTANCE))) {
+ deltaY > mMinLockSnapReverseDistance))) {
mSnapScrollMode = SNAP_Y_LOCK;
}
}
@@ -3557,18 +3695,6 @@ public class WebView extends AbsoluteLayout
mLastTouchTime = eventTime;
mUserScroll = true;
}
-
- boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
- boolean showMagnify = canZoomScrollOut();
- if (mZoomControls != null && (showPlusMinus || showMagnify)) {
- if (mZoomControls.getVisibility() == View.VISIBLE) {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- } else {
- mZoomControls.show(showPlusMinus, showMagnify);
- }
- mPrivateHandler.postDelayed(mZoomControlRunnable,
- ZOOM_CONTROLS_TIMEOUT);
- }
if (done) {
// return false to indicate that we can't pan out of the
// view space
@@ -3577,22 +3703,19 @@ public class WebView extends AbsoluteLayout
break;
}
case MotionEvent.ACTION_UP: {
+ mLastTouchUpTime = eventTime;
switch (mTouchMode) {
case TOUCH_INIT_MODE: // tap
+ case TOUCH_SHORTPRESS_START_MODE:
+ case TOUCH_SHORTPRESS_MODE:
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- if (getSettings().supportZoom()) {
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(RELEASE_SINGLE_TAP),
- DOUBLE_TAP_TIMEOUT);
- } else {
- // do short press now
- mTouchMode = TOUCH_DONE_MODE;
- doShortPress();
- }
+ mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ mTouchMode = TOUCH_DONE_MODE;
+ doShortPress();
break;
case TOUCH_SELECT_MODE:
commitCopy();
- mTouchSelection = mExtendSelection = false;
+ mTouchSelection = false;
break;
case SCROLL_ZOOM_ANIMATION_IN:
case SCROLL_ZOOM_ANIMATION_OUT:
@@ -3616,28 +3739,6 @@ public class WebView extends AbsoluteLayout
invalidate();
}
break;
- case TOUCH_SHORTPRESS_START_MODE:
- case TOUCH_SHORTPRESS_MODE: {
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- if (eventTime - mLastTouchTime < TAP_TIMEOUT
- && getSettings().supportZoom()) {
- // Note: window manager will not release ACTION_UP
- // until all the previous action events are
- // returned. If GC happens, it can cause
- // SWITCH_TO_SHORTPRESS message fired before
- // ACTION_UP sent even time stamp of ACTION_UP is
- // less than the tap time out. We need to treat this
- // as tap instead of short press.
- mTouchMode = TOUCH_INIT_MODE;
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(RELEASE_SINGLE_TAP),
- DOUBLE_TAP_TIMEOUT);
- } else {
- mTouchMode = TOUCH_DONE_MODE;
- doShortPress();
- }
- break;
- }
case TOUCH_DRAG_MODE:
// if the user waits a while w/o moving before the
// up, we don't want to do a fling
@@ -3678,7 +3779,6 @@ public class WebView extends AbsoluteLayout
}
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
mTouchMode = TOUCH_DONE_MODE;
int contentX = viewToContent((int) mLastTouchX + mScrollX);
int contentY = viewToContent((int) mLastTouchY + mScrollY);
@@ -3705,6 +3805,7 @@ public class WebView extends AbsoluteLayout
private static final int TRACKBALL_WAIT = 100;
private static final int TRACKBALL_SCALE = 400;
private static final int TRACKBALL_SCROLL_COUNT = 5;
+ private static final int TRACKBALL_MOVE_COUNT = 10;
private static final int TRACKBALL_MULTIPLIER = 3;
private static final int SELECT_CURSOR_OFFSET = 16;
private int mSelectX = 0;
@@ -3740,7 +3841,7 @@ public class WebView extends AbsoluteLayout
mPrivateHandler.removeMessages(SWITCH_TO_ENTER);
mTrackballDown = true;
if (mNativeClass != 0) {
- nativeRecordButtons(true, true);
+ nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
}
if (time - mLastFocusTime <= TRACKBALL_TIMEOUT
&& !mLastFocusBounds.equals(nativeGetFocusRingBounds())) {
@@ -3751,6 +3852,7 @@ public class WebView extends AbsoluteLayout
+ " time=" + time
+ " mLastFocusTime=" + mLastFocusTime);
}
+ if (isInTouchMode()) requestFocusFromTouch();
return false; // let common code in onKeyDown at it
}
if (ev.getAction() == MotionEvent.ACTION_UP) {
@@ -3759,7 +3861,11 @@ public class WebView extends AbsoluteLayout
mTrackballDown = false;
mTrackballUpTime = time;
if (mShiftIsPressed) {
- mExtendSelection = true;
+ if (mExtendSelection) {
+ commitCopy();
+ } else {
+ mExtendSelection = true;
+ }
}
if (LOGV_ENABLED) {
Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
@@ -3836,7 +3942,7 @@ public class WebView extends AbsoluteLayout
int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET
: mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
: 0;
- pinScrollBy(scrollX, scrollY, true);
+ pinScrollBy(scrollX, scrollY, true, 0);
Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1);
requestRectangleOnScreen(select);
invalidate();
@@ -3942,6 +4048,7 @@ public class WebView extends AbsoluteLayout
KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
KeyEvent.KEYCODE_DPAD_RIGHT;
+ count = Math.min(count, TRACKBALL_MOVE_COUNT);
if (LOGV_ENABLED) {
Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
+ " count=" + count
@@ -3971,7 +4078,7 @@ public class WebView extends AbsoluteLayout
yMove = 0;
}
if (xMove != 0 || yMove != 0) {
- pinScrollBy(xMove, yMove, true);
+ pinScrollBy(xMove, yMove, true, 0);
}
mUserScroll = true;
}
@@ -4044,43 +4151,30 @@ public class WebView extends AbsoluteLayout
}
}
+ // TODO: deprecate
/**
* Returns a view containing zoom controls i.e. +/- buttons. The caller is
* in charge of installing this view to the view hierarchy. This view will
* become visible when the user starts scrolling via touch and fade away if
* the user does not interact with it.
+ * <p/>
+ * From 1.5, WebView change to use ZoomButtonsController. This will return
+ * an invisible dummy view for backwards compatibility.
*/
public View getZoomControls() {
- if (!getSettings().supportZoom()) {
- Log.w(LOGTAG, "This WebView doesn't support zoom.");
- return null;
- }
- if (mZoomControls == null) {
- mZoomControls = createZoomControls();
-
- /*
- * need to be set to VISIBLE first so that getMeasuredHeight() in
- * {@link #onSizeChanged()} can return the measured value for proper
- * layout.
- */
- mZoomControls.setVisibility(View.VISIBLE);
- mZoomControlRunnable = new Runnable() {
- public void run() {
-
- /* Don't dismiss the controls if the user has
- * focus on them. Wait and check again later.
- */
- if (!mZoomControls.hasFocus()) {
- mZoomControls.hide();
- } else {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- mPrivateHandler.postDelayed(mZoomControlRunnable,
- ZOOM_CONTROLS_TIMEOUT);
- }
- }
- };
- }
- return mZoomControls;
+ return mZoomButtonsController.getDummyZoomControls();
+ }
+
+ /**
+ * Gets the {@link ZoomButtonsController} which can be used to add
+ * additional buttons to the zoom controls window.
+ *
+ * @return The instance of {@link ZoomButtonsController} used by this class,
+ * or null if it is unavailable.
+ * @hide pending API council
+ */
+ public ZoomButtonsController getZoomButtonsController() {
+ return mZoomButtonsController;
}
/**
@@ -4103,38 +4197,6 @@ public class WebView extends AbsoluteLayout
return zoomWithPreview(mActualScale * 0.8f);
}
- private ExtendedZoomControls createZoomControls() {
- ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext
- , null);
- zoomControls.setOnZoomInClickListener(new OnClickListener() {
- public void onClick(View v) {
- // reset time out
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- mPrivateHandler.postDelayed(mZoomControlRunnable,
- ZOOM_CONTROLS_TIMEOUT);
- zoomIn();
- }
- });
- zoomControls.setOnZoomOutClickListener(new OnClickListener() {
- public void onClick(View v) {
- // reset time out
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- mPrivateHandler.postDelayed(mZoomControlRunnable,
- ZOOM_CONTROLS_TIMEOUT);
- zoomOut();
- }
- });
- zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() {
- public void onClick(View v) {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- mPrivateHandler.postDelayed(mZoomControlRunnable,
- ZOOM_CONTROLS_TIMEOUT);
- zoomScrollOut();
- }
- });
- return zoomControls;
- }
-
private void updateSelection() {
if (mNativeClass == 0) {
return;
@@ -4142,9 +4204,8 @@ public class WebView extends AbsoluteLayout
// mLastTouchX and mLastTouchY are the point in the current viewport
int contentX = viewToContent((int) mLastTouchX + mScrollX);
int contentY = viewToContent((int) mLastTouchY + mScrollY);
- int contentSize = ViewConfiguration.getTouchSlop();
- Rect rect = new Rect(contentX - contentSize, contentY - contentSize,
- contentX + contentSize, contentY + contentSize);
+ Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop,
+ contentX + mNavSlop, contentY + mNavSlop);
// If we were already focused on a textfield, update its cache.
if (inEditingMode()) {
mTextEntry.updateCachedTextfield();
@@ -4157,8 +4218,7 @@ public class WebView extends AbsoluteLayout
View v = mTextEntry;
int x = viewToContent((v.getLeft() + v.getRight()) >> 1);
int y = viewToContent((v.getTop() + v.getBottom()) >> 1);
- int contentSize = ViewConfiguration.getTouchSlop();
- nativeMotionUp(x, y, contentSize, true);
+ nativeMotionUp(x, y, mNavSlop, true);
}
}
@@ -4167,22 +4227,25 @@ public class WebView extends AbsoluteLayout
return;
}
switchOutDrawHistory();
- // call uiOverride to check whether it is a special node,
- // phone/email/address, which are not handled by WebKit
- if (nativeUpdateFocusNode()) {
- FocusNode node = mFocusNode;
- if (!node.mIsTextField && !node.mIsTextArea) {
- if (mCallbackProxy.uiOverrideUrlLoading(node.mText)) {
- return;
- }
- }
- playSoundEffect(SoundEffectConstants.CLICK);
- }
// mLastTouchX and mLastTouchY are the point in the current viewport
int contentX = viewToContent((int) mLastTouchX + mScrollX);
int contentY = viewToContent((int) mLastTouchY + mScrollY);
- int contentSize = ViewConfiguration.getTouchSlop();
- nativeMotionUp(contentX, contentY, contentSize, true);
+ if (nativeMotionUp(contentX, contentY, mNavSlop, true)) {
+ if (mLogEvent) {
+ Checkin.updateStats(mContext.getContentResolver(),
+ Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0);
+ }
+ }
+ if (nativeUpdateFocusNode() && !mFocusNode.mIsTextField
+ && !mFocusNode.mIsTextArea) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ }
+ }
+
+ // Called by JNI to handle a touch on a node representing an email address,
+ // address, or phone number
+ private void overrideLoading(String url) {
+ mCallbackProxy.uiOverrideUrlLoading(url);
}
@Override
@@ -4309,7 +4372,7 @@ public class WebView extends AbsoluteLayout
}
if ((scrollYDelta | scrollXDelta) != 0) {
- return pinScrollBy(scrollXDelta, scrollYDelta, !immediate);
+ return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
}
return false;
@@ -4322,6 +4385,7 @@ public class WebView extends AbsoluteLayout
arg.put("replace", replace);
arg.put("start", new Integer(newStart));
arg.put("end", new Integer(newEnd));
+ mTextGeneration++;
mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
}
@@ -4357,6 +4421,11 @@ public class WebView extends AbsoluteLayout
class PrivateHandler extends Handler {
@Override
public void handleMessage(Message msg) {
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
+ > INVAL_RECT_MSG_ID ? Integer.toString(msg.what)
+ : HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
+ }
switch (msg.what) {
case REMEMBER_PASSWORD: {
mDatabase.setUsernamePassword(
@@ -4385,11 +4454,6 @@ public class WebView extends AbsoluteLayout
updateTextEntry();
break;
}
- case RELEASE_SINGLE_TAP: {
- mTouchMode = TOUCH_DONE_MODE;
- doShortPress();
- break;
- }
case SWITCH_TO_ENTER:
if (LOGV_ENABLED) Log.v(LOGTAG, "SWITCH_TO_ENTER");
mTouchMode = TOUCH_DONE_MODE;
@@ -4398,7 +4462,7 @@ public class WebView extends AbsoluteLayout
, KeyEvent.KEYCODE_ENTER));
break;
case SCROLL_BY_MSG_ID:
- setContentScrollBy(msg.arg1, msg.arg2);
+ setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj);
break;
case SYNC_SCROLL_TO_MSG_ID:
if (mUserScroll) {
@@ -4436,6 +4500,10 @@ public class WebView extends AbsoluteLayout
0, 0);
}
}
+ if (!mMinZoomScaleFixed) {
+ mMinZoomScale = (float) getViewWidth()
+ / Math.max(ZOOM_OUT_WIDTH, draw.mWidthHeight.x);
+ }
// We update the layout (i.e. request a layout from the
// view system) if the last view size that we sent to
// WebCore matches the view size of the picture we just
@@ -4494,8 +4562,10 @@ public class WebView extends AbsoluteLayout
int minScale = (Integer) scaleLimit.get("minScale");
if (minScale == 0) {
mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
+ mMinZoomScaleFixed = false;
} else {
mMinZoomScale = (float) (minScale / 100.0);
+ mMinZoomScaleFixed = true;
}
int maxScale = (Integer) scaleLimit.get("maxScale");
if (maxScale == 0) {
@@ -4544,6 +4614,15 @@ public class WebView extends AbsoluteLayout
}
break;
case UPDATE_TEXT_ENTRY_MSG_ID:
+ // this is sent after finishing resize in WebViewCore. Make
+ // sure the text edit box is still on the screen.
+ boolean alreadyThere = inEditingMode();
+ if (alreadyThere && nativeUpdateFocusNode()) {
+ FocusNode node = mFocusNode;
+ if (node.mIsTextField || node.mIsTextArea) {
+ mTextEntry.bringIntoView();
+ }
+ }
updateTextEntry();
break;
case RECOMPUTE_FOCUS_MSG_ID:
@@ -4551,6 +4630,17 @@ public class WebView extends AbsoluteLayout
nativeRecomputeFocus();
}
break;
+ case INVAL_RECT_MSG_ID: {
+ Rect r = (Rect)msg.obj;
+ if (r == null) {
+ invalidate();
+ } else {
+ // we need to scale r from content into view coords,
+ // which viewInvalidate() does for us
+ viewInvalidate(r.left, r.top, r.right, r.bottom);
+ }
+ break;
+ }
case UPDATE_TEXT_ENTRY_ADAPTER:
HashMap data = (HashMap) msg.obj;
if (mTextEntry.isSameTextField(msg.arg1)) {
@@ -4595,12 +4685,12 @@ public class WebView extends AbsoluteLayout
break;
case PREVENT_TOUCH_ID:
- // update may have already been paused by touch; restore since
- // this effectively aborts touch and skips logic in touch up
- if (mTouchMode == TOUCH_DRAG_MODE) {
- WebViewCore.resumeUpdate(mWebViewCore);
+ if (msg.arg1 == MotionEvent.ACTION_DOWN) {
+ mPreventDrag = msg.arg2 == 1;
+ if (mPreventDrag) {
+ mTouchMode = TOUCH_DONE_MODE;
+ }
}
- mTouchMode = TOUCH_DONE_MODE;
break;
default:
@@ -4764,8 +4854,16 @@ public class WebView extends AbsoluteLayout
});
if (mSelection != -1) {
listView.setSelection(mSelection);
+ listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ listView.setItemChecked(mSelection, true);
}
}
+ dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ mWebViewCore.sendMessage(
+ EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
+ }
+ });
dialog.show();
}
}
@@ -4910,11 +5008,11 @@ public class WebView extends AbsoluteLayout
// FIXME: Necessary because ScrollView/ListView do not scroll left/right
int maxH = Math.min(viewFocus.right - visRect.right, maxXScroll);
if (maxH > 0) {
- pinScrollBy(maxH, 0, true);
+ pinScrollBy(maxH, 0, true, 0);
} else {
maxH = Math.max(viewFocus.left - visRect.left, -maxXScroll);
if (maxH < 0) {
- pinScrollBy(maxH, 0, true);
+ pinScrollBy(maxH, 0, true, 0);
}
}
if (mLastFocusBounds.isEmpty()) return keyHandled;
@@ -4967,8 +5065,10 @@ public class WebView extends AbsoluteLayout
private native boolean nativeUpdateFocusNode();
private native Rect nativeGetFocusRingBounds();
private native Rect nativeGetNavBounds();
+ private native void nativeInstrumentReport();
private native void nativeMarkNodeInvalid(int node);
- private native void nativeMotionUp(int x, int y, int slop, boolean isClick);
+ // return true if the page has been scrolled
+ private native boolean nativeMotionUp(int x, int y, int slop, boolean isClick);
// returns false if it handled the key
private native boolean nativeMoveFocus(int keyCode, int count,
boolean noScroll);
@@ -4976,8 +5076,8 @@ public class WebView extends AbsoluteLayout
private native void nativeRecomputeFocus();
// Like many other of our native methods, you must make sure that
// mNativeClass is not null before calling this method.
- private native void nativeRecordButtons(boolean pressed,
- boolean invalidate);
+ private native void nativeRecordButtons(boolean focused,
+ boolean pressed, boolean invalidate);
private native void nativeResetFocus();
private native void nativeResetNavClipBounds();
private native void nativeSelectBestAt(Rect rect);
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 323b44d..3e4daf7 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -317,7 +317,7 @@ final class WebViewCore {
should this be called nativeSetViewPortSize?
*/
private native void nativeSetSize(int width, int height, int screenWidth,
- float scale);
+ float scale, int realScreenWidth, int screenHeight);
private native int nativeGetContentMinPrefWidth();
@@ -330,8 +330,7 @@ final class WebViewCore {
String currentText, int keyCode, int keyValue, boolean down,
boolean cap, boolean fn, boolean sym);
- private native void nativeSaveDocumentState(int frame, int node, int x,
- int y);
+ private native void nativeSaveDocumentState(int frame);
private native void nativeSetFinalFocus(int framePtr, int nodePtr, int x,
int y, boolean block);
@@ -502,6 +501,51 @@ final class WebViewCore {
int mY;
}
+ static final String[] HandlerDebugString = {
+ "LOAD_URL", // = 100;
+ "STOP_LOADING", // = 101;
+ "RELOAD", // = 102;
+ "KEY_DOWN", // = 103;
+ "KEY_UP", // = 104;
+ "VIEW_SIZE_CHANGED", // = 105;
+ "GO_BACK_FORWARD", // = 106;
+ "SET_SCROLL_OFFSET", // = 107;
+ "RESTORE_STATE", // = 108;
+ "PAUSE_TIMERS", // = 109;
+ "RESUME_TIMERS", // = 110;
+ "CLEAR_CACHE", // = 111;
+ "CLEAR_HISTORY", // = 112;
+ "SET_SELECTION", // = 113;
+ "REPLACE_TEXT", // = 114;
+ "PASS_TO_JS", // = 115;
+ "SET_GLOBAL_BOUNDS", // = 116;
+ "UPDATE_CACHE_AND_TEXT_ENTRY", // = 117;
+ "CLICK", // = 118;
+ "119",
+ "DOC_HAS_IMAGES", // = 120;
+ "SET_SNAP_ANCHOR", // = 121;
+ "DELETE_SELECTION", // = 122;
+ "LISTBOX_CHOICES", // = 123;
+ "SINGLE_LISTBOX_CHOICE", // = 124;
+ "125",
+ "SET_BACKGROUND_COLOR", // = 126;
+ "UNBLOCK_FOCUS", // = 127;
+ "SAVE_DOCUMENT_STATE", // = 128;
+ "GET_SELECTION", // = 129;
+ "WEBKIT_DRAW", // = 130;
+ "SYNC_SCROLL", // = 131;
+ "REFRESH_PLUGINS", // = 132;
+ "SPLIT_PICTURE_SET", // = 133;
+ "CLEAR_CONTENT", // = 134;
+ "SET_FINAL_FOCUS", // = 135;
+ "SET_KIT_FOCUS", // = 136;
+ "REQUEST_FOCUS_HREF", // = 137;
+ "ADD_JS_INTERFACE", // = 138;
+ "LOAD_DATA", // = 139;
+ "TOUCH_UP", // = 140;
+ "TOUCH_EVENT", // = 141;
+ };
+
class EventHub {
// Message Ids
static final int LOAD_URL = 100;
@@ -596,6 +640,11 @@ final class WebViewCore {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, msg.what < LOAD_URL || msg.what
+ > TOUCH_EVENT ? Integer.toString(msg.what)
+ : HandlerDebugString[msg.what - LOAD_URL]);
+ }
switch (msg.what) {
case WEBKIT_DRAW:
webkitDraw();
@@ -777,8 +826,7 @@ final class WebViewCore {
case SAVE_DOCUMENT_STATE: {
FocusData fDat = (FocusData) msg.obj;
- nativeSaveDocumentState(fDat.mFrame, fDat.mNode,
- fDat.mX, fDat.mY);
+ nativeSaveDocumentState(fDat.mFrame);
break;
}
@@ -799,12 +847,11 @@ final class WebViewCore {
case TOUCH_EVENT: {
TouchEventData ted = (TouchEventData) msg.obj;
- if (nativeHandleTouchEvent(ted.mAction, ted.mX,
- ted.mY)) {
- Message.obtain(mWebView.mPrivateHandler,
- WebView.PREVENT_TOUCH_ID)
- .sendToTarget();
- }
+ Message.obtain(
+ mWebView.mPrivateHandler,
+ WebView.PREVENT_TOUCH_ID, ted.mAction,
+ nativeHandleTouchEvent(ted.mAction, ted.mX,
+ ted.mY) ? 1 : 0).sendToTarget();
break;
}
@@ -1137,6 +1184,10 @@ final class WebViewCore {
// notify webkit that our virtual view size changed size (after inv-zoom)
private void viewSizeChanged(int w, int h, float scale) {
if (LOGV_ENABLED) Log.v(LOGTAG, "CORE onSizeChanged");
+ if (w == 0) {
+ Log.w(LOGTAG, "skip viewSizeChanged as w is 0");
+ return;
+ }
if (mSettings.getUseWideViewPort()
&& (w < mViewportWidth || mViewportWidth == -1)) {
int width = mViewportWidth;
@@ -1160,9 +1211,10 @@ final class WebViewCore {
width = Math.max(w, nativeGetContentMinPrefWidth());
}
}
- nativeSetSize(width, Math.round((float) width * h / w), w, scale);
+ nativeSetSize(width, Math.round((float) width * h / w), w, scale,
+ w, h);
} else {
- nativeSetSize(w, h, w, scale);
+ nativeSetSize(w, h, w, scale, w, h);
}
// Remember the current width and height
boolean needInvalidate = (mCurrentViewWidth == 0);
@@ -1327,7 +1379,9 @@ final class WebViewCore {
for (int i = 0; i < size; i++) {
list.getItemAtIndex(i).inflate(mBrowserFrame.mNativeFrame);
}
+ mBrowserFrame.mLoadInitFromJava = true;
list.restoreIndex(mBrowserFrame.mNativeFrame, index);
+ mBrowserFrame.mLoadInitFromJava = false;
}
//-------------------------------------------------------------------------
@@ -1352,14 +1406,15 @@ final class WebViewCore {
}
// called by JNI
- private void contentScrollBy(int dx, int dy) {
+ private void contentScrollBy(int dx, int dy, boolean animate) {
if (!mBrowserFrame.firstLayoutDone()) {
// Will this happen? If yes, we need to do something here.
return;
}
if (mWebView != null) {
Message.obtain(mWebView.mPrivateHandler,
- WebView.SCROLL_BY_MSG_ID, dx, dy).sendToTarget();
+ WebView.SCROLL_BY_MSG_ID, dx, dy,
+ new Boolean(animate)).sendToTarget();
}
}
@@ -1434,10 +1489,15 @@ final class WebViewCore {
}
}
- // called by JNI
+ /* Called by JNI. The coordinates are in doc coordinates, so they need to
+ be scaled before they can be used by the view system, which happens
+ in WebView since it (and its thread) know the current scale factor.
+ */
private void sendViewInvalidate(int left, int top, int right, int bottom) {
if (mWebView != null) {
- mWebView.postInvalidate(left, top, right, bottom);
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.INVAL_RECT_MSG_ID,
+ new Rect(left, top, right, bottom)).sendToTarget();
}
}
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 96f3698..1004e30 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -531,33 +531,34 @@ public class WebViewDatabase {
* @param url The url
* @return CacheResult The CacheManager.CacheResult
*/
- @SuppressWarnings("deprecation")
CacheResult getCache(String url) {
if (url == null || mCacheDatabase == null) {
return null;
}
- CacheResult ret = null;
- final String s = "SELECT filepath, lastmodify, etag, expires, mimetype, encoding, httpstatus, location, contentlength FROM cache WHERE url = ";
- StringBuilder sb = new StringBuilder(256);
- sb.append(s);
- DatabaseUtils.appendEscapedSQLString(sb, url);
- Cursor cursor = mCacheDatabase.rawQuery(sb.toString(), null);
+ Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath, lastmodify, etag, expires, "
+ + "mimetype, encoding, httpstatus, location, contentlength "
+ + "FROM cache WHERE url = ?",
+ new String[] { url });
- if (cursor.moveToFirst()) {
- ret = new CacheResult();
- ret.localPath = cursor.getString(0);
- ret.lastModified = cursor.getString(1);
- ret.etag = cursor.getString(2);
- ret.expires = cursor.getLong(3);
- ret.mimeType = cursor.getString(4);
- ret.encoding = cursor.getString(5);
- ret.httpStatusCode = cursor.getInt(6);
- ret.location = cursor.getString(7);
- ret.contentLength = cursor.getLong(8);
+ try {
+ if (cursor.moveToFirst()) {
+ CacheResult ret = new CacheResult();
+ ret.localPath = cursor.getString(0);
+ ret.lastModified = cursor.getString(1);
+ ret.etag = cursor.getString(2);
+ ret.expires = cursor.getLong(3);
+ ret.mimeType = cursor.getString(4);
+ ret.encoding = cursor.getString(5);
+ ret.httpStatusCode = cursor.getInt(6);
+ ret.location = cursor.getString(7);
+ ret.contentLength = cursor.getLong(8);
+ return ret;
+ }
+ } finally {
+ if (cursor != null) cursor.close();
}
- cursor.close();
- return ret;
+ return null;
}
/**
@@ -565,16 +566,12 @@ public class WebViewDatabase {
*
* @param url The url
*/
- @SuppressWarnings("deprecation")
void removeCache(String url) {
if (url == null || mCacheDatabase == null) {
return;
}
- StringBuilder sb = new StringBuilder(256);
- sb.append("DELETE FROM cache WHERE url = ");
- DatabaseUtils.appendEscapedSQLString(sb, url);
- mCacheDatabase.execSQL(sb.toString());
+ mCacheDatabase.execSQL("DELETE FROM cache WHERE url = ?", new String[] { url });
}
/**
diff --git a/core/java/android/webkit/gears/DesktopAndroid.java b/core/java/android/webkit/gears/DesktopAndroid.java
index ee8ca49..a7a144b 100644
--- a/core/java/android/webkit/gears/DesktopAndroid.java
+++ b/core/java/android/webkit/gears/DesktopAndroid.java
@@ -31,6 +31,7 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
+import android.provider.Browser;
import android.util.Log;
import android.webkit.WebView;
@@ -78,7 +79,10 @@ public class DesktopAndroid {
Intent viewWebPage = new Intent(Intent.ACTION_VIEW);
viewWebPage.setData(Uri.parse(url));
- viewWebPage.addCategory(Intent.CATEGORY_BROWSABLE);
+ long urlHash = url.hashCode();
+ long uniqueId = (urlHash << 32) | viewWebPage.hashCode();
+ viewWebPage.putExtra(Browser.EXTRA_APPLICATION_ID,
+ Long.toString(uniqueId));
Intent intent = new Intent(ACTION_INSTALL_SHORTCUT);
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, viewWebPage);
diff --git a/core/java/android/webkit/gears/GearsPluginSettings.java b/core/java/android/webkit/gears/GearsPluginSettings.java
deleted file mode 100644
index d36d3fb..0000000
--- a/core/java/android/webkit/gears/GearsPluginSettings.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2008 The Android Open Source Project
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice,
-// this list of conditions and the following disclaimer.
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-// 3. Neither the name of Google Inc. nor the names of its contributors may be
-// used to endorse or promote products derived from this software without
-// specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
-// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package android.webkit.gears;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.util.Log;
-import android.webkit.Plugin;
-import android.webkit.Plugin.PreferencesClickHandler;
-
-/**
- * Simple bridge class intercepting the click in the
- * browser plugin list and calling the Gears settings
- * dialog.
- */
-public class GearsPluginSettings {
-
- private static final String TAG = "Gears-J-GearsPluginSettings";
- private Context context;
-
- public GearsPluginSettings(Plugin plugin) {
- plugin.setClickHandler(new ClickHandler());
- }
-
- /**
- * We do not need to call the dialog synchronously here (doing so
- * actually cause a lot of problems as the main message loop is also
- * blocked), which is why we simply call it via a thread.
- */
- private class ClickHandler implements PreferencesClickHandler {
- public void handleClickEvent(Context aContext) {
- context = aContext;
- Thread startService = new Thread(new StartService());
- startService.run();
- }
- }
-
- private static native void runSettingsDialog(Context c);
-
- /**
- * StartService is the runnable we use to open the dialog.
- * We bind the service to serviceConnection; upon
- * onServiceConnected the dialog will be called from the
- * native side using the runSettingsDialog method.
- */
- private class StartService implements Runnable {
- public void run() {
- HtmlDialogAndroid.bindToService(context, serviceConnection);
- }
- }
-
- /**
- * ServiceConnection instance.
- * onServiceConnected is called upon connection with the service;
- * we can then safely open the dialog.
- */
- private ServiceConnection serviceConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- IGearsDialogService gearsDialogService =
- IGearsDialogService.Stub.asInterface(service);
- HtmlDialogAndroid.setGearsDialogService(gearsDialogService);
- runSettingsDialog(context);
- context.unbindService(serviceConnection);
- HtmlDialogAndroid.setGearsDialogService(null);
- }
- public void onServiceDisconnected(ComponentName className) {
- HtmlDialogAndroid.setGearsDialogService(null);
- }
- };
-}
diff --git a/core/java/android/webkit/gears/HtmlDialogAndroid.java b/core/java/android/webkit/gears/HtmlDialogAndroid.java
deleted file mode 100644
index 6209ab9..0000000
--- a/core/java/android/webkit/gears/HtmlDialogAndroid.java
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright 2008 The Android Open Source Project
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice,
-// this list of conditions and the following disclaimer.
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-// 3. Neither the name of Google Inc. nor the names of its contributors may be
-// used to endorse or promote products derived from this software without
-// specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
-// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package android.webkit.gears;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.webkit.CacheManager;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-
-/**
- * Utility class to call a modal HTML dialog on Android
- */
-public class HtmlDialogAndroid {
-
- private static final String TAG = "Gears-J-HtmlDialog";
- private static final String DIALOG_PACKAGE = "com.android.browser";
- private static final String DIALOG_SERVICE = DIALOG_PACKAGE
- + ".GearsDialogService";
- private static final String DIALOG_INTERFACE = DIALOG_PACKAGE
- + ".IGearsDialogService";
-
- private static IGearsDialogService gearsDialogService;
-
- public static void setGearsDialogService(IGearsDialogService service) {
- gearsDialogService = service;
- }
-
- /**
- * Bind to the GearsDialogService.
- */
- public static boolean bindToService(Context context,
- ServiceConnection serviceConnection) {
- Intent dialogIntent = new Intent();
- dialogIntent.setClassName(DIALOG_PACKAGE, DIALOG_SERVICE);
- dialogIntent.setAction(DIALOG_INTERFACE);
- return context.bindService(dialogIntent, serviceConnection,
- Context.BIND_AUTO_CREATE);
- }
-
- /**
- * Bind to the GearsDialogService synchronously.
- * The service is started using our own defaultServiceConnection
- * handler, and we wait until the handler notifies us.
- */
- public void synchronousBindToService(Context context) {
- try {
- if (bindToService(context, defaultServiceConnection)) {
- if (gearsDialogService == null) {
- synchronized(defaultServiceConnection) {
- defaultServiceConnection.wait(3000); // timeout after 3s
- }
- }
- }
- } catch (InterruptedException e) {
- Log.e(TAG, "exception: " + e);
- }
- }
-
- /**
- * Read the HTML content from the disk
- */
- public String readHTML(String filePath) {
- FileInputStream inputStream = null;
- String content = "";
- try {
- inputStream = new FileInputStream(filePath);
- StringBuffer out = new StringBuffer();
- byte[] buffer = new byte[4096];
- for (int n; (n = inputStream.read(buffer)) != -1;) {
- out.append(new String(buffer, 0, n));
- }
- content = out.toString();
- } catch (IOException e) {
- Log.e(TAG, "exception: " + e);
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "exception: " + e);
- }
- }
- }
- return content;
- }
-
- /**
- * Open an HTML dialog synchronously and waits for its completion.
- * The dialog is accessed through the GearsDialogService provided by
- * the Android Browser.
- * We can be called either directly, and then gearsDialogService will
- * not be set and we will bind to the service synchronously, and unbind
- * after calling the service, or called indirectly via GearsPluginSettings.
- * In the latter case, GearsPluginSettings does the binding/unbinding.
- */
- public String showDialog(Context context, String htmlFilePath,
- String arguments) {
-
- CacheManager.endCacheTransaction();
-
- String ret = null;
- boolean synchronousCall = false;
- if (gearsDialogService == null) {
- synchronousCall = true;
- synchronousBindToService(context);
- }
-
- try {
- if (gearsDialogService != null) {
- String htmlContent = readHTML(htmlFilePath);
- if (htmlContent.length() > 0) {
- ret = gearsDialogService.showDialog(htmlContent, arguments,
- !synchronousCall);
- }
- } else {
- Log.e(TAG, "Could not connect to the GearsDialogService!");
- }
- if (synchronousCall) {
- context.unbindService(defaultServiceConnection);
- gearsDialogService = null;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "remote exception: " + e);
- gearsDialogService = null;
- }
-
- CacheManager.startCacheTransaction();
-
- return ret;
- }
-
- private ServiceConnection defaultServiceConnection =
- new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- synchronized (defaultServiceConnection) {
- gearsDialogService = IGearsDialogService.Stub.asInterface(service);
- defaultServiceConnection.notify();
- }
- }
- public void onServiceDisconnected(ComponentName className) {
- gearsDialogService = null;
- }
- };
-}
diff --git a/core/java/android/webkit/gears/HttpRequestAndroid.java b/core/java/android/webkit/gears/HttpRequestAndroid.java
deleted file mode 100644
index 30f855f..0000000
--- a/core/java/android/webkit/gears/HttpRequestAndroid.java
+++ /dev/null
@@ -1,745 +0,0 @@
-// Copyright 2008, The Android Open Source Project
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice,
-// this list of conditions and the following disclaimer.
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-// 3. Neither the name of Google Inc. nor the names of its contributors may be
-// used to endorse or promote products derived from this software without
-// specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
-// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package android.webkit.gears;
-
-import android.net.http.Headers;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.webkit.CacheManager;
-import android.webkit.CacheManager.CacheResult;
-import android.webkit.CookieManager;
-
-import org.apache.http.conn.ssl.StrictHostnameVerifier;
-import org.apache.http.impl.cookie.DateUtils;
-import org.apache.http.util.CharArrayBuffer;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import javax.net.ssl.*;
-
-/**
- * Performs the underlying HTTP/HTTPS GET and POST requests.
- * <p> These are performed synchronously (blocking). The caller should
- * ensure that it is in a background thread if asynchronous behavior
- * is required. All data is pushed, so there is no need for JNI native
- * callbacks.
- * <p> This uses the java.net.HttpURLConnection class to perform most
- * of the underlying network activity. The Android brower's cache,
- * android.webkit.CacheManager, is also used when caching is enabled,
- * and updated with new data. The android.webkit.CookieManager is also
- * queried and updated as necessary.
- * <p> The public interface is designed to be called by native code
- * through JNI, and to simplify coding none of the public methods will
- * surface a checked exception. Unchecked exceptions may still be
- * raised but only if the system is in an ill state, such as out of
- * memory.
- * <p> TODO: This isn't plumbed into LocalServer yet. Mutually
- * dependent on LocalServer - will attach the two together once both
- * are submitted.
- */
-public final class HttpRequestAndroid {
- /** Debug logging tag. */
- private static final String LOG_TAG = "Gears-J";
- /** HTTP response header line endings are CR-LF style. */
- private static final String HTTP_LINE_ENDING = "\r\n";
- /** Safe MIME type to use whenever it isn't specified. */
- private static final String DEFAULT_MIME_TYPE = "text/plain";
- /** Case-sensitive header keys */
- public static final String KEY_CONTENT_LENGTH = "Content-Length";
- public static final String KEY_EXPIRES = "Expires";
- public static final String KEY_LAST_MODIFIED = "Last-Modified";
- public static final String KEY_ETAG = "ETag";
- public static final String KEY_LOCATION = "Location";
- public static final String KEY_CONTENT_TYPE = "Content-Type";
- /** Number of bytes to send and receive on the HTTP connection in
- * one go. */
- private static final int BUFFER_SIZE = 4096;
- /** The first element of the String[] value in a headers map is the
- * unmodified (case-sensitive) key. */
- public static final int HEADERS_MAP_INDEX_KEY = 0;
- /** The second element of the String[] value in a headers map is the
- * associated value. */
- public static final int HEADERS_MAP_INDEX_VALUE = 1;
-
- /** Enable/disable all logging in this class. */
- private static boolean logEnabled = false;
- /** The underlying HTTP or HTTPS network connection. */
- private HttpURLConnection connection;
- /** HTTP body stream, setup after connection. */
- private InputStream inputStream;
- /** The complete response line e.g "HTTP/1.0 200 OK" */
- private String responseLine;
- /** Request headers, as a lowercase key -> [ unmodified key, value ] map. */
- private Map<String, String[]> requestHeaders =
- new HashMap<String, String[]>();
- /** Response headers, as a lowercase key -> [ unmodified key, value ] map. */
- private Map<String, String[]> responseHeaders;
- /** True if the child thread is in performing blocking IO. */
- private boolean inBlockingOperation = false;
- /** True when the thread acknowledges the abort. */
- private boolean abortReceived = false;
- /** The URL used for createCacheResult() */
- private String cacheResultUrl;
- /** CacheResult being saved into, if inserting a new cache entry. */
- private CacheResult cacheResult;
- /** Initialized by initChildThread(). Used to target abort(). */
- private Thread childThread;
-
- /**
- * Convenience debug function. Calls Android logging mechanism.
- * @param str String to log to the Android console.
- */
- private static void log(String str) {
- if (logEnabled) {
- Log.i(LOG_TAG, str);
- }
- }
-
- /**
- * Turn on/off logging in this class.
- * @param on Logging enable state.
- */
- public static void enableLogging(boolean on) {
- logEnabled = on;
- }
-
- /**
- * Initialize childThread using the TLS value of
- * Thread.currentThread(). Called on start up of the native child
- * thread.
- */
- public synchronized void initChildThread() {
- childThread = Thread.currentThread();
- }
-
- /**
- * Analagous to the native-side HttpRequest::open() function. This
- * initializes an underlying java.net.HttpURLConnection, but does
- * not go to the wire. On success, this enables a call to send() to
- * initiate the transaction.
- *
- * @param method The HTTP method, e.g GET or POST.
- * @param url The URL to open.
- * @return True on success with a complete HTTP response.
- * False on failure.
- */
- public synchronized boolean open(String method, String url) {
- if (logEnabled)
- log("open " + method + " " + url);
- // Reset the response between calls to open().
- inputStream = null;
- responseLine = null;
- responseHeaders = null;
- if (!method.equals("GET") && !method.equals("POST")) {
- log("Method " + method + " not supported");
- return false;
- }
- // Setup the connection. This doesn't go to the wire yet - it
- // doesn't block.
- try {
- URL url_object = new URL(url);
- // Check that the protocol is indeed HTTP(S).
- String protocol = url_object.getProtocol();
- if (protocol == null) {
- log("null protocol for URL " + url);
- return false;
- }
- protocol = protocol.toLowerCase();
- if (!"http".equals(protocol) && !"https".equals(protocol)) {
- log("Url has wrong protocol: " + url);
- return false;
- }
-
- connection = (HttpURLConnection) url_object.openConnection();
- connection.setRequestMethod(method);
- // Manually follow redirects.
- connection.setInstanceFollowRedirects(false);
- // Manually cache.
- connection.setUseCaches(false);
- // Enable data output in POST method requests.
- connection.setDoOutput(method.equals("POST"));
- // Enable data input in non-HEAD method requests.
- // TODO: HEAD requests not tested.
- connection.setDoInput(!method.equals("HEAD"));
- if (connection instanceof javax.net.ssl.HttpsURLConnection) {
- // Verify the certificate matches the origin.
- ((HttpsURLConnection) connection).setHostnameVerifier(
- new StrictHostnameVerifier());
- }
- return true;
- } catch (IOException e) {
- log("Got IOException in open: " + e.toString());
- return false;
- }
- }
-
- /**
- * Interrupt a blocking IO operation. This will cause the child
- * thread to expediently return from an operation if it was stuck at
- * the time. Note that this inherently races, and unfortunately
- * requires the caller to loop.
- */
- public synchronized void interrupt() {
- if (childThread == null) {
- log("interrupt() called but no child thread");
- return;
- }
- synchronized (this) {
- if (inBlockingOperation) {
- log("Interrupting blocking operation");
- childThread.interrupt();
- } else {
- log("Nothing to interrupt");
- }
- }
- }
-
- /**
- * Set a header to send with the HTTP request. Will not take effect
- * on a transaction already in progress. The key is associated
- * case-insensitive, but stored case-sensitive.
- * @param name The name of the header, e.g "Set-Cookie".
- * @param value The value for this header, e.g "text/html".
- */
- public synchronized void setRequestHeader(String name, String value) {
- String[] mapValue = { name, value };
- requestHeaders.put(name.toLowerCase(), mapValue);
- }
-
- /**
- * Returns the value associated with the given request header.
- * @param name The name of the request header, non-null, case-insensitive.
- * @return The value associated with the request header, or null if
- * not set, or error.
- */
- public synchronized String getRequestHeader(String name) {
- String[] value = requestHeaders.get(name.toLowerCase());
- if (value != null) {
- return value[HEADERS_MAP_INDEX_VALUE];
- } else {
- return null;
- }
- }
-
- /**
- * Returns the value associated with the given response header.
- * @param name The name of the response header, non-null, case-insensitive.
- * @return The value associated with the response header, or null if
- * not set or error.
- */
- public synchronized String getResponseHeader(String name) {
- if (responseHeaders != null) {
- String[] value = responseHeaders.get(name.toLowerCase());
- if (value != null) {
- return value[HEADERS_MAP_INDEX_VALUE];
- } else {
- return null;
- }
- } else {
- log("getResponseHeader() called but response not received");
- return null;
- }
- }
-
- /**
- * Set a response header and associated value. The key is associated
- * case-insensitively, but stored case-sensitively.
- * @param name Case sensitive request header key.
- * @param value The associated value.
- */
- private void setResponseHeader(String name, String value) {
- if (logEnabled)
- log("Set response header " + name + ": " + value);
- String mapValue[] = { name, value };
- responseHeaders.put(name.toLowerCase(), mapValue);
- }
-
- /**
- * Apply the contents of the Map requestHeaders to the connection
- * object. Calls to setRequestHeader() after this will not affect
- * the connection.
- */
- private synchronized void applyRequestHeadersToConnection() {
- Iterator<String[]> it = requestHeaders.values().iterator();
- while (it.hasNext()) {
- // Set the key case-sensitive.
- String[] entry = it.next();
- connection.setRequestProperty(
- entry[HEADERS_MAP_INDEX_KEY],
- entry[HEADERS_MAP_INDEX_VALUE]);
- }
- }
-
- /**
- * Return all response headers, separated by CR-LF line endings, and
- * ending with a trailing blank line. This mimics the format of the
- * raw response header up to but not including the body.
- * @return A string containing the entire response header.
- */
- public synchronized String getAllResponseHeaders() {
- if (responseHeaders == null) {
- log("getAllResponseHeaders() called but response not received");
- return null;
- }
- String result = new String();
- Iterator<String[]> it = responseHeaders.values().iterator();
- while (it.hasNext()) {
- String[] entry = it.next();
- // Output the "key: value" lines.
- result += entry[HEADERS_MAP_INDEX_KEY] + ": "
- + entry[HEADERS_MAP_INDEX_VALUE] + HTTP_LINE_ENDING;
- }
- result += HTTP_LINE_ENDING;
- return result;
- }
-
- /**
- * Get the complete response line of the HTTP request. Only valid on
- * completion of the transaction.
- * @return The complete HTTP response line, e.g "HTTP/1.0 200 OK".
- */
- public synchronized String getResponseLine() {
- return responseLine;
- }
-
- /**
- * Get the cookie for the given URL.
- * @param url The fully qualified URL.
- * @return A string containing the cookie for the URL if it exists,
- * or null if not.
- */
- public static String getCookieForUrl(String url) {
- // Get the cookie for this URL, set as a header
- return CookieManager.getInstance().getCookie(url);
- }
-
- /**
- * Set the cookie for the given URL.
- * @param url The fully qualified URL.
- * @param cookie The new cookie value.
- * @return A string containing the cookie for the URL if it exists,
- * or null if not.
- */
- public static void setCookieForUrl(String url, String cookie) {
- // Get the cookie for this URL, set as a header
- CookieManager.getInstance().setCookie(url, cookie);
- }
-
- /**
- * Perform a request using LocalServer if possible. Initializes
- * class members so that receive() will obtain data from the stream
- * provided by the response.
- * @param url The fully qualified URL to try in LocalServer.
- * @return True if the url was found and is now setup to receive.
- * False if not found, with no side-effect.
- */
- public synchronized boolean useLocalServerResult(String url) {
- UrlInterceptHandlerGears handler = UrlInterceptHandlerGears.getInstance();
- if (handler == null) {
- return false;
- }
- UrlInterceptHandlerGears.ServiceResponse serviceResponse =
- handler.getServiceResponse(url, requestHeaders);
- if (serviceResponse == null) {
- log("No response in LocalServer");
- return false;
- }
- // LocalServer will handle this URL. Initialize stream and
- // response.
- inputStream = serviceResponse.getInputStream();
- responseLine = serviceResponse.getStatusLine();
- responseHeaders = serviceResponse.getResponseHeaders();
- if (logEnabled)
- log("Got response from LocalServer: " + responseLine);
- return true;
- }
-
- /**
- * Perform a request using the cache result if present. Initializes
- * class members so that receive() will obtain data from the cache.
- * @param url The fully qualified URL to try in the cache.
- * @return True is the url was found and is now setup to receive
- * from cache. False if not found, with no side-effect.
- */
- public synchronized boolean useCacheResult(String url) {
- // Try the browser's cache. CacheManager wants a Map<String, String>.
- Map<String, String> cacheRequestHeaders = new HashMap<String, String>();
- Iterator<Map.Entry<String, String[]>> it =
- requestHeaders.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<String, String[]> entry = it.next();
- cacheRequestHeaders.put(
- entry.getKey(),
- entry.getValue()[HEADERS_MAP_INDEX_VALUE]);
- }
- CacheResult cacheResult =
- CacheManager.getCacheFile(url, cacheRequestHeaders);
- if (cacheResult == null) {
- if (logEnabled)
- log("No CacheResult for " + url);
- return false;
- }
- if (logEnabled)
- log("Got CacheResult from browser cache");
- // Check for expiry. -1 is "never", otherwise milliseconds since 1970.
- // Can be compared to System.currentTimeMillis().
- long expires = cacheResult.getExpires();
- if (expires >= 0 && System.currentTimeMillis() >= expires) {
- log("CacheResult expired "
- + (System.currentTimeMillis() - expires)
- + " milliseconds ago");
- // Cache hit has expired. Do not return it.
- return false;
- }
- // Setup the inputStream to come from the cache.
- inputStream = cacheResult.getInputStream();
- if (inputStream == null) {
- // Cache result may have gone away.
- log("No inputStream for CacheResult " + url);
- return false;
- }
- // Cache hit. Parse headers.
- synthesizeHeadersFromCacheResult(cacheResult);
- return true;
- }
-
- /**
- * Take the limited set of headers in a CacheResult and synthesize
- * response headers.
- * @param cacheResult A CacheResult to populate responseHeaders with.
- */
- private void synthesizeHeadersFromCacheResult(CacheResult cacheResult) {
- int statusCode = cacheResult.getHttpStatusCode();
- // The status message is informal, so we can greatly simplify it.
- String statusMessage;
- if (statusCode >= 200 && statusCode < 300) {
- statusMessage = "OK";
- } else if (statusCode >= 300 && statusCode < 400) {
- statusMessage = "MOVED";
- } else {
- statusMessage = "UNAVAILABLE";
- }
- // Synthesize the response line.
- responseLine = "HTTP/1.1 " + statusCode + " " + statusMessage;
- if (logEnabled)
- log("Synthesized " + responseLine);
- // Synthesize the returned headers from cache.
- responseHeaders = new HashMap<String, String[]>();
- String contentLength = Long.toString(cacheResult.getContentLength());
- setResponseHeader(KEY_CONTENT_LENGTH, contentLength);
- long expires = cacheResult.getExpires();
- if (expires >= 0) {
- // "Expires" header is valid and finite. Milliseconds since 1970
- // epoch, formatted as RFC-1123.
- String expiresString = DateUtils.formatDate(new Date(expires));
- setResponseHeader(KEY_EXPIRES, expiresString);
- }
- String lastModified = cacheResult.getLastModified();
- if (lastModified != null) {
- // Last modification time of the page. Passed end-to-end, but
- // not used by us.
- setResponseHeader(KEY_LAST_MODIFIED, lastModified);
- }
- String eTag = cacheResult.getETag();
- if (eTag != null) {
- // Entity tag. A kind of GUID to identify identical resources.
- setResponseHeader(KEY_ETAG, eTag);
- }
- String location = cacheResult.getLocation();
- if (location != null) {
- // If valid, refers to the location of a redirect.
- setResponseHeader(KEY_LOCATION, location);
- }
- String mimeType = cacheResult.getMimeType();
- if (mimeType == null) {
- // Use a safe default MIME type when none is
- // specified. "text/plain" is safe to render in the browser
- // window (even if large) and won't be intepreted as anything
- // that would cause execution.
- mimeType = DEFAULT_MIME_TYPE;
- }
- String encoding = cacheResult.getEncoding();
- // Encoding may not be specified. No default.
- String contentType = mimeType;
- if (encoding != null && encoding.length() > 0) {
- contentType += "; charset=" + encoding;
- }
- setResponseHeader(KEY_CONTENT_TYPE, contentType);
- }
-
- /**
- * Create a CacheResult for this URL. This enables the repsonse body
- * to be sent in calls to appendCacheResult().
- * @param url The fully qualified URL to add to the cache.
- * @param responseCode The response code returned for the request, e.g 200.
- * @param mimeType The MIME type of the body, e.g "text/plain".
- * @param encoding The encoding, e.g "utf-8". Use "" for unknown.
- */
- public synchronized boolean createCacheResult(
- String url, int responseCode, String mimeType, String encoding) {
- if (logEnabled)
- log("Making cache entry for " + url);
- // Take the headers and parse them into a format needed by
- // CacheManager.
- Headers cacheHeaders = new Headers();
- Iterator<Map.Entry<String, String[]>> it =
- responseHeaders.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<String, String[]> entry = it.next();
- // Headers.parseHeader() expects lowercase keys.
- String keyValue = entry.getKey() + ": "
- + entry.getValue()[HEADERS_MAP_INDEX_VALUE];
- CharArrayBuffer buffer = new CharArrayBuffer(keyValue.length());
- buffer.append(keyValue);
- // Parse it into the header container.
- cacheHeaders.parseHeader(buffer);
- }
- cacheResult = CacheManager.createCacheFile(
- url, responseCode, cacheHeaders, mimeType, true);
- if (cacheResult != null) {
- if (logEnabled)
- log("Saving into cache");
- cacheResult.setEncoding(encoding);
- cacheResultUrl = url;
- return true;
- } else {
- log("Couldn't create cacheResult");
- return false;
- }
- }
-
- /**
- * Add data from the response body to the CacheResult created with
- * createCacheResult().
- * @param data A byte array of the next sequential bytes in the
- * response body.
- * @param bytes The number of bytes to write from the start of
- * the array.
- * @return True if all bytes successfully written, false on failure.
- */
- public synchronized boolean appendCacheResult(byte[] data, int bytes) {
- if (cacheResult == null) {
- log("appendCacheResult() called without a CacheResult initialized");
- return false;
- }
- try {
- cacheResult.getOutputStream().write(data, 0, bytes);
- } catch (IOException ex) {
- log("Got IOException writing cache data: " + ex);
- return false;
- }
- return true;
- }
-
- /**
- * Save the completed CacheResult into the CacheManager. This must
- * have been created first with createCacheResult().
- * @return Returns true if the entry has been successfully saved.
- */
- public synchronized boolean saveCacheResult() {
- if (cacheResult == null || cacheResultUrl == null) {
- log("Tried to save cache result but createCacheResult not called");
- return false;
- }
- if (logEnabled)
- log("Saving cache result");
- CacheManager.saveCacheFile(cacheResultUrl, cacheResult);
- cacheResult = null;
- cacheResultUrl = null;
- return true;
- }
-
- /**
- * Perform an HTTP request on the network. The underlying
- * HttpURLConnection is connected to the remote server and the
- * response headers are received.
- * @return True if the connection succeeded and headers have been
- * received. False on connection failure.
- */
- public boolean connectToRemote() {
- synchronized (this) {
- // Transfer a snapshot of our internally maintained map of request
- // headers to the connection object.
- applyRequestHeadersToConnection();
- // Note blocking I/O so abort() can interrupt us.
- inBlockingOperation = true;
- }
- boolean success;
- try {
- if (logEnabled)
- log("Connecting to remote");
- connection.connect();
- if (logEnabled)
- log("Connected");
- success = true;
- } catch (IOException e) {
- log("Got IOException in connect(): " + e.toString());
- success = false;
- } finally {
- synchronized (this) {
- // No longer blocking.
- inBlockingOperation = false;
- }
- }
- return success;
- }
-
- /**
- * Receive all headers from the server and populate
- * responseHeaders. This converts from the slightly odd format
- * returned by java.net.HttpURLConnection to a simpler
- * java.util.Map.
- * @return True if headers are successfully received, False on
- * connection error.
- */
- public synchronized boolean parseHeaders() {
- responseHeaders = new HashMap<String, String[]>();
- /* HttpURLConnection contains a null terminated list of
- * key->value response pairs. If the key is null, then the value
- * contains the complete status line. If both key and value are
- * null for an index, we've reached the end.
- */
- for (int i = 0; ; ++i) {
- String key = connection.getHeaderFieldKey(i);
- String value = connection.getHeaderField(i);
- if (logEnabled)
- log("header " + key + " -> " + value);
- if (key == null && value == null) {
- // End of list.
- break;
- } else if (key == null) {
- // The pair with null key has the complete status line in
- // the value, e.g "HTTP/1.0 200 OK".
- responseLine = value;
- } else if (value != null) {
- // If key and value are non-null, this is a response pair, e.g
- // "Content-Length" -> "5". Use setResponseHeader() to
- // correctly deal with case-insensitivity of the key.
- setResponseHeader(key, value);
- } else {
- // The key is non-null but value is null. Unexpected
- // condition.
- return false;
- }
- }
- return true;
- }
-
- /**
- * Receive the next sequential bytes of the response body after
- * successful connection. This will receive up to the size of the
- * provided byte array. If there is no body, this will return 0
- * bytes on the first call after connection.
- * @param buf A pre-allocated byte array to receive data into.
- * @return The number of bytes from the start of the array which
- * have been filled, 0 on EOF, or negative on error.
- */
- public int receive(byte[] buf) {
- if (inputStream == null) {
- // If this is the first call, setup the InputStream. This may
- // fail if there were headers, but no body returned by the
- // server.
- try {
- inputStream = connection.getInputStream();
- } catch (IOException inputException) {
- log("Failed to connect InputStream: " + inputException);
- // Not unexpected. For example, 404 response return headers,
- // and sometimes a body with a detailed error. Try the error
- // stream.
- inputStream = connection.getErrorStream();
- if (inputStream == null) {
- // No error stream either. Treat as a 0 byte response.
- log("No InputStream");
- return 0; // EOF.
- }
- }
- }
- synchronized (this) {
- // Note blocking I/O so abort() can interrupt us.
- inBlockingOperation = true;
- }
- int ret;
- try {
- int got = inputStream.read(buf);
- if (got > 0) {
- // Got some bytes, not EOF.
- ret = got;
- } else {
- // EOF.
- inputStream.close();
- ret = 0;
- }
- } catch (IOException e) {
- // An abort() interrupts us by calling close() on our stream.
- log("Got IOException in inputStream.read(): " + e.toString());
- ret = -1;
- } finally {
- synchronized (this) {
- // No longer blocking.
- inBlockingOperation = false;
- }
- }
- return ret;
- }
-
- /**
- * For POST method requests, send a stream of data provided by the
- * native side in repeated callbacks.
- * @param data A byte array containing the data to sent, or null
- * if indicating EOF.
- * @param bytes The number of bytes from the start of the array to
- * send, or 0 if indicating EOF.
- * @return True if all bytes were successfully sent, false on error.
- */
- public boolean sendPostData(byte[] data, int bytes) {
- synchronized (this) {
- // Note blocking I/O so abort() can interrupt us.
- inBlockingOperation = true;
- }
- boolean success;
- try {
- OutputStream outputStream = connection.getOutputStream();
- if (data == null && bytes == 0) {
- outputStream.close();
- } else {
- outputStream.write(data, 0, bytes);
- }
- success = true;
- } catch (IOException e) {
- log("Got IOException in post: " + e.toString());
- success = false;
- } finally {
- synchronized (this) {
- // No longer blocking.
- inBlockingOperation = false;
- }
- }
- return success;
- }
-}
diff --git a/core/java/android/webkit/gears/IGearsDialogService.java b/core/java/android/webkit/gears/IGearsDialogService.java
deleted file mode 100644
index 82a3bd9..0000000
--- a/core/java/android/webkit/gears/IGearsDialogService.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * This file is auto-generated. DO NOT MODIFY.
- * Original file: android.webkit.gears/IGearsDialogService.aidl
- */
-package android.webkit.gears;
-import java.lang.String;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Binder;
-import android.os.Parcel;
-public interface IGearsDialogService extends android.os.IInterface
-{
-/** Local-side IPC implementation stub class. */
-public static abstract class Stub extends android.os.Binder implements android.webkit.gears.IGearsDialogService
-{
-private static final java.lang.String DESCRIPTOR = "com.android.browser.IGearsDialogService";
-/** Construct the stub at attach it to the interface. */
-public Stub()
-{
-this.attachInterface(this, DESCRIPTOR);
-}
-/**
- * Cast an IBinder object into an IGearsDialogService interface,
- * generating a proxy if needed.
- */
-public static android.webkit.gears.IGearsDialogService asInterface(android.os.IBinder obj)
-{
-if ((obj==null)) {
-return null;
-}
-android.webkit.gears.IGearsDialogService in = (android.webkit.gears.IGearsDialogService)obj.queryLocalInterface(DESCRIPTOR);
-if ((in!=null)) {
-return in;
-}
-return new android.webkit.gears.IGearsDialogService.Stub.Proxy(obj);
-}
-public android.os.IBinder asBinder()
-{
-return this;
-}
-public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
-{
-switch (code)
-{
-case INTERFACE_TRANSACTION:
-{
-reply.writeString(DESCRIPTOR);
-return true;
-}
-case TRANSACTION_showDialog:
-{
-data.enforceInterface(DESCRIPTOR);
-java.lang.String _arg0;
-_arg0 = data.readString();
-java.lang.String _arg1;
-_arg1 = data.readString();
-boolean _arg2;
-_arg2 = (0!=data.readInt());
-java.lang.String _result = this.showDialog(_arg0, _arg1, _arg2);
-reply.writeNoException();
-reply.writeString(_result);
-return true;
-}
-}
-return super.onTransact(code, data, reply, flags);
-}
-private static class Proxy implements android.webkit.gears.IGearsDialogService
-{
-private android.os.IBinder mRemote;
-Proxy(android.os.IBinder remote)
-{
-mRemote = remote;
-}
-public android.os.IBinder asBinder()
-{
-return mRemote;
-}
-public java.lang.String getInterfaceDescriptor()
-{
-return DESCRIPTOR;
-}
-public java.lang.String showDialog(java.lang.String htmlContent, java.lang.String dialogArguments, boolean inSettings) throws android.os.RemoteException
-{
-android.os.Parcel _data = android.os.Parcel.obtain();
-android.os.Parcel _reply = android.os.Parcel.obtain();
-java.lang.String _result;
-try {
-_data.writeInterfaceToken(DESCRIPTOR);
-_data.writeString(htmlContent);
-_data.writeString(dialogArguments);
-_data.writeInt(((inSettings)?(1):(0)));
-mRemote.transact(Stub.TRANSACTION_showDialog, _data, _reply, 0);
-_reply.readException();
-_result = _reply.readString();
-}
-finally {
-_reply.recycle();
-_data.recycle();
-}
-return _result;
-}
-}
-static final int TRANSACTION_showDialog = (IBinder.FIRST_CALL_TRANSACTION + 0);
-}
-public java.lang.String showDialog(java.lang.String htmlContent, java.lang.String dialogArguments, boolean inSettings) throws android.os.RemoteException;
-}
diff --git a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
index 288240e..43104bf 100644
--- a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
+++ b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
@@ -25,16 +25,14 @@
package android.webkit.gears;
-import android.net.http.Headers;
import android.util.Log;
-import android.webkit.CacheManager;
import android.webkit.CacheManager.CacheResult;
import android.webkit.Plugin;
+import android.webkit.PluginData;
import android.webkit.UrlInterceptRegistry;
import android.webkit.UrlInterceptHandler;
import android.webkit.WebView;
-import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.util.CharArrayBuffer;
import java.io.*;
@@ -53,22 +51,16 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
private static final String LOG_TAG = "Gears-J";
/** Buffer size for reading/writing streams. */
private static final int BUFFER_SIZE = 4096;
- /**
- * Number of milliseconds to expire LocalServer temporary entries in
- * the browser's cache. Somewhat arbitrarily chosen as a compromise
- * between being a) long enough not to expire during page load and
- * b) short enough to evict entries during a session. */
- private static final int CACHE_EXPIRY_MS = 60000; // 1 minute.
/** Enable/disable all logging in this class. */
private static boolean logEnabled = false;
/** The unmodified (case-sensitive) key in the headers map is the
* same index as used by HttpRequestAndroid. */
public static final int HEADERS_MAP_INDEX_KEY =
- HttpRequestAndroid.HEADERS_MAP_INDEX_KEY;
+ ApacheHttpRequestAndroid.HEADERS_MAP_INDEX_KEY;
/** The associated value in the headers map is the same index as
* used by HttpRequestAndroid. */
public static final int HEADERS_MAP_INDEX_VALUE =
- HttpRequestAndroid.HEADERS_MAP_INDEX_VALUE;
+ ApacheHttpRequestAndroid.HEADERS_MAP_INDEX_VALUE;
/**
* Object passed to the native side, containing information about
@@ -140,6 +132,8 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
private String encoding;
// The stream which contains the body when read().
private InputStream inputStream;
+ // The length of the content body.
+ private long contentLength;
/**
* Initialize members using an in-memory array to return the body.
@@ -160,6 +154,7 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
this.mimeType = mimeType;
this.encoding = encoding;
// Setup a stream to read out of the byte array.
+ this.contentLength = body.length;
this.inputStream = new ByteArrayInputStream(body);
}
@@ -185,7 +180,9 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
this.encoding = encoding;
try {
// Setup a stream to read out of a file on disk.
- this.inputStream = new FileInputStream(new File(path));
+ File file = new File(path);
+ this.contentLength = file.length();
+ this.inputStream = new FileInputStream(file);
return true;
} catch (java.io.FileNotFoundException ex) {
log("File not found: " + path);
@@ -274,6 +271,13 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
public InputStream getInputStream() {
return inputStream;
}
+
+ /**
+ * @return The length of the response body.
+ */
+ public long getContentLength() {
+ return contentLength;
+ }
}
/**
@@ -319,44 +323,32 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
UrlInterceptRegistry.unregisterHandler(this);
}
- /**
- * Copy the entire InputStream to OutputStream.
- * @param inputStream The stream to read from.
- * @param outputStream The stream to write to.
- * @return True if the entire stream copied successfully, false on error.
- */
- private boolean copyStream(InputStream inputStream,
- OutputStream outputStream) {
- try {
- // Temporary buffer to copy stream through.
- byte[] buf = new byte[BUFFER_SIZE];
- for (;;) {
- // Read up to BUFFER_SIZE bytes.
- int bytes = inputStream.read(buf);
- if (bytes < 0) {
- break;
- }
- // Write the number of bytes we just read.
- outputStream.write(buf, 0, bytes);
- }
- } catch (IOException ex) {
- log("Got IOException copying stream: " + ex);
- return false;
+ /**
+ * Given an URL, returns the CacheResult which contains the
+ * surrogate response for the request, or null if the handler is
+ * not interested.
+ *
+ * @param url URL string.
+ * @param headers The headers associated with the request. May be null.
+ * @return The CacheResult containing the surrogate response.
+ * @Deprecated Use PluginData getPluginData(String url,
+ * Map<String, String> headers); instead
+ */
+ @Deprecated
+ public CacheResult service(String url, Map<String, String> headers) {
+ throw new UnsupportedOperationException("unimplemented");
}
- return true;
- }
/**
- * Given an URL, returns a CacheResult which contains the response
- * for the request. This implements the UrlInterceptHandler interface.
+ * Given an URL, returns a PluginData instance which contains the
+ * response for the request. This implements the UrlInterceptHandler
+ * interface.
*
- * @param url The fully qualified URL being requested.
+ * @param url The fully qualified URL being requested.
* @param requestHeaders The request headers for this URL.
- * @return If a response can be crafted, a CacheResult initialized
- * to return the surrogate response. If this URL cannot
- * be serviced, returns null.
+ * @return a PluginData object.
*/
- public CacheResult service(String url, Map<String, String> requestHeaders) {
+ public PluginData getPluginData(String url, Map<String, String> requestHeaders) {
// Thankfully the browser does call us with case-sensitive
// headers. We just need to map it case-insensitive.
Map<String, String[]> lowercaseRequestHeaders =
@@ -374,86 +366,10 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
// No result for this URL.
return null;
}
- // Translate the ServiceResponse to a CacheResult.
- // Translate http -> gears, https -> gearss, so we don't overwrite
- // existing entries.
- String gearsUrl = "gears" + url.substring("http".length());
- // Set the result to expire, so that entries don't pollute the
- // browser's cache for too long.
- long now_ms = System.currentTimeMillis();
- String expires = DateUtils.formatDate(new Date(now_ms + CACHE_EXPIRY_MS));
- response.setResponseHeader(HttpRequestAndroid.KEY_EXPIRES, expires);
- // The browser is only interested in a small subset of headers,
- // contained in a Headers object. Iterate the map of all headers
- // and add them to Headers.
- Headers headers = new Headers();
- Iterator<Map.Entry<String, String[]>> responseHeadersIt =
- response.getResponseHeaders().entrySet().iterator();
- while (responseHeadersIt.hasNext()) {
- Map.Entry<String, String[]> entry = responseHeadersIt.next();
- // Headers.parseHeader() expects lowercase keys.
- String keyValue = entry.getKey() + ": "
- + entry.getValue()[HEADERS_MAP_INDEX_VALUE];
- CharArrayBuffer buffer = new CharArrayBuffer(keyValue.length());
- buffer.append(keyValue);
- // Parse it into the header container.
- headers.parseHeader(buffer);
- }
- CacheResult cacheResult = CacheManager.createCacheFile(
- gearsUrl,
- response.getStatusCode(),
- headers,
- response.getMimeType(),
- true); // forceCache
-
- if (cacheResult == null) {
- // With the no-cache policy we could end up
- // with a null result
- return null;
- }
-
- // Set encoding if specified.
- String encoding = response.getEncoding();
- if (encoding != null) {
- cacheResult.setEncoding(encoding);
- }
- // Copy the response body to the CacheResult. This handles all
- // combinations of memory vs on-disk on both sides.
- InputStream inputStream = response.getInputStream();
- OutputStream outputStream = cacheResult.getOutputStream();
- boolean copied = copyStream(inputStream, outputStream);
- // Close the input and output streams to relinquish their
- // resources earlier.
- try {
- inputStream.close();
- } catch (IOException ex) {
- log("IOException closing InputStream: " + ex);
- copied = false;
- }
- try {
- outputStream.close();
- } catch (IOException ex) {
- log("IOException closing OutputStream: " + ex);
- copied = false;
- }
- if (!copied) {
- log("copyStream of local result failed");
- return null;
- }
- // Save the entry into the browser's cache.
- CacheManager.saveCacheFile(gearsUrl, cacheResult);
- // Get it back from the cache, this time properly initialized to
- // be used for input.
- cacheResult = CacheManager.getCacheFile(gearsUrl, null);
- if (cacheResult != null) {
- log("Returning surrogate result");
- return cacheResult;
- } else {
- // Not an expected condition, but handle gracefully. Perhaps out
- // of memory or disk?
- Log.e(LOG_TAG, "Lost CacheResult between save and get. Can't serve.\n");
- return null;
- }
+ return new PluginData(response.getInputStream(),
+ response.getContentLength(),
+ response.getResponseHeaders(),
+ response.getStatusCode());
}
/**
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 19ec77d..bd4bba8 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -17,7 +17,6 @@
package android.widget;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -28,9 +27,11 @@ import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -40,7 +41,9 @@ import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
-import android.view.WindowManagerImpl;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
import android.view.ContextMenu.ContextMenuInfo;
import com.android.internal.R;
@@ -369,7 +372,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private int mLastTouchMode = TOUCH_MODE_UNKNOWN;
- // TODO: REMOVE WHEN WE'RE DONE WITH PROFILING
private static final boolean PROFILE_SCROLLING = false;
private boolean mScrollProfilingStarted = false;
@@ -423,6 +425,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
private FastScroller mFastScroller;
+ private int mTouchSlop;
+
+ private float mDensityScale;
+
/**
* Interface definition for a callback to be invoked when the list or grid
* has been scrolled.
@@ -558,6 +564,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public boolean isFastScrollEnabled() {
return mFastScrollEnabled;
}
+
+ /**
+ * If fast scroll is visible, then don't draw the vertical scrollbar.
+ * @hide
+ */
+ @Override
+ protected boolean isVerticalScrollBarHidden() {
+ return mFastScroller != null && mFastScroller.isVisible();
+ }
/**
* When smooth scrollbar is enabled, the position and size of the scrollbar thumb
@@ -696,6 +711,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
setWillNotDraw(false);
setAlwaysDrawnWithCacheEnabled(false);
setScrollingCacheEnabled(true);
+
+ mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ mDensityScale = getContext().getResources().getDisplayMetrics().density;
}
private void useDefaultSelector() {
@@ -877,15 +895,22 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mSyncMode = SYNC_FIRST_POSITION;
}
- // Don't restore the type filter window when there is no keyboard
- int keyboardHidden = getContext().getResources().getConfiguration().keyboardHidden;
- if (keyboardHidden != Configuration.KEYBOARDHIDDEN_YES) {
- String filterText = ss.filter;
- setFilterText(filterText);
- }
+ setFilterText(ss.filter);
+
requestLayout();
}
+ private boolean acceptFilter() {
+ if (!mTextFilterEnabled || !(getAdapter() instanceof Filterable) ||
+ ((Filterable) getAdapter()).getFilter() == null) {
+ return false;
+ }
+ final Context context = mContext;
+ final InputMethodManager inputManager = (InputMethodManager)
+ context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ return !inputManager.isFullscreenMode();
+ }
+
/**
* Sets the initial value for the text filter.
* @param filterText The text to use for the filter.
@@ -893,7 +918,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @see #setTextFilterEnabled
*/
public void setFilterText(String filterText) {
- if (mTextFilterEnabled && filterText != null && filterText.length() > 0) {
+ // TODO: Should we check for acceptFilter()?
+ if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
createTextFilter(false);
// This is going to call our listener onTextChanged, but we might not
// be ready to bring up a window yet
@@ -913,6 +939,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ /**
+ * Returns the list's text filter, if available.
+ * @return the list's text filter or null if filtering isn't enabled
+ * @hide pending API Council approval
+ */
+ public CharSequence getTextFilter() {
+ if (mTextFilterEnabled && mTextFilter != null) {
+ return mTextFilter.getText();
+ }
+ return null;
+ }
+
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
@@ -944,6 +982,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mSelectorRect.setEmpty();
invalidate();
}
+
+ /**
+ * The list is empty and we need to change the layout, so *really* clear everything out.
+ * @hide - for AutoCompleteTextView & SearchDialog only
+ */
+ /* package */ void resetListAndClearViews() {
+ rememberSyncState();
+ removeAllViewsInLayout();
+ mRecycler.clear();
+ mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
+ requestLayout();
+ }
@Override
protected int computeVerticalScrollExtent() {
@@ -1063,6 +1113,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mInLayout = false;
}
+ /**
+ * @hide
+ */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ final boolean changed = super.setFrame(left, top, right, bottom);
+
+ // Reposition the popup when the frame has changed. This includes
+ // translating the widget, not just changing its dimension. The
+ // filter popup needs to follow the widget.
+ if (mFiltered && changed && getWindowVisibility() == View.VISIBLE && mPopup != null &&
+ mPopup.isShowing()) {
+ positionPopup();
+ }
+
+ return changed;
+ }
+
protected void layoutChildren() {
}
@@ -1366,7 +1434,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final View v = getChildAt(mSelectedPosition - mFirstPosition);
- if (v != null) v.setPressed(true);
+ if (v != null) {
+ if (v.hasFocusable()) return;
+ v.setPressed(true);
+ }
setPressed(true);
final boolean longClickable = isLongClickable();
@@ -1610,6 +1681,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
handled = super.showContextMenuForChild(AbsListView.this);
}
+ if (handled) {
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
return handled;
}
@@ -1758,8 +1832,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Check if we have moved far enough that it looks more like a
// scroll than a tap
final int distance = Math.abs(deltaY);
- int touchSlop = ViewConfiguration.getTouchSlop();
- if (distance > touchSlop) {
+ if (distance > mTouchSlop) {
createScrollingCache();
mTouchMode = TOUCH_MODE_SCROLL;
mMotionCorrection = deltaY;
@@ -1979,8 +2052,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
velocityTracker.computeCurrentVelocity(1000);
int initialVelocity = (int)velocityTracker.getYVelocity();
- if ((Math.abs(initialVelocity) > ViewConfiguration.getMinimumFlingVelocity()) &&
- (getChildCount() > 0)){
+ if ((Math.abs(initialVelocity) >
+ ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
+ (getChildCount() > 0)) {
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
}
@@ -2571,10 +2645,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
clearScrollingCache();
mSpecificTop = selectedTop;
selectedPos = lookForSelectablePosition(selectedPos, down);
- if (selectedPos >= 0) {
+ if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
mLayoutMode = LAYOUT_SPECIFIC;
setSelectionInt(selectedPos);
invokeOnItemScrollListener();
+ } else {
+ selectedPos = INVALID_POSITION;
}
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
@@ -2711,17 +2787,28 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private void showPopup() {
// Make sure we have a window before showing the popup
if (getWindowVisibility() == View.VISIBLE) {
- int screenHeight = WindowManagerImpl.getDefault().getDefaultDisplay().getHeight();
- final int[] xy = new int[2];
- getLocationOnScreen(xy);
- int bottomGap = screenHeight - xy[1] - getHeight() + 20;
- mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
- xy[0], bottomGap);
+ createTextFilter(true);
+ positionPopup();
// Make sure we get focus if we are showing the popup
checkFocus();
}
}
+ private void positionPopup() {
+ int screenHeight = getResources().getDisplayMetrics().heightPixels;
+ final int[] xy = new int[2];
+ getLocationOnScreen(xy);
+ // TODO: The 20 below should come from the theme and be expressed in dip
+ // TODO: And the gravity should be defined in the theme as well
+ final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
+ if (!mPopup.isShowing()) {
+ mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ xy[0], bottomGap);
+ } else {
+ mPopup.update(xy[0], bottomGap, -1, -1);
+ }
+ }
+
/**
* What is the distance between the source and destination rectangles given the direction of
* focus navigation between them? The direction basically helps figure out more quickly what is
@@ -2783,8 +2870,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @return True if the text filter handled the event, false otherwise.
*/
boolean sendToTextFilter(int keyCode, int count, KeyEvent event) {
- if (!mTextFilterEnabled || !(getAdapter() instanceof Filterable) ||
- ((Filterable) getAdapter()).getFilter() == null) {
+ if (!acceptFilter()) {
return false;
}
@@ -2840,6 +2926,30 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
/**
+ * Return an InputConnection for editing of the filter text.
+ */
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ if (isTextFilterEnabled()) {
+ // XXX we need to have the text filter created, so we can get an
+ // InputConnection to proxy to. Unfortunately this means we pretty
+ // much need to make it as soon as a list view gets focus.
+ createTextFilter(false);
+ return mTextFilter.onCreateInputConnection(outAttrs);
+ }
+ return null;
+ }
+
+ /**
+ * For filtering we proxy an input connection to an internal text editor,
+ * and this allows the proxying to happen.
+ */
+ @Override
+ public boolean checkInputConnectionProxy(View view) {
+ return view == mTextFilter;
+ }
+
+ /**
* Creates the window for the text filter and populates it with an EditText field;
*
* @param animateEntrance true if the window should appear with an animation
@@ -2848,13 +2958,19 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mPopup == null) {
Context c = getContext();
PopupWindow p = new PopupWindow(c);
- LayoutInflater layoutInflater = (LayoutInflater) c
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ LayoutInflater layoutInflater = (LayoutInflater)
+ c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mTextFilter = (EditText) layoutInflater.inflate(
com.android.internal.R.layout.typing_filter, null);
+ // For some reason setting this as the "real" input type changes
+ // the text view in some way that it doesn't work, and I don't
+ // want to figure out why this is.
+ mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_FILTER);
mTextFilter.addTextChangedListener(this);
p.setFocusable(false);
p.setTouchable(false);
+ p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
p.setContentView(mTextFilter);
p.setWidth(LayoutParams.WRAP_CONTENT);
p.setHeight(LayoutParams.WRAP_CONTENT);
@@ -2915,7 +3031,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* filtering as the text changes.
*/
public void onTextChanged(CharSequence s, int start, int before, int count) {
- if (mPopup != null) {
+ if (mPopup != null && isTextFilterEnabled()) {
int length = s.length();
boolean showing = mPopup.isShowing();
if (!showing && length > 0) {
@@ -3070,6 +3186,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
int viewType;
+ /**
+ * When this boolean is set, the view has been added to the AbsListView
+ * at least once. It is used to know whether headers/footers have already
+ * been added to the list view and whether they should be treated as
+ * recycled views or not.
+ */
+ boolean recycledHeaderFooter;
+
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
@@ -3203,7 +3327,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (lp != null && lp.viewType != AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
// However, we will NOT place them into scrap views.
- activeViews[i] = getChildAt(i);
+ activeViews[i] = child;
}
}
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 9ebfa86..37d5bfe 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -40,9 +40,15 @@ public abstract class AbsSeekBar extends ProgressBar {
* Whether this is user seekable.
*/
boolean mIsUserSeekable = true;
+
+ /**
+ * On key presses (right or left), the amount to increment/decrement the
+ * progress.
+ */
+ private int mKeyProgressIncrement = 1;
private static final int NO_ALPHA = 0xFF;
- float mDisabledAlpha;
+ private float mDisabledAlpha;
public AbsSeekBar(Context context) {
super(context);
@@ -101,6 +107,39 @@ public abstract class AbsSeekBar extends ProgressBar {
invalidate();
}
+ /**
+ * Sets the amount of progress changed via the arrow keys.
+ *
+ * @param increment The amount to increment or decrement when the user
+ * presses the arrow keys.
+ */
+ public void setKeyProgressIncrement(int increment) {
+ mKeyProgressIncrement = increment < 0 ? -increment : increment;
+ }
+
+ /**
+ * Returns the amount of progress changed via the arrow keys.
+ * <p>
+ * By default, this will be a value that is derived from the max progress.
+ *
+ * @return The amount to increment or decrement when the user presses the
+ * arrow keys. This will be positive.
+ */
+ public int getKeyProgressIncrement() {
+ return mKeyProgressIncrement;
+ }
+
+ @Override
+ public synchronized void setMax(int max) {
+ super.setMax(max);
+
+ if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
+ // It will take the user too long to change this via keys, change it
+ // to something more reasonable
+ setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
+ }
+ }
+
@Override
protected boolean verifyDrawable(Drawable who) {
return who == mThumb || super.verifyDrawable(who);
@@ -316,12 +355,12 @@ public abstract class AbsSeekBar extends ProgressBar {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (progress <= 0) break;
- setProgress(progress - 1, true);
+ setProgress(progress - mKeyProgressIncrement, true);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (progress >= getMax()) break;
- setProgress(progress + 1, true);
+ setProgress(progress + mKeyProgressIncrement, true);
return true;
}
diff --git a/core/java/android/widget/Adapter.java b/core/java/android/widget/Adapter.java
index e952dd5..f2b3e2a 100644
--- a/core/java/android/widget/Adapter.java
+++ b/core/java/android/widget/Adapter.java
@@ -116,7 +116,7 @@ public interface Adapter {
* can be converted to the other in {@link #getView}. Note: Integers must be in the
* range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can
* also be returned.
- * @see IGNORE_ITEM_VIEW_TYPE
+ * @see #IGNORE_ITEM_VIEW_TYPE
*/
int getItemViewType(int position);
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index fbb0105..f847bc3 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.text.format.Time;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.RemoteViews.RemoteView;
import java.util.TimeZone;
@@ -35,6 +36,7 @@ import java.util.TimeZone;
* This widget display an analogic clock with two hands for hours and
* minutes.
*/
+@RemoteView
public class AnalogClock extends View {
private Time mCalendar;
@@ -46,7 +48,6 @@ public class AnalogClock extends View {
private int mDialHeight;
private boolean mAttached;
- private long mLastTime;
private final Handler mHandler = new Handler();
private float mMinutes;
@@ -94,7 +95,6 @@ public class AnalogClock extends View {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- onTimeChanged();
if (!mAttached) {
mAttached = true;
IntentFilter filter = new IntentFilter();
@@ -105,6 +105,15 @@ public class AnalogClock extends View {
getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
}
+
+ // NOTE: It's safe to do these after registering the receiver since the receiver always runs
+ // in the main thread, therefore the receiver can't run before this method returns.
+
+ // The time zone may have changed while the receiver wasn't registered, so update the Time
+ mCalendar = new Time();
+
+ // Make sure we update to the current time
+ onTimeChanged();
}
@Override
@@ -210,9 +219,7 @@ public class AnalogClock extends View {
}
private void onTimeChanged() {
- long time = System.currentTimeMillis();
- mCalendar.set(time);
- mLastTime = time;
+ mCalendar.setToNow();
int hour = mCalendar.hour;
int minute = mCalendar.minute;
@@ -229,8 +236,6 @@ public class AnalogClock extends View {
if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
String tz = intent.getStringExtra("time-zone");
mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
- } else {
- mCalendar = new Time();
}
onTimeChanged();
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index c65a3ce..c28210d 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -25,6 +25,8 @@ import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Comparator;
+import java.util.Collections;
/**
* A ListAdapter that manages a ListView backed by an array of arbitrary
@@ -227,6 +229,17 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
}
/**
+ * Sorts the content of this adapter using the specified comparator.
+ *
+ * @param comparator The comparator used to sort the objects contained
+ * in this adapter.
+ */
+ public void sort(Comparator<? super T> comparator) {
+ Collections.sort(mObjects, comparator);
+ if (mNotifyOnChange) notifyDataSetChanged();
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 7d52901..e613541 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -78,6 +78,8 @@ import com.android.internal.R;
* @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
* @attr ref android.R.styleable#AutoCompleteTextView_completionHintView
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
*/
public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
static final boolean DEBUG = false;
@@ -96,6 +98,9 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private DropDownListView mDropDownList;
private int mDropDownVerticalOffset;
private int mDropDownHorizontalOffset;
+ private int mDropDownAnchorId;
+ private View mDropDownAnchorView; // view is retrieved lazily from id once needed
+ private int mDropDownWidth;
private Drawable mDropDownListHighlight;
@@ -110,8 +115,14 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private Validator mValidator = null;
+ private boolean mBlockCompletion;
+
private AutoCompleteTextView.ListSelectorHider mHideSelector;
+ // Indicates whether this AutoCompleteTextView is attached to a window or not
+ // The widget is attached to a window when mAttachCount > 0
+ private int mAttachCount;
+
public AutoCompleteTextView(Context context) {
this(context, null);
}
@@ -141,23 +152,29 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
mDropDownHorizontalOffset = (int)
a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
+
+ // Get the anchor's id now, but the view won't be ready, so wait to actually get the
+ // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
+ // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return
+ // this TextView, as a default anchoring point.
+ mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
+ View.NO_ID);
+
+ // For dropdown width, the developer can specify a specific width, or FILL_PARENT
+ // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
+ mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
R.layout.simple_dropdown_hint);
- // A little trickiness for backwards compatibility: if the app
- // didn't specify an explicit content type, then we will fill in the
- // auto complete flag for them.
- int contentType = a.getInt(
- R.styleable.AutoCompleteTextView_inputType,
- EditorInfo.TYPE_NULL);
- if (contentType == EditorInfo.TYPE_NULL) {
- contentType = getInputType();
- if ((contentType&EditorInfo.TYPE_MASK_CLASS)
- == EditorInfo.TYPE_CLASS_TEXT) {
- contentType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
- setRawInputType(contentType);
- }
+ // Always turn on the auto complete input type flag, since it
+ // makes no sense to use this widget without it.
+ int inputType = getInputType();
+ if ((inputType&EditorInfo.TYPE_MASK_CLASS)
+ == EditorInfo.TYPE_CLASS_TEXT) {
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
+ setRawInputType(inputType);
}
a.recycle();
@@ -187,6 +204,49 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void setCompletionHint(CharSequence hint) {
mHintText = hint;
}
+
+ /**
+ * <p>Returns the current width for the auto-complete drop down list. This can
+ * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
+ *
+ * @return the width for the drop down list
+ */
+ public int getDropDownWidth() {
+ return mDropDownWidth;
+ }
+
+ /**
+ * <p>Sets the current width for the auto-complete drop down list. This can
+ * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
+ *
+ * @param width the width to use
+ */
+ public void setDropDownWidth(int width) {
+ mDropDownWidth = width;
+ }
+
+ /**
+ * <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
+ *
+ * @return the view's id, or {@link View#NO_ID} if none specified
+ */
+ public int getDropDownAnchor() {
+ return mDropDownAnchorId;
+ }
+
+ /**
+ * <p>Sets the view to which the auto-complete drop down list should anchor. The view
+ * corresponding to this id will not be loaded until the next time it is needed to avoid
+ * loading a view which is not yet instantiated.</p>
+ *
+ * @param id the id to anchor the drop down list view to
+ */
+ public void setDropDownAnchor(int id) {
+ mDropDownAnchorId = id;
+ mDropDownAnchorView = null;
+ }
/**
* <p>Returns the number of characters the user must type before the drop
@@ -300,6 +360,15 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* <p>Changes the list of data used for auto completion. The provided list
* must be a filterable list adapter.</p>
+ *
+ * <p>The caller is still responsible for managing any resources used by the adapter.
+ * Notably, when the AutoCompleteTextView is closed or released, the adapter is not notified.
+ * A common case is the use of {@link android.widget.CursorAdapter}, which
+ * contains a {@link android.database.Cursor} that must be closed. This can be done
+ * automatically (see
+ * {@link android.app.Activity#startManagingCursor(android.database.Cursor)
+ * startManagingCursor()}),
+ * or by manually closing the cursor when the AutoCompleteTextView is dismissed.</p>
*
* @param adapter the adapter holding the auto completion data
*
@@ -368,7 +437,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
&& keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
int curIndex = mDropDownList.getSelectedItemPosition();
boolean consumed;
- if (keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= 0) {
+ final boolean below = !mPopup.isAboveAnchor();
+ if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= 0) ||
+ (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >=
+ mDropDownList.getAdapter().getCount() - 1)) {
// When the selection is at the top, we block the key
// event to prevent focus from moving.
mDropDownList.hideSelector();
@@ -389,14 +461,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
// by ensuring it has focus and getting its window out
// of touch mode.
mDropDownList.requestFocusFromTouch();
- if (false) {
- // Update whether the pop-up is in front of or behind
- // the input method, depending on whether the user has
- // moved down in it.
- mPopup.setInputMethodMode(curIndex > 0
- ? PopupWindow.INPUT_METHOD_NOT_NEEDED
- : PopupWindow.INPUT_METHOD_NEEDED);
- }
mPopup.update();
switch (keyCode) {
@@ -409,13 +473,15 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
return true;
}
} else {
- if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+ if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
// when the selection is at the bottom, we block the
// event to avoid going to the next focusable widget
Adapter adapter = mDropDownList.getAdapter();
if (adapter != null && curIndex == adapter.getCount() - 1) {
return true;
}
+ } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex == 0) {
+ return true;
}
}
}
@@ -430,6 +496,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
boolean handled = super.onKeyDown(keyCode, event);
mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+ if (handled && isPopupShowing() && mDropDownList != null) {
+ clearListSelection();
+ }
+
return handled;
}
@@ -462,6 +532,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
void doBeforeTextChanged() {
+ if (mBlockCompletion) return;
+
// when text is changed, inserted or deleted, we attempt to show
// the drop down
mOpenBefore = isPopupShowing();
@@ -469,6 +541,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
void doAfterTextChanged() {
+ if (mBlockCompletion) return;
+
// if the list was open before the keystroke, but closed afterwards,
// then something in the keystroke processing (an input filter perhaps)
// called performCompletion() and we shouldn't do any more processing.
@@ -556,6 +630,16 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
/**
+ * We're changing the adapter and its views so really, really clear everything out
+ * @hide - for SearchDialog only
+ */
+ public void resetListAndClearViews() {
+ if (mDropDownList != null) {
+ mDropDownList.resetListAndClearViews();
+ }
+ }
+
+ /**
* <p>Starts filtering the content of the drop down list. The filtering
* pattern is the content of the edit box. Subclasses should override this
* method to filter with a different pattern, for instance a substring of
@@ -579,9 +663,13 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
performCompletion(null, -1, -1);
}
- @Override public void onCommitCompletion(CompletionInfo completion) {
+ @Override
+ public void onCommitCompletion(CompletionInfo completion) {
if (isPopupShowing()) {
+ mBlockCompletion = true;
replaceText(completion.getText());
+ mBlockCompletion = false;
+
if (mItemClickListener != null) {
final DropDownListView list = mDropDownList;
// Note that we don't have a View here, so we will need to
@@ -604,7 +692,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
Log.w(TAG, "performCompletion: no selected item");
return;
}
+
+ mBlockCompletion = true;
replaceText(convertSelectionToString(selectedItem));
+ mBlockCompletion = false;
if (mItemClickListener != null) {
final DropDownListView list = mDropDownList;
@@ -620,6 +711,14 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
dismissDropDown();
}
+
+ /**
+ * Identifies whether the view is currently performing a text completion, so subclasses
+ * can decide whether to respond to text changed events.
+ */
+ public boolean isPerformingCompletion() {
+ return mBlockCompletion;
+ }
/**
* <p>Performs the text completion by replacing the current text by the
@@ -636,6 +735,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
public void onFilterComplete(int count) {
+ if (mAttachCount <= 0) return;
+
/*
* This checks enoughToFilter() again because filtering requests
* are asynchronous, so the result may come back after enough text
@@ -671,8 +772,15 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mAttachCount++;
+ }
+
+ @Override
protected void onDetachedFromWindow() {
dismissDropDown();
+ mAttachCount--;
super.onDetachedFromWindow();
}
@@ -694,11 +802,23 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
boolean result = super.setFrame(l, t, r, b);
if (mPopup.isShowing()) {
- mPopup.update(this, getMeasuredWidth() - mPaddingLeft - mPaddingRight, -1);
+ mPopup.update(this, r - l, -1);
}
return result;
}
+
+ /**
+ * <p>Used for lazy instantiation of the anchor view from the id we have. If the value of
+ * the id is NO_ID or we can't find a view for the given id, we return this TextView as
+ * the default anchoring point.</p>
+ */
+ private View getDropDownAnchorView() {
+ if (mDropDownAnchorView == null && mDropDownAnchorId != View.NO_ID) {
+ mDropDownAnchorView = getRootView().findViewById(mDropDownAnchorId);
+ }
+ return mDropDownAnchorView == null ? this : mDropDownAnchorView;
+ }
/**
* <p>Displays the drop down on screen.</p>
@@ -706,16 +826,36 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void showDropDown() {
int height = buildDropDown();
if (mPopup.isShowing()) {
- mPopup.update(this, mDropDownHorizontalOffset, mDropDownVerticalOffset,
- getMeasuredWidth() - mPaddingLeft - mPaddingRight, height);
+ int widthSpec;
+ if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+ // The call to PopupWindow's update method below can accept -1 for any
+ // value you do not want to update.
+ widthSpec = -1;
+ } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ widthSpec = getDropDownAnchorView().getWidth();
+ } else {
+ widthSpec = mDropDownWidth;
+ }
+ mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
+ mDropDownVerticalOffset, widthSpec, height);
} else {
- mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
- mPopup.setWidth(getMeasuredWidth() - mPaddingLeft - mPaddingRight);
+ if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+ mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ } else {
+ mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
+ if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ mPopup.setWidth(getDropDownAnchorView().getWidth());
+ } else {
+ mPopup.setWidth(mDropDownWidth);
+ }
+ }
mPopup.setHeight(height);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
mPopup.setOutsideTouchable(true);
mPopup.setTouchInterceptor(new PopupTouchIntercepter());
- mPopup.showAsDropDown(this, mDropDownHorizontalOffset, mDropDownVerticalOffset);
+ mPopup.showAsDropDown(getDropDownAnchorView(),
+ mDropDownHorizontalOffset, mDropDownVerticalOffset);
mDropDownList.setSelection(ListView.INVALID_POSITION);
mDropDownList.hideSelector();
mDropDownList.requestFocus();
@@ -739,7 +879,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
int N = mAdapter.getCount();
if (N > 20) N = 20;
CompletionInfo[] completions = new CompletionInfo[N];
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
Object item = mAdapter.getItem(i);
long id = mAdapter.getItemId(i);
completions[i] = new CompletionInfo(id, i,
@@ -783,7 +923,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
// measure the hint's height to find how much more vertical space
// we need to add to the drop down's height
- int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
+ int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST);
int heightSpec = MeasureSpec.UNSPECIFIED;
hintView.measure(widthSpec, heightSpec);
@@ -807,8 +947,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
// Max height available on the screen for a popup anchored to us
- final int maxHeight = mPopup.getMaxAvailableHeight(this);
- otherHeights += dropDownView.getPaddingTop() + dropDownView.getPaddingBottom();
+ final int maxHeight = mPopup.getMaxAvailableHeight(this, mDropDownVerticalOffset);
+ //otherHeights += dropDownView.getPaddingTop() + dropDownView.getPaddingBottom();
return mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights;
@@ -984,6 +1124,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
protected int[] onCreateDrawableState(int extraSpace) {
int[] res = super.onCreateDrawableState(extraSpace);
+ //noinspection ConstantIfStatement
if (false) {
StringBuilder sb = new StringBuilder("Created drawable state: [");
for (int i=0; i<res.length; i++) {
diff --git a/core/java/android/widget/BaseAdapter.java b/core/java/android/widget/BaseAdapter.java
index 1921d73..532fd76 100644
--- a/core/java/android/widget/BaseAdapter.java
+++ b/core/java/android/widget/BaseAdapter.java
@@ -42,6 +42,10 @@ public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
mDataSetObservable.unregisterObserver(observer);
}
+ /**
+ * Notifies the attached View that the underlying data has been changed
+ * and it should refresh itself.
+ */
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 7086ae2..91add58 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -46,6 +46,18 @@ import java.util.Locale;
public class Chronometer extends TextView {
private static final String TAG = "Chronometer";
+ /**
+ * A callback that notifies when the chronometer has incremented on its own.
+ */
+ public interface OnChronometerTickListener {
+
+ /**
+ * Notification that the chronometer has changed.
+ */
+ void onChronometerTick(Chronometer chronometer);
+
+ }
+
private long mBase;
private boolean mVisible;
private boolean mStarted;
@@ -56,7 +68,11 @@ public class Chronometer extends TextView {
private Locale mFormatterLocale;
private Object[] mFormatterArgs = new Object[1];
private StringBuilder mFormatBuilder;
-
+ private OnChronometerTickListener mOnChronometerTickListener;
+ private StringBuilder mRecycle = new StringBuilder(8);
+
+ private static final int TICK_WHAT = 2;
+
/**
* Initialize this Chronometer object.
* Sets the base to the current time.
@@ -99,8 +115,10 @@ public class Chronometer extends TextView {
*
* @param base Use the {@link SystemClock#elapsedRealtime} time base.
*/
+ @android.view.RemotableViewMethod
public void setBase(long base) {
mBase = base;
+ dispatchChronometerTick();
updateText(SystemClock.elapsedRealtime());
}
@@ -122,6 +140,7 @@ public class Chronometer extends TextView {
*
* @param format the format string.
*/
+ @android.view.RemotableViewMethod
public void setFormat(String format) {
mFormat = format;
if (format != null && mFormatBuilder == null) {
@@ -137,6 +156,23 @@ public class Chronometer extends TextView {
}
/**
+ * Sets the listener to be called when the chronometer changes.
+ *
+ * @param listener The listener.
+ */
+ public void setOnChronometerTickListener(OnChronometerTickListener listener) {
+ mOnChronometerTickListener = listener;
+ }
+
+ /**
+ * @return The listener (may be null) that is listening for chronometer change
+ * events.
+ */
+ public OnChronometerTickListener getOnChronometerTickListener() {
+ return mOnChronometerTickListener;
+ }
+
+ /**
* Start counting up. This does not affect the base as set from {@link #setBase}, just
* the view display.
*
@@ -161,6 +197,15 @@ public class Chronometer extends TextView {
updateRunning();
}
+ /**
+ * The same as calling {@link #start} or {@link #stop}.
+ */
+ @android.view.RemotableViewMethod
+ public void setStarted(boolean started) {
+ mStarted = started;
+ updateRunning();
+ }
+
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -175,10 +220,10 @@ public class Chronometer extends TextView {
updateRunning();
}
- private void updateText(long now) {
+ private synchronized void updateText(long now) {
long seconds = now - mBase;
seconds /= 1000;
- String text = DateUtils.formatElapsedTime(seconds);
+ String text = DateUtils.formatElapsedTime(mRecycle, seconds);
if (mFormat != null) {
Locale loc = Locale.getDefault();
@@ -206,7 +251,10 @@ public class Chronometer extends TextView {
if (running != mRunning) {
if (running) {
updateText(SystemClock.elapsedRealtime());
- mHandler.sendMessageDelayed(Message.obtain(), 1000);
+ dispatchChronometerTick();
+ mHandler.sendMessageDelayed(Message.obtain(mHandler, TICK_WHAT), 1000);
+ } else {
+ mHandler.removeMessages(TICK_WHAT);
}
mRunning = running;
}
@@ -214,10 +262,17 @@ public class Chronometer extends TextView {
private Handler mHandler = new Handler() {
public void handleMessage(Message m) {
- if (mStarted) {
+ if (mRunning) {
updateText(SystemClock.elapsedRealtime());
- sendMessageDelayed(Message.obtain(), 1000);
+ dispatchChronometerTick();
+ sendMessageDelayed(Message.obtain(this, TICK_WHAT), 1000);
}
}
};
+
+ void dispatchChronometerTick() {
+ if (mOnChronometerTickListener != null) {
+ mOnChronometerTickListener.onChronometerTick(this);
+ }
+ }
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index e56a741..d4482dc 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -251,7 +251,12 @@ public abstract class CompoundButton extends Button implements Checkable {
invalidate();
}
}
-
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mButtonDrawable;
+ }
+
static class SavedState extends BaseSavedState {
boolean checked;
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index 3d758e7..898e501 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -348,6 +348,21 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
mFilterQueryProvider = filterQueryProvider;
}
+ /**
+ * Called when the {@link ContentObserver} on the cursor receives a change notification.
+ * The default implementation provides the auto-requery logic, but may be overridden by
+ * sub classes.
+ *
+ * @see ContentObserver#onChange(boolean)
+ * @hide pending API Council approval
+ */
+ protected void onContentChanged() {
+ if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
+ if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
+ mDataValid = mCursor.requery();
+ }
+ }
+
private class ChangeObserver extends ContentObserver {
public ChangeObserver() {
super(new Handler());
@@ -360,10 +375,7 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
@Override
public void onChange(boolean selfChange) {
- if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
- if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
- mDataValid = mCursor.requery();
- }
+ onContentChanged();
}
}
diff --git a/core/java/android/widget/CursorFilter.java b/core/java/android/widget/CursorFilter.java
index afd5b10..dbded69 100644
--- a/core/java/android/widget/CursorFilter.java
+++ b/core/java/android/widget/CursorFilter.java
@@ -60,11 +60,10 @@ class CursorFilter extends Filter {
}
@Override
- protected void publishResults(CharSequence constraint,
- FilterResults results) {
+ protected void publishResults(CharSequence constraint, FilterResults results) {
Cursor oldCursor = mClient.getCursor();
- if (results.values != oldCursor) {
+ if (results.values != null && results.values != oldCursor) {
mClient.changeCursor((Cursor) results.values);
}
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 67010b2..54f2707 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -47,11 +47,8 @@ public class DatePicker extends FrameLayout {
/* UI Components */
private final NumberPicker mDayPicker;
private final NumberPicker mMonthPicker;
- private final NumberPicker mYearPicker;
-
- private final int mStartYear;
- private final int mEndYear;
-
+ private final NumberPicker mYearPicker;
+
/**
* How we notify users the date has changed.
*/
@@ -87,12 +84,9 @@ public class DatePicker extends FrameLayout {
public DatePicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- LayoutInflater inflater =
- (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.date_picker,
- this, // we are the parent
- true);
-
+ LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.date_picker, this, true);
+
mDayPicker = (NumberPicker) findViewById(R.id.day);
mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
mDayPicker.setSpeed(100);
@@ -134,20 +128,17 @@ public class DatePicker extends FrameLayout {
});
// attributes
- TypedArray a = context
- .obtainStyledAttributes(attrs, R.styleable.DatePicker);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker);
- mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR);
- mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
+ int mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR);
+ int mEndYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
mYearPicker.setRange(mStartYear, mEndYear);
a.recycle();
// initialize to current date
Calendar cal = Calendar.getInstance();
- init(cal.get(Calendar.YEAR),
- cal.get(Calendar.MONTH),
- cal.get(Calendar.DAY_OF_MONTH), null);
+ init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null);
// re-order the number pickers to match the current date format
reorderPickers();
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 3de561a..0fc8f49 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -187,6 +187,9 @@ public class ExpandableListView extends ListView {
private Drawable mChildDivider;
private boolean mClipChildDivider;
+ // Bounds of the indicator to be drawn
+ private final Rect mIndicatorRect = new Rect();
+
public ExpandableListView(Context context) {
this(context, null);
}
@@ -247,17 +250,16 @@ public class ExpandableListView extends ListView {
final int myB = mBottom;
- PositionMetadata pos = null;
+ PositionMetadata pos;
View item;
Drawable indicator;
int t, b;
// Start at a value that is neither child nor group
int lastItemType = ~(ExpandableListPosition.CHILD | ExpandableListPosition.GROUP);
-
- // Bounds of the indicator to be drawn
- Rect indicatorRect = new Rect();
-
+
+ final Rect indicatorRect = mIndicatorRect;
+
// The "child" mentioned in the following two lines is this
// View's child, not referring to an expandable list's
// notion of a child (as opposed to a group)
@@ -303,11 +305,11 @@ public class ExpandableListView extends ListView {
// Use item's full height + the divider height
if (mStackFromBottom) {
// See ListView#dispatchDraw
- indicatorRect.top = t - mDividerHeight;
+ indicatorRect.top = t;// - mDividerHeight;
indicatorRect.bottom = b;
} else {
indicatorRect.top = t;
- indicatorRect.bottom = b + mDividerHeight;
+ indicatorRect.bottom = b;// + mDividerHeight;
}
// Get the indicator (with its state set to the item's state)
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index bdcfeef..3368477 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -26,7 +26,6 @@ import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.SystemClock;
-import android.util.TypedValue;
import android.view.MotionEvent;
/**
@@ -34,7 +33,8 @@ import android.view.MotionEvent;
*/
class FastScroller {
-
+ // Minimum number of pages to justify showing a fast scroll thumb
+ private static int MIN_PAGES = 4;
// Scroll thumb not showing
private static final int STATE_NONE = 0;
// Not implemented yet - fade-in transition
@@ -61,6 +61,8 @@ class FastScroller {
private int mVisibleItem;
private Paint mPaint;
private int mListOffset;
+ private int mItemCount = -1;
+ private boolean mLongList;
private Object [] mSections;
private String mSectionText;
@@ -154,6 +156,10 @@ class FastScroller {
setState(STATE_NONE);
}
+ boolean isVisible() {
+ return !(mState == STATE_NONE);
+ }
+
public void draw(Canvas canvas) {
if (mState == STATE_NONE) {
@@ -214,7 +220,17 @@ class FastScroller {
void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
-
+ // Are there enough pages to require fast scroll? Recompute only if total count changes
+ if (mItemCount != totalItemCount && visibleItemCount > 0) {
+ mItemCount = totalItemCount;
+ mLongList = mItemCount / visibleItemCount >= MIN_PAGES;
+ }
+ if (!mLongList) {
+ if (mState != STATE_NONE) {
+ setState(STATE_NONE);
+ }
+ return;
+ }
if (totalItemCount - visibleItemCount > 0 && mState != STATE_DRAGGING ) {
mThumbY = ((mList.getHeight() - mThumbH) * firstVisibleItem)
/ (totalItemCount - visibleItemCount);
@@ -296,12 +312,17 @@ class FastScroller {
// Non-existent letter
while (section > 0) {
section--;
- prevIndex = mSectionIndexer.getPositionForSection(section);
- if (prevIndex != index) {
- prevSection = section;
- sectionIndex = section;
- break;
- }
+ prevIndex = mSectionIndexer.getPositionForSection(section);
+ if (prevIndex != index) {
+ prevSection = section;
+ sectionIndex = section;
+ break;
+ } else if (section == 0) {
+ // When section reaches 0 here, sectionIndex must follow it.
+ // Assuming mSectionIndexer.getPositionForSection(0) == 0.
+ sectionIndex = 0;
+ break;
+ }
}
}
// Find the next index, in case the assumed next index is not
diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java
index 7f1601e..7e55c78 100644
--- a/core/java/android/widget/Filter.java
+++ b/core/java/android/widget/Filter.java
@@ -20,6 +20,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.util.Log;
/**
* <p>A filter constrains data with a filtering pattern.</p>
@@ -36,14 +37,16 @@ import android.os.Message;
* @see android.widget.Filterable
*/
public abstract class Filter {
+ private static final String LOG_TAG = "Filter";
+
private static final String THREAD_NAME = "Filter";
private static final int FILTER_TOKEN = 0xD0D0F00D;
private static final int FINISH_TOKEN = 0xDEADBEEF;
-
+
private Handler mThreadHandler;
private Handler mResultHandler;
- private String mConstraint;
- private boolean mConstraintIsValid = false;
+
+ private final Object mLock = new Object();
/**
* <p>Creates a new asynchronous filter.</p>
@@ -80,15 +83,7 @@ public abstract class Filter {
* @see #publishResults(CharSequence, android.widget.Filter.FilterResults)
*/
public final void filter(CharSequence constraint, FilterListener listener) {
- synchronized (this) {
- String constraintAsString = constraint != null ? constraint.toString() : null;
- if (mConstraintIsValid && (
- (constraintAsString == null && mConstraint == null) ||
- (constraintAsString != null && constraintAsString.equals(mConstraint)))) {
- // nothing to do
- return;
- }
-
+ synchronized (mLock) {
if (mThreadHandler == null) {
HandlerThread thread = new HandlerThread(THREAD_NAME);
thread.start();
@@ -100,16 +95,13 @@ public abstract class Filter {
RequestArguments args = new RequestArguments();
// make sure we use an immutable copy of the constraint, so that
// it doesn't change while the filter operation is in progress
- args.constraint = constraintAsString;
+ args.constraint = constraint != null ? constraint.toString() : null;
args.listener = listener;
message.obj = args;
mThreadHandler.removeMessages(FILTER_TOKEN);
mThreadHandler.removeMessages(FINISH_TOKEN);
mThreadHandler.sendMessage(message);
-
- mConstraint = constraintAsString;
- mConstraintIsValid = true;
}
}
@@ -221,13 +213,16 @@ public abstract class Filter {
RequestArguments args = (RequestArguments) msg.obj;
try {
args.results = performFiltering(args.constraint);
+ } catch (Exception e) {
+ args.results = new FilterResults();
+ Log.w(LOG_TAG, "An exception occured during performFiltering()!", e);
} finally {
message = mResultHandler.obtainMessage(what);
message.obj = args;
message.sendToTarget();
}
- synchronized (this) {
+ synchronized (mLock) {
if (mThreadHandler != null) {
Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN);
mThreadHandler.sendMessageDelayed(finishMessage, 3000);
@@ -235,7 +230,7 @@ public abstract class Filter {
}
break;
case FINISH_TOKEN:
- synchronized (this) {
+ synchronized (mLock) {
if (mThreadHandler != null) {
mThreadHandler.getLooper().quit();
mThreadHandler = null;
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index b4ed3ba..8aafee2 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -93,6 +93,7 @@ public class FrameLayout extends ViewGroup {
*
* @attr ref android.R.styleable#FrameLayout_foregroundGravity
*/
+ @android.view.RemotableViewMethod
public void setForegroundGravity(int foregroundGravity) {
if (mForegroundGravity != foregroundGravity) {
if ((foregroundGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
@@ -348,6 +349,7 @@ public class FrameLayout extends ViewGroup {
*
* @attr ref android.R.styleable#FrameLayout_measureAllChildren
*/
+ @android.view.RemotableViewMethod
public void setMeasureAllChildren(boolean measureAll) {
mMeasureAllChildren = measureAll;
}
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index 7b9735c..e7b303a 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -27,6 +27,7 @@ import android.util.Config;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -180,7 +181,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
public Gallery(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mGestureDetector = new GestureDetector(this);
+ mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setIsLongpressEnabled(true);
TypedArray a = context.obtainStyledAttributes(
@@ -994,6 +995,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
return;
}
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
long id = getItemIdAtPosition(mDownTouchPosition);
dispatchLongPress(mDownTouchView, mDownTouchPosition, id);
}
@@ -1086,6 +1088,10 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
handled = super.showContextMenuForChild(this);
}
+ if (handled) {
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
+
return handled;
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 38bfc7c..11fab8f 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -924,32 +924,24 @@ public class GridView extends AbsListView {
final int count = mItemCount;
if (count > 0) {
final View child = obtainView(0);
- final int childViewType = mAdapter.getItemViewType(0);
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+ if (p == null) {
+ p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
- child.setLayoutParams(lp);
- }
- lp.viewType = childViewType;
-
- final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
- mListPadding.left + mListPadding.right, lp.width);
-
- int lpHeight = lp.height;
-
- int childHeightSpec;
- if (lpHeight > 0) {
- childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
- } else {
- childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ child.setLayoutParams(p);
}
+ p.viewType = mAdapter.getItemViewType(0);
+ int childHeightSpec = getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
+ int childWidthSpec = getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
child.measure(childWidthSpec, childHeightSpec);
+
childHeight = child.getMeasuredHeight();
- if (mRecycler.shouldRecycleViewType(childViewType)) {
+ if (mRecycler.shouldRecycleViewType(p.viewType)) {
mRecycler.addScrapView(child);
}
}
@@ -1337,11 +1329,8 @@ public class GridView extends AbsListView {
*/
@Override
void setSelectionInt(int position) {
- mBlockLayoutRequests = true;
setNextSelectedPositionInt(position);
layoutChildren();
-
- mBlockLayoutRequests = false;
}
@Override
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
new file mode 100644
index 0000000..652e30c
--- /dev/null
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -0,0 +1,1197 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.util.AttributeSet;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.KeyEvent;
+import android.view.FocusFinder;
+import android.view.MotionEvent;
+import android.view.ViewParent;
+import android.view.animation.AnimationUtils;
+import android.content.Context;
+import android.content.res.TypedArray;
+
+import java.util.List;
+
+/**
+ * Layout container for a view hierarchy that can be scrolled by the user,
+ * allowing it to be larger than the physical display. A HorizontalScrollView
+ * is a {@link FrameLayout}, meaning you should place one child in it
+ * containing the entire contents to scroll; this child may itself be a layout
+ * manager with a complex hierarchy of objects. A child that is often used
+ * is a {@link LinearLayout} in a horizontal orientation, presenting a horizontal
+ * array of top-level items that the user can scroll through.
+ *
+ * <p>You should never use a HorizontalScrollView with a {@link ListView}, since
+ * ListView takes care of its own scrolling. Most importantly, doing this
+ * defeats all of the important optimizations in ListView for dealing with
+ * large lists, since it effectively forces the ListView to display its entire
+ * list of items to fill up the infinite container supplied by HorizontalScrollView.
+ *
+ * <p>The {@link TextView} class also
+ * takes care of its own scrolling, so does not require a ScrollView, but
+ * using the two together is possible to achieve the effect of a text view
+ * within a larger container.
+ *
+ * <p>HorizontalScrollView only supports horizontal scrolling.
+ */
+public class HorizontalScrollView extends FrameLayout {
+ private static final int ANIMATED_SCROLL_GAP = ScrollView.ANIMATED_SCROLL_GAP;
+
+ private static final float MAX_SCROLL_FACTOR = ScrollView.MAX_SCROLL_FACTOR;
+
+
+ private long mLastScroll;
+
+ private final Rect mTempRect = new Rect();
+ private Scroller mScroller;
+
+ /**
+ * Flag to indicate that we are moving focus ourselves. This is so the
+ * code that watches for focus changes initiated outside this ScrollView
+ * knows that it does not have to do anything.
+ */
+ private boolean mScrollViewMovedFocus;
+
+ /**
+ * Position of the last motion event.
+ */
+ private float mLastMotionX;
+
+ /**
+ * True when the layout has changed but the traversal has not come through yet.
+ * Ideally the view hierarchy would keep track of this for us.
+ */
+ private boolean mIsLayoutDirty = true;
+
+ /**
+ * The child to give focus to in the event that a child has requested focus while the
+ * layout is dirty. This prevents the scroll from being wrong if the child has not been
+ * laid out before requesting focus.
+ */
+ private View mChildToScrollTo = null;
+
+ /**
+ * True if the user is currently dragging this ScrollView around. This is
+ * not the same as 'is being flinged', which can be checked by
+ * mScroller.isFinished() (flinging begins when the user lifts his finger).
+ */
+ private boolean mIsBeingDragged = false;
+
+ /**
+ * Determines speed during touch scrolling
+ */
+ private VelocityTracker mVelocityTracker;
+
+ /**
+ * When set to true, the scroll view measure its child to make it fill the currently
+ * visible area.
+ */
+ private boolean mFillViewport;
+
+ /**
+ * Whether arrow scrolling is animated.
+ */
+ private boolean mSmoothScrollingEnabled = true;
+
+ private int mTouchSlop;
+
+ public HorizontalScrollView(Context context) {
+ this(context, null);
+ }
+
+ public HorizontalScrollView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.horizontalScrollViewStyle);
+ }
+
+ public HorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initScrollView();
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ android.R.styleable.HorizontalScrollView, defStyle, 0);
+
+ setFillViewport(a.getBoolean(android.R.styleable.HorizontalScrollView_fillViewport, false));
+
+ a.recycle();
+ }
+
+ @Override
+ protected float getLeftFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+
+ final int length = getHorizontalFadingEdgeLength();
+ if (mScrollX < length) {
+ return mScrollX / (float) length;
+ }
+
+ return 1.0f;
+ }
+
+ @Override
+ protected float getRightFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+
+ final int length = getHorizontalFadingEdgeLength();
+ final int rightEdge = getWidth() - mPaddingRight;
+ final int span = getChildAt(0).getRight() - mScrollX - rightEdge;
+ if (span < length) {
+ return span / (float) length;
+ }
+
+ return 1.0f;
+ }
+
+ /**
+ * @return The maximum amount this scroll view will scroll in response to
+ * an arrow event.
+ */
+ public int getMaxScrollAmount() {
+ return (int) (MAX_SCROLL_FACTOR * (mRight - mLeft));
+ }
+
+
+ private void initScrollView() {
+ mScroller = new Scroller(getContext());
+ setFocusable(true);
+ setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+ setWillNotDraw(false);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ @Override
+ public void addView(View child) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("HorizontalScrollView can host only one direct child");
+ }
+
+ super.addView(child);
+ }
+
+ @Override
+ public void addView(View child, int index) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("HorizontalScrollView can host only one direct child");
+ }
+
+ super.addView(child, index);
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("HorizontalScrollView can host only one direct child");
+ }
+
+ super.addView(child, params);
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("HorizontalScrollView can host only one direct child");
+ }
+
+ super.addView(child, index, params);
+ }
+
+ /**
+ * @return Returns true this HorizontalScrollView can be scrolled
+ */
+ private boolean canScroll() {
+ View child = getChildAt(0);
+ if (child != null) {
+ int childWidth = child.getWidth();
+ return getWidth() < childWidth + mPaddingLeft + mPaddingRight ;
+ }
+ return false;
+ }
+
+ /**
+ * Indicates whether this ScrollView's content is stretched to fill the viewport.
+ *
+ * @return True if the content fills the viewport, false otherwise.
+ */
+ public boolean isFillViewport() {
+ return mFillViewport;
+ }
+
+ /**
+ * Indicates this ScrollView whether it should stretch its content width to fill
+ * the viewport or not.
+ *
+ * @param fillViewport True to stretch the content's width to the viewport's
+ * boundaries, false otherwise.
+ */
+ public void setFillViewport(boolean fillViewport) {
+ if (fillViewport != mFillViewport) {
+ mFillViewport = fillViewport;
+ requestLayout();
+ }
+ }
+
+ /**
+ * @return Whether arrow scrolling will animate its transition.
+ */
+ public boolean isSmoothScrollingEnabled() {
+ return mSmoothScrollingEnabled;
+ }
+
+ /**
+ * Set whether arrow scrolling will animate its transition.
+ * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
+ */
+ public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
+ mSmoothScrollingEnabled = smoothScrollingEnabled;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (!mFillViewport) {
+ return;
+ }
+
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ if (widthMode == MeasureSpec.UNSPECIFIED) {
+ return;
+ }
+
+ final View child = getChildAt(0);
+ int width = getMeasuredWidth();
+ if (child.getMeasuredHeight() < width) {
+ final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop
+ + mPaddingBottom, lp.height);
+ width -= mPaddingLeft;
+ width -= mPaddingRight;
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ // Let the focused view and/or our descendants get the key first
+ boolean handled = super.dispatchKeyEvent(event);
+ if (handled) {
+ return true;
+ }
+ return executeKeyEvent(event);
+ }
+
+ /**
+ * You can call this function yourself to have the scroll view perform
+ * scrolling from a key event, just as if the event had been dispatched to
+ * it by the view hierarchy.
+ *
+ * @param event The key event to execute.
+ * @return Return true if the event was handled, else false.
+ */
+ public boolean executeKeyEvent(KeyEvent event) {
+ mTempRect.setEmpty();
+
+ if (!canScroll()) {
+ if (isFocused()) {
+ View currentFocused = findFocus();
+ if (currentFocused == this) currentFocused = null;
+ View nextFocused = FocusFinder.getInstance().findNextFocus(this,
+ currentFocused, View.FOCUS_RIGHT);
+ return nextFocused != null && nextFocused != this &&
+ nextFocused.requestFocus(View.FOCUS_RIGHT);
+ }
+ return false;
+ }
+
+ boolean handled = false;
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_LEFT);
+ } else {
+ handled = fullScroll(View.FOCUS_LEFT);
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_RIGHT);
+ } else {
+ handled = fullScroll(View.FOCUS_RIGHT);
+ }
+ break;
+ case KeyEvent.KEYCODE_SPACE:
+ pageScroll(event.isShiftPressed() ? View.FOCUS_LEFT : View.FOCUS_RIGHT);
+ break;
+ }
+ }
+
+ return handled;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onMotionEvent will be called and we do the actual
+ * scrolling there.
+ */
+
+ /*
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
+ return true;
+ }
+
+ if (!canScroll()) {
+ mIsBeingDragged = false;
+ return false;
+ }
+
+ final float x = ev.getX();
+
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ /*
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+
+ /*
+ * Locally do absolute value. mLastMotionX is set to the x value
+ * of the down event.
+ */
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ if (xDiff > mTouchSlop) {
+ mIsBeingDragged = true;
+ if (mParent != null) mParent.requestDisallowInterceptTouchEvent(true);
+ }
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ /* Remember location of down touch */
+ mLastMotionX = x;
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ mIsBeingDragged = !mScroller.isFinished();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ /* Release the drag */
+ mIsBeingDragged = false;
+ break;
+ }
+
+ /*
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
+ return mIsBeingDragged;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+ // Don't handle edge touches immediately -- they may actually belong to one of our
+ // descendants.
+ return false;
+ }
+
+ if (!canScroll()) {
+ return false;
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ final int action = ev.getAction();
+ final float x = ev.getX();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ // Remember where the motion event started
+ mLastMotionX = x;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Scroll to follow the motion event
+ final int deltaX = (int) (mLastMotionX - x);
+ mLastMotionX = x;
+
+ if (deltaX < 0) {
+ if (mScrollX > 0) {
+ scrollBy(deltaX, 0);
+ }
+ } else if (deltaX > 0) {
+ final int rightEdge = getWidth() - mPaddingRight;
+ final int availableToScroll = getChildAt(0).getRight() - mScrollX - rightEdge;
+ if (availableToScroll > 0) {
+ scrollBy(Math.min(availableToScroll, deltaX), 0);
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000);
+ int initialVelocity = (int) velocityTracker.getXVelocity();
+
+ if ((Math.abs(initialVelocity) >
+ ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
+ getChildCount() > 0) {
+ fling(-initialVelocity);
+ }
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * <p>
+ * Finds the next focusable component that fits in this View's bounds
+ * (excluding fading edges) pretending that this View's left is located at
+ * the parameter left.
+ * </p>
+ *
+ * @param leftFocus look for a candidate is the one at the left of the bounds
+ * if leftFocus is true, or at the right of the bounds if leftFocus
+ * is false
+ * @param left the left offset of the bounds in which a focusable must be
+ * found (the fading edge is assumed to start at this position)
+ * @param preferredFocusable the View that has highest priority and will be
+ * returned if it is within my bounds (null is valid)
+ * @return the next focusable component in the bounds or null if none can be found
+ */
+ private View findFocusableViewInMyBounds(final boolean leftFocus,
+ final int left, View preferredFocusable) {
+ /*
+ * The fading edge's transparent side should be considered for focus
+ * since it's mostly visible, so we divide the actual fading edge length
+ * by 2.
+ */
+ final int fadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
+ final int leftWithoutFadingEdge = left + fadingEdgeLength;
+ final int rightWithoutFadingEdge = left + getWidth() - fadingEdgeLength;
+
+ if ((preferredFocusable != null)
+ && (preferredFocusable.getLeft() < rightWithoutFadingEdge)
+ && (preferredFocusable.getRight() > leftWithoutFadingEdge)) {
+ return preferredFocusable;
+ }
+
+ return findFocusableViewInBounds(leftFocus, leftWithoutFadingEdge,
+ rightWithoutFadingEdge);
+ }
+
+ /**
+ * <p>
+ * Finds the next focusable component that fits in the specified bounds.
+ * </p>
+ *
+ * @param leftFocus look for a candidate is the one at the left of the bounds
+ * if leftFocus is true, or at the right of the bounds if
+ * leftFocus is false
+ * @param left the left offset of the bounds in which a focusable must be
+ * found
+ * @param right the right offset of the bounds in which a focusable must
+ * be found
+ * @return the next focusable component in the bounds or null if none can
+ * be found
+ */
+ private View findFocusableViewInBounds(boolean leftFocus, int left, int right) {
+
+ List<View> focusables = getFocusables(View.FOCUS_FORWARD);
+ View focusCandidate = null;
+
+ /*
+ * A fully contained focusable is one where its left is below the bound's
+ * left, and its right is above the bound's right. A partially
+ * contained focusable is one where some part of it is within the
+ * bounds, but it also has some part that is not within bounds. A fully contained
+ * focusable is preferred to a partially contained focusable.
+ */
+ boolean foundFullyContainedFocusable = false;
+
+ int count = focusables.size();
+ for (int i = 0; i < count; i++) {
+ View view = focusables.get(i);
+ int viewLeft = view.getLeft();
+ int viewRight = view.getRight();
+
+ if (left < viewRight && viewLeft < right) {
+ /*
+ * the focusable is in the target area, it is a candidate for
+ * focusing
+ */
+
+ final boolean viewIsFullyContained = (left < viewLeft) &&
+ (viewRight < right);
+
+ if (focusCandidate == null) {
+ /* No candidate, take this one */
+ focusCandidate = view;
+ foundFullyContainedFocusable = viewIsFullyContained;
+ } else {
+ final boolean viewIsCloserToBoundary =
+ (leftFocus && viewLeft < focusCandidate.getLeft()) ||
+ (!leftFocus && viewRight > focusCandidate.getRight());
+
+ if (foundFullyContainedFocusable) {
+ if (viewIsFullyContained && viewIsCloserToBoundary) {
+ /*
+ * We're dealing with only fully contained views, so
+ * it has to be closer to the boundary to beat our
+ * candidate
+ */
+ focusCandidate = view;
+ }
+ } else {
+ if (viewIsFullyContained) {
+ /* Any fully contained view beats a partially contained view */
+ focusCandidate = view;
+ foundFullyContainedFocusable = true;
+ } else if (viewIsCloserToBoundary) {
+ /*
+ * Partially contained view beats another partially
+ * contained view if it's closer
+ */
+ focusCandidate = view;
+ }
+ }
+ }
+ }
+ }
+
+ return focusCandidate;
+ }
+
+ /**
+ * <p>Handles scrolling in response to a "page up/down" shortcut press. This
+ * method will scroll the view by one page left or right and give the focus
+ * to the leftmost/rightmost component in the new visible area. If no
+ * component is a good candidate for focus, this scrollview reclaims the
+ * focus.</p>
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_LEFT}
+ * to go one page left or {@link android.view.View#FOCUS_RIGHT}
+ * to go one page right
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ public boolean pageScroll(int direction) {
+ boolean right = direction == View.FOCUS_RIGHT;
+ int width = getWidth();
+
+ if (right) {
+ mTempRect.left = getScrollX() + width;
+ int count = getChildCount();
+ if (count > 0) {
+ View view = getChildAt(count - 1);
+ if (mTempRect.left + width > view.getRight()) {
+ mTempRect.left = view.getRight() - width;
+ }
+ }
+ } else {
+ mTempRect.left = getScrollX() - width;
+ if (mTempRect.left < 0) {
+ mTempRect.left = 0;
+ }
+ }
+ mTempRect.right = mTempRect.left + width;
+
+ return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
+ }
+
+ /**
+ * <p>Handles scrolling in response to a "home/end" shortcut press. This
+ * method will scroll the view to the left or right and give the focus
+ * to the leftmost/rightmost component in the new visible area. If no
+ * component is a good candidate for focus, this scrollview reclaims the
+ * focus.</p>
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_LEFT}
+ * to go the left of the view or {@link android.view.View#FOCUS_RIGHT}
+ * to go the right
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ public boolean fullScroll(int direction) {
+ boolean right = direction == View.FOCUS_RIGHT;
+ int width = getWidth();
+
+ mTempRect.left = 0;
+ mTempRect.right = width;
+
+ if (right) {
+ int count = getChildCount();
+ if (count > 0) {
+ View view = getChildAt(count - 1);
+ mTempRect.right = view.getRight();
+ mTempRect.left = mTempRect.right - width;
+ }
+ }
+
+ return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
+ }
+
+ /**
+ * <p>Scrolls the view to make the area defined by <code>left</code> and
+ * <code>right</code> visible. This method attempts to give the focus
+ * to a component visible in this area. If no component can be focused in
+ * the new visible area, the focus is reclaimed by this scrollview.</p>
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_LEFT}
+ * to go left {@link android.view.View#FOCUS_RIGHT} to right
+ * @param left the left offset of the new area to be made visible
+ * @param right the right offset of the new area to be made visible
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ private boolean scrollAndFocus(int direction, int left, int right) {
+ boolean handled = true;
+
+ int width = getWidth();
+ int containerLeft = getScrollX();
+ int containerRight = containerLeft + width;
+ boolean goLeft = direction == View.FOCUS_LEFT;
+
+ View newFocused = findFocusableViewInBounds(goLeft, left, right);
+ if (newFocused == null) {
+ newFocused = this;
+ }
+
+ if (left >= containerLeft && right <= containerRight) {
+ handled = false;
+ } else {
+ int delta = goLeft ? (left - containerLeft) : (right - containerRight);
+ doScrollX(delta);
+ }
+
+ if (newFocused != findFocus() && newFocused.requestFocus(direction)) {
+ mScrollViewMovedFocus = true;
+ mScrollViewMovedFocus = false;
+ }
+
+ return handled;
+ }
+
+ /**
+ * Handle scrolling in response to a left or right arrow click.
+ *
+ * @param direction The direction corresponding to the arrow key that was
+ * pressed
+ * @return True if we consumed the event, false otherwise
+ */
+ public boolean arrowScroll(int direction) {
+
+ View currentFocused = findFocus();
+ if (currentFocused == this) currentFocused = null;
+
+ View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
+
+ final int maxJump = getMaxScrollAmount();
+
+ if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump)) {
+ nextFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(nextFocused, mTempRect);
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ doScrollX(scrollDelta);
+ nextFocused.requestFocus(direction);
+ } else {
+ // no new focus
+ int scrollDelta = maxJump;
+
+ if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) {
+ scrollDelta = getScrollX();
+ } else if (direction == View.FOCUS_RIGHT) {
+
+ int daRight = getChildAt(getChildCount() - 1).getRight();
+
+ int screenRight = getScrollX() + getWidth();
+
+ if (daRight - screenRight < maxJump) {
+ scrollDelta = daRight - screenRight;
+ }
+ }
+ if (scrollDelta == 0) {
+ return false;
+ }
+ doScrollX(direction == View.FOCUS_RIGHT ? scrollDelta : -scrollDelta);
+ }
+
+ if (currentFocused != null && currentFocused.isFocused()
+ && isOffScreen(currentFocused)) {
+ // previously focused item still has focus and is off screen, give
+ // it up (take it back to ourselves)
+ // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
+ // sure to
+ // get it)
+ final int descendantFocusability = getDescendantFocusability(); // save
+ setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ requestFocus();
+ setDescendantFocusability(descendantFocusability); // restore
+ }
+ return true;
+ }
+
+ /**
+ * @return whether the descendant of this scroll view is scrolled off
+ * screen.
+ */
+ private boolean isOffScreen(View descendant) {
+ return !isWithinDeltaOfScreen(descendant, 0);
+ }
+
+ /**
+ * @return whether the descendant of this scroll view is within delta
+ * pixels of being on the screen.
+ */
+ private boolean isWithinDeltaOfScreen(View descendant, int delta) {
+ descendant.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(descendant, mTempRect);
+
+ return (mTempRect.right + delta) >= getScrollX()
+ && (mTempRect.left - delta) <= (getScrollX() + getWidth());
+ }
+
+ /**
+ * Smooth scroll by a X delta
+ *
+ * @param delta the number of pixels to scroll by on the X axis
+ */
+ private void doScrollX(int delta) {
+ if (delta != 0) {
+ if (mSmoothScrollingEnabled) {
+ smoothScrollBy(delta, 0);
+ } else {
+ scrollBy(delta, 0);
+ }
+ }
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param dx the number of pixels to scroll by on the X axis
+ * @param dy the number of pixels to scroll by on the Y axis
+ */
+ public final void smoothScrollBy(int dx, int dy) {
+ long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
+ if (duration > ANIMATED_SCROLL_GAP) {
+ mScroller.startScroll(mScrollX, mScrollY, dx, dy);
+ invalidate();
+ } else {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+ scrollBy(dx, dy);
+ }
+ mLastScroll = AnimationUtils.currentAnimationTimeMillis();
+ }
+
+ /**
+ * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
+ *
+ * @param x the position where to scroll on the X axis
+ * @param y the position where to scroll on the Y axis
+ */
+ public final void smoothScrollTo(int x, int y) {
+ smoothScrollBy(x - mScrollX, y - mScrollY);
+ }
+
+ /**
+ * <p>The scroll range of a scroll view is the overall width of all of its
+ * children.</p>
+ */
+ @Override
+ protected int computeHorizontalScrollRange() {
+ int count = getChildCount();
+ return count == 0 ? getWidth() : getChildAt(0).getRight();
+ }
+
+
+ @Override
+ protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
+ ViewGroup.LayoutParams lp = child.getLayoutParams();
+
+ int childWidthMeasureSpec;
+ int childHeightMeasureSpec;
+
+ childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop
+ + mPaddingBottom, lp.height);
+
+ childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ @Override
+ protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
+ int parentHeightMeasureSpec, int heightUsed) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+ final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+ mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ + heightUsed, lp.height);
+ final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ // This is called at drawing time by ViewGroup. We don't want to
+ // re-show the scrollbars at this point, which scrollTo will do,
+ // so we replicate most of scrollTo here.
+ //
+ // It's a little odd to call onScrollChanged from inside the drawing.
+ //
+ // It is, except when you remember that computeScroll() is used to
+ // animate scrolling. So unless we want to defer the onScrollChanged()
+ // until the end of the animated scrolling, we don't really have a
+ // choice here.
+ //
+ // I agree. The alternative, which I think would be worse, is to post
+ // something and tell the subclasses later. This is bad because there
+ // will be a window where mScrollX/Y is different from what the app
+ // thinks it is.
+ //
+ int oldX = mScrollX;
+ int oldY = mScrollY;
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+ mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+ } else {
+ mScrollX = x;
+ mScrollY = y;
+ }
+ if (oldX != mScrollX || oldY != mScrollY) {
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+ }
+
+ // Keep on drawing until the animation has finished.
+ postInvalidate();
+ }
+ }
+
+ /**
+ * Scrolls the view to the given child.
+ *
+ * @param child the View to scroll to
+ */
+ private void scrollToChild(View child) {
+ child.getDrawingRect(mTempRect);
+
+ /* Offset from child's local coordinates to ScrollView coordinates */
+ offsetDescendantRectToMyCoords(child, mTempRect);
+
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+
+ if (scrollDelta != 0) {
+ scrollBy(scrollDelta, 0);
+ }
+ }
+
+ /**
+ * If rect is off screen, scroll just enough to get it (or at least the
+ * first screen size chunk of it) on screen.
+ *
+ * @param rect The rectangle.
+ * @param immediate True to scroll immediately without animation
+ * @return true if scrolling was performed
+ */
+ private boolean scrollToChildRect(Rect rect, boolean immediate) {
+ final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
+ final boolean scroll = delta != 0;
+ if (scroll) {
+ if (immediate) {
+ scrollBy(delta, 0);
+ } else {
+ smoothScrollBy(delta, 0);
+ }
+ }
+ return scroll;
+ }
+
+ /**
+ * Compute the amount to scroll in the X direction in order to get
+ * a rectangle completely on the screen (or, if taller than the screen,
+ * at least the first screen size chunk of it).
+ *
+ * @param rect The rect.
+ * @return The scroll delta.
+ */
+ protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
+
+ int width = getWidth();
+ int screenLeft = getScrollX();
+ int screenRight = screenLeft + width;
+
+ int fadingEdge = getHorizontalFadingEdgeLength();
+
+ // leave room for left fading edge as long as rect isn't at very left
+ if (rect.left > 0) {
+ screenLeft += fadingEdge;
+ }
+
+ // leave room for right fading edge as long as rect isn't at very right
+ if (rect.right < getChildAt(0).getWidth()) {
+ screenRight -= fadingEdge;
+ }
+
+ int scrollXDelta = 0;
+
+ if (rect.right > screenRight && rect.left > screenLeft) {
+ // need to move right to get it in view: move right just enough so
+ // that the entire rectangle is in view (or at least the first
+ // screen size chunk).
+
+ if (rect.width() > width) {
+ // just enough to get screen size chunk on
+ scrollXDelta += (rect.left - screenLeft);
+ } else {
+ // get entire rect at right of screen
+ scrollXDelta += (rect.right - screenRight);
+ }
+
+ // make sure we aren't scrolling beyond the end of our content
+ int right = getChildAt(getChildCount() - 1).getRight();
+ int distanceToRight = right - screenRight;
+ scrollXDelta = Math.min(scrollXDelta, distanceToRight);
+
+ } else if (rect.left < screenLeft && rect.right < screenRight) {
+ // need to move right to get it in view: move right just enough so that
+ // entire rectangle is in view (or at least the first screen
+ // size chunk of it).
+
+ if (rect.width() > width) {
+ // screen size chunk
+ scrollXDelta -= (screenRight - rect.right);
+ } else {
+ // entire rect at left
+ scrollXDelta -= (screenLeft - rect.left);
+ }
+
+ // make sure we aren't scrolling any further than the left our content
+ scrollXDelta = Math.max(scrollXDelta, -getScrollX());
+ }
+ return scrollXDelta;
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (!mScrollViewMovedFocus) {
+ if (!mIsLayoutDirty) {
+ scrollToChild(focused);
+ } else {
+ // The child may not be laid out yet, we can't compute the scroll yet
+ mChildToScrollTo = focused;
+ }
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+
+ /**
+ * When looking for focus in children of a scroll view, need to be a little
+ * more careful not to give focus to something that is scrolled off screen.
+ *
+ * This is more expensive than the default {@link android.view.ViewGroup}
+ * implementation, otherwise this behavior might have been made the default.
+ */
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction,
+ Rect previouslyFocusedRect) {
+
+ // convert from forward / backward notation to up / down / left / right
+ // (ugh).
+ if (direction == View.FOCUS_FORWARD) {
+ direction = View.FOCUS_RIGHT;
+ } else if (direction == View.FOCUS_BACKWARD) {
+ direction = View.FOCUS_LEFT;
+ }
+
+ final View nextFocus = previouslyFocusedRect == null ?
+ FocusFinder.getInstance().findNextFocus(this, null, direction) :
+ FocusFinder.getInstance().findNextFocusFromRect(this,
+ previouslyFocusedRect, direction);
+
+ if (nextFocus == null) {
+ return false;
+ }
+
+ if (isOffScreen(nextFocus)) {
+ return false;
+ }
+
+ return nextFocus.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+ boolean immediate) {
+ // offset into coordinate space of this scroll view
+ rectangle.offset(child.getLeft() - child.getScrollX(),
+ child.getTop() - child.getScrollY());
+
+ return scrollToChildRect(rectangle, immediate);
+ }
+
+ @Override
+ public void requestLayout() {
+ mIsLayoutDirty = true;
+ super.requestLayout();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ mIsLayoutDirty = false;
+ // Give a child focus if it needs it
+ if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
+ scrollToChild(mChildToScrollTo);
+ }
+ mChildToScrollTo = null;
+
+ // Calling this with the present values causes it to re-clam them
+ scrollTo(mScrollX, mScrollY);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ View currentFocused = findFocus();
+ if (null == currentFocused || this == currentFocused)
+ return;
+
+ final int maxJump = mRight - mLeft;
+
+ if (isWithinDeltaOfScreen(currentFocused, maxJump)) {
+ currentFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ doScrollX(scrollDelta);
+ }
+ }
+
+ /**
+ * Return true if child is an descendant of parent, (or equal to the parent).
+ */
+ private boolean isViewDescendantOf(View child, View parent) {
+ if (child == parent) {
+ return true;
+ }
+
+ final ViewParent theParent = child.getParent();
+ return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+ }
+
+ /**
+ * Fling the scroll view
+ *
+ * @param velocityX The initial velocity in the X direction. Positive
+ * numbers mean that the finger/curor is moving down the screen,
+ * which means we want to scroll towards the left.
+ */
+ public void fling(int velocityX) {
+ int width = getWidth() - mPaddingRight - mPaddingLeft;
+ int right = getChildAt(0).getWidth();
+
+ mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, right - width, 0, 0);
+
+ final boolean movingRight = velocityX > 0;
+
+ View newFocused = findFocusableViewInMyBounds(movingRight,
+ mScroller.getFinalX(), findFocus());
+
+ if (newFocused == null) {
+ newFocused = this;
+ }
+
+ if (newFocused != findFocus()
+ && newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT)) {
+ mScrollViewMovedFocus = true;
+ mScrollViewMovedFocus = false;
+ }
+
+ invalidate();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This version also clamps the scrolling to the bounds of our child.
+ */
+ public void scrollTo(int x, int y) {
+ // we rely on the fact the View.scrollBy calls scrollTo.
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+ y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+ if (x != mScrollX || y != mScrollY) {
+ super.scrollTo(x, y);
+ }
+ }
+ }
+
+ private int clamp(int n, int my, int child) {
+ if (my >= child || n < 0) {
+ return 0;
+ }
+ if ((my + n) > child) {
+ return child - my;
+ }
+ return n;
+ }
+}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index b5d4e2d..480b0b8 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -48,6 +48,7 @@ import android.widget.RemoteViews.RemoteView;
* @attr ref android.R.styleable#ImageView_maxHeight
* @attr ref android.R.styleable#ImageView_tint
* @attr ref android.R.styleable#ImageView_scaleType
+ * @attr ref android.R.styleable#ImageView_cropToPadding
*/
@RemoteView
public class ImageView extends View {
@@ -192,6 +193,7 @@ public class ImageView extends View {
*
* @attr ref android.R.styleable#ImageView_adjustViewBounds
*/
+ @android.view.RemotableViewMethod
public void setAdjustViewBounds(boolean adjustViewBounds) {
mAdjustViewBounds = adjustViewBounds;
if (adjustViewBounds) {
@@ -216,6 +218,7 @@ public class ImageView extends View {
*
* @attr ref android.R.styleable#ImageView_maxWidth
*/
+ @android.view.RemotableViewMethod
public void setMaxWidth(int maxWidth) {
mMaxWidth = maxWidth;
}
@@ -237,6 +240,7 @@ public class ImageView extends View {
*
* @attr ref android.R.styleable#ImageView_maxHeight
*/
+ @android.view.RemotableViewMethod
public void setMaxHeight(int maxHeight) {
mMaxHeight = maxHeight;
}
@@ -255,6 +259,7 @@ public class ImageView extends View {
*
* @attr ref android.R.styleable#ImageView_src
*/
+ @android.view.RemotableViewMethod
public void setImageResource(int resId) {
if (mUri != null || mResource != resId) {
updateDrawable(null);
@@ -271,6 +276,7 @@ public class ImageView extends View {
*
* @param uri The Uri of an image
*/
+ @android.view.RemotableViewMethod
public void setImageURI(Uri uri) {
if (mResource != 0 ||
(mUri != uri &&
@@ -305,6 +311,7 @@ public class ImageView extends View {
*
* @param bm The bitmap to set
*/
+ @android.view.RemotableViewMethod
public void setImageBitmap(Bitmap bm) {
// if this is used frequently, may handle bitmaps explicitly
// to reduce the intermediate drawable object
@@ -326,6 +333,13 @@ public class ImageView extends View {
resizeFromDrawable();
}
+ /**
+ * Sets the image level, when it is constructed from a
+ * {@link android.graphics.drawable.LevelListDrawable}.
+ *
+ * @param level The new level for the image.
+ */
+ @android.view.RemotableViewMethod
public void setImageLevel(int level) {
mLevel = level;
if (mDrawable != null) {
@@ -832,7 +846,7 @@ public class ImageView extends View {
@Override
public int getBaseline() {
- return mBaselineAligned ? getHeight() : -1;
+ return mBaselineAligned ? getMeasuredHeight() : -1;
}
/**
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 36ed8bd..a9822f8 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -136,6 +136,7 @@ public class LinearLayout extends ViewGroup {
*
* @attr ref android.R.styleable#LinearLayout_baselineAligned
*/
+ @android.view.RemotableViewMethod
public void setBaselineAligned(boolean baselineAligned) {
mBaselineAligned = baselineAligned;
}
@@ -208,6 +209,7 @@ public class LinearLayout extends ViewGroup {
*
* @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex
*/
+ @android.view.RemotableViewMethod
public void setBaselineAlignedChildIndex(int i) {
if ((i < 0) || (i >= getChildCount())) {
throw new IllegalArgumentException("base aligned child index out "
@@ -265,6 +267,7 @@ public class LinearLayout extends ViewGroup {
* to 0.0f if the weight sum should be computed from the children's
* layout_weight
*/
+ @android.view.RemotableViewMethod
public void setWeightSum(float weightSum) {
mWeightSum = Math.max(0.0f, weightSum);
}
@@ -336,7 +339,7 @@ public class LinearLayout extends ViewGroup {
// heightMode is either UNSPECIFIED OR AT_MOST, and this child
// wanted to stretch to fill available space. Translate that to
// WRAP_CONTENT so that it does not end up with a height of 0
- oldHeight = lp.height;
+ oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}
@@ -475,8 +478,6 @@ public class LinearLayout extends ViewGroup {
matchWidthLocally ? margin : measuredWidth);
allFillParent = allFillParent && lp.width == LayoutParams.FILL_PARENT;
- alternativeMaxWidth = Math.max(alternativeMaxWidth,
- matchWidthLocally ? margin : measuredWidth);
mTotalLength += child.getMeasuredHeight() + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child);
@@ -607,7 +608,7 @@ public class LinearLayout extends ViewGroup {
// widthMode is either UNSPECIFIED OR AT_MOST, and this child
// wanted to stretch to fill available space. Translate that to
// WRAP_CONTENT so that it does not end up with a width of 0
- oldWidth = lp.width;
+ oldWidth = 0;
lp.width = LayoutParams.WRAP_CONTENT;
}
@@ -766,8 +767,6 @@ public class LinearLayout extends ViewGroup {
matchHeightLocally ? margin : childHeight);
allFillParent = allFillParent && lp.height == LayoutParams.FILL_PARENT;
- alternativeMaxHeight = Math.max(alternativeMaxHeight,
- matchHeightLocally ? margin : childHeight);
if (baselineAligned) {
final int childBaseline = child.getBaseline();
@@ -803,8 +802,7 @@ public class LinearLayout extends ViewGroup {
maxHeight = Math.max(maxHeight, ascent + descent);
}
} else {
- alternativeMaxHeight = Math.max(alternativeMaxHeight,
- weightedMaxHeight);
+ alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);
}
if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
@@ -1154,6 +1152,7 @@ public class LinearLayout extends ViewGroup {
*
* @attr ref android.R.styleable#LinearLayout_gravity
*/
+ @android.view.RemotableViewMethod
public void setGravity(int gravity) {
if (mGravity != gravity) {
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
@@ -1169,6 +1168,7 @@ public class LinearLayout extends ViewGroup {
}
}
+ @android.view.RemotableViewMethod
public void setHorizontalGravity(int horizontalGravity) {
final int gravity = horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != gravity) {
@@ -1177,6 +1177,7 @@ public class LinearLayout extends ViewGroup {
}
}
+ @android.view.RemotableViewMethod
public void setVerticalGravity(int verticalGravity) {
final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index dfc7bc3..aced533 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -26,7 +26,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
-import android.util.SparseArray;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -127,13 +126,7 @@ public class ListView extends AbsListView {
private SparseBooleanArray mCheckStates;
// used for temporary calculations.
- private Rect mTempRect = new Rect();
-
- /**
- * Used to save / restore the state of the focused child in {@link #layoutChildren()}
- */
- private SparseArray<Parcelable> mfocusRestoreChildState = new SparseArray<Parcelable>();
-
+ private final Rect mTempRect = new Rect();
// the single allocated result per list view; kinda cheesey but avoids
// allocating these thingies too often.
@@ -1011,34 +1004,13 @@ public class ListView extends AbsListView {
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0);
- final int childViewType = mAdapter.getItemViewType(0);
-
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, 0);
- child.setLayoutParams(lp);
- }
- lp.viewType = childViewType;
-
- final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
- mListPadding.left + mListPadding.right, lp.width);
-
- int lpHeight = lp.height;
-
- int childHeightSpec;
- if (lpHeight > 0) {
- childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
- } else {
- childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- }
- child.measure(childWidthSpec, childHeightSpec);
+ measureScrapChild(child, 0, widthMeasureSpec);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
- if (mRecycler.shouldRecycleViewType(childViewType)) {
+ if (recycleOnMeasure()) {
mRecycler.addScrapView(child);
}
}
@@ -1055,13 +1027,41 @@ public class ListView extends AbsListView {
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
- heightSize = measureHeightOfChildren(
- MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
- 0, NO_POSITION, heightSize, -1);
+ heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
setMeasuredDimension(widthSize, heightSize);
- mWidthMeasureSpec = widthMeasureSpec;
+ mWidthMeasureSpec = widthMeasureSpec;
+ }
+
+ private void measureScrapChild(View child, int position, int widthMeasureSpec) {
+ LayoutParams p = (LayoutParams) child.getLayoutParams();
+ if (p == null) {
+ p = new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ child.setLayoutParams(p);
+ }
+ p.viewType = mAdapter.getItemViewType(position);
+
+ int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+ mListPadding.left + mListPadding.right, p.width);
+ int lpHeight = p.height;
+ int childHeightSpec;
+ if (lpHeight > 0) {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+ } else {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+ child.measure(childWidthSpec, childHeightSpec);
+ }
+
+ /**
+ * @return True to recycle the views used to measure this ListView in
+ * UNSPECIFIED/AT_MOST modes, false otherwise.
+ * @hide
+ */
+ protected boolean recycleOnMeasure() {
+ return true;
}
/**
@@ -1090,8 +1090,8 @@ public class ListView extends AbsListView {
* startPosition is 0).
* @return The height of this ListView with the given children.
*/
- final int measureHeightOfChildren(final int widthMeasureSpec, final int startPosition,
- int endPosition, final int maxHeight, int disallowPartialChildPosition) {
+ final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
+ final int maxHeight, int disallowPartialChildPosition) {
final ListAdapter adapter = mAdapter;
if (adapter == null) {
@@ -1110,29 +1110,20 @@ public class ListView extends AbsListView {
// mItemCount - 1 since endPosition parameter is inclusive
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
+ final boolean recyle = recycleOnMeasure();
+
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i);
- final int childViewType = adapter.getItemViewType(i);
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, 0);
- child.setLayoutParams(lp);
- }
- lp.viewType = childViewType;
+ measureScrapChild(child, i, widthMeasureSpec);
if (i > 0) {
// Count the divider for all but one child
returnedHeight += dividerHeight;
}
- child.measure(widthMeasureSpec, lp.height >= 0
- ? MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY)
- : MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-
// Recycle the view before we possibly return from the method
- if (recycleBin.shouldRecycleViewType(childViewType)) {
+ if (recyle) {
recycleBin.addScrapView(child);
}
@@ -1329,6 +1320,8 @@ public class ListView extends AbsListView {
final boolean blockLayoutRequests = mBlockLayoutRequests;
if (!blockLayoutRequests) {
mBlockLayoutRequests = true;
+ } else {
+ return;
}
try {
@@ -1438,14 +1431,12 @@ public class ListView extends AbsListView {
// we can remember the focused view to restore after relayout if the
// data hasn't changed, or if the focused position is a header or footer
if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
- focusLayoutRestoreDirectChild = getFocusedChild();
- if (focusLayoutRestoreDirectChild != null) {
-
- // remember its state
- focusLayoutRestoreDirectChild.saveHierarchyState(mfocusRestoreChildState);
-
- // remember the specific view that had focus
- focusLayoutRestoreView = findFocus();
+ focusLayoutRestoreDirectChild = focusedChild;
+ // remember the specific view that had focus
+ focusLayoutRestoreView = findFocus();
+ if (focusLayoutRestoreView != null) {
+ // tell it we are going to mess with it
+ focusLayoutRestoreView.onStartTemporaryDetach();
}
}
requestFocus();
@@ -1528,10 +1519,6 @@ public class ListView extends AbsListView {
sel.setSelected(false);
mSelectorRect.setEmpty();
}
-
- if (sel == focusLayoutRestoreDirectChild) {
- focusLayoutRestoreDirectChild.restoreHierarchyState(mfocusRestoreChildState);
- }
} else {
positionSelector(sel);
}
@@ -1544,10 +1531,16 @@ public class ListView extends AbsListView {
// focus (i.e. something focusable in touch mode)
if (hasFocus() && focusLayoutRestoreView != null) {
focusLayoutRestoreView.requestFocus();
- focusLayoutRestoreDirectChild.restoreHierarchyState(mfocusRestoreChildState);
}
}
+ // tell focus view we are done mucking with it, if it is still in
+ // our view hierarchy.
+ if (focusLayoutRestoreView != null
+ && focusLayoutRestoreView.getWindowToken() != null) {
+ focusLayoutRestoreView.onFinishTemporaryDetach();
+ }
+
mLayoutMode = LAYOUT_NORMAL;
mDataChanged = false;
mNeedSync = false;
@@ -1656,16 +1649,20 @@ public class ListView extends AbsListView {
// Respect layout params that are already in the view. Otherwise make some up...
// noinspection unchecked
- AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+ AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
if (p == null) {
p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}
p.viewType = mAdapter.getItemViewType(position);
- if (recycled) {
+ if (recycled || (p.recycledHeaderFooter &&
+ p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
attachViewToParent(child, flowDown ? -1 : 0, p);
} else {
+ if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
+ p.recycledHeaderFooter = true;
+ }
addViewInLayout(child, flowDown ? -1 : 0, p, true);
}
@@ -1675,7 +1672,7 @@ public class ListView extends AbsListView {
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
if (child instanceof Checkable) {
- ((Checkable)child).setChecked(mCheckStates.get(position));
+ ((Checkable) child).setChecked(mCheckStates.get(position));
}
}
@@ -1718,12 +1715,11 @@ public class ListView extends AbsListView {
}
/**
- * Sets the currently selected item
+ * Sets the currently selected item. If in touch mode, the item will not be selected
+ * but it will still be positioned appropriately. If the specified selection position
+ * is less than 0, then the item at position 0 will be selected.
*
* @param position Index (starting at 0) of the data item to be selected.
- *
- * If in touch mode, the item will not be selected but it will still be positioned
- * appropriately.
*/
@Override
public void setSelection(int position) {
@@ -1773,10 +1769,8 @@ public class ListView extends AbsListView {
*/
@Override
void setSelectionInt(int position) {
- mBlockLayoutRequests = true;
setNextSelectedPositionInt(position);
layoutChildren();
- mBlockLayoutRequests = false;
}
/**
@@ -2180,6 +2174,10 @@ public class ListView extends AbsListView {
&& !isViewAncestorOf(selectedView, this)) {
selectedView = null;
hideSelector();
+
+ // but we don't want to set the ressurect position (that would make subsequent
+ // unhandled key events bring back the item we just scrolled off!)
+ mResurrectToPosition = INVALID_POSITION;
}
if (needToRedraw) {
@@ -2646,6 +2644,7 @@ public class ListView extends AbsListView {
final int listBottom = getHeight() - mListPadding.bottom;
final int listTop = mListPadding.top;
+ final AbsListView.RecycleBin recycleBin = mRecycler;
if (amount < 0) {
// shifted items up
@@ -2673,8 +2672,13 @@ public class ListView extends AbsListView {
// top views may be panned off screen
View first = getChildAt(0);
while (first.getBottom() < listTop) {
- removeViewInLayout(first);
- mRecycler.addScrapView(first);
+ AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
+ if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
+ removeViewInLayout(first);
+ recycleBin.addScrapView(first);
+ } else {
+ detachViewFromParent(first);
+ }
first = getChildAt(0);
mFirstPosition++;
}
@@ -2699,8 +2703,13 @@ public class ListView extends AbsListView {
// bottom view may be panned off screen
while (last.getTop() > listBottom) {
- removeViewInLayout(last);
- mRecycler.addScrapView(last);
+ AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
+ if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
+ removeViewInLayout(last);
+ recycleBin.addScrapView(last);
+ } else {
+ detachViewFromParent(last);
+ }
last = getChildAt(--lastIndex);
}
}
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index f2cec92..227fb95 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -451,6 +451,7 @@ public class MediaController extends FrameLayout {
public void onProgressChanged(SeekBar bar, int progress, boolean fromtouch) {
if (fromtouch) {
mDragging = true;
+ duration = mPlayer.getDuration();
long newposition = (duration * progress) / 1000L;
mPlayer.seekTo( (int) newposition);
if (mCurrentTime != null)
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
index 59a9310..05abc26 100644
--- a/core/java/android/widget/MultiAutoCompleteTextView.java
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -126,7 +126,7 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
Editable text = getText();
int end = getSelectionEnd();
- if (end < 0) {
+ if (end < 0 || mTokenizer == null) {
return false;
}
@@ -147,7 +147,7 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
public void performValidation() {
Validator v = getValidator();
- if (v == null) {
+ if (v == null || mTokenizer == null) {
return;
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 50248c1..a4f729f 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -24,15 +24,20 @@ import android.view.View;
import android.view.WindowManager;
import android.view.Gravity;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.View.OnTouchListener;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
import android.os.IBinder;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import java.lang.ref.WeakReference;
+
/**
* <p>A popup window that can be used to display an arbitrary view. The popup
* windows is a floating container that appears on top of the current
@@ -98,6 +103,8 @@ public class PopupWindow {
private Rect mTempRect = new Rect();
private Drawable mBackground;
+ private Drawable mAboveAnchorBackgroundDrawable;
+ private Drawable mBelowAnchorBackgroundDrawable;
private boolean mAboveAnchor;
@@ -109,7 +116,23 @@ public class PopupWindow {
private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
com.android.internal.R.attr.state_above_anchor
};
-
+
+ private WeakReference<View> mAnchor;
+ private OnScrollChangedListener mOnScrollChangedListener =
+ new OnScrollChangedListener() {
+ public void onScrollChanged() {
+ View anchor = mAnchor.get();
+ if (anchor != null && mPopupView != null) {
+ WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+ mPopupView.getLayoutParams();
+
+ mAboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff);
+ update(p.x, p.y, -1, -1, true);
+ }
+ }
+ };
+ private int mAnchorXoff, mAnchorYoff;
+
/**
* <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
*
@@ -144,6 +167,43 @@ public class PopupWindow {
mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+ // If this is a StateListDrawable, try to find and store the drawable to be
+ // used when the drop-down is placed above its anchor view, and the one to be
+ // used when the drop-down is placed below its anchor view. We extract
+ // the drawables ourselves to work around a problem with using refreshDrawableState
+ // that it will take into account the padding of all drawables specified in a
+ // StateListDrawable, thus adding superfluous padding to drop-down views.
+ //
+ // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
+ // at least one other drawable, intended for the 'below-anchor state'.
+ if (mBackground instanceof StateListDrawable) {
+ StateListDrawable background = (StateListDrawable) mBackground;
+
+ // Find the above-anchor view - this one's easy, it should be labeled as such.
+ int aboveAnchorStateIndex = background.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
+
+ // Now, for the below-anchor view, look for any other drawable specified in the
+ // StateListDrawable which is not for the above-anchor state and use that.
+ int count = background.getStateCount();
+ int belowAnchorStateIndex = -1;
+ for (int i = 0; i < count; i++) {
+ if (i != aboveAnchorStateIndex) {
+ belowAnchorStateIndex = i;
+ break;
+ }
+ }
+
+ // Store the drawables we found, if we found them. Otherwise, set them both
+ // to null so that we'll just use refreshDrawableState.
+ if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
+ mAboveAnchorBackgroundDrawable = background.getStateDrawable(aboveAnchorStateIndex);
+ mBelowAnchorBackgroundDrawable = background.getStateDrawable(belowAnchorStateIndex);
+ } else {
+ mBelowAnchorBackgroundDrawable = null;
+ mAboveAnchorBackgroundDrawable = null;
+ }
+ }
+
a.recycle();
}
@@ -579,6 +639,8 @@ public class PopupWindow {
return;
}
+ unregisterForScrollChanged();
+
mIsShowing = true;
mIsDropdown = false;
@@ -617,6 +679,8 @@ public class PopupWindow {
* the popup in its entirety, this method tries to find a parent scroll
* view to scroll. If no parent scroll view can be scrolled, the bottom-left
* corner of the popup is pinned at the top left corner of the anchor view.</p>
+ * <p>If the view later scrolls to move <code>anchor</code> to a different
+ * location, the popup will be moved correspondingly.</p>
*
* @param anchor the view on which to pin the popup window
*
@@ -627,22 +691,54 @@ public class PopupWindow {
return;
}
+ registerForScrollChanged(anchor, xoff, yoff);
+
mIsShowing = true;
mIsDropdown = true;
WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
preparePopup(p);
+ mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
+
if (mBackground != null) {
- mPopupView.refreshDrawableState();
+ // If the background drawable provided was a StateListDrawable with above-anchor
+ // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
+ // do the job.
+ if (mAboveAnchorBackgroundDrawable != null) {
+ if (mAboveAnchor) {
+ mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable);
+ } else {
+ mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable);
+ }
+ } else {
+ mPopupView.refreshDrawableState();
+ }
}
- mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
+
if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
+
p.windowAnimations = computeAnimationResource();
+
invokePopup(p);
}
/**
+ * Indicates whether the popup is showing above (the y coordinate of the popup's bottom
+ * is less than the y coordinate of the anchor) or below the anchor view (the y coordinate
+ * of the popup is greater than y coordinate of the anchor's bottom).
+ *
+ * The value returned
+ * by this method is meaningful only after {@link #showAsDropDown(android.view.View)}
+ * or {@link #showAsDropDown(android.view.View, int, int)} was invoked.
+ *
+ * @return True if this popup is showing above the anchor view, false otherwise.
+ */
+ public boolean isAboveAnchor() {
+ return mAboveAnchor;
+ }
+
+ /**
* <p>Prepare the popup by embedding in into a new ViewGroup if the
* background drawable is not null. If embedding is required, the layout
* parameters' height is mnodified to take into account the background's
@@ -652,28 +748,22 @@ public class PopupWindow {
*/
private void preparePopup(WindowManager.LayoutParams p) {
if (mBackground != null) {
+ final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
+ int height = ViewGroup.LayoutParams.FILL_PARENT;
+ if (layoutParams != null &&
+ layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ }
+
// when a background is available, we embed the content view
// within another view that owns the background drawable
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT
+ ViewGroup.LayoutParams.FILL_PARENT, height
);
popupViewContainer.setBackgroundDrawable(mBackground);
popupViewContainer.addView(mContentView, listParams);
- if (p.height >= 0) {
- // accomodate the popup's height to take into account the
- // background's padding
- p.height += popupViewContainer.getPaddingTop() +
- popupViewContainer.getPaddingBottom();
- }
- if (p.width >= 0) {
- // accomodate the popup's width to take into account the
- // background's padding
- p.width += popupViewContainer.getPaddingLeft() +
- popupViewContainer.getPaddingRight();
- }
mPopupView = popupViewContainer;
} else {
mPopupView = mContentView;
@@ -720,7 +810,8 @@ public class PopupWindow {
p.flags = computeFlags(p.flags);
p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
p.token = token;
-
+ p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
+
return p;
}
@@ -746,7 +837,7 @@ public class PopupWindow {
if (!mTouchable) {
curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
- if (mTouchable) {
+ if (mOutsideTouchable) {
curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
}
if (!mClippingEnabled) {
@@ -781,7 +872,9 @@ public class PopupWindow {
*
* @return true if the popup is translated upwards to fit on screen
*/
- private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, int yoff) {
+ private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p,
+ int xoff, int yoff) {
+
anchor.getLocationInWindow(mDrawingLocation);
p.x = mDrawingLocation[0] + xoff;
p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
@@ -795,8 +888,7 @@ public class PopupWindow {
anchor.getWindowVisibleDisplayFrame(displayFrame);
final View root = anchor.getRootView();
- if (mScreenLocation[1] + anchor.getMeasuredHeight() + yoff + mPopupHeight > displayFrame.bottom
- || p.x + mPopupWidth - root.getWidth() > 0) {
+ if (p.y + mPopupHeight > displayFrame.bottom || p.x + mPopupWidth - root.getWidth() > 0) {
// if the drop down disappears at the bottom of the screen. we try to
// scroll a parent scrollview or move the drop down back up on top of
// the edit box
@@ -815,11 +907,11 @@ public class PopupWindow {
// determine whether there is more space above or below the anchor
anchor.getLocationOnScreen(mScreenLocation);
- onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getMeasuredHeight() - yoff)
- < (mScreenLocation[1] - yoff - displayFrame.top);
+ onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getMeasuredHeight() - yoff) <
+ (mScreenLocation[1] - yoff - displayFrame.top);
if (onTop) {
p.gravity = Gravity.LEFT | Gravity.BOTTOM;
- p.y = root.getHeight() - mDrawingLocation[1] - yoff;
+ p.y = root.getHeight() - mDrawingLocation[1] + yoff;
} else {
p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
}
@@ -841,15 +933,30 @@ public class PopupWindow {
* shown.
*/
public int getMaxAvailableHeight(View anchor) {
+ return getMaxAvailableHeight(anchor, 0);
+ }
+
+ /**
+ * Returns the maximum height that is available for the popup to be
+ * completely shown. It is recommended that this height be the maximum for
+ * the popup's height, otherwise it is possible that the popup will be
+ * clipped.
+ *
+ * @param anchor The view on which the popup window must be anchored.
+ * @param yOffset y offset from the view's bottom edge
+ * @return The maximum available height for the popup to be completely
+ * shown.
+ */
+ public int getMaxAvailableHeight(View anchor, int yOffset) {
final Rect displayFrame = new Rect();
anchor.getWindowVisibleDisplayFrame(displayFrame);
final int[] anchorPos = mDrawingLocation;
anchor.getLocationOnScreen(anchorPos);
- final int distanceToBottom = displayFrame.bottom
- - (anchorPos[1] + anchor.getHeight());
- final int distanceToTop = anchorPos[1] - displayFrame.top;
+ final int distanceToBottom = displayFrame.bottom -
+ (anchorPos[1] + anchor.getHeight()) - yOffset;
+ final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
// anchorPos[1] is distance from anchor to top of screen
int returnedHeight = Math.max(distanceToBottom, distanceToTop);
@@ -870,6 +977,8 @@ public class PopupWindow {
*/
public void dismiss() {
if (isShowing() && mPopupView != null) {
+ unregisterForScrollChanged();
+
mWindowManager.removeView(mPopupView);
if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
((ViewGroup) mPopupView).removeView(mContentView);
@@ -938,11 +1047,32 @@ public class PopupWindow {
* @param height the new height, can be -1 to ignore
*/
public void update(int x, int y, int width, int height) {
+ update(x, y, width, height, false);
+ }
+
+ /**
+ * <p>Updates the position and the dimension of the popup window. Width and
+ * height can be set to -1 to update location only. Calling this function
+ * also updates the window with the current popup state as
+ * described for {@link #update()}.</p>
+ *
+ * @param x the new x location
+ * @param y the new y location
+ * @param width the new width, can be -1 to ignore
+ * @param height the new height, can be -1 to ignore
+ * @param force reposition the window even if the specified position
+ * already seems to correspond to the LayoutParams
+ *
+ * @hide pending API council approval
+ */
+ public void update(int x, int y, int width, int height, boolean force) {
if (width != -1) {
+ mLastWidth = width;
setWidth(width);
}
if (height != -1) {
+ mLastHeight = height;
setHeight(height);
}
@@ -953,7 +1083,7 @@ public class PopupWindow {
WindowManager.LayoutParams p = (WindowManager.LayoutParams)
mPopupView.getLayoutParams();
- boolean update = false;
+ boolean update = force;
final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
if (width != -1 && p.width != finalWidth) {
@@ -990,22 +1120,6 @@ public class PopupWindow {
}
if (update) {
- if (mPopupView != mContentView) {
- final View popupViewContainer = mPopupView;
- if (p.height >= 0) {
- // accomodate the popup's height to take into account the
- // background's padding
- p.height += popupViewContainer.getPaddingTop() +
- popupViewContainer.getPaddingBottom();
- }
- if (p.width >= 0) {
- // accomodate the popup's width to take into account the
- // background's padding
- p.width += popupViewContainer.getPaddingLeft() +
- popupViewContainer.getPaddingRight();
- }
- }
-
mWindowManager.updateViewLayout(mPopupView, p);
}
}
@@ -1029,6 +1143,8 @@ public class PopupWindow {
* height can be set to -1 to update location only. Calling this function
* also updates the window with the current popup state as
* described for {@link #update()}.</p>
+ * <p>If the view later scrolls to move <code>anchor</code> to a different
+ * location, the popup will be moved correspondingly.</p>
*
* @param anchor the popup's anchor view
* @param xoff x offset from the view's left edge
@@ -1041,6 +1157,12 @@ public class PopupWindow {
return;
}
+ WeakReference<View> oldAnchor = mAnchor;
+ if (oldAnchor == null || oldAnchor.get() != anchor ||
+ mAnchorXoff != xoff || mAnchorYoff != yoff) {
+ registerForScrollChanged(anchor, xoff, yoff);
+ }
+
WindowManager.LayoutParams p = (WindowManager.LayoutParams)
mPopupView.getLayoutParams();
@@ -1055,10 +1177,10 @@ public class PopupWindow {
mPopupHeight = height;
}
- findDropDownPosition(anchor, p, xoff, yoff);
+ mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
update(p.x, p.y, width, height);
}
-
+
/**
* Listener that is called when this popup window is dismissed.
*/
@@ -1068,7 +1190,33 @@ public class PopupWindow {
*/
public void onDismiss();
}
-
+
+ private void unregisterForScrollChanged() {
+ WeakReference<View> anchorRef = mAnchor;
+ View anchor = null;
+ if (anchorRef != null) {
+ anchor = anchorRef.get();
+ }
+ if (anchor != null) {
+ ViewTreeObserver vto = anchor.getViewTreeObserver();
+ vto.removeOnScrollChangedListener(mOnScrollChangedListener);
+ }
+ mAnchor = null;
+ }
+
+ private void registerForScrollChanged(View anchor, int xoff, int yoff) {
+ unregisterForScrollChanged();
+
+ mAnchor = new WeakReference<View>(anchor);
+ ViewTreeObserver vto = anchor.getViewTreeObserver();
+ if (vto != null) {
+ vto.addOnScrollChangedListener(mOnScrollChangedListener);
+ }
+
+ mAnchorXoff = xoff;
+ mAnchorYoff = yoff;
+ }
+
private class PopupViewContainer extends FrameLayout {
public PopupViewContainer(Context context) {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 2e04b5d..441414a 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -344,6 +344,7 @@ public class ProgressBar extends View {
*
* @param indeterminate true to enable the indeterminate mode
*/
+ @android.view.RemotableViewMethod
public synchronized void setIndeterminate(boolean indeterminate) {
if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
mIndeterminate = indeterminate;
@@ -525,10 +526,12 @@ public class ProgressBar extends View {
* @see #getProgress()
* @see #incrementProgressBy(int)
*/
+ @android.view.RemotableViewMethod
public synchronized void setProgress(int progress) {
setProgress(progress, false);
}
+ @android.view.RemotableViewMethod
synchronized void setProgress(int progress, boolean fromUser) {
if (mIndeterminate) {
return;
@@ -560,6 +563,7 @@ public class ProgressBar extends View {
* @see #getSecondaryProgress()
* @see #incrementSecondaryProgressBy(int)
*/
+ @android.view.RemotableViewMethod
public synchronized void setSecondaryProgress(int secondaryProgress) {
if (mIndeterminate) {
return;
@@ -633,6 +637,7 @@ public class ProgressBar extends View {
* @see #setProgress(int)
* @see #setSecondaryProgress(int)
*/
+ @android.view.RemotableViewMethod
public synchronized void setMax(int max) {
if (max < 0) {
max = 0;
@@ -758,10 +763,10 @@ public class ProgressBar extends View {
@Override
public void invalidateDrawable(Drawable dr) {
if (!mInDrawing) {
- if (dr == mProgressDrawable || dr == mIndeterminateDrawable) {
+ if (verifyDrawable(dr)) {
final Rect dirty = dr.getBounds();
final int scrollX = mScrollX + mPaddingLeft;
- final int scrollY = mScrollY + mPaddingRight;
+ final int scrollY = mScrollY + mPaddingTop;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index ed8df22..393346a 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -122,6 +122,23 @@ public class RadioGroup extends LinearLayout {
}
}
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (child instanceof RadioButton) {
+ final RadioButton button = (RadioButton) child;
+ if (button.isChecked()) {
+ mProtectFromCheckedChange = true;
+ if (mCheckedId != -1) {
+ setCheckedStateForView(mCheckedId, false);
+ }
+ mProtectFromCheckedChange = false;
+ setCheckedId(button.getId());
+ }
+ }
+
+ super.addView(child, index, params);
+ }
+
/**
* <p>Sets the selection to the radio button whose identifier is passed in
* parameter. Using -1 as the selection identifier clears the selection;
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 91d5805..52c421c 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -22,6 +22,7 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.Gravity;
+import android.view.ViewDebug;
import android.widget.RemoteViews.RemoteView;
import android.graphics.Rect;
import com.android.internal.R;
@@ -152,7 +153,7 @@ public class RelativeLayout extends ViewGroup {
private void initFromAttributes(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RelativeLayout);
- mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, 0);
+ mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID);
mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity);
a.recycle();
}
@@ -168,6 +169,7 @@ public class RelativeLayout extends ViewGroup {
*
* @attr ref android.R.styleable#RelativeLayout_ignoreGravity
*/
+ @android.view.RemotableViewMethod
public void setIgnoreGravity(int viewId) {
mIgnoreGravity = viewId;
}
@@ -183,6 +185,7 @@ public class RelativeLayout extends ViewGroup {
*
* @attr ref android.R.styleable#RelativeLayout_gravity
*/
+ @android.view.RemotableViewMethod
public void setGravity(int gravity) {
if (mGravity != gravity) {
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
@@ -198,6 +201,7 @@ public class RelativeLayout extends ViewGroup {
}
}
+ @android.view.RemotableViewMethod
public void setHorizontalGravity(int horizontalGravity) {
final int gravity = horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != gravity) {
@@ -206,6 +210,7 @@ public class RelativeLayout extends ViewGroup {
}
}
+ @android.view.RemotableViewMethod
public void setVerticalGravity(int verticalGravity) {
final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
@@ -263,7 +268,7 @@ public class RelativeLayout extends ViewGroup {
int right = Integer.MIN_VALUE;
int bottom = Integer.MIN_VALUE;
- if ((horizontalGravity || verticalGravity) && mIgnoreGravity != 0) {
+ if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
ignore = findViewById(mIgnoreGravity);
}
@@ -799,13 +804,33 @@ public class RelativeLayout extends ViewGroup {
* @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
*/
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+ @ViewDebug.ExportedProperty(resolveId = true, indexMapping = {
+ @ViewDebug.IntToString(from = ABOVE, to = "above"),
+ @ViewDebug.IntToString(from = ALIGN_BASELINE, to = "alignBaseline"),
+ @ViewDebug.IntToString(from = ALIGN_BOTTOM, to = "alignBottom"),
+ @ViewDebug.IntToString(from = ALIGN_LEFT, to = "alignLeft"),
+ @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"),
+ @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT, to = "alignParentLeft"),
+ @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT, to = "alignParentRight"),
+ @ViewDebug.IntToString(from = ALIGN_PARENT_TOP, to = "alignParentTop"),
+ @ViewDebug.IntToString(from = ALIGN_RIGHT, to = "alignRight"),
+ @ViewDebug.IntToString(from = ALIGN_TOP, to = "alignTop"),
+ @ViewDebug.IntToString(from = BELOW, to = "below"),
+ @ViewDebug.IntToString(from = CENTER_HORIZONTAL, to = "centerHorizontal"),
+ @ViewDebug.IntToString(from = CENTER_IN_PARENT, to = "center"),
+ @ViewDebug.IntToString(from = CENTER_VERTICAL, to = "centerVertical"),
+ @ViewDebug.IntToString(from = LEFT_OF, to = "leftOf"),
+ @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf")
+ }, mapping = { @ViewDebug.IntToString(from = TRUE, to = "true") })
private int[] mRules = new int[VERB_COUNT];
+
private int mLeft, mTop, mRight, mBottom;
/**
* When true, uses the parent as the anchor if the anchor doesn't exist or if
* the anchor's visibility is GONE.
*/
+ @ViewDebug.ExportedProperty
public boolean alignWithParent;
public LayoutParams(Context c, AttributeSet attrs) {
diff --git a/core/java/android/gadget/GadgetInfo.aidl b/core/java/android/widget/RemoteViews.aidl
index 7231545..ec86410 100644
--- a/core/java/android/gadget/GadgetInfo.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.gadget;
+package android.widget;
-parcelable GadgetInfo;
+parcelable RemoteViews;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 5721095..e000d2e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -30,15 +31,21 @@ import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater.Filter;
import android.view.View.OnClickListener;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import java.lang.Class;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -77,19 +84,22 @@ public class RemoteViews implements Parcelable, Filter {
/**
- * This annotation indicates that a subclass of View is alllowed to be used with the
- * {@link android.widget.RemoteViews} mechanism.
+ * This annotation indicates that a subclass of View is alllowed to be used
+ * with the {@link android.widget.RemoteViews} mechanism.
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RemoteView {
}
-
+
/**
* Exception to send when something goes wrong executing an action
*
*/
public static class ActionException extends RuntimeException {
+ public ActionException(Exception ex) {
+ super(ex);
+ }
public ActionException(String message) {
super(message);
}
@@ -107,320 +117,349 @@ public class RemoteViews implements Parcelable, Filter {
return 0;
}
};
-
- /**
- * Equivalent to calling View.setVisibility
- */
- private class SetViewVisibility extends Action {
- public SetViewVisibility(int id, int vis) {
- viewId = id;
- visibility = vis;
- }
-
- public SetViewVisibility(Parcel parcel) {
- viewId = parcel.readInt();
- visibility = parcel.readInt();
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeInt(visibility);
- }
-
- @Override
- public void apply(View root) {
- View target = root.findViewById(viewId);
- if (target != null) {
- target.setVisibility(visibility);
- }
- }
-
- private int viewId;
- private int visibility;
- public final static int TAG = 0;
- }
-
+
/**
- * Equivalent to calling TextView.setText
+ * Equivalent to calling
+ * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
+ * to launch the provided {@link PendingIntent}.
*/
- private class SetTextViewText extends Action {
- public SetTextViewText(int id, CharSequence t) {
- viewId = id;
- text = t;
+ private class SetOnClickPendingIntent extends Action {
+ public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
+ this.viewId = id;
+ this.pendingIntent = pendingIntent;
}
- public SetTextViewText(Parcel parcel) {
+ public SetOnClickPendingIntent(Parcel parcel) {
viewId = parcel.readInt();
- text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(TAG);
dest.writeInt(viewId);
- TextUtils.writeToParcel(text, dest, flags);
+ pendingIntent.writeToParcel(dest, 0 /* no flags */);
}
@Override
public void apply(View root) {
- TextView target = (TextView) root.findViewById(viewId);
- if (target != null) {
- target.setText(text);
+ final View target = root.findViewById(viewId);
+ if (target != null && pendingIntent != null) {
+ OnClickListener listener = new OnClickListener() {
+ public void onClick(View v) {
+ try {
+ // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
+ pendingIntent.send();
+ } catch (CanceledException e) {
+ throw new ActionException(e.toString());
+ }
+ }
+ };
+ target.setOnClickListener(listener);
}
}
int viewId;
- CharSequence text;
+ PendingIntent pendingIntent;
+
public final static int TAG = 1;
}
/**
- * Equivalent to calling ImageView.setResource
+ * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+ * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
+ * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
+ * <p>
+ * These operations will be performed on the {@link Drawable} returned by the
+ * target {@link View#getBackground()} by default. If targetBackground is false,
+ * we assume the target is an {@link ImageView} and try applying the operations
+ * to {@link ImageView#getDrawable()}.
+ * <p>
+ * You can omit specific calls by marking their values with null or -1.
*/
- private class SetImageViewResource extends Action {
- public SetImageViewResource(int id, int src) {
- viewId = id;
- srcId = src;
+ private class SetDrawableParameters extends Action {
+ public SetDrawableParameters(int id, boolean targetBackground, int alpha,
+ int colorFilter, PorterDuff.Mode mode, int level) {
+ this.viewId = id;
+ this.targetBackground = targetBackground;
+ this.alpha = alpha;
+ this.colorFilter = colorFilter;
+ this.filterMode = mode;
+ this.level = level;
}
- public SetImageViewResource(Parcel parcel) {
+ public SetDrawableParameters(Parcel parcel) {
viewId = parcel.readInt();
- srcId = parcel.readInt();
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeInt(srcId);
- }
-
- @Override
- public void apply(View root) {
- ImageView target = (ImageView) root.findViewById(viewId);
- Drawable d = mContext.getResources().getDrawable(srcId);
- if (target != null) {
- target.setImageDrawable(d);
+ targetBackground = parcel.readInt() != 0;
+ alpha = parcel.readInt();
+ colorFilter = parcel.readInt();
+ boolean hasMode = parcel.readInt() != 0;
+ if (hasMode) {
+ filterMode = PorterDuff.Mode.valueOf(parcel.readString());
+ } else {
+ filterMode = null;
}
- }
-
- int viewId;
- int srcId;
- public final static int TAG = 2;
- }
-
- /**
- * Equivalent to calling ImageView.setImageURI
- */
- private class SetImageViewUri extends Action {
- public SetImageViewUri(int id, Uri u) {
- viewId = id;
- uri = u;
- }
-
- public SetImageViewUri(Parcel parcel) {
- viewId = parcel.readInt();
- uri = Uri.CREATOR.createFromParcel(parcel);
+ level = parcel.readInt();
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(TAG);
dest.writeInt(viewId);
- Uri.writeToParcel(dest, uri);
+ dest.writeInt(targetBackground ? 1 : 0);
+ dest.writeInt(alpha);
+ dest.writeInt(colorFilter);
+ if (filterMode != null) {
+ dest.writeInt(1);
+ dest.writeString(filterMode.toString());
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(level);
}
@Override
public void apply(View root) {
- ImageView target = (ImageView) root.findViewById(viewId);
- if (target != null) {
- target.setImageURI(uri);
+ final View target = root.findViewById(viewId);
+ if (target == null) {
+ return;
+ }
+
+ // Pick the correct drawable to modify for this view
+ Drawable targetDrawable = null;
+ if (targetBackground) {
+ targetDrawable = target.getBackground();
+ } else if (target instanceof ImageView) {
+ ImageView imageView = (ImageView) target;
+ targetDrawable = imageView.getDrawable();
+ }
+
+ // Perform modifications only if values are set correctly
+ if (alpha != -1) {
+ targetDrawable.setAlpha(alpha);
+ }
+ if (colorFilter != -1 && filterMode != null) {
+ targetDrawable.setColorFilter(colorFilter, filterMode);
+ }
+ if (level != -1) {
+ targetDrawable.setLevel(level);
}
}
int viewId;
- Uri uri;
+ boolean targetBackground;
+ int alpha;
+ int colorFilter;
+ PorterDuff.Mode filterMode;
+ int level;
+
public final static int TAG = 3;
}
-
+
/**
- * Equivalent to calling ImageView.setImageBitmap
+ * Base class for the reflection actions.
*/
- private class SetImageViewBitmap extends Action {
- public SetImageViewBitmap(int id, Bitmap src) {
- viewId = id;
- bitmap = src;
- }
+ private class ReflectionAction extends Action {
+ static final int TAG = 2;
+
+ static final int BOOLEAN = 1;
+ static final int BYTE = 2;
+ static final int SHORT = 3;
+ static final int INT = 4;
+ static final int LONG = 5;
+ static final int FLOAT = 6;
+ static final int DOUBLE = 7;
+ static final int CHAR = 8;
+ static final int STRING = 9;
+ static final int CHAR_SEQUENCE = 10;
+ static final int URI = 11;
+ static final int BITMAP = 12;
- public SetImageViewBitmap(Parcel parcel) {
- viewId = parcel.readInt();
- bitmap = Bitmap.CREATOR.createFromParcel(parcel);
+ int viewId;
+ String methodName;
+ int type;
+ Object value;
+
+ ReflectionAction(int viewId, String methodName, int type, Object value) {
+ this.viewId = viewId;
+ this.methodName = methodName;
+ this.type = type;
+ this.value = value;
+ }
+
+ ReflectionAction(Parcel in) {
+ this.viewId = in.readInt();
+ this.methodName = in.readString();
+ this.type = in.readInt();
+ if (false) {
+ Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
+ + " methodName=" + this.methodName + " type=" + this.type);
+ }
+ switch (this.type) {
+ case BOOLEAN:
+ this.value = in.readInt() != 0;
+ break;
+ case BYTE:
+ this.value = in.readByte();
+ break;
+ case SHORT:
+ this.value = (short)in.readInt();
+ break;
+ case INT:
+ this.value = in.readInt();
+ break;
+ case LONG:
+ this.value = in.readLong();
+ break;
+ case FLOAT:
+ this.value = in.readFloat();
+ break;
+ case DOUBLE:
+ this.value = in.readDouble();
+ break;
+ case CHAR:
+ this.value = (char)in.readInt();
+ break;
+ case STRING:
+ this.value = in.readString();
+ break;
+ case CHAR_SEQUENCE:
+ this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ break;
+ case URI:
+ this.value = Uri.CREATOR.createFromParcel(in);
+ break;
+ case BITMAP:
+ this.value = Bitmap.CREATOR.createFromParcel(in);
+ break;
+ default:
+ break;
+ }
}
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- if (bitmap != null) {
- bitmap.writeToParcel(dest, flags);
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(TAG);
+ out.writeInt(this.viewId);
+ out.writeString(this.methodName);
+ out.writeInt(this.type);
+ if (false) {
+ Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
+ + " methodName=" + this.methodName + " type=" + this.type);
+ }
+ switch (this.type) {
+ case BOOLEAN:
+ out.writeInt(((Boolean)this.value).booleanValue() ? 1 : 0);
+ break;
+ case BYTE:
+ out.writeByte(((Byte)this.value).byteValue());
+ break;
+ case SHORT:
+ out.writeInt(((Short)this.value).shortValue());
+ break;
+ case INT:
+ out.writeInt(((Integer)this.value).intValue());
+ break;
+ case LONG:
+ out.writeLong(((Long)this.value).longValue());
+ break;
+ case FLOAT:
+ out.writeFloat(((Float)this.value).floatValue());
+ break;
+ case DOUBLE:
+ out.writeDouble(((Double)this.value).doubleValue());
+ break;
+ case CHAR:
+ out.writeInt((int)((Character)this.value).charValue());
+ break;
+ case STRING:
+ out.writeString((String)this.value);
+ break;
+ case CHAR_SEQUENCE:
+ TextUtils.writeToParcel((CharSequence)this.value, out, flags);
+ break;
+ case URI:
+ ((Uri)this.value).writeToParcel(out, flags);
+ break;
+ case BITMAP:
+ ((Bitmap)this.value).writeToParcel(out, flags);
+ break;
+ default:
+ break;
}
}
- @Override
- public void apply(View root) {
- if (bitmap != null) {
- ImageView target = (ImageView) root.findViewById(viewId);
- Drawable d = new BitmapDrawable(bitmap);
- if (target != null) {
- target.setImageDrawable(d);
- }
+ private Class getParameterType() {
+ switch (this.type) {
+ case BOOLEAN:
+ return boolean.class;
+ case BYTE:
+ return byte.class;
+ case SHORT:
+ return short.class;
+ case INT:
+ return int.class;
+ case LONG:
+ return long.class;
+ case FLOAT:
+ return float.class;
+ case DOUBLE:
+ return double.class;
+ case CHAR:
+ return char.class;
+ case STRING:
+ return String.class;
+ case CHAR_SEQUENCE:
+ return CharSequence.class;
+ case URI:
+ return Uri.class;
+ case BITMAP:
+ return Bitmap.class;
+ default:
+ return null;
}
}
- int viewId;
- Bitmap bitmap;
- public final static int TAG = 4;
- }
-
- /**
- * Equivalent to calling Chronometer.setBase, Chronometer.setFormat,
- * and Chronometer.start/stop.
- */
- private class SetChronometer extends Action {
- public SetChronometer(int id, long base, String format, boolean running) {
- this.viewId = id;
- this.base = base;
- this.format = format;
- this.running = running;
- }
-
- public SetChronometer(Parcel parcel) {
- viewId = parcel.readInt();
- base = parcel.readLong();
- format = parcel.readString();
- running = parcel.readInt() != 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeLong(base);
- dest.writeString(format);
- dest.writeInt(running ? 1 : 0);
- }
-
@Override
public void apply(View root) {
- Chronometer target = (Chronometer) root.findViewById(viewId);
- if (target != null) {
- target.setBase(base);
- target.setFormat(format);
- if (running) {
- target.start();
- } else {
- target.stop();
- }
+ final View view = root.findViewById(viewId);
+ if (view == null) {
+ throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
}
- }
-
- int viewId;
- boolean running;
- long base;
- String format;
- public final static int TAG = 5;
- }
-
- /**
- * Equivalent to calling ProgressBar.setMax, ProgressBar.setProgress and
- * ProgressBar.setIndeterminate
- */
- private class SetProgressBar extends Action {
- public SetProgressBar(int id, int max, int progress, boolean indeterminate) {
- this.viewId = id;
- this.progress = progress;
- this.max = max;
- this.indeterminate = indeterminate;
- }
-
- public SetProgressBar(Parcel parcel) {
- viewId = parcel.readInt();
- progress = parcel.readInt();
- max = parcel.readInt();
- indeterminate = parcel.readInt() != 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeInt(progress);
- dest.writeInt(max);
- dest.writeInt(indeterminate ? 1 : 0);
- }
-
- @Override
- public void apply(View root) {
- ProgressBar target = (ProgressBar) root.findViewById(viewId);
- if (target != null) {
- target.setIndeterminate(indeterminate);
- if (!indeterminate) {
- target.setMax(max);
- target.setProgress(progress);
- }
+ Class param = getParameterType();
+ if (param == null) {
+ throw new ActionException("bad type: " + this.type);
}
- }
-
- int viewId;
- boolean indeterminate;
- int progress;
- int max;
- public final static int TAG = 6;
- }
-
- /**
- * Equivalent to calling
- * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
- * to launch the provided {@link PendingIntent}.
- */
- private class SetOnClickPendingIntent extends Action {
- public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
- this.viewId = id;
- this.pendingIntent = pendingIntent;
- }
-
- public SetOnClickPendingIntent(Parcel parcel) {
- viewId = parcel.readInt();
- pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- pendingIntent.writeToParcel(dest, 0 /* no flags */);
- }
-
- @Override
- public void apply(View root) {
- final View target = root.findViewById(viewId);
- if (target != null && pendingIntent != null) {
- OnClickListener listener = new OnClickListener() {
- public void onClick(View v) {
- try {
- // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
- pendingIntent.send();
- } catch (CanceledException e) {
- throw new ActionException(e.toString());
- }
- }
- };
- target.setOnClickListener(listener);
+ Class klass = view.getClass();
+ Method method = null;
+ try {
+ method = klass.getMethod(this.methodName, getParameterType());
+ }
+ catch (NoSuchMethodException ex) {
+ throw new ActionException("view: " + klass.getName() + " doesn't have method: "
+ + this.methodName + "(" + param.getName() + ")");
+ }
+
+ if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
+ throw new ActionException("view: " + klass.getName()
+ + " can't use method with RemoteViews: "
+ + this.methodName + "(" + param.getName() + ")");
}
- }
-
- int viewId;
- PendingIntent pendingIntent;
- public final static int TAG = 7;
+ try {
+ if (false) {
+ Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+ + this.methodName + "(" + param.getName() + ") with "
+ + (this.value == null ? "null" : this.value.getClass().getName()));
+ }
+ method.invoke(view, this.value);
+ }
+ catch (Exception ex) {
+ throw new ActionException(ex);
+ }
+ }
}
+
/**
* Create a new RemoteViews object that will display the views contained
* in the specified layout file.
@@ -447,32 +486,17 @@ public class RemoteViews implements Parcelable, Filter {
for (int i=0; i<count; i++) {
int tag = parcel.readInt();
switch (tag) {
- case SetViewVisibility.TAG:
- mActions.add(new SetViewVisibility(parcel));
- break;
- case SetTextViewText.TAG:
- mActions.add(new SetTextViewText(parcel));
- break;
- case SetImageViewResource.TAG:
- mActions.add(new SetImageViewResource(parcel));
- break;
- case SetImageViewUri.TAG:
- mActions.add(new SetImageViewUri(parcel));
- break;
- case SetImageViewBitmap.TAG:
- mActions.add(new SetImageViewBitmap(parcel));
- break;
- case SetChronometer.TAG:
- mActions.add(new SetChronometer(parcel));
- break;
- case SetProgressBar.TAG:
- mActions.add(new SetProgressBar(parcel));
- break;
case SetOnClickPendingIntent.TAG:
mActions.add(new SetOnClickPendingIntent(parcel));
break;
+ case SetDrawableParameters.TAG:
+ mActions.add(new SetDrawableParameters(parcel));
+ break;
+ case ReflectionAction.TAG:
+ mActions.add(new ReflectionAction(parcel));
+ break;
default:
- throw new ActionException("Tag " + tag + "not found");
+ throw new ActionException("Tag " + tag + " not found");
}
}
}
@@ -505,7 +529,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param visibility The new visibility for the view
*/
public void setViewVisibility(int viewId, int visibility) {
- addAction(new SetViewVisibility(viewId, visibility));
+ setInt(viewId, "setVisibility", visibility);
}
/**
@@ -515,7 +539,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param text The new text for the view
*/
public void setTextViewText(int viewId, CharSequence text) {
- addAction(new SetTextViewText(viewId, text));
+ setCharSequence(viewId, "setText", text);
}
/**
@@ -525,7 +549,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param srcId The new resource id for the drawable
*/
public void setImageViewResource(int viewId, int srcId) {
- addAction(new SetImageViewResource(viewId, srcId));
+ setInt(viewId, "setImageResource", srcId);
}
/**
@@ -535,7 +559,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param uri The Uri for the image
*/
public void setImageViewUri(int viewId, Uri uri) {
- addAction(new SetImageViewUri(viewId, uri));
+ setUri(viewId, "setImageURI", uri);
}
/**
@@ -545,7 +569,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param bitmap The new Bitmap for the drawable
*/
public void setImageViewBitmap(int viewId, Bitmap bitmap) {
- addAction(new SetImageViewBitmap(viewId, bitmap));
+ setBitmap(viewId, "setImageBitmap", bitmap);
}
/**
@@ -560,16 +584,20 @@ public class RemoteViews implements Parcelable, Filter {
* {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
* @param format The Chronometer format string, or null to
* simply display the timer value.
- * @param running True if you want the clock to be running, false if not.
+ * @param started True if you want the clock to be started, false if not.
*/
- public void setChronometer(int viewId, long base, String format, boolean running) {
- addAction(new SetChronometer(viewId, base, format, running));
+ public void setChronometer(int viewId, long base, String format, boolean started) {
+ setLong(viewId, "setBase", base);
+ setString(viewId, "setFormat", format);
+ setBoolean(viewId, "setStarted", started);
}
/**
* Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
* {@link ProgressBar#setProgress ProgressBar.setProgress}, and
* {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
+ *
+ * If indeterminate is true, then the values for max and progress are ignored.
*
* @param viewId The id of the view whose text should change
* @param max The 100% value for the progress bar
@@ -579,7 +607,11 @@ public class RemoteViews implements Parcelable, Filter {
*/
public void setProgressBar(int viewId, int max, int progress,
boolean indeterminate) {
- addAction(new SetProgressBar(viewId, max, progress, indeterminate));
+ setBoolean(viewId, "setIndeterminate", indeterminate);
+ if (!indeterminate) {
+ setInt(viewId, "setMax", max);
+ setInt(viewId, "setProgress", progress);
+ }
}
/**
@@ -595,6 +627,97 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * @hide
+ * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+ * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
+ * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
+ * view.
+ * <p>
+ * You can omit specific calls by marking their values with null or -1.
+ *
+ * @param viewId The id of the view that contains the target
+ * {@link Drawable}
+ * @param targetBackground If true, apply these parameters to the
+ * {@link Drawable} returned by
+ * {@link android.view.View#getBackground()}. Otherwise, assume
+ * the target view is an {@link ImageView} and apply them to
+ * {@link ImageView#getDrawable()}.
+ * @param alpha Specify an alpha value for the drawable, or -1 to leave
+ * unchanged.
+ * @param colorFilter Specify a color for a
+ * {@link android.graphics.ColorFilter} for this drawable, or -1
+ * to leave unchanged.
+ * @param mode Specify a PorterDuff mode for this drawable, or null to leave
+ * unchanged.
+ * @param level Specify the level for the drawable, or -1 to leave
+ * unchanged.
+ */
+ public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
+ int colorFilter, PorterDuff.Mode mode, int level) {
+ addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
+ colorFilter, mode, level));
+ }
+
+ /**
+ * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
+ *
+ * @param viewId The id of the view whose text should change
+ * @param color Sets the text color for all the states (normal, selected,
+ * focused) to be this color.
+ */
+ public void setTextColor(int viewId, int color) {
+ setInt(viewId, "setTextColor", color);
+ }
+
+ public void setBoolean(int viewId, String methodName, boolean value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
+ }
+
+ public void setByte(int viewId, String methodName, byte value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
+ }
+
+ public void setShort(int viewId, String methodName, short value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
+ }
+
+ public void setInt(int viewId, String methodName, int value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
+ }
+
+ public void setLong(int viewId, String methodName, long value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
+ }
+
+ public void setFloat(int viewId, String methodName, float value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
+ }
+
+ public void setDouble(int viewId, String methodName, double value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
+ }
+
+ public void setChar(int viewId, String methodName, char value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
+ }
+
+ public void setString(int viewId, String methodName, String value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
+ }
+
+ public void setCharSequence(int viewId, String methodName, CharSequence value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
+ }
+
+ public void setUri(int viewId, String methodName, Uri value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
+ }
+
+ public void setBitmap(int viewId, String methodName, Bitmap value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value));
+ }
+
+ /**
* Inflates the view hierarchy represented by this object and applies
* all of the actions.
*
diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
index 9052ae3..a5dbd98 100644
--- a/core/java/android/widget/ResourceCursorAdapter.java
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -46,10 +46,30 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
public ResourceCursorAdapter(Context context, int layout, Cursor c) {
super(context, c);
mLayout = mDropDownLayout = layout;
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
+ * Constructor.
+ *
+ * @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. Unless you override them later, this will
+ * define both the item views and the drop down views.
+ * @param c The cursor from which to get the data.
+ * @param autoRequery If true the adapter will call requery() on the
+ * cursor whenever it changes so the most recent
+ * data is always displayed.
+ * @hide Pending API Council approval
+ */
+ public ResourceCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
+ super(context, c, autoRequery);
+ mLayout = mDropDownLayout = layout;
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ /**
* Inflates view(s) from the specified XML file.
*
* @see android.widget.CursorAdapter#newView(android.content.Context,
diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java
index 17f9128..3b113ae 100644
--- a/core/java/android/widget/ScrollBarDrawable.java
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -152,10 +152,12 @@ public class ScrollBarDrawable extends Drawable {
} else {
track = mHorizontalTrack;
}
- if (mChanged) {
- track.setBounds(bounds);
+ if (track != null) {
+ if (mChanged) {
+ track.setBounds(bounds);
+ }
+ track.draw(canvas);
}
- track.draw(canvas);
}
protected void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 20166cf..c9b3751 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -44,13 +44,7 @@ import java.util.List;
* manager with a complex hierarchy of objects. A child that is often used
* is a {@link LinearLayout} in a vertical orientation, presenting a vertical
* array of top-level items that the user can scroll through.
- *
- * <p>You should never use a ScrollView with a {@link ListView}, since
- * ListView takes care of its own scrolling. Most importantly, doing this
- * defeats all of the important optimizations in ListView for dealing with
- * large lists, since it effectively forces the ListView to display its entire
- * list of items to fill up the infinite container supplied by ScrollView.
- *
+ *
* <p>The {@link TextView} class also
* takes care of its own scrolling, so does not require a ScrollView, but
* using the two together is possible to achieve the effect of a text view
@@ -62,13 +56,9 @@ public class ScrollView extends FrameLayout {
static final String TAG = "ScrollView";
static final boolean localLOGV = false || Config.LOGV;
- private static final int ANIMATED_SCROLL_GAP = 250;
+ static final int ANIMATED_SCROLL_GAP = 250;
- /**
- * When arrow scrolling, ListView will never scroll more than this factor
- * times the height of the list.
- */
- private static final float MAX_SCROLL_FACTOR = 0.5f;
+ static final float MAX_SCROLL_FACTOR = 0.5f;
private long mLastScroll;
@@ -124,6 +114,8 @@ public class ScrollView extends FrameLayout {
*/
private boolean mSmoothScrollingEnabled = true;
+ private int mTouchSlop;
+
public ScrollView(Context context) {
this(context, null);
}
@@ -165,8 +157,8 @@ public class ScrollView extends FrameLayout {
}
final int length = getVerticalFadingEdgeLength();
- final int bottom = getChildAt(0).getBottom();
- final int span = bottom - mScrollY - getHeight();
+ final int bottomEdge = getHeight() - mPaddingBottom;
+ final int span = getChildAt(0).getBottom() - mScrollY - bottomEdge;
if (span < length) {
return span / (float) length;
}
@@ -188,6 +180,7 @@ public class ScrollView extends FrameLayout {
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@Override
@@ -401,7 +394,7 @@ public class ScrollView extends FrameLayout {
* of the down event.
*/
final int yDiff = (int) Math.abs(y - mLastMotionY);
- if (yDiff > ViewConfiguration.getTouchSlop()) {
+ if (yDiff > mTouchSlop) {
mIsBeingDragged = true;
}
break;
@@ -488,8 +481,9 @@ public class ScrollView extends FrameLayout {
velocityTracker.computeCurrentVelocity(1000);
int initialVelocity = (int) velocityTracker.getYVelocity();
- if ((Math.abs(initialVelocity) > ViewConfiguration.getMinimumFlingVelocity()) &&
- (getChildCount() > 0)) {
+ if ((Math.abs(initialVelocity) >
+ ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
+ getChildCount() > 0) {
fling(-initialVelocity);
}
@@ -812,7 +806,7 @@ public class ScrollView extends FrameLayout {
/**
* Smooth scroll by a Y delta
*
- * @param delta the number of pixels to scroll by on the X axis
+ * @param delta the number of pixels to scroll by on the Y axis
*/
private void doScrollY(int delta) {
if (delta != 0) {
@@ -923,8 +917,8 @@ public class ScrollView extends FrameLayout {
int y = mScroller.getCurrY();
if (getChildCount() > 0) {
View child = getChildAt(0);
- mScrollX = clamp(x, this.getWidth(), child.getWidth());
- mScrollY = clamp(y, this.getHeight(), child.getHeight());
+ mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+ mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
if (localLOGV) Log.v(TAG, "mScrollY=" + mScrollY + " y=" + y
+ " height=" + this.getHeight()
+ " child height=" + child.getHeight());
@@ -1167,8 +1161,8 @@ public class ScrollView extends FrameLayout {
* which means we want to scroll towards the top.
*/
public void fling(int velocityY) {
- int height = getHeight();
- int bottom = getChildAt(getChildCount() - 1).getBottom();
+ int height = getHeight() - mPaddingBottom - mPaddingTop;
+ int bottom = getChildAt(0).getHeight();
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, bottom - height);
@@ -1198,8 +1192,8 @@ public class ScrollView extends FrameLayout {
// we rely on the fact the View.scrollBy calls scrollTo.
if (getChildCount() > 0) {
View child = getChildAt(0);
- x = clamp(x, this.getWidth(), child.getWidth());
- y = clamp(y, this.getHeight(), child.getHeight());
+ x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+ y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
if (x != mScrollX || y != mScrollY) {
super.scrollTo(x, y);
}
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index fbe5e57..febc956 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -132,6 +132,26 @@ public class Scroller {
}
/**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ * @hide pending API council
+ */
+ public final int getStartX() {
+ return mStartX;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ * @hide pending API council
+ */
+ public final int getStartY() {
+ return mStartY;
+ }
+
+ /**
* Returns where the scroll will end. Valid only for "fling" scrolls.
*
* @return The final X offset as an absolute distance from the origin.
diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java
index 261da9f..093c24e 100644
--- a/core/java/android/widget/SimpleAdapter.java
+++ b/core/java/android/widget/SimpleAdapter.java
@@ -36,12 +36,16 @@ import java.util.Map;
* Binding data to views occurs in two phases. First, if a
* {@link android.widget.SimpleAdapter.ViewBinder} is available,
* {@link ViewBinder#setViewValue(android.view.View, Object, String)}
- * is invoked. If the returned value is true, binding has occured. If the
- * returned value is false and the view to bind is a TextView,
- * {@link #setViewText(TextView, String)} is invoked. If the returned value
- * is false and the view to bind is an ImageView,
- * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is
- * invoked. If no appropriate binding can be found, an {@link IllegalStateException} is thrown.
+ * is invoked. If the returned value is true, binding has occurred.
+ * If the returned value is false, the following views are then tried in order:
+ * <ul>
+ * <li> A view that implements Checkable (e.g. CheckBox). The expected bind value is a boolean.
+ * <li> TextView. The expected bind value is a string and {@link #setViewText(TextView, String)}
+ * is invoked.
+ * <li> ImageView. The expected bind value is a resource id or a string and
+ * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked.
+ * </ul>
+ * If no appropriate binding can be found, an {@link IllegalStateException} is thrown.
*/
public class SimpleAdapter extends BaseAdapter implements Filterable {
private int[] mTo;
@@ -176,7 +180,16 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
}
if (!bound) {
- if (v instanceof TextView) {
+ if (v instanceof Checkable) {
+ if (data instanceof Boolean) {
+ ((Checkable) v).setChecked((Boolean) data);
+ } else {
+ throw new IllegalStateException(v.getClass().getName() +
+ " should be bound to a Boolean, not a " + data.getClass());
+ }
+ } else if (v instanceof TextView) {
+ // Note: keep the instanceof TextView check at the bottom of these
+ // ifs since a lot of views are TextViews (e.g. CheckBoxes).
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
if (data instanceof Integer) {
diff --git a/core/java/com/android/internal/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
index a4045d5..e0f1bb4 100644
--- a/core/java/com/android/internal/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.widget;
import android.view.ViewGroup;
import android.view.View;
@@ -30,7 +30,7 @@ import android.graphics.Bitmap;
import android.os.SystemClock;
import android.os.Handler;
import android.os.Message;
-import com.android.internal.R;
+import android.R;
/**
* SlidingDrawer hides content out of the screen and allows the user to drag a handle
@@ -48,7 +48,7 @@ import com.android.internal.R;
* content:
*
* <pre class="prettyprint">
- * &lt;com.android.internal.widget.SlidingDrawer
+ * &lt;SlidingDrawer
* android:id="@+id/drawer"
* android:layout_width="fill_parent"
* android:layout_height="fill_parent"
@@ -66,16 +66,16 @@ import com.android.internal.R;
* android:layout_width="fill_parent"
* android:layout_height="fill_parent" /&gt;
*
- * &lt;/com.android.internal.widget.SlidingDrawer&gt;
+ * &lt;/SlidingDrawer&gt;
* </pre>
*
- * @attr ref com.android.internal.R.styleable#SlidingDrawer_content
- * @attr ref com.android.internal.R.styleable#SlidingDrawer_handle
- * @attr ref com.android.internal.R.styleable#SlidingDrawer_topOffset
- * @attr ref com.android.internal.R.styleable#SlidingDrawer_bottomOffset
- * @attr ref com.android.internal.R.styleable#SlidingDrawer_orientation
- * @attr ref com.android.internal.R.styleable#SlidingDrawer_allowSingleTap
- * @attr ref com.android.internal.R.styleable#SlidingDrawer_animateOnClick
+ * @attr ref android.R.styleable#SlidingDrawer_content
+ * @attr ref android.R.styleable#SlidingDrawer_handle
+ * @attr ref android.R.styleable#SlidingDrawer_topOffset
+ * @attr ref android.R.styleable#SlidingDrawer_bottomOffset
+ * @attr ref android.R.styleable#SlidingDrawer_orientation
+ * @attr ref android.R.styleable#SlidingDrawer_allowSingleTap
+ * @attr ref android.R.styleable#SlidingDrawer_animateOnClick
*/
public class SlidingDrawer extends ViewGroup {
public static final int ORIENTATION_HORIZONTAL = 0;
@@ -128,6 +128,13 @@ public class SlidingDrawer extends ViewGroup {
private boolean mAllowSingleTap;
private boolean mAnimateOnClick;
+ private final int mTapThreshold;
+ private final int mMaximumTapVelocity;
+ private final int mMaximumMinorVelocity;
+ private final int mMaximumMajorVelocity;
+ private final int mMaximumAcceleration;
+ private final int mVelocityUnits;
+
/**
* Callback invoked when the drawer is opened.
*/
@@ -206,6 +213,14 @@ public class SlidingDrawer extends ViewGroup {
mHandleId = handleId;
mContentId = contentId;
+ final float density = getResources().getDisplayMetrics().density;
+ mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
+ mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
+ mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
+ mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
+ mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
+ mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);
+
a.recycle();
setAlwaysDrawnWithCacheEnabled(false);
@@ -343,15 +358,17 @@ public class SlidingDrawer extends ViewGroup {
}
if (action == MotionEvent.ACTION_DOWN) {
- if (mOnDrawerScrollListener != null) {
- mOnDrawerScrollListener.onScrollStarted();
- }
mTracking = true;
handle.setPressed(true);
// Must be called before prepareTracking()
prepareContent();
+ // Must be called after prepareContent()
+ if (mOnDrawerScrollListener != null) {
+ mOnDrawerScrollListener.onScrollStarted();
+ }
+
if (mVertical) {
final int top = mHandle.getTop();
mTouchDelta = (int) y - top;
@@ -383,7 +400,7 @@ public class SlidingDrawer extends ViewGroup {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(VELOCITY_UNITS);
+ velocityTracker.computeCurrentVelocity(mVelocityUnits);
float yVelocity = velocityTracker.getYVelocity();
float xVelocity = velocityTracker.getXVelocity();
@@ -395,16 +412,16 @@ public class SlidingDrawer extends ViewGroup {
if (xVelocity < 0) {
xVelocity = -xVelocity;
}
- if (xVelocity > MAXIMUM_MINOR_VELOCITY) {
- xVelocity = MAXIMUM_MINOR_VELOCITY;
+ if (xVelocity > mMaximumMinorVelocity) {
+ xVelocity = mMaximumMinorVelocity;
}
} else {
negative = xVelocity < 0;
if (yVelocity < 0) {
yVelocity = -yVelocity;
}
- if (yVelocity > MAXIMUM_MINOR_VELOCITY) {
- yVelocity = MAXIMUM_MINOR_VELOCITY;
+ if (yVelocity > mMaximumMinorVelocity) {
+ yVelocity = mMaximumMinorVelocity;
}
}
@@ -416,13 +433,13 @@ public class SlidingDrawer extends ViewGroup {
final int top = mHandle.getTop();
final int left = mHandle.getLeft();
- if (Math.abs(velocity) < MAXIMUM_TAP_VELOCITY) {
- if (vertical ? (mExpanded && top < TAP_THRESHOLD + mTopOffset) ||
+ if (Math.abs(velocity) < mMaximumTapVelocity) {
+ if (vertical ? (mExpanded && top < mTapThreshold + mTopOffset) ||
(!mExpanded && top > mBottomOffset + mBottom - mTop -
- mHandleHeight - TAP_THRESHOLD) :
- (mExpanded && left < TAP_THRESHOLD + mTopOffset) ||
+ mHandleHeight - mTapThreshold) :
+ (mExpanded && left < mTapThreshold + mTopOffset) ||
(!mExpanded && left > mBottomOffset + mRight - mLeft -
- mHandleWidth - TAP_THRESHOLD)) {
+ mHandleWidth - mTapThreshold)) {
if (mAllowSingleTap) {
playSoundEffect(SoundEffectConstants.CLICK);
@@ -432,6 +449,8 @@ public class SlidingDrawer extends ViewGroup {
} else {
animateOpen(vertical ? top : left);
}
+ } else {
+ performFling(vertical ? top : left, velocity, false);
}
} else {
@@ -450,12 +469,12 @@ public class SlidingDrawer extends ViewGroup {
private void animateClose(int position) {
prepareTracking(position);
- performFling(position, 2000.0f, true);
+ performFling(position, mMaximumAcceleration, true);
}
private void animateOpen(int position) {
prepareTracking(position);
- performFling(position, -2000.0f, true);
+ performFling(position, -mMaximumAcceleration, true);
}
private void performFling(int position, float velocity, boolean always) {
@@ -463,35 +482,35 @@ public class SlidingDrawer extends ViewGroup {
mAnimatedVelocity = velocity;
if (mExpanded) {
- if (always || (velocity > MAXIMUM_MAJOR_VELOCITY ||
+ if (always || (velocity > mMaximumMajorVelocity ||
(position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth) &&
- velocity > -MAXIMUM_MAJOR_VELOCITY))) {
+ velocity > -mMaximumMajorVelocity))) {
// We are expanded, but they didn't move sufficiently to cause
// us to retract. Animate back to the expanded position.
- mAnimatedAcceleration = MAXIMUM_ACCELERATION;
+ mAnimatedAcceleration = mMaximumAcceleration;
if (velocity < 0) {
mAnimatedVelocity = 0;
}
} else {
// We are expanded and are now going to animate away.
- mAnimatedAcceleration = -MAXIMUM_ACCELERATION;
+ mAnimatedAcceleration = -mMaximumAcceleration;
if (velocity > 0) {
mAnimatedVelocity = 0;
}
}
} else {
- if (!always && (velocity > MAXIMUM_MAJOR_VELOCITY ||
+ if (!always && (velocity > mMaximumMajorVelocity ||
(position > (mVertical ? getHeight() : getWidth()) / 2 &&
- velocity > -MAXIMUM_MAJOR_VELOCITY))) {
+ velocity > -mMaximumMajorVelocity))) {
// We are collapsed, and they moved enough to allow us to expand.
- mAnimatedAcceleration = MAXIMUM_ACCELERATION;
+ mAnimatedAcceleration = mMaximumAcceleration;
if (velocity < 0) {
mAnimatedVelocity = 0;
}
} else {
// We are collapsed, but they didn't move sufficiently to cause
// us to retract. Animate back to the collapsed position.
- mAnimatedAcceleration = -MAXIMUM_ACCELERATION;
+ mAnimatedAcceleration = -mMaximumAcceleration;
if (velocity > 0) {
mAnimatedVelocity = 0;
}
@@ -512,8 +531,8 @@ public class SlidingDrawer extends ViewGroup {
mVelocityTracker = VelocityTracker.obtain();
boolean opening = !mExpanded;
if (opening) {
- mAnimatedAcceleration = MAXIMUM_ACCELERATION;
- mAnimatedVelocity = 200;
+ mAnimatedAcceleration = mMaximumAcceleration;
+ mAnimatedVelocity = mMaximumMajorVelocity;
mAnimationPosition = mBottomOffset +
(mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth);
moveHandle((int) mAnimationPosition);
@@ -747,11 +766,11 @@ public class SlidingDrawer extends ViewGroup {
* @see #toggle()
*/
public void animateClose() {
+ prepareContent();
final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
if (scrollListener != null) {
scrollListener.onScrollStarted();
}
- prepareContent();
animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft());
if (scrollListener != null) {
scrollListener.onScrollEnded();
@@ -768,11 +787,11 @@ public class SlidingDrawer extends ViewGroup {
* @see #toggle()
*/
public void animateOpen() {
+ prepareContent();
final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
if (scrollListener != null) {
scrollListener.onScrollStarted();
}
- prepareContent();
animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());
if (scrollListener != null) {
scrollListener.onScrollEnded();
@@ -782,6 +801,7 @@ public class SlidingDrawer extends ViewGroup {
private void closeDrawer() {
moveHandle(COLLAPSED_FULL_CLOSED);
mContent.setVisibility(View.GONE);
+ mContent.destroyDrawingCache();
if (!mExpanded) {
return;
@@ -796,8 +816,6 @@ public class SlidingDrawer extends ViewGroup {
private void openDrawer() {
moveHandle(EXPANDED_FULL_OPEN);
mContent.setVisibility(View.VISIBLE);
- // TODO: Should we uncomment to preserve memory, but increase memory churn?
- // mContent.destroyDrawingCache();
if (mExpanded) {
return;
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index da4a077..dc2c70d 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -405,7 +405,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
* Specify a label and icon as the tab indicator.
*/
public TabSpec setIndicator(CharSequence label, Drawable icon) {
- mIndicatorStrategy = new LabelAndIconIndicatorStategy(label, icon);
+ mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
return this;
}
@@ -497,12 +497,12 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
/**
* How we create a tab indicator that has a label and an icon
*/
- private class LabelAndIconIndicatorStategy implements IndicatorStrategy {
+ private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {
private final CharSequence mLabel;
private final Drawable mIcon;
- private LabelAndIconIndicatorStategy(CharSequence label, Drawable icon) {
+ private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
mLabel = label;
mIcon = icon;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index aa70663..7b62b50 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -17,6 +17,7 @@
package android.widget;
import android.content.Context;
+import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -32,6 +33,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.Message;
import android.text.BoringLayout;
@@ -41,7 +43,9 @@ import android.text.GetChars;
import android.text.GraphicsOperations;
import android.text.ClipboardManager;
import android.text.InputFilter;
+import android.text.InputType;
import android.text.Layout;
+import android.text.ParcelableSpan;
import android.text.Selection;
import android.text.SpanWatcher;
import android.text.Spannable;
@@ -69,7 +73,6 @@ 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;
import android.util.Log;
@@ -83,9 +86,11 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
+import android.view.ViewRoot;
import android.view.ViewTreeObserver;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AnimationUtils;
+import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
@@ -168,6 +173,9 @@ import org.xmlpull.v1.XmlPullParserException;
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
+ static final String TAG = "TextView";
+ static final boolean DEBUG_EXTRACT = false;
+
private static int PRIORITY = 100;
private ColorStateList mTextColor;
@@ -177,6 +185,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mCurHintTextColor;
private boolean mFreezesText;
private boolean mFrozenWithFocus;
+ private boolean mTemporaryDetach;
+
+ private boolean mEatTouchRelease = false;
+ private boolean mScrolled = false;
private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
@@ -207,16 +219,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
int mDrawablePadding;
- };
+ }
private Drawables mDrawables;
private CharSequence mError;
private boolean mErrorWasChanged;
private PopupWindow mPopup;
+ /**
+ * This flag is set if the TextView tries to display an error before it
+ * is attached to the window (so its position is still unknown).
+ * It causes the error to be shown later, when onAttachedToWindow()
+ * is called.
+ */
+ private boolean mShowErrorAfterAttach;
private CharWrapper mCharWrapper = null;
private boolean mSelectionMoved = false;
+ private boolean mTouchFocusSelected = false;
private Marquee mMarquee;
private boolean mRestartMarquee;
@@ -224,8 +244,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mMarqueeRepeatLimit = 3;
class InputContentType {
- String privateContentType;
+ int imeOptions = EditorInfo.IME_NULL;
+ String privateImeOptions;
+ CharSequence imeActionLabel;
+ int imeActionId;
Bundle extras;
+ OnEditorActionListener onEditorActionListener;
+ boolean enterDown;
}
InputContentType mInputContentType;
@@ -235,7 +260,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
float[] mTmpOffset = new float[2];
ExtractedTextRequest mExtracting;
final ExtractedText mTmpExtracted = new ExtractedText();
- boolean mBatchEditing;
+ int mBatchEditNesting;
+ boolean mCursorChanged;
+ boolean mSelectionModeChanged;
+ boolean mContentChanged;
+ int mChangedStart, mChangedEnd, mChangedDelta;
}
InputMethodState mInputMethodState;
@@ -250,6 +279,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
p.measureText("H");
}
+ /**
+ * Interface definition for a callback to be invoked when an action is
+ * performed on the editor.
+ */
+ public interface OnEditorActionListener {
+ /**
+ * Called when an action is being performed.
+ *
+ * @param v The view that was clicked.
+ * @param actionId Identifier of the action. This will be either the
+ * identifier you supplied, or {@link EditorInfo#IME_NULL
+ * EditorInfo.IME_NULL} if being called due to the enter key
+ * being pressed.
+ * @param event If triggered by an enter key, this is the event;
+ * otherwise, this is null.
+ * @return Return true if you have consumed the action, else false.
+ */
+ boolean onEditorAction(TextView v, int actionId, KeyEvent event);
+ }
+
public TextView(Context context) {
this(context, null);
}
@@ -358,7 +407,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int shadowcolor = 0;
float dx = 0, dy = 0, r = 0;
boolean password = false;
- int contentType = EditorInfo.TYPE_NULL;
+ int inputType = EditorInfo.TYPE_NULL;
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
@@ -592,11 +641,34 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_inputType:
- contentType = a.getInt(attr, mInputType);
+ inputType = a.getInt(attr, mInputType);
break;
- case com.android.internal.R.styleable.TextView_editorPrivateContentType:
- setPrivateContentType(a.getString(attr));
+ case com.android.internal.R.styleable.TextView_imeOptions:
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeOptions = a.getInt(attr,
+ mInputContentType.imeOptions);
+ break;
+
+ case com.android.internal.R.styleable.TextView_imeActionLabel:
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeActionLabel = a.getText(attr);
+ break;
+
+ case com.android.internal.R.styleable.TextView_imeActionId:
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeActionId = a.getInt(attr,
+ mInputContentType.imeActionId);
+ break;
+
+ case com.android.internal.R.styleable.TextView_privateImeOptions:
+ setPrivateImeOptions(a.getString(attr));
break;
case com.android.internal.R.styleable.TextView_editorExtras:
@@ -614,7 +686,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
BufferType bufferType = BufferType.EDITABLE;
- if ((contentType&(EditorInfo.TYPE_MASK_CLASS
+ if ((inputType&(EditorInfo.TYPE_MASK_CLASS
|EditorInfo.TYPE_MASK_VARIATION))
== (EditorInfo.TYPE_CLASS_TEXT
|EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
@@ -638,57 +710,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
throw new RuntimeException(ex);
}
try {
- mInputType = contentType != EditorInfo.TYPE_NULL
- ? contentType
+ mInputType = inputType != EditorInfo.TYPE_NULL
+ ? inputType
: mInput.getInputType();
} catch (IncompatibleClassChangeError e) {
mInputType = EditorInfo.TYPE_CLASS_TEXT;
}
} else if (digits != null) {
mInput = DigitsKeyListener.getInstance(digits.toString());
- mInputType = contentType;
- } else if (contentType != EditorInfo.TYPE_NULL) {
- setInputType(contentType, true);
- singleLine = (contentType&(EditorInfo.TYPE_MASK_CLASS
+ mInputType = inputType;
+ } else if (inputType != EditorInfo.TYPE_NULL) {
+ setInputType(inputType, true);
+ singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) !=
(EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
} else if (phone) {
mInput = DialerKeyListener.getInstance();
- contentType = EditorInfo.TYPE_CLASS_PHONE;
+ inputType = EditorInfo.TYPE_CLASS_PHONE;
} else if (numeric != 0) {
mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
(numeric & DECIMAL) != 0);
- contentType = EditorInfo.TYPE_CLASS_NUMBER;
+ inputType = EditorInfo.TYPE_CLASS_NUMBER;
if ((numeric & SIGNED) != 0) {
- contentType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
+ inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
}
if ((numeric & DECIMAL) != 0) {
- contentType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
+ inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
}
- mInputType = contentType;
+ mInputType = inputType;
} else if (autotext || autocap != -1) {
TextKeyListener.Capitalize cap;
- contentType = EditorInfo.TYPE_CLASS_TEXT;
+ inputType = EditorInfo.TYPE_CLASS_TEXT;
if (!singleLine) {
- contentType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
}
switch (autocap) {
case 1:
cap = TextKeyListener.Capitalize.SENTENCES;
- contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
break;
case 2:
cap = TextKeyListener.Capitalize.WORDS;
- contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
break;
case 3:
cap = TextKeyListener.Capitalize.CHARACTERS;
- contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
break;
default:
@@ -697,7 +769,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
mInput = TextKeyListener.getInstance(autotext, cap);
- mInputType = contentType;
+ mInputType = inputType;
} else if (editable) {
mInput = TextKeyListener.getInstance();
mInputType = EditorInfo.TYPE_CLASS_TEXT;
@@ -769,6 +841,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (password) {
setTransformationMethod(PasswordTransformationMethod.getInstance());
typefaceIndex = MONOSPACE;
+ } else if ((mInputType&(EditorInfo.TYPE_MASK_CLASS
+ |EditorInfo.TYPE_MASK_VARIATION))
+ == (EditorInfo.TYPE_CLASS_TEXT
+ |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
+ typefaceIndex = MONOSPACE;
}
setTypefaceByIndex(typefaceIndex, styleIndex);
@@ -1057,6 +1134,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_singleLine
*/
public final void setTransformationMethod(TransformationMethod method) {
+ if (method == mTransformation) {
+ // Avoid the setText() below if the transformation is
+ // the same.
+ return;
+ }
if (mTransformation != null) {
if (mText instanceof Spannable) {
((Spannable) mText).removeSpan(mTransformation);
@@ -1330,7 +1412,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
final Resources resources = getContext().getResources();
- setCompoundDrawables(left != 0 ? resources.getDrawable(left) : null,
+ setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
top != 0 ? resources.getDrawable(top) : null,
right != 0 ? resources.getDrawable(right) : null,
bottom != 0 ? resources.getDrawable(bottom) : null);
@@ -1412,10 +1494,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public void setPadding(int left, int top, int right, int bottom) {
- if (left != getPaddingLeft() ||
- right != getPaddingRight() ||
- top != getPaddingTop() ||
- bottom != getPaddingBottom()) {
+ if (left != mPaddingLeft ||
+ right != mPaddingRight ||
+ top != mPaddingTop ||
+ bottom != mPaddingBottom) {
nullLayouts();
}
@@ -1504,6 +1586,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textSize
*/
+ @android.view.RemotableViewMethod
public void setTextSize(float size) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
@@ -1555,6 +1638,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textScaleX
*/
+ @android.view.RemotableViewMethod
public void setTextScaleX(float size) {
if (size != mTextPaint.getTextScaleX()) {
mTextPaint.setTextScaleX(size);
@@ -1603,6 +1687,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textColor
*/
+ @android.view.RemotableViewMethod
public void setTextColor(int color) {
mTextColor = ColorStateList.valueOf(color);
updateTextColors();
@@ -1645,6 +1730,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textColorHighlight
*/
+ @android.view.RemotableViewMethod
public void setHighlightColor(int color) {
if (mHighlightColor != color) {
mHighlightColor = color;
@@ -1686,6 +1772,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_autoLink
*/
+ @android.view.RemotableViewMethod
public final void setAutoLinkMask(int mask) {
mAutoLinkMask = mask;
}
@@ -1698,6 +1785,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_linksClickable
*/
+ @android.view.RemotableViewMethod
public final void setLinksClickable(boolean whether) {
mLinksClickable = whether;
}
@@ -1734,6 +1822,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textColorHint
*/
+ @android.view.RemotableViewMethod
public final void setHintTextColor(int color) {
mHintTextColor = ColorStateList.valueOf(color);
updateTextColors();
@@ -1772,6 +1861,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textColorLink
*/
+ @android.view.RemotableViewMethod
public final void setLinkTextColor(int color) {
mLinkTextColor = ColorStateList.valueOf(color);
updateTextColors();
@@ -1859,6 +1949,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* reflows the text if they are different from the old flags.
* @see Paint#setFlags
*/
+ @android.view.RemotableViewMethod
public void setPaintFlags(int flags) {
if (mTextPaint.getFlags() != flags) {
mTextPaint.setFlags(flags);
@@ -1892,6 +1983,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_minLines
*/
+ @android.view.RemotableViewMethod
public void setMinLines(int minlines) {
mMinimum = minlines;
mMinMode = LINES;
@@ -1905,6 +1997,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_minHeight
*/
+ @android.view.RemotableViewMethod
public void setMinHeight(int minHeight) {
mMinimum = minHeight;
mMinMode = PIXELS;
@@ -1918,6 +2011,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_maxLines
*/
+ @android.view.RemotableViewMethod
public void setMaxLines(int maxlines) {
mMaximum = maxlines;
mMaxMode = LINES;
@@ -1931,6 +2025,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_maxHeight
*/
+ @android.view.RemotableViewMethod
public void setMaxHeight(int maxHeight) {
mMaximum = maxHeight;
mMaxMode = PIXELS;
@@ -1944,6 +2039,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_lines
*/
+ @android.view.RemotableViewMethod
public void setLines(int lines) {
mMaximum = mMinimum = lines;
mMaxMode = mMinMode = LINES;
@@ -1959,6 +2055,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_height
*/
+ @android.view.RemotableViewMethod
public void setHeight(int pixels) {
mMaximum = mMinimum = pixels;
mMaxMode = mMinMode = PIXELS;
@@ -1972,6 +2069,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_minEms
*/
+ @android.view.RemotableViewMethod
public void setMinEms(int minems) {
mMinWidth = minems;
mMinWidthMode = EMS;
@@ -1985,6 +2083,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_minWidth
*/
+ @android.view.RemotableViewMethod
public void setMinWidth(int minpixels) {
mMinWidth = minpixels;
mMinWidthMode = PIXELS;
@@ -1998,6 +2097,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_maxEms
*/
+ @android.view.RemotableViewMethod
public void setMaxEms(int maxems) {
mMaxWidth = maxems;
mMaxWidthMode = EMS;
@@ -2011,6 +2111,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_maxWidth
*/
+ @android.view.RemotableViewMethod
public void setMaxWidth(int maxpixels) {
mMaxWidth = maxpixels;
mMaxWidthMode = PIXELS;
@@ -2024,6 +2125,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_ems
*/
+ @android.view.RemotableViewMethod
public void setEms(int ems) {
mMaxWidth = mMinWidth = ems;
mMaxWidthMode = mMinWidthMode = EMS;
@@ -2039,6 +2141,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_width
*/
+ @android.view.RemotableViewMethod
public void setWidth(int pixels) {
mMaxWidth = mMinWidth = pixels;
mMaxWidthMode = mMinWidthMode = PIXELS;
@@ -2150,6 +2253,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int selEnd;
CharSequence text;
boolean frozenWithFocus;
+ CharSequence error;
SavedState(Parcelable superState) {
super(superState);
@@ -2162,6 +2266,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
out.writeInt(selEnd);
out.writeInt(frozenWithFocus ? 1 : 0);
TextUtils.writeToParcel(text, out, flags);
+
+ if (error == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(1);
+ TextUtils.writeToParcel(error, out, flags);
+ }
}
@Override
@@ -2192,6 +2303,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
selEnd = in.readInt();
frozenWithFocus = (in.readInt() != 0);
text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+
+ if (in.readInt() != 0) {
+ error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
}
}
@@ -2244,6 +2359,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ss.frozenWithFocus = true;
}
+ ss.error = mError;
+
return ss;
}
@@ -2289,6 +2406,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
}
+
+ if (ss.error != null) {
+ setError(ss.error);
+ }
}
/**
@@ -2304,6 +2425,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_freezesText
*/
+ @android.view.RemotableViewMethod
public void setFreezesText(boolean freezesText) {
mFreezesText = freezesText;
}
@@ -2342,12 +2464,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Sets the string value of the TextView. TextView <em>does not</em> accept
* HTML-like formatting, which you can do with text strings in XML resource files.
* To style your strings, attach android.text.style.* objects to a
- * {@link android.text.SpannableString SpannableString}, or see
- * <a href="{@docRoot}reference/available-resources.html#stringresources">
- * String Resources</a> for an example of setting formatted text in the XML resource file.
+ * {@link android.text.SpannableString SpannableString}, or see the
+ * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
+ * Available Resource Types</a> documentation for an example of setting
+ * formatted text in the XML resource file.
*
* @attr ref android.R.styleable#TextView_text
*/
+ @android.view.RemotableViewMethod
public final void setText(CharSequence text) {
setText(text, mBufferType);
}
@@ -2360,6 +2484,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @see #setText(CharSequence)
*/
+ @android.view.RemotableViewMethod
public final void setTextKeepState(CharSequence text) {
setTextKeepState(text, mBufferType);
}
@@ -2630,6 +2755,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ @android.view.RemotableViewMethod
public final void setText(int resid) {
setText(getContext().getResources().getText(resid));
}
@@ -2648,6 +2774,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_hint
*/
+ @android.view.RemotableViewMethod
public final void setHint(CharSequence hint) {
mHint = TextUtils.stringOrSpannedString(hint);
@@ -2668,6 +2795,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_hint
*/
+ @android.view.RemotableViewMethod
public final void setHint(int resid) {
setHint(getContext().getResources().getText(resid));
}
@@ -2698,20 +2826,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public void setInputType(int type) {
setInputType(type, false);
- if ((type&(EditorInfo.TYPE_MASK_CLASS
- |EditorInfo.TYPE_MASK_VARIATION))
+ final int variation = type&(EditorInfo.TYPE_MASK_CLASS
+ |EditorInfo.TYPE_MASK_VARIATION);
+ final boolean isPassword = variation
== (EditorInfo.TYPE_CLASS_TEXT
- |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
+ |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
+ boolean forceUpdate = false;
+ if (isPassword) {
setTransformationMethod(PasswordTransformationMethod.getInstance());
setTypefaceByIndex(MONOSPACE, 0);
+ } else if (mTransformation == PasswordTransformationMethod.getInstance()) {
+ // We need to clean up if we were previously in password mode.
+ if (variation != (EditorInfo.TYPE_CLASS_TEXT
+ |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) {
+ setTypefaceByIndex(-1, -1);
+ }
+ forceUpdate = true;
+ } else if (variation == (EditorInfo.TYPE_CLASS_TEXT
+ |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) {
+ setTypefaceByIndex(MONOSPACE, 0);
}
+
boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
(EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
- if (mSingleLine == multiLine) {
- setSingleLine(!multiLine);
- }
+
+ // We need to update the single line mode if it has changed or we
+ // were previously in password mode.
+ if (mSingleLine == multiLine || forceUpdate) {
+ // Change single line mode, but only change the transformation if
+ // we are not in password mode.
+ applySingleLine(!multiLine, !isPassword);
+ }
+
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) imm.restartInput(this);
}
@@ -2719,7 +2867,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Directly change the content type integer of the text view, without
* modifying any other state.
- * @see #setContentType
+ * @see #setInputType(int)
* @see android.text.InputType
* @attr ref android.R.styleable#TextView_inputType
*/
@@ -2783,28 +2931,175 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Change the editor type integer associated with the text view, which
+ * will be reported to an IME with {@link EditorInfo#imeOptions} when it
+ * has focus.
+ * @see #getImeOptions
+ * @see android.view.inputmethod.EditorInfo
+ * @attr ref android.R.styleable#TextView_imeOptions
+ */
+ public void setImeOptions(int imeOptions) {
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeOptions = imeOptions;
+ }
+
+ /**
+ * Get the type of the IME editor.
+ *
+ * @see #setImeOptions(int)
+ * @see android.view.inputmethod.EditorInfo
+ */
+ public int getImeOptions() {
+ return mInputContentType != null
+ ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
+ }
+
+ /**
+ * Change the custom IME action associated with the text view, which
+ * will be reported to an IME with {@link EditorInfo#actionLabel}
+ * and {@link EditorInfo#actionId} when it has focus.
+ * @see #getImeActionLabel
+ * @see #getImeActionId
+ * @see android.view.inputmethod.EditorInfo
+ * @attr ref android.R.styleable#TextView_imeActionLabel
+ * @attr ref android.R.styleable#TextView_imeActionId
+ */
+ public void setImeActionLabel(CharSequence label, int actionId) {
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeActionLabel = label;
+ mInputContentType.imeActionId = actionId;
+ }
+
+ /**
+ * Get the IME action label previous set with {@link #setImeActionLabel}.
+ *
+ * @see #setImeActionLabel
+ * @see android.view.inputmethod.EditorInfo
+ */
+ public CharSequence getImeActionLabel() {
+ return mInputContentType != null
+ ? mInputContentType.imeActionLabel : null;
+ }
+
+ /**
+ * Get the IME action ID previous set with {@link #setImeActionLabel}.
+ *
+ * @see #setImeActionLabel
+ * @see android.view.inputmethod.EditorInfo
+ */
+ public int getImeActionId() {
+ return mInputContentType != null
+ ? mInputContentType.imeActionId : 0;
+ }
+
+ /**
+ * Set a special listener to be called when an action is performed
+ * on the text view. This will be called when the enter key is pressed,
+ * or when an action supplied to the IME is selected by the user. Setting
+ * this means that the normal hard key event will not insert a newline
+ * into the text view, even if it is multi-line; holding down the ALT
+ * modifier will, however, allow the user to insert a newline character.
+ */
+ public void setOnEditorActionListener(OnEditorActionListener l) {
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.onEditorActionListener = l;
+ }
+
+ /**
+ * Called when an attached input method calls
+ * {@link InputConnection#performEditorAction(int)
+ * InputConnection.performEditorAction()}
+ * for this text view. The default implementation will call your action
+ * listener supplied to {@link #setOnEditorActionListener}, or perform
+ * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
+ * EditorInfo.IME_ACTION_NEXT} or {@link EditorInfo#IME_ACTION_DONE
+ * EditorInfo.IME_ACTION_DONE}.
+ *
+ * <p>For backwards compatibility, if no IME options have been set and the
+ * text view would not normally advance focus on enter, then
+ * the NEXT and DONE actions received here will be turned into an enter
+ * key down/up pair to go through the normal key handling.
+ *
+ * @param actionCode The code of the action being performed.
+ *
+ * @see #setOnEditorActionListener
+ */
+ public void onEditorAction(int actionCode) {
+ final InputContentType ict = mInputContentType;
+ if (ict != null) {
+ if (ict.onEditorActionListener != null) {
+ if (ict.onEditorActionListener.onEditorAction(this,
+ actionCode, null)) {
+ return;
+ }
+ }
+ }
+ if (ict != null || !shouldAdvanceFocusOnEnter()) {
+ // This is the handling for some default action.
+ // Note that for backwards compatibility we don't do this
+ // default handling if explicit ime options have not been given,
+ // to instead turn this into the normal enter key codes that an
+ // app may be expecting.
+ if (actionCode == EditorInfo.IME_ACTION_NEXT) {
+ View v = focusSearch(FOCUS_DOWN);
+ if (v != null) {
+ if (!v.requestFocus(FOCUS_DOWN)) {
+ throw new IllegalStateException("focus search returned a view " +
+ "that wasn't able to take focus!");
+ }
+ }
+ return;
+
+ } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+ return;
+ }
+ }
+
+ Handler h = getHandler();
+ long eventTime = SystemClock.uptimeMillis();
+ h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ }
+
+ /**
* Set the private content type of the text, which is the
- * {@link EditorInfo#privateContentType TextBoxAttribute.privateContentType}
+ * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
* field that will be filled in when creating an input connection.
*
- * @see #getPrivateContentType()
- * @see EditorInfo#privateContentType
- * @attr ref android.R.styleable#TextView_editorPrivateContentType
+ * @see #getPrivateImeOptions()
+ * @see EditorInfo#privateImeOptions
+ * @attr ref android.R.styleable#TextView_privateImeOptions
*/
- public void setPrivateContentType(String type) {
+ public void setPrivateImeOptions(String type) {
if (mInputContentType == null) mInputContentType = new InputContentType();
- mInputContentType.privateContentType = type;
+ mInputContentType.privateImeOptions = type;
}
/**
* Get the private type of the content.
*
- * @see #setPrivateContentType(String)
- * @see EditorInfo#privateContentType
+ * @see #setPrivateImeOptions(String)
+ * @see EditorInfo#privateImeOptions
*/
- public String getPrivateContentType() {
+ public String getPrivateImeOptions() {
return mInputContentType != null
- ? mInputContentType.privateContentType : null;
+ ? mInputContentType.privateImeOptions : null;
}
/**
@@ -2814,7 +3109,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* given integer is the resource ID of an XML resource holding an
* {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
*
- * @see #getInputExtras()
+ * @see #getInputExtras(boolean)
* @see EditorInfo#extras
* @attr ref android.R.styleable#TextView_editorExtras
*/
@@ -2832,7 +3127,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @param create If true, the extras will be created if they don't already
* exist. Otherwise, null will be returned if none have been created.
- * @see #setInputExtras(int)
+ * @see #setInputExtras(int)View
* @see EditorInfo#extras
* @attr ref android.R.styleable#TextView_editorExtras
*/
@@ -2865,6 +3160,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* <code>error</code> is <code>null</code>, the error message and icon
* will be cleared.
*/
+ @android.view.RemotableViewMethod
public void setError(CharSequence error) {
if (error == null) {
setError(null, null);
@@ -2916,13 +3212,39 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void showError() {
+ if (getWindowToken() == null) {
+ mShowErrorAfterAttach = true;
+ return;
+ }
+
if (mPopup == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
- TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
+ final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
null);
- mPopup = new PopupWindow(err, 200, 50);
+ mPopup = new PopupWindow(err, 200, 50) {
+ private boolean mAbove = false;
+
+ @Override
+ public void update(int x, int y, int w, int h, boolean force) {
+ super.update(x, y, w, h, force);
+
+ boolean above = isAboveAnchor();
+ if (above != mAbove) {
+ mAbove = above;
+
+ if (above) {
+ err.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above);
+ } else {
+ err.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error);
+ }
+ }
+ }
+ };
mPopup.setFocusable(false);
+ // The user is entering text, so the input method is needed. We
+ // don't want the popup to be displayed on top of it.
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}
TextView tv = (TextView) mPopup.getContentView();
@@ -2979,6 +3301,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mPopup.dismiss();
}
}
+
+ mShowErrorAfterAttach = false;
}
private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
@@ -3269,6 +3593,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mTemporaryDetach = false;
+
+ if (mShowErrorAfterAttach) {
+ showError();
+ mShowErrorAfterAttach = false;
+ }
+ }
+
+ @Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -3311,6 +3647,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
+ protected boolean verifyDrawable(Drawable who) {
+ final boolean verified = super.verifyDrawable(who);
+ if (!verified && mDrawables != null) {
+ return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
+ who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
+ }
+ return verified;
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
// Draw the background for this view
super.onDraw(canvas);
@@ -3505,38 +3851,48 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
*/
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (highlight != null && mInputMethodState != null
- && !mInputMethodState.mBatchEditing && imm != null) {
- 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);
+ final InputMethodState ims = mInputMethodState;
+ if (ims != null && ims.mBatchEditNesting == 0) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ if (imm.isActive(this)) {
+ boolean reported = false;
+ if (ims.mContentChanged || ims.mSelectionModeChanged) {
+ // We are in extract mode and the content has changed
+ // in some way... just report complete new text to the
+ // input method.
+ reported = reportExtractedText();
+ }
+ if (!reported && highlight != null) {
+ 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) && highlight != null) {
+ highlight.computeBounds(ims.mTmpRectF, true);
+ ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
+
+ canvas.getMatrix().mapPoints(ims.mTmpOffset);
+ ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
+
+ ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
+
+ ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
+ (int)(ims.mTmpRectF.top + 0.5),
+ (int)(ims.mTmpRectF.right + 0.5),
+ (int)(ims.mTmpRectF.bottom + 0.5));
+
+ imm.updateCursor(this,
+ ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
+ ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
}
- imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
- }
-
- if (imm.isWatchingCursor(this)) {
- final InputMethodState ims = mInputMethodState;
- highlight.computeBounds(ims.mTmpRectF, true);
- ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
-
- canvas.getMatrix().mapPoints(ims.mTmpOffset);
- ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
-
- ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
-
- ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
- (int)(ims.mTmpRectF.top + 0.5),
- (int)(ims.mTmpRectF.right + 0.5),
- (int)(ims.mTmpRectF.bottom + 0.5));
-
- imm.updateCursor(this,
- ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
- ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
}
}
@@ -3634,7 +3990,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- int which = doKeyDown(keyCode, event);
+ int which = doKeyDown(keyCode, event, null);
if (which == 0) {
// Go through default dispatching.
return super.onKeyDown(keyCode, event);
@@ -3647,12 +4003,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN);
- int which = doKeyDown(keyCode, down);
+ int which = doKeyDown(keyCode, down, event);
if (which == 0) {
// Go through default dispatching.
return super.onKeyMultiple(keyCode, repeatCount, event);
}
+ if (which == -1) {
+ // Consumed the whole thing.
+ return true;
+ }
+ repeatCount--;
+
// We are going to dispatch the remaining events to either the input
// or movement method. To do this, we will just send a repeated stream
// of down and up events until we have done the complete repeatCount.
@@ -3680,15 +4042,67 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
}
- private int doKeyDown(int keyCode, KeyEvent event) {
+ /**
+ * Returns true if pressing ENTER in this field advances focus instead
+ * of inserting the character. This is true mostly in single-line fields,
+ * but also in mail addresses and subjects which will display on multiple
+ * lines but where it doesn't make sense to insert newlines.
+ */
+ private boolean shouldAdvanceFocusOnEnter() {
+ if (mInput == null) {
+ return false;
+ }
+
+ if (mSingleLine) {
+ return true;
+ }
+
+ if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+ int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
+
+ if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
+ variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isInterestingEnter(KeyEvent event) {
+ if ((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 &&
+ mInputContentType != null &&
+ (mInputContentType.imeOptions &
+ EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+ // If this enter key came from a soft keyboard, and the
+ // text editor has been configured to not do a default
+ // action for software enter keys, then we aren't interested.
+ return false;
+ }
+ return true;
+ }
+
+ private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
if (!isEnabled()) {
return 0;
}
switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
- if (mSingleLine && mInput != null) {
+ if (!isInterestingEnter(event)) {
+ // Ignore enter key we aren't interested in.
+ return -1;
+ }
+ if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0
+ && mInputContentType != null
+ && mInputContentType.onEditorActionListener != null) {
+ mInputContentType.enterDown = true;
+ // We are consuming the enter key for them.
+ return -1;
+ }
+ // fall through...
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (shouldAdvanceFocusOnEnter()) {
return 0;
}
}
@@ -3702,20 +4116,63 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
mErrorWasChanged = false;
- if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
- if (mError != null && !mErrorWasChanged) {
- setError(null, null);
+ boolean doDown = true;
+ if (otherEvent != null) {
+ try {
+ beginBatchEdit();
+ boolean handled = mInput.onKeyOther(this, (Editable) mText,
+ otherEvent);
+ if (mError != null && !mErrorWasChanged) {
+ setError(null, null);
+ }
+ doDown = false;
+ if (handled) {
+ return -1;
+ }
+ } catch (AbstractMethodError e) {
+ // onKeyOther was added after 1.0, so if it isn't
+ // implemented we need to try to dispatch as a regular down.
+ } finally {
+ endBatchEdit();
+ }
+ }
+
+ if (doDown) {
+ beginBatchEdit();
+ if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
+ endBatchEdit();
+ if (mError != null && !mErrorWasChanged) {
+ setError(null, null);
+ }
+ return 1;
}
- return 1;
+ endBatchEdit();
}
}
// bug 650865: sometimes we get a key event before a layout.
// don't try to move around if we don't know the layout.
- if (mMovement != null && mLayout != null)
- if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
- return 2;
+ if (mMovement != null && mLayout != null) {
+ boolean doDown = true;
+ if (otherEvent != null) {
+ try {
+ boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
+ otherEvent);
+ doDown = false;
+ if (handled) {
+ return -1;
+ }
+ } catch (AbstractMethodError e) {
+ // onKeyOther was added after 1.0, so if it isn't
+ // implemented we need to try to dispatch as a regular down.
+ }
+ }
+ if (doDown) {
+ if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
+ return 2;
+ }
+ }
return 0;
}
@@ -3728,8 +4185,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
+ /*
+ * If there is a click listener, just call through to
+ * super, which will invoke it.
+ *
+ * If there isn't a click listener, try to show the soft
+ * input method. (It will also
+ * call performClick(), but that won't do anything in
+ * this case.)
+ */
+ if (mOnClickListener == null) {
+ if (mMovement != null && mText instanceof Editable
+ && mLayout != null && onCheckIsTextEditor()) {
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(this, 0);
+ }
+ }
+ return super.onKeyUp(keyCode, event);
+
case KeyEvent.KEYCODE_ENTER:
- if (mSingleLine && mInput != null) {
+ if (mInputContentType != null
+ && mInputContentType.onEditorActionListener != null
+ && mInputContentType.enterDown) {
+ mInputContentType.enterDown = false;
+ if (mInputContentType.onEditorActionListener.onEditorAction(
+ this, EditorInfo.IME_NULL, event)) {
+ return true;
+ }
+ }
+
+ if (shouldAdvanceFocusOnEnter()) {
/*
* If there is a click listener, just call through to
* super, which will invoke it.
@@ -3756,6 +4242,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
super.onKeyUp(keyCode, event);
return true;
+ } else if ((event.getFlags()
+ & KeyEvent.FLAG_SOFT_KEYBOARD) != 0) {
+ // No target for next focus, but make sure the IME
+ // if this came from it.
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
}
}
@@ -3784,11 +4278,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mInputMethodState = new InputMethodState();
}
outAttrs.inputType = mInputType;
- outAttrs.hintText = mHint;
if (mInputContentType != null) {
- outAttrs.privateContentType = mInputContentType.privateContentType;
+ outAttrs.imeOptions = mInputContentType.imeOptions;
+ outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
+ outAttrs.actionLabel = mInputContentType.imeActionLabel;
+ outAttrs.actionId = mInputContentType.imeActionId;
outAttrs.extras = mInputContentType.extras;
+ } else {
+ outAttrs.imeOptions = EditorInfo.IME_NULL;
+ }
+ if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
+ == EditorInfo.IME_ACTION_UNSPECIFIED) {
+ if (focusSearch(FOCUS_DOWN) != null) {
+ // An action has not been set, but the enter key will move to
+ // the next focus, so set the action to that.
+ outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
+ } else {
+ // An action has not been set, and there is no focus to move
+ // to, so let's just supply a "done" action.
+ outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
+ }
+ if (!shouldAdvanceFocusOnEnter()) {
+ outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
+ }
}
+ outAttrs.hintText = mHint;
if (mText instanceof Editable) {
InputConnection ic = new EditableInputConnection(this);
outAttrs.initialSelStart = Selection.getSelectionStart(mText);
@@ -3803,14 +4317,69 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* If this TextView contains editable content, extract a portion of it
* based on the information in <var>request</var> in to <var>outText</var>.
- * @return Returns true if the text is editable and was successfully
- * extracted, else false.
+ * @return Returns true if the text was successfully extracted, else false.
*/
public boolean extractText(ExtractedTextRequest request,
ExtractedText outText) {
- Editable content = getEditableText();
+ return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
+ EXTRACT_UNKNOWN, outText);
+ }
+
+ static final int EXTRACT_NOTHING = -2;
+ static final int EXTRACT_UNKNOWN = -1;
+
+ boolean extractTextInternal(ExtractedTextRequest request,
+ int partialStartOffset, int partialEndOffset, int delta,
+ ExtractedText outText) {
+ final CharSequence content = mText;
if (content != null) {
- outText.text = content.subSequence(0, content.length());
+ if (partialStartOffset != EXTRACT_NOTHING) {
+ final int N = content.length();
+ if (partialStartOffset < 0) {
+ outText.partialStartOffset = outText.partialEndOffset = -1;
+ partialStartOffset = 0;
+ partialEndOffset = N;
+ } else {
+ // Adjust offsets to ensure we contain full spans.
+ if (content instanceof Spanned) {
+ Spanned spanned = (Spanned)content;
+ Object[] spans = spanned.getSpans(partialStartOffset,
+ partialEndOffset, ParcelableSpan.class);
+ int i = spans.length;
+ while (i > 0) {
+ i--;
+ int j = spanned.getSpanStart(spans[i]);
+ if (j < partialStartOffset) partialStartOffset = j;
+ j = spanned.getSpanEnd(spans[i]);
+ if (j > partialEndOffset) partialEndOffset = j;
+ }
+ }
+ outText.partialStartOffset = partialStartOffset;
+ outText.partialEndOffset = partialEndOffset;
+ // Now use the delta to determine the actual amount of text
+ // we need.
+ partialEndOffset += delta;
+ if (partialEndOffset > N) {
+ partialEndOffset = N;
+ } else if (partialEndOffset < 0) {
+ partialEndOffset = 0;
+ }
+ }
+ if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
+ outText.text = content.subSequence(partialStartOffset,
+ partialEndOffset);
+ } else {
+ outText.text = TextUtils.substring(content, partialStartOffset,
+ partialEndOffset);
+ }
+ }
+ outText.flags = 0;
+ if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
+ outText.flags |= ExtractedText.FLAG_SELECTING;
+ }
+ if (mSingleLine) {
+ outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
+ }
outText.startOffset = 0;
outText.selectionStart = Selection.getSelectionStart(content);
outText.selectionEnd = Selection.getSelectionEnd(content);
@@ -3819,19 +4388,50 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
- void reportExtractedText() {
- if (mInputMethodState != null) {
+ boolean reportExtractedText() {
+ final InputMethodState ims = mInputMethodState;
+ final boolean contentChanged = ims.mContentChanged;
+ if (ims != null && (contentChanged || ims.mSelectionModeChanged)) {
+ ims.mContentChanged = false;
+ ims.mSelectionModeChanged = false;
final ExtractedTextRequest req = mInputMethodState.mExtracting;
if (req != null) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
- if (extractText(req, mInputMethodState.mTmpExtracted)) {
+ if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
+ + ims.mChangedStart + " end=" + ims.mChangedEnd
+ + " delta=" + ims.mChangedDelta);
+ if (ims.mChangedStart < 0 && !contentChanged) {
+ ims.mChangedStart = EXTRACT_NOTHING;
+ }
+ if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
+ ims.mChangedDelta, ims.mTmpExtracted)) {
+ if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
+ + ims.mTmpExtracted.partialStartOffset
+ + " end=" + ims.mTmpExtracted.partialEndOffset
+ + ": " + ims.mTmpExtracted.text);
imm.updateExtractedText(this, req.token,
mInputMethodState.mTmpExtracted);
+ return true;
}
}
}
}
+ return false;
+ }
+
+ /**
+ * This is used to remove all style-impacting spans from text before new
+ * extracted text is being replaced into it, so that we don't have any
+ * lingering spans applied during the replace.
+ */
+ static void removeParcelableSpans(Spannable spannable, int start, int end) {
+ Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
+ int i = spans.length;
+ while (i > 0) {
+ i--;
+ spannable.removeSpan(spans[i]);
+ }
}
/**
@@ -3839,9 +4439,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
*/
public void setExtractedText(ExtractedText text) {
- setText(text.text, TextView.BufferType.EDITABLE);
- Selection.setSelection((Spannable)getText(),
- text.selectionStart, text.selectionEnd);
+ Editable content = getEditableText();
+ if (text.text != null) {
+ if (content == null) {
+ setText(text.text, TextView.BufferType.EDITABLE);
+ } else if (text.partialStartOffset < 0) {
+ removeParcelableSpans(content, 0, content.length());
+ content.replace(0, content.length(), text.text);
+ } else {
+ final int N = content.length();
+ int start = text.partialStartOffset;
+ if (start > N) start = N;
+ int end = text.partialEndOffset;
+ if (end > N) end = N;
+ removeParcelableSpans(content, start, end);
+ content.replace(start, end, text.text);
+ }
+ }
+
+ // Now set the selection position... make sure it is in range, to
+ // avoid crashes. If this is a partial update, it is possible that
+ // the underlying text may have changed, causing us problems here.
+ // Also we just don't want to trust clients to do the right thing.
+ Spannable sp = (Spannable)getText();
+ final int N = sp.length();
+ int start = text.selectionStart;
+ if (start < 0) start = 0;
+ else if (start > N) start = N;
+ int end = text.selectionEnd;
+ if (end < 0) end = 0;
+ else if (end > N) end = N;
+ Selection.setSelection(sp, start, end);
+
+ // Finally, update the selection mode.
+ if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
+ MetaKeyKeyListener.startSelecting(this, sp);
+ } else {
+ MetaKeyKeyListener.stopSelecting(this, sp);
+ }
}
/**
@@ -3866,36 +4501,91 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void onCommitCompletion(CompletionInfo text) {
}
+ public void beginBatchEdit() {
+ final InputMethodState ims = mInputMethodState;
+ if (ims != null) {
+ int nesting = ++ims.mBatchEditNesting;
+ if (nesting == 1) {
+ ims.mCursorChanged = false;
+ ims.mChangedDelta = 0;
+ if (ims.mContentChanged) {
+ // We already have a pending change from somewhere else,
+ // so turn this into a full update.
+ ims.mChangedStart = 0;
+ ims.mChangedEnd = mText.length();
+ } else {
+ ims.mChangedStart = EXTRACT_UNKNOWN;
+ ims.mChangedEnd = EXTRACT_UNKNOWN;
+ ims.mContentChanged = false;
+ }
+ onBeginBatchEdit();
+ }
+ }
+ }
+
+ public void endBatchEdit() {
+ final InputMethodState ims = mInputMethodState;
+ if (ims != null) {
+ int nesting = --ims.mBatchEditNesting;
+ if (nesting == 0) {
+ finishBatchEdit(ims);
+ }
+ }
+ }
+
+ void ensureEndedBatchEdit() {
+ final InputMethodState ims = mInputMethodState;
+ if (ims != null && ims.mBatchEditNesting != 0) {
+ ims.mBatchEditNesting = 0;
+ finishBatchEdit(ims);
+ }
+ }
+
+ void finishBatchEdit(final InputMethodState ims) {
+ onEndBatchEdit();
+
+ if (ims.mContentChanged || ims.mSelectionModeChanged) {
+ updateAfterEdit();
+ reportExtractedText();
+ } else if (ims.mCursorChanged) {
+ // Cheezy way to get us to report the current cursor location.
+ invalidateCursor();
+ }
+ }
+
+ void updateAfterEdit() {
+ invalidate();
+ int curs = Selection.getSelectionStart(mText);
+
+ if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
+ Gravity.BOTTOM) {
+ registerForPreDraw();
+ }
+
+ if (curs >= 0) {
+ mHighlightPathBogus = true;
+
+ if (isFocused()) {
+ mShowCursor = SystemClock.uptimeMillis();
+ makeBlink();
+ }
+ }
+
+ checkForResize();
+ }
+
/**
* Called by the framework in response to a request to begin a batch
- * of edit operations from the current input method, as a result of
- * it calling {@link InputConnection#beginBatchEdit
- * InputConnection.beginBatchEdit()}. The default implementation sets
- * up the TextView's internal state to take care of this; if overriding
- * you should call through to the super class.
+ * of edit operations through a call to link {@link #beginBatchEdit()}.
*/
public void onBeginBatchEdit() {
- if (mInputMethodState != null) {
- // XXX we should be smarter here, such as not doing invalidates
- // until all edits are done.
- mInputMethodState.mBatchEditing = true;
- }
}
/**
* Called by the framework in response to a request to end a batch
- * of edit operations from the current input method, as a result of
- * it calling {@link InputConnection#endBatchEdit
- * InputConnection.endBatchEdit()}. The default implementation sets
- * up the TextView's internal state to take care of this; if overriding
- * you should call through to the super class.
+ * of edit operations through a call to link {@link #endBatchEdit}.
*/
public void onEndBatchEdit() {
- if (mInputMethodState != null) {
- mInputMethodState.mBatchEditing = false;
- // Cheezy way to get us to report the current cursor location.
- invalidateCursor();
- }
}
/**
@@ -4542,9 +5232,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Returns true if anything changed.
+ * Move the point, specified by the offset, into the view if it is needed.
+ * This has to be called after layout. Returns true if anything changed.
*/
- private boolean bringPointIntoView(int offset) {
+ public boolean bringPointIntoView(int offset) {
boolean changed = false;
int line = mLayout.getLineForOffset(offset);
@@ -4793,8 +5484,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_singleLine
*/
+ @android.view.RemotableViewMethod
public void setSingleLine(boolean singleLine) {
- mSingleLine = singleLine;
if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
== EditorInfo.TYPE_CLASS_TEXT) {
if (singleLine) {
@@ -4803,19 +5494,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
}
}
+ applySingleLine(singleLine, true);
+ }
+ private void applySingleLine(boolean singleLine, boolean applyTransformation) {
+ mSingleLine = singleLine;
if (singleLine) {
setLines(1);
setHorizontallyScrolling(true);
- setTransformationMethod(SingleLineTransformationMethod.
- getInstance());
+ if (applyTransformation) {
+ setTransformationMethod(SingleLineTransformationMethod.
+ getInstance());
+ }
} else {
setMaxLines(Integer.MAX_VALUE);
setHorizontallyScrolling(false);
- setTransformationMethod(null);
+ if (applyTransformation) {
+ setTransformationMethod(null);
+ }
}
}
-
+
/**
* Causes words in the text that are longer than the view is wide
* to be ellipsized instead of broken in the middle. You may also
@@ -4860,6 +5559,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_selectAllOnFocus
*/
+ @android.view.RemotableViewMethod
public void setSelectAllOnFocus(boolean selectAllOnFocus) {
mSelectAllOnFocus = selectAllOnFocus;
@@ -4873,6 +5573,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_cursorVisible
*/
+ @android.view.RemotableViewMethod
public void setCursorVisible(boolean visible) {
mCursorVisible = visible;
invalidate();
@@ -4938,6 +5639,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
float mScroll;
Marquee(TextView v) {
+ final float density = v.getContext().getResources().getDisplayMetrics().density;
+ mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
mView = new WeakReference<TextView>(v);
}
@@ -5006,7 +5709,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (textView != null && textView.mLayout != null) {
mStatus = MARQUEE_STARTING;
mScroll = 0.0f;
- mScrollUnit = MARQUEE_PIXELS_PER_SECOND / (float) MARQUEE_RESOLUTION;
mMaxScroll = textView.mLayout.getLineWidth(0) - (textView.getWidth() -
textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight());
textView.invalidate();
@@ -5046,6 +5748,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * This method is called when the selection has changed, in case any
+ * subclasses would like to know.
+ *
+ * @param selStart The new selection start location.
+ * @param selEnd The new selection end location.
+ */
+ protected void onSelectionChanged(int selStart, int selEnd) {
+ }
+
+ /**
* Adds a TextWatcher to the list of those whose methods are called
* whenever this TextView's text changes.
* <p>
@@ -5123,26 +5835,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
void handleTextChanged(CharSequence buffer, int start,
int before, int after) {
- invalidate();
-
- int curs = Selection.getSelectionStart(buffer);
-
- if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
- Gravity.BOTTOM) {
- registerForPreDraw();
- }
-
- if (curs >= 0) {
- mHighlightPathBogus = true;
-
- if (isFocused()) {
- mShowCursor = SystemClock.uptimeMillis();
- makeBlink();
+ final InputMethodState ims = mInputMethodState;
+ if (ims == null || ims.mBatchEditNesting == 0) {
+ updateAfterEdit();
+ }
+ if (ims != null) {
+ ims.mContentChanged = true;
+ if (ims.mChangedStart < 0) {
+ ims.mChangedStart = start;
+ ims.mChangedEnd = start+before;
+ } else {
+ if (ims.mChangedStart > start) ims.mChangedStart = start;
+ if (ims.mChangedEnd < (start+before)) ims.mChangedEnd = start+before;
}
+ ims.mChangedDelta += after-before;
}
-
- checkForResize();
-
+
sendOnTextChanged(buffer, start, before, after);
onTextChanged(buffer, start, before, after);
}
@@ -5151,19 +5859,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Not private so it can be called from an inner class without going
* through a thunk.
*/
- void spanChange(Spanned buf, Object what, int o, int n) {
+ void spanChange(Spanned buf, Object what, int oldStart, int newStart,
+ int oldEnd, int newEnd) {
// XXX Make the start and end move together if this ends up
// spending too much time invalidating.
+ boolean selChanged = false;
+ int newSelStart=-1, newSelEnd=-1;
+
+ final InputMethodState ims = mInputMethodState;
+
if (what == Selection.SELECTION_END) {
mHighlightPathBogus = true;
+ selChanged = true;
+ newSelEnd = newStart;
if (!isFocused()) {
mSelectionMoved = true;
}
- if (o >= 0 || n >= 0) {
- invalidateCursor(Selection.getSelectionStart(buf), o, n);
+ if (oldStart >= 0 || newStart >= 0) {
+ invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
registerForPreDraw();
if (isFocused()) {
@@ -5175,28 +5891,84 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (what == Selection.SELECTION_START) {
mHighlightPathBogus = true;
+ selChanged = true;
+ newSelStart = newStart;
if (!isFocused()) {
mSelectionMoved = true;
}
- if (o >= 0 || n >= 0) {
- invalidateCursor(Selection.getSelectionEnd(buf), o, n);
+ if (oldStart >= 0 || newStart >= 0) {
+ int end = Selection.getSelectionEnd(buf);
+ invalidateCursor(end, oldStart, newStart);
}
}
+ if (selChanged) {
+ if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
+ if (newSelStart < 0) {
+ newSelStart = Selection.getSelectionStart(buf);
+ }
+ if (newSelEnd < 0) {
+ newSelEnd = Selection.getSelectionEnd(buf);
+ }
+ onSelectionChanged(newSelStart, newSelEnd);
+ }
+ }
+
if (what instanceof UpdateAppearance ||
what instanceof ParagraphStyle) {
- invalidate();
- mHighlightPathBogus = true;
- checkForResize();
+ if (ims == null || ims.mBatchEditNesting == 0) {
+ invalidate();
+ mHighlightPathBogus = true;
+ checkForResize();
+ } else {
+ ims.mContentChanged = true;
+ }
}
if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
mHighlightPathBogus = true;
+ if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
+ ims.mSelectionModeChanged = true;
+ }
if (Selection.getSelectionStart(buf) >= 0) {
- invalidateCursor();
+ if (ims == null || ims.mBatchEditNesting == 0) {
+ invalidateCursor();
+ } else {
+ ims.mCursorChanged = true;
+ }
+ }
+ }
+
+ if (what instanceof ParcelableSpan) {
+ // If this is a span that can be sent to a remote process,
+ // the current extract editor would be interested in it.
+ if (ims != null && ims.mExtracting != null) {
+ if (ims.mBatchEditNesting != 0) {
+ if (oldStart >= 0) {
+ if (ims.mChangedStart > oldStart) {
+ ims.mChangedStart = oldStart;
+ }
+ if (ims.mChangedStart > oldEnd) {
+ ims.mChangedStart = oldEnd;
+ }
+ }
+ if (newStart >= 0) {
+ if (ims.mChangedStart > newStart) {
+ ims.mChangedStart = newStart;
+ }
+ if (ims.mChangedStart > newEnd) {
+ ims.mChangedStart = newEnd;
+ }
+ }
+ } else {
+ if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
+ + oldStart + "-" + oldEnd + ","
+ + newStart + "-" + newEnd + what);
+ ims.mContentChanged = true;
+ }
}
}
}
@@ -5205,36 +5977,45 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
implements TextWatcher, SpanWatcher {
public void beforeTextChanged(CharSequence buffer, int start,
int before, int after) {
+ if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
+ + " before=" + before + " after=" + after + ": " + buffer);
TextView.this.sendBeforeTextChanged(buffer, start, before, after);
}
public void onTextChanged(CharSequence buffer, int start,
int before, int after) {
+ if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
+ + " before=" + before + " after=" + after + ": " + buffer);
TextView.this.handleTextChanged(buffer, start, before, after);
}
public void afterTextChanged(Editable buffer) {
+ if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
TextView.this.sendAfterTextChanged(buffer);
if (MetaKeyKeyListener.getMetaState(buffer,
MetaKeyKeyListener.META_SELECTING) != 0) {
MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
}
-
- TextView.this.reportExtractedText();
}
public void onSpanChanged(Spannable buf,
Object what, int s, int e, int st, int en) {
- TextView.this.spanChange(buf, what, s, st);
+ if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
+ + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
+ TextView.this.spanChange(buf, what, s, st, e, en);
}
public void onSpanAdded(Spannable buf, Object what, int s, int e) {
- TextView.this.spanChange(buf, what, -1, s);
+ if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
+ + " what=" + what + ": " + buf);
+ TextView.this.spanChange(buf, what, -1, s, -1, e);
}
public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
- TextView.this.spanChange(buf, what, s, -1);
+ if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
+ + " what=" + what + ": " + buf);
+ TextView.this.spanChange(buf, what, s, -1, e, -1);
}
}
@@ -5255,9 +6036,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
+ public void onStartTemporaryDetach() {
+ mTemporaryDetach = true;
+ }
+
+ @Override
+ public void onFinishTemporaryDetach() {
+ mTemporaryDetach = false;
+ }
+
+ @Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ if (mTemporaryDetach) {
+ // If we are temporarily in the detach state, then do nothing.
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ return;
+ }
+
mShowCursor = SystemClock.uptimeMillis();
+ ensureEndedBatchEdit();
+
if (focused) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
@@ -5286,6 +6085,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Selection.setSelection((Spannable) mText, selStart, selEnd);
}
+ mTouchFocusSelected = true;
}
mFrozenWithFocus = false;
@@ -5338,11 +6138,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// Don't leave us in the middle of a batch edit.
onEndBatchEdit();
+ if (mInputContentType != null) {
+ mInputContentType.enterDown = false;
+ }
}
startStopMarquee(hasWindowFocus);
}
+ /**
+ * Use {@link BaseInputConnection#removeComposingSpans
+ * BaseInputConnection.removeComposingSpans()} to remove any IME composing
+ * state from this text view.
+ */
+ public void clearComposingText() {
+ if (mText instanceof Spannable) {
+ BaseInputConnection.removeComposingSpans((Spannable)mText);
+ }
+ }
+
@Override
public void setSelected(boolean selected) {
boolean wasSelected = isSelected();
@@ -5358,8 +6172,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ class CommitSelectionReceiver extends ResultReceiver {
+ int mNewStart;
+ int mNewEnd;
+
+ CommitSelectionReceiver() {
+ super(getHandler());
+ }
+
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode != InputMethodManager.RESULT_SHOWN) {
+ Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
+ }
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_DOWN) {
+ // Reset this state; it will be re-set if super.onTouchEvent
+ // causes focus to move to the view.
+ mTouchFocusSelected = false;
+ }
+
final boolean superResult = super.onTouchEvent(event);
/*
@@ -5367,24 +6203,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* move the selection away from whatever the menu action was
* trying to affect.
*/
- if (mEatTouchRelease && event.getAction() == MotionEvent.ACTION_UP) {
+ if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
mEatTouchRelease = false;
return superResult;
}
- if (mMovement != null && mText instanceof Spannable &&
- mLayout != null) {
- boolean moved = mMovement.onTouchEvent(this, (Spannable) mText, event);
+ if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ mScrolled = false;
+ }
+
+ boolean handled = false;
+
+ int oldSelStart = Selection.getSelectionStart(mText);
+ int oldSelEnd = Selection.getSelectionEnd(mText);
+
+ if (mMovement != null) {
+ handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
+ }
if (mText instanceof Editable && onCheckIsTextEditor()) {
- if (event.getAction() == MotionEvent.ACTION_UP && isFocused()) {
+ if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(this, 0);
+
+ // This is going to be gross... if tapping on the text view
+ // causes the IME to be displayed, we don't want the selection
+ // to change. But the selection has already changed, and
+ // we won't know right away whether the IME is getting
+ // displayed, so...
+
+ int newSelStart = Selection.getSelectionStart(mText);
+ int newSelEnd = Selection.getSelectionEnd(mText);
+ CommitSelectionReceiver csr = null;
+ if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
+ csr = new CommitSelectionReceiver();
+ csr.mNewStart = newSelStart;
+ csr.mNewEnd = newSelEnd;
+ }
+
+ if (imm.showSoftInput(this, 0, csr) && csr != null) {
+ // The IME might get shown -- revert to the old
+ // selection, and change to the new when we finally
+ // find out of it is okay.
+ Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
+ handled = true;
+ }
}
}
- if (moved) {
+ if (handled) {
return true;
}
}
@@ -5392,6 +6261,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return superResult;
}
+ /**
+ * Returns true, only while processing a touch gesture, if the initial
+ * touch down event caused focus to move to the text view and as a result
+ * its selection changed. Only valid while processing the touch gesture
+ * of interest.
+ */
+ public boolean didTouchFocusSelect() {
+ return mTouchFocusSelected;
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+ mScrolled = true;
+ }
+
@Override
public boolean onTrackballEvent(MotionEvent event) {
if (mMovement != null && mText instanceof Spannable &&
@@ -5408,8 +6293,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mScroller = s;
}
- private static class Blink extends Handler
- implements Runnable {
+ private static class Blink extends Handler implements Runnable {
private WeakReference<TextView> mView;
private boolean mCancelled;
@@ -5514,6 +6398,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.computeVerticalScrollRange();
}
+ @Override
+ protected int computeVerticalScrollExtent() {
+ return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
+ }
+
public enum BufferType {
NORMAL, SPANNABLE, EDITABLE,
}
@@ -5568,28 +6457,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch (keyCode) {
case KeyEvent.KEYCODE_A:
if (canSelectAll()) {
- return onMenu(ID_SELECT_ALL);
+ return onTextContextMenuItem(ID_SELECT_ALL);
}
break;
case KeyEvent.KEYCODE_X:
if (canCut()) {
- return onMenu(ID_CUT);
+ return onTextContextMenuItem(ID_CUT);
}
break;
case KeyEvent.KEYCODE_C:
if (canCopy()) {
- return onMenu(ID_COPY);
+ return onTextContextMenuItem(ID_COPY);
}
break;
case KeyEvent.KEYCODE_V:
if (canPaste()) {
- return onMenu(ID_PASTE);
+ return onTextContextMenuItem(ID_PASTE);
}
break;
@@ -5655,6 +6544,79 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
+ /**
+ * Returns a word to add to the dictionary from the context menu,
+ * or null if there is no cursor or no word at the cursor.
+ */
+ private String getWordForDictionary() {
+ /*
+ * Quick return if the input type is one where adding words
+ * to the dictionary doesn't make any sense.
+ */
+ int klass = mInputType & InputType.TYPE_MASK_CLASS;
+ if (klass == InputType.TYPE_CLASS_NUMBER ||
+ klass == InputType.TYPE_CLASS_PHONE ||
+ klass == InputType.TYPE_CLASS_DATETIME) {
+ return null;
+ }
+
+ int variation = mInputType & InputType.TYPE_MASK_VARIATION;
+ if (variation == InputType.TYPE_TEXT_VARIATION_URI ||
+ variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
+ variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
+ variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
+ variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+ return null;
+ }
+
+ int end = getSelectionEnd();
+
+ if (end < 0) {
+ return null;
+ }
+
+ int start = end;
+ int len = mText.length();
+
+ for (; start > 0; start--) {
+ char c = mTransformed.charAt(start - 1);
+ int type = Character.getType(c);
+
+ if (c != '\'' &&
+ type != Character.UPPERCASE_LETTER &&
+ type != Character.LOWERCASE_LETTER &&
+ type != Character.TITLECASE_LETTER &&
+ type != Character.MODIFIER_LETTER &&
+ type != Character.DECIMAL_DIGIT_NUMBER) {
+ break;
+ }
+ }
+
+ for (; end < len; end++) {
+ char c = mTransformed.charAt(end);
+ int type = Character.getType(c);
+
+ if (c != '\'' &&
+ type != Character.UPPERCASE_LETTER &&
+ type != Character.LOWERCASE_LETTER &&
+ type != Character.TITLECASE_LETTER &&
+ type != Character.MODIFIER_LETTER &&
+ type != Character.DECIMAL_DIGIT_NUMBER) {
+ break;
+ }
+ }
+
+ if (start == end) {
+ return null;
+ }
+
+ if (end - start > 48) {
+ return null;
+ }
+
+ return TextUtils.substring(mTransformed, start, end);
+ }
+
@Override
protected void onCreateContextMenu(ContextMenu menu) {
super.onCreateContextMenu(menu);
@@ -5696,7 +6658,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setOnMenuItemClickListener(handler);
added = true;
} else {
- menu.add(0, ID_SELECT_TEXT, 0,
+ menu.add(0, ID_START_SELECTING_TEXT, 0,
com.android.internal.R.string.selectText).
setOnMenuItemClickListener(handler);
added = true;
@@ -5755,11 +6717,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null && imm.isActive(this)) {
- menu.add(1, ID_SWITCH_IME, 0, com.android.internal.R.string.inputMethod).
+ if (isInputMethodTarget()) {
+ menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
+ setOnMenuItemClickListener(handler);
+ added = true;
+ }
+
+ String word = getWordForDictionary();
+ if (word != null) {
+ menu.add(1, ID_ADD_TO_DICTIONARY, 0,
+ getContext().getString(com.android.internal.R.string.addToDictionary, word)).
setOnMenuItemClickListener(handler);
added = true;
+
}
if (added) {
@@ -5767,22 +6737,40 @@ 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;
- private static final int ID_COPY_URL = com.android.internal.R.id.copyUrl;
- private static final int ID_SWITCH_IME = com.android.internal.R.id.inputMethod;
+ /**
+ * Returns whether this text view is a current input method target. The
+ * default implementation just checks with {@link InputMethodManager}.
+ */
+ public boolean isInputMethodTarget() {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ return imm != null && imm.isActive(this);
+ }
+
+ private static final int ID_SELECT_ALL = android.R.id.selectAll;
+ private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
+ private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
+ private static final int ID_CUT = android.R.id.cut;
+ private static final int ID_COPY = android.R.id.copy;
+ private static final int ID_PASTE = android.R.id.paste;
+ private static final int ID_COPY_URL = android.R.id.copyUrl;
+ private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
+ private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
private class MenuHandler implements MenuItem.OnMenuItemClickListener {
public boolean onMenuItemClick(MenuItem item) {
- return onMenu(item.getItemId());
+ return onTextContextMenuItem(item.getItemId());
}
}
- private boolean onMenu(int id) {
+ /**
+ * Called when a context menu option for the text view is selected. Currently
+ * this will be one of: {@link android.R.id#selectAll},
+ * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+ * {@link android.R.id#cut}, {@link android.R.id#copy},
+ * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
+ * or {@link android.R.id#switchInputMethod}.
+ */
+ public boolean onTextContextMenuItem(int id) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
@@ -5807,10 +6795,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch (id) {
case ID_SELECT_ALL:
Selection.setSelection((Spannable) mText, 0,
- mText.length());
+ mText.length());
return true;
- case ID_SELECT_TEXT:
+ case ID_START_SELECTING_TEXT:
MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
return true;
@@ -5865,12 +6853,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
- case ID_SWITCH_IME:
+ case ID_SWITCH_INPUT_METHOD:
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
imm.showInputMethodPicker();
}
return true;
+
+ case ID_ADD_TO_DICTIONARY:
+ String word = getWordForDictionary();
+
+ if (word != null) {
+ Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
+ i.putExtra("word", word);
+ getContext().startActivity(i);
+ }
+
+ return true;
}
return false;
@@ -5885,8 +6884,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
- private boolean mEatTouchRelease = false;
-
@ViewDebug.ExportedProperty
private CharSequence mText;
private CharSequence mTransformed;
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 1227afd..4c5df2f 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -46,8 +46,10 @@ import java.io.IOException;
* such as scaling and tinting.
*/
public class VideoView extends SurfaceView implements MediaPlayerControl {
+ private String TAG = "VideoView";
// settable by the client
private Uri mUri;
+ private int mDuration;
// All the stuff we need for playing and showing a video
private SurfaceHolder mSurfaceHolder = null;
@@ -184,6 +186,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mMediaPlayer.setOnPreparedListener(mPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mIsPrepared = false;
+ Log.v(TAG, "reset duration to -1 in openVideo");
+ mDuration = -1;
mMediaPlayer.setOnCompletionListener(mCompletionListener);
mMediaPlayer.setOnErrorListener(mErrorListener);
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
@@ -195,10 +199,10 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mMediaPlayer.prepareAsync();
attachMediaController();
} catch (IOException ex) {
- Log.w("VideoView", "Unable to open content: " + mUri, ex);
+ Log.w(TAG, "Unable to open content: " + mUri, ex);
return;
} catch (IllegalArgumentException ex) {
- Log.w("VideoView", "Unable to open content: " + mUri, ex);
+ Log.w(TAG, "Unable to open content: " + mUri, ex);
return;
}
}
@@ -298,15 +302,15 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
private MediaPlayer.OnErrorListener mErrorListener =
new MediaPlayer.OnErrorListener() {
- public boolean onError(MediaPlayer mp, int a, int b) {
- Log.d("VideoView", "Error: " + a + "," + b);
+ public boolean onError(MediaPlayer mp, int framework_err, int impl_err) {
+ Log.d(TAG, "Error: " + framework_err + "," + impl_err);
if (mMediaController != null) {
mMediaController.hide();
}
/* If an error handler has been supplied, use it and finish. */
if (mOnErrorListener != null) {
- if (mOnErrorListener.onError(mMediaPlayer, a, b)) {
+ if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
return true;
}
}
@@ -318,9 +322,17 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
*/
if (getWindowToken() != null) {
Resources r = mContext.getResources();
+ int messageId;
+
+ if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
+ messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback;
+ } else {
+ messageId = com.android.internal.R.string.VideoView_error_text_unknown;
+ }
+
new AlertDialog.Builder(mContext)
.setTitle(com.android.internal.R.string.VideoView_error_title)
- .setMessage(com.android.internal.R.string.VideoView_error_text_unknown)
+ .setMessage(messageId)
.setPositiveButton(com.android.internal.R.string.VideoView_error_button,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
@@ -497,9 +509,14 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
public int getDuration() {
if (mMediaPlayer != null && mIsPrepared) {
- return mMediaPlayer.getDuration();
+ if (mDuration > 0) {
+ return mDuration;
+ }
+ mDuration = mMediaPlayer.getDuration();
+ return mDuration;
}
- return -1;
+ mDuration = -1;
+ return mDuration;
}
public int getCurrentPosition() {
diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java
index acc9c46..fa8935e 100644
--- a/core/java/android/widget/ViewAnimator.java
+++ b/core/java/android/widget/ViewAnimator.java
@@ -28,6 +28,9 @@ import android.view.animation.AnimationUtils;
/**
* Base class for a {@link FrameLayout} container that will perform animations
* when switching between its views.
+ *
+ * @attr ref android.R.styleable#ViewAnimator_inAnimation
+ * @attr ref android.R.styleable#ViewAnimator_outAnimation
*/
public class ViewAnimator extends FrameLayout {
@@ -144,6 +147,56 @@ public class ViewAnimator extends FrameLayout {
}
}
+ @Override
+ public void removeAllViews() {
+ super.removeAllViews();
+ mWhichChild = 0;
+ mFirstTime = true;
+ }
+
+ @Override
+ public void removeView(View view) {
+ final int index = indexOfChild(view);
+ if (index >= 0) {
+ removeViewAt(index);
+ }
+ }
+
+ @Override
+ public void removeViewAt(int index) {
+ super.removeViewAt(index);
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ mWhichChild = 0;
+ mFirstTime = true;
+ } else if (mWhichChild >= childCount) {
+ // Displayed is above child count, so float down to top of stack
+ setDisplayedChild(childCount - 1);
+ } else if (mWhichChild == index) {
+ // Displayed was removed, so show the new child living in its place
+ setDisplayedChild(mWhichChild);
+ }
+ }
+
+ public void removeViewInLayout(View view) {
+ removeView(view);
+ }
+
+ public void removeViews(int start, int count) {
+ super.removeViews(start, count);
+ if (getChildCount() == 0) {
+ mWhichChild = 0;
+ mFirstTime = true;
+ } else if (mWhichChild >= start && mWhichChild < start + count) {
+ // Try showing new displayed child, wrapping if needed
+ setDisplayedChild(mWhichChild);
+ }
+ }
+
+ public void removeViewsInLayout(int start, int count) {
+ removeViews(start, count);
+ }
+
/**
* Returns the View corresponding to the currently displayed child.
*
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index a3c15d9..8a7946b 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -22,11 +22,14 @@ import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
+import android.widget.RemoteViews.RemoteView;
/**
* Simple {@link ViewAnimator} that will animate between two or more views
* that have been added to it. Only one child is shown at a time. If
* requested, can automatically flip between each child at a regular interval.
+ *
+ * @attr ref android.R.styleable#ViewFlipper_flipInterval
*/
public class ViewFlipper extends ViewAnimator {
private int mFlipInterval = 3000;
@@ -52,6 +55,7 @@ public class ViewFlipper extends ViewAnimator {
* @param milliseconds
* time in milliseconds
*/
+ @android.view.RemotableViewMethod
public void setFlipInterval(int milliseconds) {
mFlipInterval = milliseconds;
}
diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java
index 5df8c8a..0df919d 100644
--- a/core/java/android/widget/ZoomButton.java
+++ b/core/java/android/widget/ZoomButton.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -54,9 +55,10 @@ public class ZoomButton extends ImageButton implements OnLongClickListener {
public ZoomButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mHandler = new Handler();
- mGestureDetector = new GestureDetector(new SimpleOnGestureListener() {
+ mGestureDetector = new GestureDetector(context, new SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
onLongClick(ZoomButton.this);
}
});
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
new file mode 100644
index 0000000..4daa419
--- /dev/null
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -0,0 +1,848 @@
+/*
+ * 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.widget;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewRoot;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.View.OnClickListener;
+import android.view.WindowManager.LayoutParams;
+
+/*
+ * Implementation notes:
+ * - The zoom controls are displayed in their own window.
+ * (Easier for the client and better performance)
+ * - This window is not touchable, and by default is not focusable.
+ * - To make the buttons clickable, it attaches a OnTouchListener to the owner
+ * view and does the hit detection locally.
+ * - When it is focusable, it forwards uninteresting events to the owner view's
+ * view hierarchy.
+ */
+/**
+ * The {@link ZoomButtonsController} handles showing and hiding the zoom
+ * controls relative to an owner view. It also gives the client access to the
+ * zoom controls container, allowing for additional accessory buttons to be
+ * shown in the zoom controls window.
+ * <p>
+ * Typical usage involves the client using the {@link GestureDetector} to
+ * forward events from
+ * {@link GestureDetector.OnDoubleTapListener#onDoubleTapEvent(MotionEvent)} to
+ * {@link #handleDoubleTapEvent(MotionEvent)}. Also, whenever the owner cannot
+ * be zoomed further, the client should update
+ * {@link #setZoomInEnabled(boolean)} and {@link #setZoomOutEnabled(boolean)}.
+ * <p>
+ * If you are using this with a custom View, please call
+ * {@link #setVisible(boolean) setVisible(false)} from the
+ * {@link View#onDetachedFromWindow}.
+ *
+ * @hide
+ */
+public class ZoomButtonsController implements View.OnTouchListener {
+
+ private static final String TAG = "ZoomButtonsController";
+
+ private static final int ZOOM_CONTROLS_TIMEOUT =
+ (int) ViewConfiguration.getZoomControlsTimeout();
+
+ private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20;
+ private int mTouchPaddingScaledSq;
+
+ private Context mContext;
+ private WindowManager mWindowManager;
+
+ /**
+ * The view that is being zoomed by this zoom controller.
+ */
+ private View mOwnerView;
+
+ /**
+ * The location of the owner view on the screen. This is recalculated
+ * each time the zoom controller is shown.
+ */
+ private int[] mOwnerViewRawLocation = new int[2];
+
+ /**
+ * The container that is added as a window.
+ */
+ private FrameLayout mContainer;
+ private LayoutParams mContainerLayoutParams;
+ private int[] mContainerRawLocation = new int[2];
+
+ private ZoomControls mControls;
+
+ /**
+ * The view (or null) that should receive touch events. This will get set if
+ * the touch down hits the container. It will be reset on the touch up.
+ */
+ private View mTouchTargetView;
+ /**
+ * The {@link #mTouchTargetView}'s location in window, set on touch down.
+ */
+ private int[] mTouchTargetWindowLocation = new int[2];
+ /**
+ * If the zoom controller is dismissed but the user is still in a touch
+ * interaction, we set this to true. This will ignore all touch events until
+ * up/cancel, and then set the owner's touch listener to null.
+ */
+ private boolean mReleaseTouchListenerOnUp;
+
+ /**
+ * Whether we are currently in the double-tap gesture, with the second tap
+ * still being performed (i.e., we're waiting for the second tap's touch up).
+ */
+ private boolean mIsSecondTapDown;
+
+ /** Whether the container has been added to the window manager. */
+ private boolean mIsVisible;
+
+ private Rect mTempRect = new Rect();
+ private int[] mTempIntArray = new int[2];
+
+ private OnZoomListener mCallback;
+
+ /**
+ * In 1.0, the ZoomControls were to be added to the UI by the client of
+ * WebView, MapView, etc. We didn't want apps to break, so we return a dummy
+ * view in place now.
+ */
+ private InvisibleView mDummyZoomControls;
+
+ /**
+ * When showing the zoom, we add the view as a new window. However, there is
+ * logic that needs to know the size of the zoom which is determined after
+ * it's laid out. Therefore, we must post this logic onto the UI thread so
+ * it will be exceuted AFTER the layout. This is the logic.
+ */
+ private Runnable mPostedVisibleInitializer;
+
+ private IntentFilter mConfigurationChangedFilter =
+ new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
+
+ /**
+ * Needed to reposition the zoom controls after configuration changes.
+ */
+ private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!mIsVisible) return;
+
+ mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED);
+ mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED);
+ }
+ };
+
+ /**
+ * The setting name that tracks whether we've shown the zoom tutorial.
+ */
+ private static final String SETTING_NAME_SHOWN_TUTORIAL = "shown_zoom_tutorial";
+ private static Dialog sTutorialDialog;
+
+ /** When configuration changes, this is called after the UI thread is idle. */
+ private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
+ /** Used to delay the zoom controller dismissal. */
+ private static final int MSG_DISMISS_ZOOM_CONTROLS = 3;
+ /**
+ * If setVisible(true) is called and the owner view's window token is null,
+ * we delay the setVisible(true) call until it is not null.
+ */
+ private static final int MSG_POST_SET_VISIBLE = 4;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_POST_CONFIGURATION_CHANGED:
+ onPostConfigurationChanged();
+ break;
+
+ case MSG_DISMISS_ZOOM_CONTROLS:
+ setVisible(false);
+ break;
+
+ case MSG_POST_SET_VISIBLE:
+ if (mOwnerView.getWindowToken() == null) {
+ // Doh, it is still null, just ignore the set visible call
+ Log.e(TAG,
+ "Cannot make the zoom controller visible if the owner view is " +
+ "not attached to a window.");
+ } else {
+ setVisible(true);
+ }
+ break;
+ }
+
+ }
+ };
+
+ /**
+ * Constructor for the {@link ZoomButtonsController}.
+ *
+ * @param ownerView The view that is being zoomed by the zoom controls. The
+ * zoom controls will be displayed aligned with this view.
+ */
+ public ZoomButtonsController(View ownerView) {
+ mContext = ownerView.getContext();
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mOwnerView = ownerView;
+
+ mTouchPaddingScaledSq = (int)
+ (ZOOM_CONTROLS_TOUCH_PADDING * mContext.getResources().getDisplayMetrics().density);
+ mTouchPaddingScaledSq *= mTouchPaddingScaledSq;
+
+ mContainer = createContainer();
+ }
+
+ /**
+ * Whether to enable the zoom in control.
+ *
+ * @param enabled Whether to enable the zoom in control.
+ */
+ public void setZoomInEnabled(boolean enabled) {
+ mControls.setIsZoomInEnabled(enabled);
+ }
+
+ /**
+ * Whether to enable the zoom out control.
+ *
+ * @param enabled Whether to enable the zoom out control.
+ */
+ public void setZoomOutEnabled(boolean enabled) {
+ mControls.setIsZoomOutEnabled(enabled);
+ }
+
+ /**
+ * Sets the delay between zoom callbacks as the user holds a zoom button.
+ *
+ * @param speed The delay in milliseconds between zoom callbacks.
+ */
+ public void setZoomSpeed(long speed) {
+ mControls.setZoomSpeed(speed);
+ }
+
+ private FrameLayout createContainer() {
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ // Controls are positioned BOTTOM | CENTER with respect to the owner view.
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
+ lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
+ LayoutParams.FLAG_NOT_FOCUSABLE |
+ LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ lp.width = LayoutParams.FILL_PARENT;
+ lp.type = LayoutParams.TYPE_APPLICATION_PANEL;
+ lp.format = PixelFormat.TRANSPARENT;
+ lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons;
+ mContainerLayoutParams = lp;
+
+ FrameLayout container = new Container(mContext);
+ container.setLayoutParams(lp);
+ container.setMeasureAllChildren(true);
+
+ LayoutInflater inflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(com.android.internal.R.layout.zoom_container, container);
+
+ mControls = (ZoomControls) container.findViewById(com.android.internal.R.id.zoomControls);
+ mControls.setOnZoomInClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ if (mCallback != null) mCallback.onZoom(true);
+ }
+ });
+ mControls.setOnZoomOutClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ if (mCallback != null) mCallback.onZoom(false);
+ }
+ });
+
+ return container;
+ }
+
+ /**
+ * Sets the {@link OnZoomListener} listener that receives callbacks to zoom.
+ *
+ * @param listener The listener that will be told to zoom.
+ */
+ public void setOnZoomListener(OnZoomListener listener) {
+ mCallback = listener;
+ }
+
+ /**
+ * Sets whether the zoom controls should be focusable. If the controls are
+ * focusable, then trackball and arrow key interactions are possible.
+ * Otherwise, only touch interactions are possible.
+ *
+ * @param focusable Whether the zoom controls should be focusable.
+ */
+ public void setFocusable(boolean focusable) {
+ int oldFlags = mContainerLayoutParams.flags;
+ if (focusable) {
+ mContainerLayoutParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mContainerLayoutParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+
+ if ((mContainerLayoutParams.flags != oldFlags) && mIsVisible) {
+ mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+ }
+ }
+
+ /**
+ * Whether the zoom controls are visible to the user.
+ *
+ * @return Whether the zoom controls are visible to the user.
+ */
+ public boolean isVisible() {
+ return mIsVisible;
+ }
+
+ /**
+ * Sets whether the zoom controls should be visible to the user.
+ *
+ * @param visible Whether the zoom controls should be visible to the user.
+ */
+ public void setVisible(boolean visible) {
+
+ if (visible) {
+ if (mOwnerView.getWindowToken() == null) {
+ /*
+ * We need a window token to show ourselves, maybe the owner's
+ * window hasn't been created yet but it will have been by the
+ * time the looper is idle, so post the setVisible(true) call.
+ */
+ if (!mHandler.hasMessages(MSG_POST_SET_VISIBLE)) {
+ mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE);
+ }
+ return;
+ }
+
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ }
+
+ if (mIsVisible == visible) {
+ return;
+ }
+ mIsVisible = visible;
+
+ if (visible) {
+ if (mContainerLayoutParams.token == null) {
+ mContainerLayoutParams.token = mOwnerView.getWindowToken();
+ }
+
+ mWindowManager.addView(mContainer, mContainerLayoutParams);
+
+ if (mPostedVisibleInitializer == null) {
+ mPostedVisibleInitializer = new Runnable() {
+ public void run() {
+ refreshPositioningVariables();
+
+ if (mCallback != null) {
+ mCallback.onVisibilityChanged(true);
+ }
+ }
+ };
+ }
+
+ mHandler.post(mPostedVisibleInitializer);
+
+ // Handle configuration changes when visible
+ mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
+
+ // Steal touches events from the owner
+ mOwnerView.setOnTouchListener(this);
+ mReleaseTouchListenerOnUp = false;
+
+ } else {
+ // Don't want to steal any more touches
+ if (mTouchTargetView != null) {
+ // We are still stealing the touch events for this touch
+ // sequence, so release the touch listener later
+ mReleaseTouchListenerOnUp = true;
+ } else {
+ mOwnerView.setOnTouchListener(null);
+ }
+
+ // No longer care about configuration changes
+ mContext.unregisterReceiver(mConfigurationChangedReceiver);
+
+ mWindowManager.removeView(mContainer);
+ mHandler.removeCallbacks(mPostedVisibleInitializer);
+
+ if (mCallback != null) {
+ mCallback.onVisibilityChanged(false);
+ }
+ }
+
+ }
+
+ /**
+ * Gets the container that is the parent of the zoom controls.
+ * <p>
+ * The client can add other views to this container to link them with the
+ * zoom controls.
+ *
+ * @return The container of the zoom controls. It will be a layout that
+ * respects the gravity of a child's layout parameters.
+ */
+ public ViewGroup getContainer() {
+ return mContainer;
+ }
+
+ private void dismissControlsDelayed(int delay) {
+ mHandler.removeMessages(MSG_DISMISS_ZOOM_CONTROLS);
+ mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_CONTROLS, delay);
+ }
+
+ /**
+ * Should be called by the client for each event belonging to the second tap
+ * (the down, move, up, and/or cancel events).
+ *
+ * @param event The event belonging to the second tap.
+ * @return Whether the event was consumed.
+ */
+ public boolean handleDoubleTapEvent(MotionEvent event) {
+ int action = event.getAction();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ /*
+ * This class will consume all events in the second tap (down,
+ * move(s), up). But, the owner already got the second tap's down,
+ * so cancel that. Do this before setVisible, since that call
+ * will set us as a touch listener.
+ */
+ MotionEvent cancelEvent = MotionEvent.obtain(event.getDownTime(),
+ SystemClock.elapsedRealtime(),
+ MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ mOwnerView.dispatchTouchEvent(cancelEvent);
+ cancelEvent.recycle();
+
+ setVisible(true);
+ centerPoint(x, y);
+ mIsSecondTapDown = true;
+ }
+
+ return true;
+ }
+
+ private void refreshPositioningVariables() {
+ // Position the zoom controls on the bottom of the owner view.
+ int ownerHeight = mOwnerView.getHeight();
+ int ownerWidth = mOwnerView.getWidth();
+ // The gap between the top of the owner and the top of the container
+ int containerOwnerYOffset = ownerHeight - mContainer.getHeight();
+
+ // Calculate the owner view's bounds
+ mOwnerView.getLocationOnScreen(mOwnerViewRawLocation);
+ mContainerRawLocation[0] = mOwnerViewRawLocation[0];
+ mContainerRawLocation[1] = mOwnerViewRawLocation[1] + containerOwnerYOffset;
+
+ int[] ownerViewWindowLoc = mTempIntArray;
+ mOwnerView.getLocationInWindow(ownerViewWindowLoc);
+
+ // lp.x and lp.y should be relative to the owner's window top-left
+ mContainerLayoutParams.x = ownerViewWindowLoc[0];
+ mContainerLayoutParams.width = ownerWidth;
+ mContainerLayoutParams.y = ownerViewWindowLoc[1] + containerOwnerYOffset;
+ if (mIsVisible) {
+ mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+ }
+
+ }
+
+ /**
+ * Centers the point (in owner view's coordinates).
+ */
+ private void centerPoint(int x, int y) {
+ if (mCallback != null) {
+ mCallback.onCenter(x, y);
+ }
+ }
+
+ /* This will only be called when the container has focus. */
+ private boolean onContainerKey(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ if (isInterestingKey(keyCode)) {
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ setVisible(false);
+ } else {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ }
+
+ // Let the container handle the key
+ return false;
+
+ } else {
+
+ ViewRoot viewRoot = getOwnerViewRoot();
+ if (viewRoot != null) {
+ viewRoot.dispatchKey(event);
+ }
+
+ // We gave the key to the owner, don't let the container handle this key
+ return true;
+ }
+ }
+
+ private boolean isInterestingKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_BACK:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private ViewRoot getOwnerViewRoot() {
+ View rootViewOfOwner = mOwnerView.getRootView();
+ if (rootViewOfOwner == null) {
+ return null;
+ }
+
+ ViewParent parentOfRootView = rootViewOfOwner.getParent();
+ if (parentOfRootView instanceof ViewRoot) {
+ return (ViewRoot) parentOfRootView;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @hide The ZoomButtonsController implements the OnTouchListener, but this
+ * does not need to be shown in its public API.
+ */
+ public boolean onTouch(View v, MotionEvent event) {
+ int action = event.getAction();
+
+ // Consume all events during the second-tap interaction (down, move, up/cancel)
+ boolean consumeEvent = mIsSecondTapDown;
+ if ((action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL)) {
+ // The second tap can no longer be down
+ mIsSecondTapDown = false;
+ }
+
+ if (mReleaseTouchListenerOnUp) {
+ // The controls were dismissed but we need to throw away all events until the up
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mOwnerView.setOnTouchListener(null);
+ setTouchTargetView(null);
+ mReleaseTouchListenerOnUp = false;
+ }
+
+ // Eat this event
+ return true;
+ }
+
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+
+ View targetView = mTouchTargetView;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ targetView = findViewForTouch((int) event.getRawX(), (int) event.getRawY());
+ setTouchTargetView(targetView);
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ setTouchTargetView(null);
+ break;
+ }
+
+ if (targetView != null) {
+ // The upperleft corner of the target view in raw coordinates
+ int targetViewRawX = mContainerRawLocation[0] + mTouchTargetWindowLocation[0];
+ int targetViewRawY = mContainerRawLocation[1] + mTouchTargetWindowLocation[1];
+
+ MotionEvent containerEvent = MotionEvent.obtain(event);
+ // Convert the motion event into the target view's coordinates (from
+ // owner view's coordinates)
+ containerEvent.offsetLocation(mOwnerViewRawLocation[0] - targetViewRawX,
+ mOwnerViewRawLocation[1] - targetViewRawY);
+ /* Disallow negative coordinates (which can occur due to
+ * ZOOM_CONTROLS_TOUCH_PADDING) */
+ if (containerEvent.getX() < 0) {
+ containerEvent.offsetLocation(-containerEvent.getX(), 0);
+ }
+ if (containerEvent.getY() < 0) {
+ containerEvent.offsetLocation(0, -containerEvent.getY());
+ }
+ boolean retValue = targetView.dispatchTouchEvent(containerEvent);
+ containerEvent.recycle();
+ return retValue || consumeEvent;
+
+ } else {
+ return consumeEvent;
+ }
+ }
+
+ private void setTouchTargetView(View view) {
+ mTouchTargetView = view;
+ if (view != null) {
+ view.getLocationInWindow(mTouchTargetWindowLocation);
+ }
+ }
+
+ /**
+ * Returns the View that should receive a touch at the given coordinates.
+ *
+ * @param rawX The raw X.
+ * @param rawY The raw Y.
+ * @return The view that should receive the touches, or null if there is not one.
+ */
+ private View findViewForTouch(int rawX, int rawY) {
+ // Reverse order so the child drawn on top gets first dibs.
+ int containerCoordsX = rawX - mContainerRawLocation[0];
+ int containerCoordsY = rawY - mContainerRawLocation[1];
+ Rect frame = mTempRect;
+
+ View closestChild = null;
+ int closestChildDistanceSq = Integer.MAX_VALUE;
+
+ for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mContainer.getChildAt(i);
+ if (child.getVisibility() != View.VISIBLE) {
+ continue;
+ }
+
+ child.getHitRect(frame);
+ if (frame.contains(containerCoordsX, containerCoordsY)) {
+ return child;
+ }
+
+ int distanceX = Math.min(Math.abs(frame.left - containerCoordsX),
+ Math.abs(containerCoordsX - frame.right));
+ int distanceY = Math.min(Math.abs(frame.top - containerCoordsY),
+ Math.abs(containerCoordsY - frame.bottom));
+ int distanceSq = distanceX * distanceX + distanceY * distanceY;
+
+ if ((distanceSq < mTouchPaddingScaledSq) &&
+ (distanceSq < closestChildDistanceSq)) {
+ closestChild = child;
+ closestChildDistanceSq = distanceSq;
+ }
+ }
+
+ return closestChild;
+ }
+
+ private void onPostConfigurationChanged() {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ refreshPositioningVariables();
+ }
+
+ /*
+ * This is static so Activities can call this instead of the Views
+ * (Activities usually do not have a reference to the ZoomButtonsController
+ * instance.)
+ */
+ /**
+ * Shows a "tutorial" (some text) to the user teaching her the new zoom
+ * invocation method. Must call from the main thread.
+ * <p>
+ * It checks the global system setting to ensure this has not been seen
+ * before. Furthermore, if the application does not have privilege to write
+ * to the system settings, it will store this bit locally in a shared
+ * preference.
+ *
+ * @hide This should only be used by our main apps--browser, maps, and
+ * gallery
+ */
+ public static void showZoomTutorialOnce(Context context) {
+
+ // TODO: remove this code, but to hit the weekend build, just never show
+ if (true) return;
+
+ ContentResolver cr = context.getContentResolver();
+ if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TUTORIAL, 0) == 1) {
+ return;
+ }
+
+ SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
+ if (sp.getInt(SETTING_NAME_SHOWN_TUTORIAL, 0) == 1) {
+ return;
+ }
+
+ if (sTutorialDialog != null && sTutorialDialog.isShowing()) {
+ sTutorialDialog.dismiss();
+ }
+
+ LayoutInflater layoutInflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ TextView textView = (TextView) layoutInflater.inflate(
+ com.android.internal.R.layout.alert_dialog_simple_text, null)
+ .findViewById(android.R.id.text1);
+ textView.setText(com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short);
+
+ sTutorialDialog = new AlertDialog.Builder(context)
+ .setView(textView)
+ .setIcon(0)
+ .create();
+
+ Window window = sTutorialDialog.getWindow();
+ window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND |
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+ window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+
+ sTutorialDialog.show();
+ }
+
+ /** @hide Should only be used by Android platform apps */
+ public static void finishZoomTutorial(Context context, boolean userNotified) {
+ if (sTutorialDialog == null) return;
+
+ sTutorialDialog.dismiss();
+ sTutorialDialog = null;
+
+ // Record that they have seen the tutorial
+ if (userNotified) {
+ try {
+ Settings.System.putInt(context.getContentResolver(), SETTING_NAME_SHOWN_TUTORIAL,
+ 1);
+ } catch (SecurityException e) {
+ /*
+ * The app does not have permission to clear this global flag, make
+ * sure the user does not see the message when he comes back to this
+ * same app at least.
+ */
+ SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
+ sp.edit().putInt(SETTING_NAME_SHOWN_TUTORIAL, 1).commit();
+ }
+ }
+ }
+
+ /** @hide Should only be used by Android platform apps */
+ public void finishZoomTutorial() {
+ finishZoomTutorial(mContext, true);
+ }
+
+ /** @hide Should only be used only be WebView and MapView */
+ public View getDummyZoomControls() {
+ if (mDummyZoomControls == null) {
+ mDummyZoomControls = new InvisibleView(mContext);
+ }
+ return mDummyZoomControls;
+ }
+
+ /**
+ * Interface that will be called when the user performs an interaction that
+ * triggers some action, for example zooming.
+ */
+ public interface OnZoomListener {
+ /**
+ * Called when the given point should be centered. The point will be in
+ * owner view coordinates.
+ *
+ * @param x The x of the point.
+ * @param y The y of the point.
+ */
+ void onCenter(int x, int y);
+
+ /**
+ * Called when the zoom controls' visibility changes.
+ *
+ * @param visible Whether the zoom controls are visible.
+ */
+ void onVisibilityChanged(boolean visible);
+
+ /**
+ * Called when the owner view needs to be zoomed.
+ *
+ * @param zoomIn The direction of the zoom: true to zoom in, false to zoom out.
+ */
+ void onZoom(boolean zoomIn);
+ }
+
+ private class Container extends FrameLayout {
+ public Container(Context context) {
+ super(context);
+ }
+
+ /*
+ * Need to override this to intercept the key events. Otherwise, we
+ * would attach a key listener to the container but its superclass
+ * ViewGroup gives it to the focused View instead of calling the key
+ * listener, and so we wouldn't get the events.
+ */
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return onContainerKey(event) ? true : super.dispatchKeyEvent(event);
+ }
+ }
+
+ /**
+ * An InvisibleView is an invisible, zero-sized View for backwards
+ * compatibility
+ */
+ private final class InvisibleView extends View {
+
+ private InvisibleView(Context context) {
+ super(context);
+ setVisibility(GONE);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(0, 0);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ }
+ }
+
+}
diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java
index 1fd662c..84d8f0e 100644
--- a/core/java/android/widget/ZoomControls.java
+++ b/core/java/android/widget/ZoomControls.java
@@ -29,8 +29,12 @@ import com.android.internal.R;
/**
* The {@code ZoomControls} class displays a simple set of controls used for zooming and
- * provides callbacks to register for events.
- */
+ * provides callbacks to register for events. */
+// TODO: pending API council
+// * <p>
+// * Instead of using this directly, consider using the {@link ZoomButtonsController} which
+// * handles displaying the zoom controls.
+// */
@Widget
public class ZoomControls extends LinearLayout {
diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
new file mode 100644
index 0000000..000f6c4
--- /dev/null
+++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IMountService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Environment;
+import android.widget.Toast;
+import android.util.Log;
+
+/**
+ * This activity is shown to the user to confirm formatting of external media.
+ * It uses the alert dialog style. It will be launched from a notification, or from settings
+ */
+public class ExternalMediaFormatActivity extends AlertActivity implements DialogInterface.OnClickListener {
+
+ private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
+
+ /** Used to detect when the media state changes, in case we need to call finish() */
+ private BroadcastReceiver mStorageReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.d("ExternalMediaFormatActivity", "got action " + action);
+
+ if (action == Intent.ACTION_MEDIA_REMOVED ||
+ action == Intent.ACTION_MEDIA_CHECKING ||
+ action == Intent.ACTION_MEDIA_MOUNTED ||
+ action == Intent.ACTION_MEDIA_SHARED) {
+ finish();
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Log.d("ExternalMediaFormatActivity", "onCreate!");
+ // Set up the "dialog"
+ final AlertController.AlertParams p = mAlertParams;
+ p.mIconId = com.android.internal.R.drawable.stat_sys_warning;
+ p.mTitle = getString(com.android.internal.R.string.extmedia_format_title);
+ p.mMessage = getString(com.android.internal.R.string.extmedia_format_message);
+ p.mPositiveButtonText = getString(com.android.internal.R.string.extmedia_format_button_format);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(com.android.internal.R.string.cancel);
+ p.mNegativeButtonListener = this;
+ setupAlert();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_MEDIA_REMOVED);
+ filter.addAction(Intent.ACTION_MEDIA_CHECKING);
+ filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
+ filter.addAction(Intent.ACTION_MEDIA_SHARED);
+ registerReceiver(mStorageReceiver, filter);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ unregisterReceiver(mStorageReceiver);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onClick(DialogInterface dialog, int which) {
+
+ if (which == POSITIVE_BUTTON) {
+ IMountService mountService = IMountService.Stub.asInterface(ServiceManager
+ .getService("mount"));
+ if (mountService != null) {
+ try {
+ mountService.formatMedia(Environment.getExternalStorageDirectory().toString());
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ // No matter what, finish the activity
+ finish();
+ }
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 434850c..edda1d9 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -19,11 +19,17 @@ package com.android.internal.app;
import com.android.internal.os.BatteryStatsImpl;
interface IBatteryStats {
- BatteryStatsImpl getStatistics();
+ byte[] getStatistics();
void noteStartWakelock(int uid, String name, int type);
void noteStopWakelock(int uid, String name, int type);
void noteStartSensor(int uid, int sensor);
void noteStopSensor(int uid, int sensor);
+ void noteStartGps(int uid);
+ void noteStopGps(int uid);
+ void noteScreenOn();
+ void noteScreenOff();
+ void notePhoneOn();
+ void notePhoneOff();
void setOnBattery(boolean onBattery);
long getAwakeTimeBattery();
long getAwakeTimePlugged();
diff --git a/core/java/com/android/internal/os/HandlerHelper.java b/core/java/com/android/internal/app/IUsageStats.aidl
index d810e6b..6b053d5 100644..100755
--- a/core/java/com/android/internal/os/HandlerHelper.java
+++ b/core/java/com/android/internal/app/IUsageStats.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * 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.
@@ -14,25 +14,14 @@
* limitations under the License.
*/
-package com.android.internal.os;
+package com.android.internal.app;
-import android.os.Handler;
-import android.os.HandlerInterface;
-import android.os.Message;
+import android.content.ComponentName;
+import com.android.internal.os.PkgUsageStats;
-/** @hide */
-public class HandlerHelper extends Handler
-{
- public HandlerHelper(HandlerInterface target)
- {
- mTarget = target;
- }
-
- public void handleMessage(Message msg)
- {
- mTarget.handleMessage(msg);
- }
-
- private HandlerInterface mTarget;
+interface IUsageStats {
+ void noteResumeComponent(in ComponentName componentName);
+ void notePauseComponent(in ComponentName componentName);
+ PkgUsageStats getPkgUsageStats(in ComponentName componentName);
+ PkgUsageStats[] getAllPkgUsageStats();
}
-
diff --git a/core/java/com/android/internal/app/UsbStorageStopActivity.java b/core/java/com/android/internal/app/UsbStorageStopActivity.java
new file mode 100644
index 0000000..557a523
--- /dev/null
+++ b/core/java/com/android/internal/app/UsbStorageStopActivity.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IMountService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.widget.Toast;
+
+/**
+ * This activity is shown to the user for him/her to disable USB mass storage.
+ * It uses the alert dialog style. It will be launched from a notification.
+ */
+public class UsbStorageStopActivity extends AlertActivity implements DialogInterface.OnClickListener {
+
+ private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
+
+ /** Used to detect when the USB cable is unplugged, so we can call finish() */
+ private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction() == Intent.ACTION_BATTERY_CHANGED) {
+ handleBatteryChanged(intent);
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Set up the "dialog"
+ final AlertController.AlertParams p = mAlertParams;
+ p.mIconId = com.android.internal.R.drawable.ic_dialog_alert;
+ p.mTitle = getString(com.android.internal.R.string.usb_storage_stop_title);
+ p.mMessage = getString(com.android.internal.R.string.usb_storage_stop_message);
+ p.mPositiveButtonText = getString(com.android.internal.R.string.usb_storage_stop_button_mount);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(com.android.internal.R.string.usb_storage_stop_button_unmount);
+ p.mNegativeButtonListener = this;
+ setupAlert();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ unregisterReceiver(mBatteryReceiver);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onClick(DialogInterface dialog, int which) {
+
+ if (which == POSITIVE_BUTTON) {
+ stopUsbStorage();
+ }
+
+ // No matter what, finish the activity
+ finish();
+ }
+
+ private void stopUsbStorage() {
+ IMountService mountService = IMountService.Stub.asInterface(ServiceManager
+ .getService("mount"));
+ if (mountService == null) {
+ showStoppingError();
+ return;
+ }
+
+ try {
+ mountService.setMassStorageEnabled(false);
+ } catch (RemoteException e) {
+ showStoppingError();
+ return;
+ }
+ }
+
+ private void handleBatteryChanged(Intent intent) {
+ int pluggedType = intent.getIntExtra("plugged", 0);
+ if (pluggedType == 0) {
+ // It was disconnected from the plug, so finish
+ finish();
+ }
+ }
+
+ private void showStoppingError() {
+ Toast.makeText(this, com.android.internal.R.string.usb_storage_stop_error_message,
+ Toast.LENGTH_LONG).show();
+ }
+
+}
diff --git a/core/java/com/android/internal/gadget/IGadgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 6f9af04..2ed4773 100644
--- a/core/java/com/android/internal/gadget/IGadgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -14,16 +14,15 @@
* limitations under the License.
*/
-package com.android.internal.gadget;
+package com.android.internal.appwidget;
import android.content.ComponentName;
-import android.gadget.GadgetInfo;
+import android.appwidget.AppWidgetProviderInfo;
+import android.widget.RemoteViews;
/** {@hide} */
-interface IGadgetService {
- int allocateGadgetId(String hostPackage);
- void deleteGadgetId(int gadgetId);
- void bindGadgetId(int gadgetId, in ComponentName provider);
- GadgetInfo getGadgetInfo(int gadgetId);
- List<GadgetInfo> getInstalledProviders();
+oneway interface IAppWidgetHost {
+ void updateAppWidget(int appWidgetId, in RemoteViews views);
+ void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
}
+
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
new file mode 100644
index 0000000..496aa1a
--- /dev/null
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.appwidget;
+
+import android.content.ComponentName;
+import android.appwidget.AppWidgetProviderInfo;
+import com.android.internal.appwidget.IAppWidgetHost;
+import android.widget.RemoteViews;
+
+/** {@hide} */
+interface IAppWidgetService {
+
+ //
+ // for AppWidgetHost
+ //
+ int[] startListening(IAppWidgetHost host, String packageName, int hostId,
+ out List<RemoteViews> updatedViews);
+ void stopListening(int hostId);
+ int allocateAppWidgetId(String packageName, int hostId);
+ void deleteAppWidgetId(int appWidgetId);
+ void deleteHost(int hostId);
+ void deleteAllHosts();
+ RemoteViews getAppWidgetViews(int appWidgetId);
+
+ //
+ // for AppWidgetManager
+ //
+ void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
+ void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views);
+ List<AppWidgetProviderInfo> getInstalledProviders();
+ AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId);
+ void bindAppWidgetId(int appWidgetId, in ComponentName provider);
+ int[] getAppWidgetIds(in ComponentName provider);
+
+}
+
diff --git a/core/java/com/android/internal/gadget/package.html b/core/java/com/android/internal/appwidget/package.html
index db6f78b..db6f78b 100644
--- a/core/java/com/android/internal/gadget/package.html
+++ b/core/java/com/android/internal/appwidget/package.html
diff --git a/core/java/com/android/internal/logging/AndroidConfig.java b/core/java/com/android/internal/logging/AndroidConfig.java
index d8be889..f8002c6 100644
--- a/core/java/com/android/internal/logging/AndroidConfig.java
+++ b/core/java/com/android/internal/logging/AndroidConfig.java
@@ -34,11 +34,14 @@ public class AndroidConfig {
super();
try {
- Logger.global.addHandler(new AndroidHandler());
- Logger.global.setLevel(Level.ALL);
+ Logger rootLogger = Logger.getLogger("");
+ rootLogger.addHandler(new AndroidHandler());
+ rootLogger.setLevel(Level.INFO);
+
+ // Turn down logging in Apache libraries.
+ Logger.getLogger("org.apache").setLevel(Level.WARNING);
} catch (Exception ex) {
ex.printStackTrace();
}
- }
-
+ }
}
diff --git a/core/java/com/android/internal/logging/AndroidHandler.java b/core/java/com/android/internal/logging/AndroidHandler.java
index a6a4c64..d9fcf60 100644
--- a/core/java/com/android/internal/logging/AndroidHandler.java
+++ b/core/java/com/android/internal/logging/AndroidHandler.java
@@ -18,14 +18,14 @@ package com.android.internal.logging;
import android.util.Log;
-import java.util.logging.Formatter;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.logging.SimpleFormatter;
+import java.util.logging.*;
+import java.util.Date;
+import java.text.MessageFormat;
+import java.io.PrintWriter;
+import java.io.StringWriter;
/**
- * Implements a {@link java.util.Logger} handler that writes to the Android log. The
+ * Implements a {@link java.util.logging.Logger} handler that writes to the Android log. The
* implementation is rather straightforward. The name of the logger serves as
* the log tag. Only the log levels need to be converted appropriately. For
* this purpose, the following mapping is being used:
@@ -81,8 +81,24 @@ public class AndroidHandler extends Handler {
/**
* Holds the formatter for all Android log handlers.
*/
- private static final Formatter THE_FORMATTER = new SimpleFormatter();
-
+ private static final Formatter THE_FORMATTER = new Formatter() {
+ @Override
+ public String format(LogRecord r) {
+ Throwable thrown = r.getThrown();
+ if (thrown != null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ sw.write(r.getMessage());
+ sw.write("\n");
+ thrown.printStackTrace(pw);
+ pw.flush();
+ return sw.toString();
+ } else {
+ return r.getMessage();
+ }
+ }
+ };
+
/**
* Constructs a new instance of the Android log handler.
*/
@@ -106,27 +122,40 @@ public class AndroidHandler extends Handler {
int level = getAndroidLevel(record.getLevel());
String tag = record.getLoggerName();
+ if (tag == null) {
+ // Anonymous logger.
+ tag = "null";
+ } else {
+ // Tags must be <= 23 characters.
+ int length = tag.length();
+ if (length > 23) {
+ // Most loggers use the full class name. Try dropping the
+ // package.
+ int lastPeriod = tag.lastIndexOf(".");
+ if (length - lastPeriod - 1 <= 23) {
+ tag = tag.substring(lastPeriod + 1);
+ } else {
+ // Use last 23 chars.
+ tag = tag.substring(tag.length() - 23);
+ }
+ }
+ }
+
if (!Log.isLoggable(tag, level)) {
return;
}
-
- String msg;
- try {
- msg = getFormatter().format(record);
- } catch (RuntimeException e) {
- Log.e("AndroidHandler", "Error formatting log record", e);
- msg = record.getMessage();
- }
- Log.println(level, tag, msg);
+
+ String message = getFormatter().format(record);
+ Log.println(level, tag, message);
} catch (RuntimeException e) {
- Log.e("AndroidHandler", "Error publishing log record", e);
+ Log.e("AndroidHandler", "Error logging message.", e);
}
}
/**
- * Converts a {@link java.util.Logger} logging level into an Android one.
+ * Converts a {@link java.util.logging.Logger} logging level into an Android one.
*
- * @param level The {@link java.util.Logger} logging level.
+ * @param level The {@link java.util.logging.Logger} logging level.
*
* @return The resulting Android logging level.
*/
diff --git a/core/java/com/android/internal/net/DbSSLSessionCache.java b/core/java/com/android/internal/net/DbSSLSessionCache.java
new file mode 100644
index 0000000..842d40b
--- /dev/null
+++ b/core/java/com/android/internal/net/DbSSLSessionCache.java
@@ -0,0 +1,289 @@
+// Copyright 2009 The Android Open Source Project
+
+package com.android.internal.net;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.net.ssl.SSLSession;
+
+/**
+ * Hook into harmony SSL cache to persist the SSL sessions.
+ *
+ * Current implementation is suitable for saving a small number of hosts -
+ * like google services. It can be extended with expiration and more features
+ * to support more hosts.
+ *
+ * {@hide}
+ */
+public class DbSSLSessionCache implements SSLClientSessionCache {
+ private static final String TAG = "DbSSLSessionCache";
+
+ /**
+ * Table where sessions are stored.
+ */
+ public static final String SSL_CACHE_TABLE = "ssl_sessions";
+
+ private static final String SSL_CACHE_ID = "_id";
+
+ /**
+ * Key is host:port - port is not optional.
+ */
+ private static final String SSL_CACHE_HOSTPORT = "hostport";
+
+ /**
+ * Base64-encoded DER value of the session.
+ */
+ private static final String SSL_CACHE_SESSION = "session";
+
+ /**
+ * Time when the record was added - should be close to the time
+ * of the initial session negotiation.
+ */
+ private static final String SSL_CACHE_TIME_SEC = "time_sec";
+
+ public static final String DATABASE_NAME = "ssl_sessions.db";
+
+ public static final int DATABASE_VERSION = 2;
+
+ /** public for testing
+ */
+ public static final int SSL_CACHE_ID_COL = 0;
+ public static final int SSL_CACHE_HOSTPORT_COL = 1;
+ public static final int SSL_CACHE_SESSION_COL = 2;
+ public static final int SSL_CACHE_TIME_SEC_COL = 3;
+
+ public static final int MAX_CACHE_SIZE = 256;
+
+ private final Map<String, byte[]> mExternalCache =
+ new HashMap<String, byte[]>();
+
+
+ private DatabaseHelper mDatabaseHelper;
+
+ private boolean mNeedsCacheLoad = true;
+
+ public static final String[] PROJECTION = new String[] {
+ SSL_CACHE_ID,
+ SSL_CACHE_HOSTPORT,
+ SSL_CACHE_SESSION,
+ SSL_CACHE_TIME_SEC
+ };
+
+ private static final Map<String,DbSSLSessionCache> sInstances =
+ new HashMap<String,DbSSLSessionCache>();
+
+ /**
+ * Returns a singleton instance of the DbSSLSessionCache that should be used for this
+ * context's package.
+ *
+ * @param context The context that should be used for getting/creating the singleton instance.
+ * @return The singleton instance for the context's package.
+ */
+ public static synchronized DbSSLSessionCache getInstanceForPackage(Context context) {
+ String packageName = context.getPackageName();
+ if (sInstances.containsKey(packageName)) {
+ return sInstances.get(packageName);
+ }
+ DbSSLSessionCache cache = new DbSSLSessionCache(context);
+ sInstances.put(packageName, cache);
+ return cache;
+ }
+
+ /**
+ * Create a SslSessionCache instance, using the specified context to
+ * initialize the database.
+ *
+ * This constructor will use the default database - created for the application
+ * context.
+ *
+ * @param activityContext
+ */
+ private DbSSLSessionCache(Context activityContext) {
+ Context appContext = activityContext.getApplicationContext();
+ mDatabaseHelper = new DatabaseHelper(appContext);
+ }
+
+ /**
+ * Create a SslSessionCache that uses a specific database.
+ *
+ *
+ * @param database
+ */
+ public DbSSLSessionCache(DatabaseHelper database) {
+ this.mDatabaseHelper = database;
+ }
+
+ public void putSessionData(SSLSession session, byte[] der) {
+ if (mDatabaseHelper == null) {
+ return;
+ }
+ synchronized (this.getClass()) {
+ SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+ if (mExternalCache.size() == MAX_CACHE_SIZE) {
+ // remove oldest.
+ // TODO: check if the new one is in cached already ( i.e. update ).
+ Cursor byTime = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE,
+ PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC);
+ if (byTime.moveToFirst()) {
+ // TODO: can I do byTime.deleteRow() ?
+ String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL);
+ db.delete(SSL_CACHE_TABLE,
+ SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort });
+ mExternalCache.remove(hostPort);
+ } else {
+ Log.w(TAG, "No rows found");
+ // something is wrong, clear it
+ clear();
+ }
+ }
+ // Serialize native session to standard DER encoding
+ long t0 = System.currentTimeMillis();
+
+ String b64 = new String(Base64.encodeBase64(der));
+ String key = session.getPeerHost() + ":" + session.getPeerPort();
+
+ ContentValues values = new ContentValues();
+ values.put(SSL_CACHE_HOSTPORT, key);
+ values.put(SSL_CACHE_SESSION, b64);
+ values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000);
+
+ mExternalCache.put(key, der);
+
+ try {
+ db.insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values);
+ } catch(SQLException ex) {
+ // Ignore - nothing we can do to recover, and caller shouldn't
+ // be affected.
+ Log.w(TAG, "Ignoring SQL exception when caching session", ex);
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ long t1 = System.currentTimeMillis();
+ Log.d(TAG, "New SSL session " + session.getPeerHost() +
+ " DER len: " + der.length + " " + (t1 - t0));
+ }
+ }
+
+ }
+
+ public byte[] getSessionData(String host, int port) {
+ // Current (simple) implementation does a single lookup to DB, then saves
+ // all entries to the cache.
+
+ // This works for google services - i.e. small number of certs.
+ // If we extend this to all processes - we should hold a separate cache
+ // or do lookups to DB each time.
+ if (mDatabaseHelper == null) {
+ return null;
+ }
+ synchronized(this.getClass()) {
+ if (mNeedsCacheLoad) {
+ // Don't try to load again, if something is wrong on the first
+ // request it'll likely be wrong each time.
+ mNeedsCacheLoad = false;
+ long t0 = System.currentTimeMillis();
+
+ Cursor cur = null;
+ try {
+ cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE,
+ PROJECTION, null, null, null, null, null);
+ if (cur.moveToFirst()) {
+ do {
+ String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL);
+ String value = cur.getString(SSL_CACHE_SESSION_COL);
+
+ if (hostPort == null || value == null) {
+ continue;
+ }
+ // TODO: blob support ?
+ byte[] der = Base64.decodeBase64(value.getBytes());
+ mExternalCache.put(hostPort, der);
+ } while (cur.moveToNext());
+
+ }
+ } catch (SQLException ex) {
+ Log.d(TAG, "Error loading SSL cached entries ", ex);
+ } finally {
+ if (cur != null) {
+ cur.close();
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ long t1 = System.currentTimeMillis();
+ Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms");
+ }
+ }
+ }
+
+ String key = host + ":" + port;
+
+ return mExternalCache.get(key);
+ }
+ }
+
+ /**
+ * Reset the database and internal state.
+ * Used for testing or to free space.
+ */
+ public void clear() {
+ synchronized(this) {
+ try {
+ mExternalCache.clear();
+ mNeedsCacheLoad = true;
+ mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE,
+ null, null);
+ } catch (SQLException ex) {
+ Log.d(TAG, "Error removing SSL cached entries ", ex);
+ // ignore - nothing we can do about it
+ }
+ }
+ }
+
+ public byte[] getSessionData(byte[] id) {
+ // We support client side only - the cache will do nothing for
+ // server-side sessions.
+ return null;
+ }
+
+ /** Visible for testing.
+ */
+ public static class DatabaseHelper extends SQLiteOpenHelper {
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null /* factory */, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + SSL_CACHE_TABLE + " (" +
+ SSL_CACHE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ SSL_CACHE_HOSTPORT + " TEXT UNIQUE ON CONFLICT REPLACE," +
+ SSL_CACHE_SESSION + " TEXT," +
+ SSL_CACHE_TIME_SEC + " INTEGER" +
+ ");");
+
+ // No index - we load on startup, index would slow down inserts.
+ // If we want to scale this to lots of rows - we could use
+ // index, but then we'll hit DB a bit too often ( including
+ // negative hits )
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ db.execSQL("DROP TABLE IF EXISTS " + SSL_CACHE_TABLE );
+ onCreate(db);
+ }
+
+ }
+
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8912960..7eea8b7 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -17,18 +17,19 @@
package com.android.internal.os;
import android.os.BatteryStats;
+import android.os.NetStat;
import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.Log;
+import android.util.Printer;
import android.util.SparseArray;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@@ -38,13 +39,15 @@ import java.util.Map;
* battery life. All times are represented in microseconds except where indicated
* otherwise.
*/
-public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
+public final class BatteryStatsImpl extends BatteryStats {
+ private static final String TAG = "BatteryStatsImpl";
+ private static final boolean DEBUG = false;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 13;
+ private static final int VERSION = 25;
private final File mFile;
private final File mBackupFile;
@@ -62,8 +65,13 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
final ArrayList<Timer> mPartialTimers = new ArrayList<Timer>();
final ArrayList<Timer> mFullTimers = new ArrayList<Timer>();
final ArrayList<Timer> mWindowTimers = new ArrayList<Timer>();
- final ArrayList<Timer> mSensorTimers = new ArrayList<Timer>();
+ final SparseArray<ArrayList<Timer>> mSensorTimers
+ = new SparseArray<ArrayList<Timer>>();
+ // These are the objects that will want to do something when the device
+ // is unplugged from power.
+ final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>();
+
int mStartCount;
long mBatteryUptime;
@@ -77,17 +85,27 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
long mRealtime;
long mRealtimeStart;
long mLastRealtime;
-
+
+ boolean mScreenOn;
+ Timer mScreenOnTimer;
+
+ boolean mPhoneOn;
+ Timer mPhoneOnTimer;
+
/**
* These provide time bases that discount the time the device is plugged
* in to power.
*/
boolean mOnBattery;
+ boolean mOnBatteryInternal;
long mTrackBatteryPastUptime;
long mTrackBatteryUptimeStart;
long mTrackBatteryPastRealtime;
long mTrackBatteryRealtimeStart;
-
+
+ long mUnpluggedBatteryUptime;
+ long mUnpluggedBatteryRealtime;
+
long mLastWriteTime = 0; // Milliseconds
// For debugging
@@ -95,101 +113,162 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
mFile = mBackupFile = null;
}
+ public static interface Unpluggable {
+ void unplug(long batteryUptime, long batteryRealtime);
+ void plug(long batteryUptime, long batteryRealtime);
+ }
+
/**
* State for keeping track of timing information.
*/
- public static final class Timer extends BatteryStats.Timer {
- ArrayList<Timer> mTimerPool;
+ public static final class Timer extends BatteryStats.Timer implements Unpluggable {
+ final int mType;
+ final ArrayList<Timer> mTimerPool;
- int mType;
int mNesting;
int mCount;
int mLoadedCount;
int mLastCount;
+ int mUnpluggedCount;
- // Times are in microseconds for better accuracy when dividing by the lock count
+ // Times are in microseconds for better accuracy when dividing by the
+ // lock count, and are in "battery realtime" units.
- long mTotalTime; // Add mUnpluggedTotalTime to get true value
- long mLoadedTotalTime;
- long mLastTotalTime;
- long mStartTime;
- long mUpdateTime;
+ /**
+ * The total time we have accumulated since the start of the original
+ * boot, to the last time something interesting happened in the
+ * current run.
+ */
+ long mTotalTime;
+
+ /**
+ * The total time we loaded for the previous runs. Subtract this from
+ * mTotalTime to find the time for the current run of the system.
+ */
+ long mLoadedTime;
+
+ /**
+ * The run time of the last run of the system, as loaded from the
+ * saved data.
+ */
+ long mLastTime;
/**
- * The value of mTotalTime when unplug() was last called, initially 0.
+ * The value of mTotalTime when unplug() was last called. Subtract
+ * this from mTotalTime to find the time since the last unplug from
+ * power.
*/
- long mTotalTimeAtLastUnplug;
+ long mUnpluggedTime;
- /** Constructor used for unmarshalling only. */
- Timer() {}
+ /**
+ * The last time at which we updated the timer. If mNesting is > 0,
+ * subtract this from the current battery time to find the amount of
+ * time we have been running since we last computed an update.
+ */
+ long mUpdateTime;
+
+ /**
+ * The total time at which the timer was acquired, to determine if
+ * was actually held for an interesting duration.
+ */
+ long mAcquireTime;
+
+ Timer(int type, ArrayList<Timer> timerPool,
+ ArrayList<Unpluggable> unpluggables, Parcel in) {
+ mType = type;
+ mTimerPool = timerPool;
+ mCount = in.readInt();
+ mLoadedCount = in.readInt();
+ mLastCount = in.readInt();
+ mUnpluggedCount = in.readInt();
+ mTotalTime = in.readLong();
+ mLoadedTime = in.readLong();
+ mLastTime = in.readLong();
+ mUpdateTime = in.readLong();
+ mUnpluggedTime = in.readLong();
+ unpluggables.add(this);
+ }
- Timer(int type, ArrayList<Timer> timerPool) {
+ Timer(int type, ArrayList<Timer> timerPool,
+ ArrayList<Unpluggable> unpluggables) {
mType = type;
mTimerPool = timerPool;
+ unpluggables.add(this);
}
- public void writeToParcel(Parcel out) {
- out.writeInt(mType);
- out.writeInt(mNesting);
+ public void writeToParcel(Parcel out, long batteryRealtime) {
out.writeInt(mCount);
out.writeInt(mLoadedCount);
out.writeInt(mLastCount);
- out.writeLong(mTotalTime);
- out.writeLong(mLoadedTotalTime);
- out.writeLong(mLastTotalTime);
- out.writeLong(mStartTime);
+ out.writeInt(mUnpluggedCount);
+ out.writeLong(computeRunTimeLocked(batteryRealtime));
+ out.writeLong(mLoadedTime);
+ out.writeLong(mLastTime);
out.writeLong(mUpdateTime);
- out.writeLong(mTotalTimeAtLastUnplug);
+ out.writeLong(mUnpluggedTime);
}
- public void readFromParcel(Parcel in) {
- mType = in.readInt();
- mNesting = in.readInt();
- mCount = in.readInt();
- mLoadedCount = in.readInt();
- mLastCount = in.readInt();
- mTotalTime = in.readLong();
- mLoadedTotalTime = in.readLong();
- mLastTotalTime = in.readLong();
- mStartTime = in.readLong();
- mUpdateTime = in.readLong();
- mTotalTimeAtLastUnplug = in.readLong();
- }
-
- private void unplug() {
- mTotalTimeAtLastUnplug += mTotalTime;
- mTotalTime = 0;
+ public void unplug(long batteryUptime, long batteryRealtime) {
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime
+ + " old mUnpluggedTime=" + mUnpluggedTime
+ + " old mUnpluggedCount=" + mUnpluggedCount);
+ }
+ mUnpluggedTime = computeRunTimeLocked(batteryRealtime);
+ mUnpluggedCount = mCount;
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "unplug #" + mType
+ + ": new mUnpluggedTime=" + mUnpluggedTime
+ + " new mUnpluggedCount=" + mUnpluggedCount);
+ }
}
+ public void plug(long batteryUptime, long batteryRealtime) {
+ if (mNesting > 0) {
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime
+ + " old mTotalTime=" + mTotalTime
+ + " old mUpdateTime=" + mUpdateTime);
+ }
+ mTotalTime = computeRunTimeLocked(batteryRealtime);
+ mUpdateTime = batteryRealtime;
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "plug #" + mType
+ + ": new mTotalTime=" + mTotalTime
+ + " old mUpdateTime=" + mUpdateTime);
+ }
+ }
+ }
+
/**
* Writes a possibly null Timer to a Parcel.
*
* @param out the Parcel to be written to.
* @param timer a Timer, or null.
*/
- public static void writeTimerToParcel(Parcel out, Timer timer) {
+ public static void writeTimerToParcel(Parcel out, Timer timer,
+ long batteryRealtime) {
if (timer == null) {
out.writeInt(0); // indicates null
return;
}
out.writeInt(1); // indicates non-null
- timer.writeToParcel(out);
+ timer.writeToParcel(out, batteryRealtime);
}
@Override
- public long getTotalTime(long now, int which) {
+ public long getTotalTime(long batteryRealtime, int which) {
long val;
if (which == STATS_LAST) {
- val = mLastTotalTime;
+ val = mLastTime;
} else {
- val = computeRunTimeLocked(now);
- if (which != STATS_UNPLUGGED) {
- val += mTotalTimeAtLastUnplug;
- }
- if ((which == STATS_CURRENT) || (which == STATS_UNPLUGGED)) {
- val -= mLoadedTotalTime;
+ val = computeRunTimeLocked(batteryRealtime);
+ if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedTime;
+ } else if (which != STATS_TOTAL) {
+ val -= mLoadedTime;
}
}
@@ -203,7 +282,9 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
val = mLastCount;
} else {
val = mCount;
- if ((which == STATS_CURRENT) || (which == STATS_UNPLUGGED)) {
+ if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedCount;
+ } else if (which != STATS_TOTAL) {
val -= mLoadedCount;
}
}
@@ -211,16 +292,37 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
return val;
}
+ public void logState() {
+ Log.i("foo", "mNesting=" + mNesting + " mCount=" + mCount
+ + " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount
+ + " mUnpluggedCount=" + mUnpluggedCount);
+ Log.i("foo", "mTotalTime=" + mTotalTime
+ + " mLoadedTime=" + mLoadedTime);
+ Log.i("foo", "mLastTime=" + mLastTime
+ + " mUnpluggedTime=" + mUnpluggedTime);
+ Log.i("foo", "mUpdateTime=" + mUpdateTime
+ + " mAcquireTime=" + mAcquireTime);
+ }
+
void startRunningLocked(BatteryStatsImpl stats) {
if (mNesting++ == 0) {
- mStartTime = mUpdateTime =
- stats.getBatteryUptimeLocked(SystemClock.elapsedRealtime() * 1000);
- // Accumulate time to all other active counters with the current value of mCount
- refreshTimersLocked(stats);
- // Add this timer to the active pool
- mTimerPool.add(this);
+ mUpdateTime = stats.getBatteryRealtimeLocked(
+ SystemClock.elapsedRealtime() * 1000);
+ if (mTimerPool != null) {
+ // Accumulate time to all currently active timers before adding
+ // this new one to the pool.
+ refreshTimersLocked(stats, mTimerPool);
+ // Add this timer to the active pool
+ mTimerPool.add(this);
+ }
// Increment the count
mCount++;
+ mAcquireTime = mTotalTime;
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "start #" + mType + ": mUpdateTime=" + mUpdateTime
+ + " mTotalTime=" + mTotalTime + " mCount=" + mCount
+ + " mAcquireTime=" + mAcquireTime);
+ }
}
}
@@ -230,83 +332,158 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
return;
}
if (--mNesting == 0) {
- // Accumulate time to all active counters with the current value of mCount
- refreshTimersLocked(stats);
- // Remove this timer from the active pool
- mTimerPool.remove(this);
- // Decrement the count
- mCount--;
+ if (mTimerPool != null) {
+ // Accumulate time to all active counters, scaled by the total
+ // active in the pool, before taking this one out of the pool.
+ refreshTimersLocked(stats, mTimerPool);
+ // Remove this timer from the active pool
+ mTimerPool.remove(this);
+ } else {
+ final long realtime = SystemClock.elapsedRealtime() * 1000;
+ final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
+ mNesting = 1;
+ mTotalTime = computeRunTimeLocked(batteryRealtime);
+ mNesting = 0;
+ }
+
+ if (DEBUG && mType < 0) {
+ Log.v(TAG, "stop #" + mType + ": mUpdateTime=" + mUpdateTime
+ + " mTotalTime=" + mTotalTime + " mCount=" + mCount
+ + " mAcquireTime=" + mAcquireTime);
+ }
+
+ if (mTotalTime == mAcquireTime) {
+ // If there was no change in the time, then discard this
+ // count. A somewhat cheezy strategy, but hey.
+ mCount--;
+ }
}
}
// Update the total time for all other running Timers with the same type as this Timer
// due to a change in timer count
- private void refreshTimersLocked(BatteryStatsImpl stats) {
- for (Timer t : mTimerPool) {
- t.updateTimeLocked(stats);
- }
- }
-
- /**
- * Update totalTime and reset updateTime
- * @param stats
- */
- private void updateTimeLocked(BatteryStatsImpl stats) {
- long realtime = SystemClock.elapsedRealtime() * 1000;
- long heldTime = stats.getBatteryUptimeLocked(realtime) - mUpdateTime;
- if (heldTime > 0) {
- mTotalTime += (heldTime * 1000) / mCount;
+ private static void refreshTimersLocked(final BatteryStatsImpl stats,
+ final ArrayList<Timer> pool) {
+ final long realtime = SystemClock.elapsedRealtime() * 1000;
+ final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
+ final int N = pool.size();
+ for (int i=N-1; i>= 0; i--) {
+ final Timer t = pool.get(i);
+ long heldTime = batteryRealtime - t.mUpdateTime;
+ if (heldTime > 0) {
+ t.mTotalTime += heldTime / N;
+ }
+ t.mUpdateTime = batteryRealtime;
}
- mUpdateTime = stats.getBatteryUptimeLocked(realtime);
}
- private long computeRunTimeLocked(long curBatteryUptime) {
- return mTotalTime +
- (mNesting > 0 ? ((curBatteryUptime * 1000) - mUpdateTime) / mCount : 0);
+ private long computeRunTimeLocked(long curBatteryRealtime) {
+ return mTotalTime + (mNesting > 0
+ ? (curBatteryRealtime - mUpdateTime)
+ / (mTimerPool != null ? mTimerPool.size() : 1)
+ : 0);
}
- void writeSummaryFromParcelLocked(Parcel out, long curBatteryUptime) {
- long runTime = computeRunTimeLocked(curBatteryUptime);
+ void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) {
+ long runTime = computeRunTimeLocked(batteryRealtime);
// Divide by 1000 for backwards compatibility
out.writeLong((runTime + 500) / 1000);
- out.writeLong(((runTime - mLoadedTotalTime) + 500) / 1000);
+ out.writeLong(((runTime - mLoadedTime) + 500) / 1000);
out.writeInt(mCount);
out.writeInt(mCount - mLoadedCount);
}
void readSummaryFromParcelLocked(Parcel in) {
// Multiply by 1000 for backwards compatibility
- mTotalTime = mLoadedTotalTime = in.readLong() * 1000;
- mLastTotalTime = in.readLong();
+ mTotalTime = mLoadedTime = in.readLong() * 1000;
+ mLastTime = in.readLong() * 1000;
+ mUnpluggedTime = mTotalTime;
mCount = mLoadedCount = in.readInt();
mLastCount = in.readInt();
+ mUnpluggedCount = mCount;
mNesting = 0;
}
}
- public void unplugTimers() {
- ArrayList<Timer> timers;
-
- timers = mPartialTimers;
- for (int i = timers.size() - 1; i >= 0; i--) {
- timers.get(i).unplug();
+ public void doUnplug(long batteryUptime, long batteryRealtime) {
+ for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
+ Uid u = mUidStats.valueAt(iu);
+ u.mStartedTcpBytesReceived = NetStat.getUidRxBytes(u.mUid);
+ u.mStartedTcpBytesSent = NetStat.getUidTxBytes(u.mUid);
+ u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived;
+ u.mTcpBytesSentAtLastUnplug = u.mCurrentTcpBytesSent;
}
- timers = mFullTimers;
- for (int i = timers.size() - 1; i >= 0; i--) {
- timers.get(i).unplug();
+ for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
+ mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
}
- timers = mWindowTimers;
- for (int i = timers.size() - 1; i >= 0; i--) {
- timers.get(i).unplug();
+ }
+
+ public void doPlug(long batteryUptime, long batteryRealtime) {
+ for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
+ Uid u = mUidStats.valueAt(iu);
+ if (u.mStartedTcpBytesReceived >= 0) {
+ u.mCurrentTcpBytesReceived = u.computeCurrentTcpBytesReceived();
+ u.mStartedTcpBytesReceived = -1;
+ }
+ if (u.mStartedTcpBytesSent >= 0) {
+ u.mCurrentTcpBytesSent = u.computeCurrentTcpBytesSent();
+ u.mStartedTcpBytesSent = -1;
+ }
}
- timers = mSensorTimers;
- for (int i = timers.size() - 1; i >= 0; i--) {
- timers.get(i).unplug();
+ for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
+ mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
}
}
- @Override
- public SparseArray<? extends BatteryStats.Uid> getUidStats() {
+ public void noteStartGps(int uid) {
+ mUidStats.get(uid).noteStartGps();
+ }
+
+ public void noteStopGps(int uid) {
+ mUidStats.get(uid).noteStopGps();
+ }
+
+ public void noteScreenOnLocked() {
+ if (!mScreenOn) {
+ mScreenOn = true;
+ mScreenOnTimer.startRunningLocked(this);
+ }
+ }
+
+ public void noteScreenOffLocked() {
+ if (mScreenOn) {
+ mScreenOn = false;
+ mScreenOnTimer.stopRunningLocked(this);
+ }
+ }
+
+ public void notePhoneOnLocked() {
+ if (!mPhoneOn) {
+ mPhoneOn = true;
+ mPhoneOnTimer.startRunningLocked(this);
+ }
+ }
+
+ public void notePhoneOffLocked() {
+ if (mPhoneOn) {
+ mPhoneOn = false;
+ mPhoneOnTimer.stopRunningLocked(this);
+ }
+ }
+
+ @Override public long getScreenOnTime(long batteryRealtime, int which) {
+ return mScreenOnTimer.getTotalTime(batteryRealtime, which);
+ }
+
+ @Override public long getPhoneOnTime(long batteryRealtime, int which) {
+ return mPhoneOnTimer.getTotalTime(batteryRealtime, which);
+ }
+
+ @Override public boolean getIsOnBattery() {
+ return mOnBattery;
+ }
+
+ @Override public SparseArray<? extends BatteryStats.Uid> getUidStats() {
return mUidStats;
}
@@ -314,7 +491,20 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
* The statistics associated with a particular uid.
*/
public final class Uid extends BatteryStats.Uid {
-
+
+ final int mUid;
+ long mLoadedTcpBytesReceived;
+ long mLoadedTcpBytesSent;
+ long mCurrentTcpBytesReceived;
+ long mCurrentTcpBytesSent;
+ long mTcpBytesReceivedAtLastUnplug;
+ long mTcpBytesSentAtLastUnplug;
+
+ // These are not saved/restored when parcelling, since we want
+ // to return from the parcel with a snapshot of the state.
+ long mStartedTcpBytesReceived = -1;
+ long mStartedTcpBytesSent = -1;
+
/**
* The statistics we have collected for this uid's wake locks.
*/
@@ -334,6 +524,10 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
* The statistics we have collected for this uid's processes.
*/
final HashMap<String, Pkg> mPackageStats = new HashMap<String, Pkg>();
+
+ public Uid(int uid) {
+ mUid = uid;
+ }
@Override
public Map<String, ? extends BatteryStats.Uid.Wakelock> getWakelockStats() {
@@ -354,20 +548,62 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
public Map<String, ? extends BatteryStats.Uid.Pkg> getPackageStats() {
return mPackageStats;
}
-
- void writeToParcelLocked(Parcel out) {
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public long getTcpBytesReceived(int which) {
+ if (which == STATS_LAST) {
+ return mLoadedTcpBytesReceived;
+ } else {
+ long current = computeCurrentTcpBytesReceived();
+ if (which == STATS_UNPLUGGED) {
+ current -= mTcpBytesReceivedAtLastUnplug;
+ } else if (which == STATS_TOTAL) {
+ current += mLoadedTcpBytesReceived;
+ }
+ return current;
+ }
+ }
+
+ public long computeCurrentTcpBytesReceived() {
+ return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
+ ? (NetStat.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
+ }
+
+ public long getTcpBytesSent(int which) {
+ if (which == STATS_LAST) {
+ return mLoadedTcpBytesSent;
+ } else {
+ long current = computeCurrentTcpBytesSent();
+ if (which == STATS_UNPLUGGED) {
+ current -= mTcpBytesSentAtLastUnplug;
+ } else if (which == STATS_TOTAL) {
+ current += mLoadedTcpBytesSent;
+ }
+ return current;
+ }
+ }
+
+ public long computeCurrentTcpBytesSent() {
+ return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
+ ? (NetStat.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
+ }
+
+ void writeToParcelLocked(Parcel out, long batteryRealtime) {
out.writeInt(mWakelockStats.size());
for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) {
out.writeString(wakelockEntry.getKey());
Uid.Wakelock wakelock = wakelockEntry.getValue();
- wakelock.writeToParcelLocked(out);
+ wakelock.writeToParcelLocked(out, batteryRealtime);
}
out.writeInt(mSensorStats.size());
for (Map.Entry<Integer, Uid.Sensor> sensorEntry : mSensorStats.entrySet()) {
out.writeInt(sensorEntry.getKey());
Uid.Sensor sensor = sensorEntry.getValue();
- sensor.writeToParcelLocked(out);
+ sensor.writeToParcelLocked(out, batteryRealtime);
}
out.writeInt(mProcessStats.size());
@@ -383,15 +619,22 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
Uid.Pkg pkg = pkgEntry.getValue();
pkg.writeToParcelLocked(out);
}
+
+ out.writeLong(mLoadedTcpBytesReceived);
+ out.writeLong(mLoadedTcpBytesSent);
+ out.writeLong(computeCurrentTcpBytesReceived());
+ out.writeLong(computeCurrentTcpBytesSent());
+ out.writeLong(mTcpBytesReceivedAtLastUnplug);
+ out.writeLong(mTcpBytesSentAtLastUnplug);
}
- void readFromParcelLocked(Parcel in) {
+ void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
int numWakelocks = in.readInt();
mWakelockStats.clear();
for (int j = 0; j < numWakelocks; j++) {
String wakelockName = in.readString();
Uid.Wakelock wakelock = new Wakelock();
- wakelock.readFromParcelLocked(in);
+ wakelock.readFromParcelLocked(unpluggables, in);
mWakelockStats.put(wakelockName, wakelock);
}
@@ -399,8 +642,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
mSensorStats.clear();
for (int k = 0; k < numSensors; k++) {
int sensorNumber = in.readInt();
- Uid.Sensor sensor = new Sensor();
- sensor.readFromParcelLocked(in);
+ Uid.Sensor sensor = new Sensor(sensorNumber);
+ sensor.readFromParcelLocked(mUnpluggables, in);
mSensorStats.put(sensorNumber, sensor);
}
@@ -421,6 +664,13 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
pkg.readFromParcelLocked(in);
mPackageStats.put(packageName, pkg);
}
+
+ mLoadedTcpBytesReceived = in.readLong();
+ mLoadedTcpBytesSent = in.readLong();
+ mCurrentTcpBytesReceived = in.readLong();
+ mCurrentTcpBytesSent = in.readLong();
+ mTcpBytesReceivedAtLastUnplug = in.readLong();
+ mTcpBytesSentAtLastUnplug = in.readLong();
}
/**
@@ -430,17 +680,17 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
/**
* How long (in ms) this uid has been keeping the device partially awake.
*/
- Timer wakeTimePartial;
+ Timer mTimerPartial;
/**
* How long (in ms) this uid has been keeping the device fully awake.
*/
- Timer wakeTimeFull;
+ Timer mTimerFull;
/**
* How long (in ms) this uid has had a window keeping the device awake.
*/
- Timer wakeTimeWindow;
+ Timer mTimerWindow;
/**
* Reads a possibly null Timer from a Parcel. The timer is associated with the
@@ -449,93 +699,85 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
* @param in the Parcel to be read from.
* return a new Timer, or null.
*/
- private Timer readTimerFromParcel(Parcel in) {
+ private Timer readTimerFromParcel(int type, ArrayList<Timer> pool,
+ ArrayList<Unpluggable> unpluggables, Parcel in) {
if (in.readInt() == 0) {
return null;
}
- Timer timer = new Timer();
- timer.readFromParcel(in);
- // Set the timer pool for the timer according to its type
- switch (timer.mType) {
- case WAKE_TYPE_PARTIAL:
- timer.mTimerPool = mPartialTimers;
- break;
- case WAKE_TYPE_FULL:
- timer.mTimerPool = mFullTimers;
- break;
- case WAKE_TYPE_WINDOW:
- timer.mTimerPool = mWindowTimers;
- break;
- }
- // If the timer is active, add it to the pool
- if (timer.mNesting > 0) {
- timer.mTimerPool.add(timer);
- }
- return timer;
+ return new Timer(type, pool, unpluggables, in);
}
- void readFromParcelLocked(Parcel in) {
- wakeTimePartial = readTimerFromParcel(in);
- wakeTimeFull = readTimerFromParcel(in);
- wakeTimeWindow = readTimerFromParcel(in);
+ void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
+ mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL,
+ mPartialTimers, unpluggables, in);
+ mTimerFull = readTimerFromParcel(WAKE_TYPE_FULL,
+ mFullTimers, unpluggables, in);
+ mTimerWindow = readTimerFromParcel(WAKE_TYPE_WINDOW,
+ mWindowTimers, unpluggables, in);
}
- void writeToParcelLocked(Parcel out) {
- Timer.writeTimerToParcel(out, wakeTimePartial);
- Timer.writeTimerToParcel(out, wakeTimeFull);
- Timer.writeTimerToParcel(out, wakeTimeWindow);
+ void writeToParcelLocked(Parcel out, long batteryRealtime) {
+ Timer.writeTimerToParcel(out, mTimerPartial, batteryRealtime);
+ Timer.writeTimerToParcel(out, mTimerFull, batteryRealtime);
+ Timer.writeTimerToParcel(out, mTimerWindow, batteryRealtime);
}
@Override
public Timer getWakeTime(int type) {
switch (type) {
- case WAKE_TYPE_FULL: return wakeTimeFull;
- case WAKE_TYPE_PARTIAL: return wakeTimePartial;
- case WAKE_TYPE_WINDOW: return wakeTimeWindow;
+ case WAKE_TYPE_FULL: return mTimerFull;
+ case WAKE_TYPE_PARTIAL: return mTimerPartial;
+ case WAKE_TYPE_WINDOW: return mTimerWindow;
default: throw new IllegalArgumentException("type = " + type);
}
}
}
public final class Sensor extends BatteryStats.Uid.Sensor {
- Timer sensorTime;
+ final int mHandle;
+ Timer mTimer;
+
+ public Sensor(int handle) {
+ mHandle = handle;
+ }
- private Timer readTimerFromParcel(Parcel in) {
+ private Timer readTimerFromParcel(ArrayList<Unpluggable> unpluggables,
+ Parcel in) {
if (in.readInt() == 0) {
return null;
}
- Timer timer = new Timer();
- timer.readFromParcel(in);
- // Set the timer pool for the timer
- timer.mTimerPool = mSensorTimers;
-
- // If the timer is active, add it to the pool
- if (timer.mNesting > 0) {
- timer.mTimerPool.add(timer);
+ ArrayList<Timer> pool = mSensorTimers.get(mHandle);
+ if (pool == null) {
+ pool = new ArrayList<Timer>();
+ mSensorTimers.put(mHandle, pool);
}
- return timer;
+ return new Timer(0, pool, unpluggables, in);
}
- void readFromParcelLocked(Parcel in) {
- sensorTime = readTimerFromParcel(in);
+ void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
+ mTimer = readTimerFromParcel(unpluggables, in);
}
- void writeToParcelLocked(Parcel out) {
- Timer.writeTimerToParcel(out, sensorTime);
+ void writeToParcelLocked(Parcel out, long batteryRealtime) {
+ Timer.writeTimerToParcel(out, mTimer, batteryRealtime);
}
@Override
public Timer getSensorTime() {
- return sensorTime;
+ return mTimer;
+ }
+
+ public int getHandle() {
+ return mHandle;
}
}
/**
* The statistics associated with a particular process.
*/
- public final class Proc extends BatteryStats.Uid.Proc {
+ public final class Proc extends BatteryStats.Uid.Proc implements Unpluggable {
/**
* Total time (in 1/100 sec) spent executing in user code.
*/
@@ -581,6 +823,34 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
*/
int mLastStarts;
+ /**
+ * The amount of user time when last unplugged.
+ */
+ long mUnpluggedUserTime;
+
+ /**
+ * The amount of system time when last unplugged.
+ */
+ long mUnpluggedSystemTime;
+
+ /**
+ * The number of times the process has started before unplugged.
+ */
+ int mUnpluggedStarts;
+
+ Proc() {
+ mUnpluggables.add(this);
+ }
+
+ public void unplug(long batteryUptime, long batteryRealtime) {
+ mUnpluggedUserTime = mUserTime;
+ mUnpluggedSystemTime = mSystemTime;
+ mUnpluggedStarts = mStarts;
+ }
+
+ public void plug(long batteryUptime, long batteryRealtime) {
+ }
+
void writeToParcelLocked(Parcel out) {
out.writeLong(mUserTime);
out.writeLong(mSystemTime);
@@ -591,6 +861,9 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
out.writeLong(mLastUserTime);
out.writeLong(mLastSystemTime);
out.writeInt(mLastStarts);
+ out.writeLong(mUnpluggedUserTime);
+ out.writeLong(mUnpluggedSystemTime);
+ out.writeInt(mUnpluggedStarts);
}
void readFromParcelLocked(Parcel in) {
@@ -603,6 +876,9 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
mLastUserTime = in.readLong();
mLastSystemTime = in.readLong();
mLastStarts = in.readInt();
+ mUnpluggedUserTime = in.readLong();
+ mUnpluggedSystemTime = in.readLong();
+ mUnpluggedStarts = in.readInt();
}
public BatteryStatsImpl getBatteryStats() {
@@ -627,6 +903,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
val = mUserTime;
if (which == STATS_CURRENT) {
val -= mLoadedUserTime;
+ } else if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedUserTime;
}
}
return val;
@@ -641,6 +919,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
val = mSystemTime;
if (which == STATS_CURRENT) {
val -= mLoadedSystemTime;
+ } else if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedSystemTime;
}
}
return val;
@@ -655,6 +935,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
val = mStarts;
if (which == STATS_CURRENT) {
val -= mLoadedStarts;
+ } else if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedStarts;
}
}
return val;
@@ -664,7 +946,7 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
/**
* The statistics associated with a particular package.
*/
- public final class Pkg extends BatteryStats.Uid.Pkg {
+ public final class Pkg extends BatteryStats.Uid.Pkg implements Unpluggable {
/**
* Number of times this package has done something that could wake up the
* device from sleep.
@@ -684,14 +966,32 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
int mLastWakeups;
/**
+ * Number of things that could wake up the device as of the
+ * last run.
+ */
+ int mUnpluggedWakeups;
+
+ /**
* The statics we have collected for this package's services.
*/
final HashMap<String, Serv> mServiceStats = new HashMap<String, Serv>();
+ Pkg() {
+ mUnpluggables.add(this);
+ }
+
+ public void unplug(long batteryUptime, long batteryRealtime) {
+ mUnpluggedWakeups = mWakeups;
+ }
+
+ public void plug(long batteryUptime, long batteryRealtime) {
+ }
+
void readFromParcelLocked(Parcel in) {
mWakeups = in.readInt();
mLoadedWakeups = in.readInt();
mLastWakeups = in.readInt();
+ mUnpluggedWakeups = in.readInt();
int numServs = in.readInt();
mServiceStats.clear();
@@ -708,6 +1008,7 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
out.writeInt(mWakeups);
out.writeInt(mLoadedWakeups);
out.writeInt(mLastWakeups);
+ out.writeInt(mUnpluggedWakeups);
out.writeInt(mServiceStats.size());
for (Map.Entry<String, Uid.Pkg.Serv> servEntry : mServiceStats.entrySet()) {
@@ -732,6 +1033,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
val = mWakeups;
if (which == STATS_CURRENT) {
val -= mLoadedWakeups;
+ } else if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedWakeups;
}
}
@@ -741,9 +1044,9 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
/**
* The statistics associated with a particular service.
*/
- public final class Serv extends BatteryStats.Uid.Pkg.Serv {
+ public final class Serv extends BatteryStats.Uid.Pkg.Serv implements Unpluggable {
/**
- * Total time (ms) the service has been left started.
+ * Total time (ms in battery uptime) the service has been left started.
*/
long mStartTime;
@@ -764,13 +1067,13 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
int mStarts;
/**
- * Total time (ms) the service has been left launched.
+ * Total time (ms in battery uptime) the service has been left launched.
*/
long mLaunchedTime;
/**
* If service has been launched and not yet exited, this is
- * when it was launched.
+ * when it was launched (ms in battery uptime).
*/
long mLaunchedSince;
@@ -785,7 +1088,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
int mLaunches;
/**
- * The amount of time spent started loaded from a previous save.
+ * The amount of time spent started loaded from a previous save
+ * (ms in battery uptime).
*/
long mLoadedStartTime;
@@ -800,7 +1104,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
int mLoadedLaunches;
/**
- * The amount of time spent started as of the last run.
+ * The amount of time spent started as of the last run (ms
+ * in battery uptime).
*/
long mLastStartTime;
@@ -814,6 +1119,35 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
*/
int mLastLaunches;
+ /**
+ * The amount of time spent started when last unplugged (ms
+ * in battery uptime).
+ */
+ long mUnpluggedStartTime;
+
+ /**
+ * The number of starts when last unplugged.
+ */
+ int mUnpluggedStarts;
+
+ /**
+ * The number of launches when last unplugged.
+ */
+ int mUnpluggedLaunches;
+
+ Serv() {
+ mUnpluggables.add(this);
+ }
+
+ public void unplug(long batteryUptime, long batteryRealtime) {
+ mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime);
+ mUnpluggedStarts = mStarts;
+ mUnpluggedLaunches = mLaunches;
+ }
+
+ public void plug(long batteryUptime, long batteryRealtime) {
+ }
+
void readFromParcelLocked(Parcel in) {
mStartTime = in.readLong();
mRunningSince = in.readLong();
@@ -829,6 +1163,9 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
mLastStartTime = in.readLong();
mLastStarts = in.readInt();
mLastLaunches = in.readInt();
+ mUnpluggedStartTime = in.readLong();
+ mUnpluggedStarts = in.readInt();
+ mUnpluggedLaunches = in.readInt();
}
void writeToParcelLocked(Parcel out) {
@@ -846,6 +1183,9 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
out.writeLong(mLastStartTime);
out.writeInt(mLastStarts);
out.writeInt(mLastLaunches);
+ out.writeLong(mUnpluggedStartTime);
+ out.writeInt(mUnpluggedStarts);
+ out.writeInt(mUnpluggedLaunches);
}
long getLaunchTimeToNowLocked(long batteryUptime) {
@@ -912,6 +1252,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
val = mLaunches;
if (which == STATS_CURRENT) {
val -= mLoadedLaunches;
+ } else if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedLaunches;
}
}
@@ -927,6 +1269,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
val = getStartTimeToNowLocked(now);
if (which == STATS_CURRENT) {
val -= mLoadedStartTime;
+ } else if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedStartTime;
}
}
@@ -942,6 +1286,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
val = mStarts;
if (which == STATS_CURRENT) {
val -= mLoadedStarts;
+ } else if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedStarts;
}
}
@@ -1014,24 +1360,24 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
Timer t = null;
switch (type) {
case WAKE_TYPE_PARTIAL:
- t = wl.wakeTimePartial;
+ t = wl.mTimerPartial;
if (t == null) {
- t = new Timer(WAKE_TYPE_PARTIAL, mPartialTimers);
- wl.wakeTimePartial = t;
+ t = new Timer(WAKE_TYPE_PARTIAL, mPartialTimers, mUnpluggables);
+ wl.mTimerPartial = t;
}
return t;
case WAKE_TYPE_FULL:
- t = wl.wakeTimeFull;
+ t = wl.mTimerFull;
if (t == null) {
- t = new Timer(WAKE_TYPE_FULL, mFullTimers);
- wl.wakeTimeFull = t;
+ t = new Timer(WAKE_TYPE_FULL, mFullTimers, mUnpluggables);
+ wl.mTimerFull = t;
}
return t;
case WAKE_TYPE_WINDOW:
- t = wl.wakeTimeWindow;
+ t = wl.mTimerWindow;
if (t == null) {
- t = new Timer(WAKE_TYPE_WINDOW, mWindowTimers);
- wl.wakeTimeWindow = t;
+ t = new Timer(WAKE_TYPE_WINDOW, mWindowTimers, mUnpluggables);
+ wl.mTimerWindow = t;
}
return t;
default:
@@ -1040,20 +1386,25 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
}
public Timer getSensorTimerLocked(int sensor, boolean create) {
- Integer sId = Integer.valueOf(sensor);
- Sensor se = mSensorStats.get(sId);
+ Sensor se = mSensorStats.get(sensor);
if (se == null) {
if (!create) {
return null;
}
- se = new Sensor();
- mSensorStats.put(sId, se);
+ se = new Sensor(sensor);
+ mSensorStats.put(sensor, se);
+ }
+ Timer t = se.mTimer;
+ if (t != null) {
+ return t;
}
- Timer t = se.sensorTime;
- if (t == null) {
- t = new Timer(0, mSensorTimers);
- se.sensorTime = t;
+ ArrayList<Timer> timers = mSensorTimers.get(sensor);
+ if (timers == null) {
+ timers = new ArrayList<Timer>();
+ mSensorTimers.put(sensor, timers);
}
+ t = new Timer(BatteryStats.SENSOR, timers, mUnpluggables);
+ se.mTimer = t;
return t;
}
@@ -1085,6 +1436,20 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
t.stopRunningLocked(BatteryStatsImpl.this);
}
}
+
+ public void noteStartGps() {
+ Timer t = getSensorTimerLocked(Sensor.GPS, true);
+ if (t != null) {
+ t.startRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+
+ public void noteStopGps() {
+ Timer t = getSensorTimerLocked(Sensor.GPS, false);
+ if (t != null) {
+ t.stopRunningLocked(BatteryStatsImpl.this);
+ }
+ }
public BatteryStatsImpl getBatteryStats() {
return BatteryStatsImpl.this;
@@ -1095,11 +1460,15 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
mFile = new File(filename);
mBackupFile = new File(filename + ".bak");
mStartCount++;
- mOnBattery = true;
+ mScreenOnTimer = new Timer(-1, null, mUnpluggables);
+ mPhoneOnTimer = new Timer(-2, null, mUnpluggables);
+ mOnBattery = mOnBatteryInternal = false;
mTrackBatteryPastUptime = 0;
mTrackBatteryPastRealtime = 0;
mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000;
mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000;
+ mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
+ mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
}
public BatteryStatsImpl(Parcel p) {
@@ -1119,18 +1488,22 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
public void setOnBattery(boolean onBattery) {
synchronized(this) {
if (mOnBattery != onBattery) {
+ mOnBattery = mOnBatteryInternal = onBattery;
+
long uptime = SystemClock.uptimeMillis() * 1000;
long mSecRealtime = SystemClock.elapsedRealtime();
long realtime = mSecRealtime * 1000;
if (onBattery) {
mTrackBatteryUptimeStart = uptime;
mTrackBatteryRealtimeStart = realtime;
- unplugTimers();
+ mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime);
+ mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime);
+ doUnplug(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
} else {
mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart;
mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart;
+ doPlug(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
}
- mOnBattery = onBattery;
if ((mLastWriteTime + (60 * 1000)) < mSecRealtime) {
if (mFile != null) {
writeLocked();
@@ -1151,10 +1524,10 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
@Override
public long computeUptime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL: return mUptime + (curTime-mUptimeStart);
- case STATS_LAST: return mLastUptime;
- case STATS_CURRENT: return (curTime-mUptimeStart);
- case STATS_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart);
+ case STATS_TOTAL: return mUptime + (curTime-mUptimeStart);
+ case STATS_LAST: return mLastUptime;
+ case STATS_CURRENT: return (curTime-mUptimeStart);
+ case STATS_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart);
}
return 0;
}
@@ -1162,10 +1535,10 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
@Override
public long computeRealtime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL: return mRealtime + (curTime-mRealtimeStart);
- case STATS_LAST: return mLastRealtime;
- case STATS_CURRENT: return (curTime-mRealtimeStart);
- case STATS_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart);
+ case STATS_TOTAL: return mRealtime + (curTime-mRealtimeStart);
+ case STATS_LAST: return mLastRealtime;
+ case STATS_CURRENT: return (curTime-mRealtimeStart);
+ case STATS_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart);
}
return 0;
}
@@ -1173,13 +1546,14 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
@Override
public long computeBatteryUptime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL:
- return mBatteryUptime + getBatteryUptime(curTime);
- case STATS_LAST:
- return mBatteryLastUptime;
- case STATS_CURRENT:
- case STATS_UNPLUGGED:
- return getBatteryUptime(curTime);
+ case STATS_TOTAL:
+ return mBatteryUptime + getBatteryUptime(curTime);
+ case STATS_LAST:
+ return mBatteryLastUptime;
+ case STATS_CURRENT:
+ return getBatteryUptime(curTime);
+ case STATS_UNPLUGGED:
+ return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime;
}
return 0;
}
@@ -1187,20 +1561,21 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
@Override
public long computeBatteryRealtime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL:
- return mBatteryRealtime + getBatteryRealtimeLocked(curTime);
- case STATS_LAST:
- return mBatteryLastRealtime;
- case STATS_CURRENT:
- case STATS_UNPLUGGED:
- return getBatteryRealtimeLocked(curTime);
+ case STATS_TOTAL:
+ return mBatteryRealtime + getBatteryRealtimeLocked(curTime);
+ case STATS_LAST:
+ return mBatteryLastRealtime;
+ case STATS_CURRENT:
+ return getBatteryRealtimeLocked(curTime);
+ case STATS_UNPLUGGED:
+ return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime;
}
return 0;
}
long getBatteryUptimeLocked(long curTime) {
long time = mTrackBatteryPastUptime;
- if (mOnBattery) {
+ if (mOnBatteryInternal) {
time += curTime - mTrackBatteryUptimeStart;
}
return time;
@@ -1217,7 +1592,7 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
long getBatteryRealtimeLocked(long curTime) {
long time = mTrackBatteryPastRealtime;
- if (mOnBattery) {
+ if (mOnBatteryInternal) {
time += curTime - mTrackBatteryRealtimeStart;
}
return time;
@@ -1234,7 +1609,7 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
public Uid getUidStatsLocked(int uid) {
Uid u = mUidStats.get(uid);
if (u == null) {
- u = new Uid();
+ u = new Uid(uid);
mUidStats.put(uid, u);
}
return u;
@@ -1246,7 +1621,7 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
public void removeUidStatsLocked(int uid) {
mUidStats.remove(uid);
}
-
+
/**
* Retrieve the statistics object for a particular process, creating
* if needed.
@@ -1373,8 +1748,8 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
private void readSummaryFromParcel(Parcel in) {
final int version = in.readInt();
if (version != VERSION) {
- Log.e("BatteryStats", "readFromParcel: version got " + version
- + ", expected " + VERSION);
+ Log.w("BatteryStats", "readFromParcel: version got " + version
+ + ", expected " + VERSION + "; erasing old stats");
return;
}
@@ -1388,15 +1763,20 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
mRealtime = in.readLong();
mLastRealtime = in.readLong();
mStartCount++;
+
+ mScreenOn = false;
+ mScreenOnTimer.readSummaryFromParcelLocked(in);
+ mPhoneOn = false;
+ mPhoneOnTimer.readSummaryFromParcelLocked(in);
final int NU = in.readInt();
- for (int iu=0; iu<NU; iu++) {
+ for (int iu = 0; iu < NU; iu++) {
int uid = in.readInt();
- Uid u = new Uid();
+ Uid u = new Uid(uid);
mUidStats.put(uid, u);
int NW = in.readInt();
- for (int iw=0; iw<NW; iw++) {
+ for (int iw = 0; iw < NW; iw++) {
String wlName = in.readString();
if (in.readInt() != 0) {
u.getWakeTimerLocked(wlName, WAKE_TYPE_FULL).readSummaryFromParcelLocked(in);
@@ -1409,18 +1789,17 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
}
}
- if (version >= 12) {
- int NSE = in.readInt();
- for (int is=0; is<NSE; is++) {
- int seNumber = in.readInt();
- if (in.readInt() != 0) {
- u.getSensorTimerLocked(seNumber, true).readSummaryFromParcelLocked(in);
- }
+ int NP = in.readInt();
+ for (int is = 0; is < NP; is++) {
+ int seNumber = in.readInt();
+ if (in.readInt() != 0) {
+ u.getSensorTimerLocked(seNumber, true)
+ .readSummaryFromParcelLocked(in);
}
}
- int NP = in.readInt();
- for (int ip=0; ip<NP; ip++) {
+ NP = in.readInt();
+ for (int ip = 0; ip < NP; ip++) {
String procName = in.readString();
Uid.Proc p = u.getProcessStatsLocked(procName);
p.mUserTime = p.mLoadedUserTime = in.readLong();
@@ -1432,13 +1811,13 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
}
NP = in.readInt();
- for (int ip=0; ip<NP; ip++) {
+ for (int ip = 0; ip < NP; ip++) {
String pkgName = in.readString();
Uid.Pkg p = u.getPackageStatsLocked(pkgName);
p.mWakeups = p.mLoadedWakeups = in.readInt();
p.mLastWakeups = in.readInt();
final int NS = in.readInt();
- for (int is=0; is<NS; is++) {
+ for (int is = 0; is < NS; is++) {
String servName = in.readString();
Uid.Pkg.Serv s = u.getServiceStatsLocked(pkgName, servName);
s.mStartTime = s.mLoadedStartTime = in.readLong();
@@ -1449,6 +1828,9 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
s.mLastLaunches = in.readInt();
}
}
+
+ u.mLoadedTcpBytesReceived = in.readLong();
+ u.mLoadedTcpBytesSent = in.readLong();
}
}
@@ -1459,9 +1841,10 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
* @param out the Parcel to be written to.
*/
public void writeSummaryToParcel(Parcel out) {
- final long NOW = getBatteryUptimeLocked();
final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000;
+ final long NOW = getBatteryUptimeLocked(NOW_SYS);
+ final long NOWREAL = getBatteryRealtimeLocked(NOWREAL_SYS);
out.writeInt(VERSION);
@@ -1474,10 +1857,13 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
out.writeLong(computeUptime(NOW_SYS, STATS_CURRENT));
out.writeLong(computeRealtime(NOWREAL_SYS, STATS_TOTAL));
out.writeLong(computeRealtime(NOWREAL_SYS, STATS_CURRENT));
+
+ mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
final int NU = mUidStats.size();
out.writeInt(NU);
- for (int iu=0; iu<NU; iu++) {
+ for (int iu = 0; iu < NU; iu++) {
out.writeInt(mUidStats.keyAt(iu));
Uid u = mUidStats.valueAt(iu);
@@ -1485,24 +1871,24 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
out.writeInt(NW);
if (NW > 0) {
for (Map.Entry<String, BatteryStatsImpl.Uid.Wakelock> ent
- : u.mWakelockStats.entrySet()) {
+ : u.mWakelockStats.entrySet()) {
out.writeString(ent.getKey());
Uid.Wakelock wl = ent.getValue();
- if (wl.wakeTimeFull != null) {
+ if (wl.mTimerFull != null) {
out.writeInt(1);
- wl.wakeTimeFull.writeSummaryFromParcelLocked(out, NOW);
+ wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL);
} else {
out.writeInt(0);
}
- if (wl.wakeTimePartial != null) {
+ if (wl.mTimerPartial != null) {
out.writeInt(1);
- wl.wakeTimePartial.writeSummaryFromParcelLocked(out, NOW);
+ wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL);
} else {
out.writeInt(0);
}
- if (wl.wakeTimeWindow != null) {
+ if (wl.mTimerWindow != null) {
out.writeInt(1);
- wl.wakeTimeWindow.writeSummaryFromParcelLocked(out, NOW);
+ wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL);
} else {
out.writeInt(0);
}
@@ -1513,12 +1899,12 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
out.writeInt(NSE);
if (NSE > 0) {
for (Map.Entry<Integer, BatteryStatsImpl.Uid.Sensor> ent
- : u.mSensorStats.entrySet()) {
+ : u.mSensorStats.entrySet()) {
out.writeInt(ent.getKey());
Uid.Sensor se = ent.getValue();
- if (se.sensorTime != null) {
+ if (se.mTimer != null) {
out.writeInt(1);
- se.sensorTime.writeSummaryFromParcelLocked(out, NOW);
+ se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL);
} else {
out.writeInt(0);
}
@@ -1568,6 +1954,9 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
}
}
}
+
+ out.writeLong(u.getTcpBytesReceived(STATS_TOTAL));
+ out.writeLong(u.getTcpBytesSent(STATS_TOTAL));
}
}
@@ -1586,6 +1975,10 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
mBatteryLastUptime = in.readLong();
mBatteryRealtime = in.readLong();
mBatteryLastRealtime = in.readLong();
+ mScreenOn = false;
+ mScreenOnTimer = new Timer(-1, null, mUnpluggables, in);
+ mPhoneOn = false;
+ mPhoneOnTimer = new Timer(-2, null, mUnpluggables, in);
mUptime = in.readLong();
mUptimeStart = in.readLong();
mLastUptime = in.readLong();
@@ -1593,10 +1986,13 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
mRealtimeStart = in.readLong();
mLastRealtime = in.readLong();
mOnBattery = in.readInt() != 0;
+ mOnBatteryInternal = false; // we are no longer really running.
mTrackBatteryPastUptime = in.readLong();
mTrackBatteryUptimeStart = in.readLong();
mTrackBatteryPastRealtime = in.readLong();
mTrackBatteryRealtimeStart = in.readLong();
+ mUnpluggedBatteryUptime = in.readLong();
+ mUnpluggedBatteryRealtime = in.readLong();
mLastWriteTime = in.readLong();
mPartialTimers.clear();
@@ -1606,10 +2002,10 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
int numUids = in.readInt();
mUidStats.clear();
for (int i = 0; i < numUids; i++) {
- int key = in.readInt();
- Uid uid = new Uid();
- uid.readFromParcelLocked(in);
- mUidStats.append(key, uid);
+ int uid = in.readInt();
+ Uid u = new Uid(uid);
+ u.readFromParcelLocked(mUnpluggables, in);
+ mUidStats.append(uid, u);
}
}
@@ -1619,12 +2015,19 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
@SuppressWarnings("unused")
void writeToParcelLocked(Parcel out, int flags) {
+ final long uSecUptime = SystemClock.uptimeMillis() * 1000;
+ final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
+ final long batteryUptime = getBatteryUptimeLocked(uSecUptime);
+ final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime);
+
out.writeInt(MAGIC);
out.writeInt(mStartCount);
out.writeLong(mBatteryUptime);
out.writeLong(mBatteryLastUptime);
out.writeLong(mBatteryRealtime);
out.writeLong(mBatteryLastRealtime);
+ mScreenOnTimer.writeToParcel(out, batteryRealtime);
+ mPhoneOnTimer.writeToParcel(out, batteryRealtime);
out.writeLong(mUptime);
out.writeLong(mUptimeStart);
out.writeLong(mLastUptime);
@@ -1632,10 +2035,12 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
out.writeLong(mRealtimeStart);
out.writeLong(mLastRealtime);
out.writeInt(mOnBattery ? 1 : 0);
- out.writeLong(mTrackBatteryPastUptime);
+ out.writeLong(batteryUptime);
out.writeLong(mTrackBatteryUptimeStart);
- out.writeLong(mTrackBatteryPastRealtime);
+ out.writeLong(batteryRealtime);
out.writeLong(mTrackBatteryRealtimeStart);
+ out.writeLong(mUnpluggedBatteryUptime);
+ out.writeLong(mUnpluggedBatteryRealtime);
out.writeLong(mLastWriteTime);
int size = mUidStats.size();
@@ -1644,7 +2049,7 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
out.writeInt(mUidStats.keyAt(i));
Uid uid = mUidStats.valueAt(i);
- uid.writeToParcelLocked(out);
+ uid.writeToParcelLocked(out, batteryRealtime);
}
}
@@ -1658,4 +2063,14 @@ public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
return new BatteryStatsImpl[size];
}
};
+
+ public void dumpLocked(Printer pw) {
+ if (DEBUG) {
+ Log.i(TAG, "*** Screen timer:");
+ mScreenOnTimer.logState();
+ Log.i(TAG, "*** Phone timer:");
+ mPhoneOnTimer.logState();
+ }
+ super.dumpLocked(pw);
+ }
}
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index 1ec74a1..932555d 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -21,7 +21,8 @@ public class HandlerCaller {
public Object arg1;
public Object arg2;
- Object arg3;
+ public Object arg3;
+ public Object arg4;
public int argi1;
public int argi2;
public int argi3;
@@ -119,6 +120,10 @@ public class HandlerCaller {
return mH.obtainMessage(what, arg1, 0);
}
+ public Message obtainMessageII(int what, int arg1, int arg2) {
+ return mH.obtainMessage(what, arg1, arg2);
+ }
+
public Message obtainMessageIO(int what, int arg1, Object arg2) {
return mH.obtainMessage(what, arg1, 0, arg2);
}
@@ -149,6 +154,16 @@ public class HandlerCaller {
return mH.obtainMessage(what, 0, 0, args);
}
+ public Message obtainMessageOOOO(int what, Object arg1, Object arg2,
+ Object arg3, Object arg4) {
+ SomeArgs args = obtainArgs();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ args.arg3 = arg3;
+ args.arg4 = arg4;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
public Message obtainMessageIIII(int what, int arg1, int arg2,
int arg3, int arg4) {
SomeArgs args = obtainArgs();
diff --git a/core/java/com/android/internal/os/HandlerThread.java b/core/java/com/android/internal/os/HandlerThread.java
deleted file mode 100644
index 1de6bfd..0000000
--- a/core/java/com/android/internal/os/HandlerThread.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.os.Handler;
-import android.os.HandlerInterface;
-import android.os.Looper;
-
-/**
- * Handy class for starting a new thread containing a Handler
- * @hide
- * @deprecated
- */
-public class HandlerThread extends Thread
-{
- Runnable mSetup;
- HandlerInterface mhi;
- Handler mh;
- Throwable mtr;
- final Object mMonitor = new Object();
-
- public
- HandlerThread(HandlerInterface h, Runnable setup, String name)
- {
- super(name);
-
- mhi = h;
- mSetup = setup;
-
- synchronized (mMonitor) {
- start();
- while (mh == null && mtr == null) {
- try {
- mMonitor.wait();
- } catch (InterruptedException ex) {
- }
- }
- }
-
- if (mtr != null) {
- throw new RuntimeException("exception while starting", mtr);
- }
- }
-
- @Override
- public void
- run()
- {
- synchronized(mMonitor) {
- try {
- Looper.prepare();
- mh = new HandlerHelper (mhi);
-
- if (mSetup != null) {
- mSetup.run();
- mSetup = null;
- }
- } catch (RuntimeException exc) {
- mtr = exc;
- }
-
- mMonitor.notify();
- }
-
- if (mtr == null) {
- Looper.loop();
- }
- }
-
- public Handler
- getHandler()
- {
- return mh;
- }
-
-}
-
diff --git a/core/java/com/android/internal/os/IResultReceiver.aidl b/core/java/com/android/internal/os/IResultReceiver.aidl
new file mode 100644
index 0000000..2b70f95
--- /dev/null
+++ b/core/java/com/android/internal/os/IResultReceiver.aidl
@@ -0,0 +1,25 @@
+/* //device/java/android/android/app/IActivityPendingResult.aidl
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.internal.os;
+
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IResultReceiver {
+ void send(int resultCode, in Bundle resultData);
+}
diff --git a/core/java/com/android/internal/os/PkgUsageStats.aidl b/core/java/com/android/internal/os/PkgUsageStats.aidl
new file mode 100755
index 0000000..8305271
--- /dev/null
+++ b/core/java/com/android/internal/os/PkgUsageStats.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/content/Intent.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.internal.os;
+
+parcelable PkgUsageStats;
diff --git a/core/java/com/android/internal/os/PkgUsageStats.java b/core/java/com/android/internal/os/PkgUsageStats.java
new file mode 100755
index 0000000..e847878
--- /dev/null
+++ b/core/java/com/android/internal/os/PkgUsageStats.java
@@ -0,0 +1,60 @@
+package com.android.internal.os;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * implementation of PkgUsageStats associated with an
+ * application package.
+ * @hide
+ */
+public class PkgUsageStats implements Parcelable {
+ public String packageName;
+ public int launchCount;
+ public long usageTime;
+
+ public static final Parcelable.Creator<PkgUsageStats> CREATOR
+ = new Parcelable.Creator<PkgUsageStats>() {
+ public PkgUsageStats createFromParcel(Parcel in) {
+ return new PkgUsageStats(in);
+ }
+
+ public PkgUsageStats[] newArray(int size) {
+ return new PkgUsageStats[size];
+ }
+ };
+
+ public String toString() {
+ return "PkgUsageStats{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ public PkgUsageStats(String pkgName, int count, long time) {
+ packageName = pkgName;
+ launchCount = count;
+ usageTime = time;
+ }
+
+ public PkgUsageStats(Parcel source) {
+ packageName = source.readString();
+ launchCount = source.readInt();
+ usageTime = source.readLong();
+ }
+
+ public PkgUsageStats(PkgUsageStats pStats) {
+ packageName = pStats.packageName;
+ launchCount = pStats.launchCount;
+ usageTime = pStats.usageTime;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags){
+ dest.writeString(packageName);
+ dest.writeInt(launchCount);
+ dest.writeLong(usageTime);
+ }
+}
diff --git a/core/java/com/android/internal/os/RecoverySystem.java b/core/java/com/android/internal/os/RecoverySystem.java
new file mode 100644
index 0000000..c938610
--- /dev/null
+++ b/core/java/com/android/internal/os/RecoverySystem.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.FileUtils;
+import android.os.Power;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Utility class for interacting with the Android recovery partition.
+ * The recovery partition is a small standalone system which can perform
+ * operations that are difficult while the main system is running, like
+ * upgrading system software or reformatting the data partition.
+ * Note that most of these operations must be run as root.
+ *
+ * @hide
+ */
+public class RecoverySystem {
+ private static final String TAG = "RecoverySystem"; // for logging
+
+ // Used to communicate with recovery. See commands/recovery/recovery.c.
+ private static File RECOVERY_DIR = new File("/cache/recovery");
+ private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
+ private static File LOG_FILE = new File(RECOVERY_DIR, "log");
+
+ // Length limits for reading files.
+ private static int LOG_FILE_MAX_LENGTH = 8 * 1024;
+
+ /**
+ * Reboot into the recovery system to install a system update.
+ * @param update package to install (must be in /cache or /data).
+ * @throws IOException if something goes wrong.
+ */
+ public static void rebootAndUpdate(File update) throws IOException {
+ String path = update.getCanonicalPath();
+ if (path.startsWith("/cache/")) {
+ path = "CACHE:" + path.substring(7);
+ } else if (path.startsWith("/data/")) {
+ path = "DATA:" + path.substring(6);
+ } else {
+ throw new IllegalArgumentException(
+ "Must start with /cache or /data: " + path);
+ }
+ bootCommand("--update_package=" + path);
+ }
+
+ /**
+ * Reboot into the recovery system to wipe the /data partition.
+ * @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
+ * @throws IOException if something goes wrong.
+ */
+ public static void rebootAndWipe() throws IOException {
+ bootCommand("--wipe_data");
+ }
+
+ /**
+ * Reboot into the recovery system with the supplied argument.
+ * @param arg to pass to the recovery utility.
+ * @throws IOException if something goes wrong.
+ */
+ private static void bootCommand(String arg) throws IOException {
+ RECOVERY_DIR.mkdirs(); // In case we need it
+ COMMAND_FILE.delete(); // In case it's not writable
+ LOG_FILE.delete();
+
+ FileWriter command = new FileWriter(COMMAND_FILE);
+ try {
+ command.write(arg);
+ command.write("\n");
+ } finally {
+ command.close();
+ }
+
+ // Having written the command file, go ahead and reboot
+ Power.reboot("recovery");
+ throw new IOException("Reboot failed (no permissions?)");
+ }
+
+ /**
+ * Called after booting to process and remove recovery-related files.
+ * @return the log file from recovery, or null if none was found.
+ */
+ public static String handleAftermath() {
+ // Record the tail of the LOG_FILE
+ String log = null;
+ try {
+ log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "No recovery log file");
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading recovery log", e);
+ }
+
+ // Delete everything in RECOVERY_DIR
+ String[] names = RECOVERY_DIR.list();
+ for (int i = 0; names != null && i < names.length; i++) {
+ File f = new File(RECOVERY_DIR, names[i]);
+ if (!f.delete()) {
+ Log.e(TAG, "Can't delete: " + f);
+ } else {
+ Log.i(TAG, "Deleted: " + f);
+ }
+ }
+
+ return log;
+ }
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index f21b62f..ac8b589 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -19,6 +19,7 @@ package com.android.internal.os;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.os.Debug;
@@ -335,32 +336,18 @@ public class ZygoteInit {
mResources.startPreloading();
if (PRELOAD_RESOURCES) {
Log.i(TAG, "Preloading resources...");
+
long startTime = SystemClock.uptimeMillis();
TypedArray ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_drawables);
- int N = ar.length();
- for (int i=0; i<N; i++) {
- if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
- if (Config.LOGV) {
- Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
- }
- runtime.gcSoftReferences();
- runtime.runFinalizationSync();
- Debug.resetGlobalAllocSize();
- }
- int id = ar.getResourceId(i, 0);
- if (Config.LOGV) {
- Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
- }
- if (id != 0) {
- Drawable dr = mResources.getDrawable(id);
- if ((dr.getChangingConfigurations()&~ActivityInfo.CONFIG_FONT_SCALE) != 0) {
- Log.w(TAG, "Preloaded drawable resource #0x"
- + Integer.toHexString(id)
- + " that varies with configuration!!");
- }
- }
- }
+ int N = preloadDrawables(runtime, ar);
+ Log.i(TAG, "...preloaded " + N + " resources in "
+ + (SystemClock.uptimeMillis()-startTime) + "ms.");
+
+ startTime = SystemClock.uptimeMillis();
+ ar = mResources.obtainTypedArray(
+ com.android.internal.R.array.preloaded_color_state_lists);
+ N = preloadColorStateLists(runtime, ar);
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
}
@@ -372,6 +359,56 @@ public class ZygoteInit {
}
}
+ private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) {
+ int N = ar.length();
+ for (int i=0; i<N; i++) {
+ if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
+ if (Config.LOGV) {
+ Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
+ }
+ runtime.gcSoftReferences();
+ runtime.runFinalizationSync();
+ Debug.resetGlobalAllocSize();
+ }
+ int id = ar.getResourceId(i, 0);
+ if (Config.LOGV) {
+ Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
+ }
+ if (id != 0) {
+ mResources.getColorStateList(id);
+ }
+ }
+ return N;
+ }
+
+
+ private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
+ int N = ar.length();
+ for (int i=0; i<N; i++) {
+ if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
+ if (Config.LOGV) {
+ Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
+ }
+ runtime.gcSoftReferences();
+ runtime.runFinalizationSync();
+ Debug.resetGlobalAllocSize();
+ }
+ int id = ar.getResourceId(i, 0);
+ if (Config.LOGV) {
+ Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
+ }
+ if (id != 0) {
+ Drawable dr = mResources.getDrawable(id);
+ if ((dr.getChangingConfigurations()&~ActivityInfo.CONFIG_FONT_SCALE) != 0) {
+ Log.w(TAG, "Preloaded drawable resource #0x"
+ + Integer.toHexString(id)
+ + " (" + ar.getString(i) + ") that varies with configuration!!");
+ }
+ }
+ }
+ return N;
+ }
+
/**
* Runs several special GCs to try to clean up a few generations of
* softly- and final-reachable objects, along with any other garbage.
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 011e944..106392d 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -1,7 +1,5 @@
package com.android.internal.view;
-import com.android.internal.view.IInputContext;
-
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -13,6 +11,8 @@ import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
+import java.lang.ref.WeakReference;
+
public class IInputConnectionWrapper extends IInputContext.Stub {
static final String TAG = "IInputConnectionWrapper";
@@ -22,18 +22,20 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
private static final int DO_GET_EXTRACTED_TEXT = 40;
private static final int DO_COMMIT_TEXT = 50;
private static final int DO_COMMIT_COMPLETION = 55;
+ private static final int DO_SET_SELECTION = 57;
+ private static final int DO_PERFORM_EDITOR_ACTION = 58;
+ private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59;
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_BEGIN_BATCH_EDIT = 90;
private static final int DO_END_BATCH_EDIT = 95;
- private static final int DO_HIDE_STATUS_ICON = 100;
- private static final int DO_SHOW_STATUS_ICON = 110;
+ private static final int DO_REPORT_FULLSCREEN_MODE = 100;
private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
private static final int DO_CLEAR_META_KEY_STATES = 130;
- private InputConnection mInputConnection;
+ private WeakReference<InputConnection> mInputConnection;
private Looper mMainLooper;
private Handler mH;
@@ -57,17 +59,21 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
}
public IInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
- mInputConnection = conn;
+ mInputConnection = new WeakReference<InputConnection>(conn);
mMainLooper = mainLooper;
mH = new MyHandler(mMainLooper);
}
- public void getTextAfterCursor(int length, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageISC(DO_GET_TEXT_AFTER_CURSOR, length, seq, callback));
+ public boolean isActive() {
+ return true;
+ }
+
+ public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
+ dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
}
- public void getTextBeforeCursor(int length, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageISC(DO_GET_TEXT_BEFORE_CURSOR, length, seq, callback));
+ public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {
+ dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
}
public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
@@ -88,6 +94,18 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
dispatchMessage(obtainMessageO(DO_COMMIT_COMPLETION, text));
}
+ public void setSelection(int start, int end) {
+ dispatchMessage(obtainMessageII(DO_SET_SELECTION, start, end));
+ }
+
+ public void performEditorAction(int id) {
+ dispatchMessage(obtainMessageII(DO_PERFORM_EDITOR_ACTION, id, 0));
+ }
+
+ public void performContextMenuAction(int id) {
+ dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0));
+ }
+
public void setComposingText(CharSequence text, int newCursorPosition) {
dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text));
}
@@ -117,12 +135,8 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
dispatchMessage(obtainMessage(DO_END_BATCH_EDIT));
}
- public void hideStatusIcon() {
- dispatchMessage(obtainMessage(DO_HIDE_STATUS_ICON));
- }
-
- public void showStatusIcon(String packageName, int resId) {
- dispatchMessage(obtainMessageIO(DO_SHOW_STATUS_ICON, resId, packageName));
+ public void reportFullscreenMode(boolean enabled) {
+ dispatchMessage(obtainMessageII(DO_REPORT_FULLSCREEN_MODE, enabled ? 1 : 0, 0));
}
public void performPrivateCommand(String action, Bundle data) {
@@ -147,8 +161,14 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
case DO_GET_TEXT_AFTER_CURSOR: {
SomeArgs args = (SomeArgs)msg.obj;
try {
- args.callback.setTextAfterCursor(mInputConnection.getTextAfterCursor(msg.arg1),
- args.seq);
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
+ args.callback.setTextAfterCursor(null, args.seq);
+ return;
+ }
+ args.callback.setTextAfterCursor(ic.getTextAfterCursor(
+ msg.arg1, msg.arg2), args.seq);
} catch (RemoteException e) {
Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
}
@@ -157,8 +177,14 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
case DO_GET_TEXT_BEFORE_CURSOR: {
SomeArgs args = (SomeArgs)msg.obj;
try {
- args.callback.setTextBeforeCursor(mInputConnection.getTextBeforeCursor(msg.arg1),
- args.seq);
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
+ args.callback.setTextBeforeCursor(null, args.seq);
+ return;
+ }
+ args.callback.setTextBeforeCursor(ic.getTextBeforeCursor(
+ msg.arg1, msg.arg2), args.seq);
} catch (RemoteException e) {
Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
}
@@ -167,7 +193,13 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
case DO_GET_CURSOR_CAPS_MODE: {
SomeArgs args = (SomeArgs)msg.obj;
try {
- args.callback.setCursorCapsMode(mInputConnection.getCursorCapsMode(msg.arg1),
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
+ args.callback.setCursorCapsMode(0, args.seq);
+ return;
+ }
+ args.callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1),
args.seq);
} catch (RemoteException e) {
Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
@@ -177,7 +209,13 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
case DO_GET_EXTRACTED_TEXT: {
SomeArgs args = (SomeArgs)msg.obj;
try {
- args.callback.setExtractedText(mInputConnection.getExtractedText(
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getExtractedText on inactive InputConnection");
+ args.callback.setExtractedText(null, args.seq);
+ return;
+ }
+ args.callback.setExtractedText(ic.getExtractedText(
(ExtractedTextRequest)args.arg1, msg.arg1), args.seq);
} catch (RemoteException e) {
Log.w(TAG, "Got RemoteException calling setExtractedText", e);
@@ -185,52 +223,134 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
return;
}
case DO_COMMIT_TEXT: {
- mInputConnection.commitText((CharSequence)msg.obj, msg.arg1);
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "commitText on inactive InputConnection");
+ return;
+ }
+ ic.commitText((CharSequence)msg.obj, msg.arg1);
+ return;
+ }
+ case DO_SET_SELECTION: {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "setSelection on inactive InputConnection");
+ return;
+ }
+ ic.setSelection(msg.arg1, msg.arg2);
+ return;
+ }
+ case DO_PERFORM_EDITOR_ACTION: {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "performEditorAction on inactive InputConnection");
+ return;
+ }
+ ic.performEditorAction(msg.arg1);
+ return;
+ }
+ case DO_PERFORM_CONTEXT_MENU_ACTION: {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "performContextMenuAction on inactive InputConnection");
+ return;
+ }
+ ic.performContextMenuAction(msg.arg1);
return;
}
case DO_COMMIT_COMPLETION: {
- mInputConnection.commitCompletion((CompletionInfo)msg.obj);
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "commitCompletion on inactive InputConnection");
+ return;
+ }
+ ic.commitCompletion((CompletionInfo)msg.obj);
return;
}
case DO_SET_COMPOSING_TEXT: {
- mInputConnection.setComposingText((CharSequence)msg.obj, msg.arg1);
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "setComposingText on inactive InputConnection");
+ return;
+ }
+ ic.setComposingText((CharSequence)msg.obj, msg.arg1);
return;
}
case DO_FINISH_COMPOSING_TEXT: {
- mInputConnection.finishComposingText();
+ InputConnection ic = mInputConnection.get();
+ // Note we do NOT check isActive() here, because this is safe
+ // for an IME to call at any time, and we need to allow it
+ // through to clean up our state after the IME has switched to
+ // another client.
+ if (ic == null) {
+ Log.w(TAG, "finishComposingText on inactive InputConnection");
+ return;
+ }
+ ic.finishComposingText();
return;
}
case DO_SEND_KEY_EVENT: {
- mInputConnection.sendKeyEvent((KeyEvent)msg.obj);
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "sendKeyEvent on inactive InputConnection");
+ return;
+ }
+ ic.sendKeyEvent((KeyEvent)msg.obj);
return;
}
case DO_CLEAR_META_KEY_STATES: {
- mInputConnection.clearMetaKeyStates(msg.arg1);
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
+ return;
+ }
+ ic.clearMetaKeyStates(msg.arg1);
return;
}
case DO_DELETE_SURROUNDING_TEXT: {
- mInputConnection.deleteSurroundingText(msg.arg1, msg.arg2);
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
+ return;
+ }
+ ic.deleteSurroundingText(msg.arg1, msg.arg2);
return;
}
case DO_BEGIN_BATCH_EDIT: {
- mInputConnection.beginBatchEdit();
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "beginBatchEdit on inactive InputConnection");
+ return;
+ }
+ ic.beginBatchEdit();
return;
}
case DO_END_BATCH_EDIT: {
- mInputConnection.beginBatchEdit();
- return;
- }
- case DO_HIDE_STATUS_ICON: {
- mInputConnection.hideStatusIcon();
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "endBatchEdit on inactive InputConnection");
+ return;
+ }
+ ic.endBatchEdit();
return;
}
- case DO_SHOW_STATUS_ICON: {
- mInputConnection.showStatusIcon((String)msg.obj, msg.arg1);
+ case DO_REPORT_FULLSCREEN_MODE: {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "showStatusIcon on inactive InputConnection");
+ return;
+ }
+ ic.reportFullscreenMode(msg.arg1 == 1);
return;
}
case DO_PERFORM_PRIVATE_COMMAND: {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "performPrivateCommand on inactive InputConnection");
+ return;
+ }
SomeArgs args = (SomeArgs)msg.obj;
- mInputConnection.performPrivateCommand((String)args.arg1,
+ ic.performPrivateCommand((String)args.arg1,
(Bundle)args.arg2);
return;
}
@@ -257,6 +377,13 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
return mH.obtainMessage(what, arg1, 0, args);
}
+ Message obtainMessageIISC(int what, int arg1, int arg2, int seq, IInputContextCallback callback) {
+ SomeArgs args = new SomeArgs();
+ args.callback = callback;
+ args.seq = seq;
+ return mH.obtainMessage(what, arg1, arg2, args);
+ }
+
Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq,
IInputContextCallback callback) {
SomeArgs args = new SomeArgs();
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index b048ce2..02cb9e4 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -29,9 +29,9 @@ import com.android.internal.view.IInputContextCallback;
* {@hide}
*/
oneway interface IInputContext {
- void getTextBeforeCursor(int length, int seq, IInputContextCallback callback);
+ void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback);
- void getTextAfterCursor(int length, int seq, IInputContextCallback callback);
+ void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback);
void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback);
@@ -48,17 +48,21 @@ import com.android.internal.view.IInputContextCallback;
void commitCompletion(in CompletionInfo completion);
+ void setSelection(int start, int end);
+
+ void performEditorAction(int actionCode);
+
+ void performContextMenuAction(int id);
+
void beginBatchEdit();
void endBatchEdit();
+ void reportFullscreenMode(boolean enabled);
+
void sendKeyEvent(in KeyEvent event);
void clearMetaKeyStates(int states);
void performPrivateCommand(String action, in Bundle data);
-
- void showStatusIcon(String packageName, int resId);
-
- void hideStatusIcon();
}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index f650713..8ff18ed 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -18,6 +18,7 @@ package com.android.internal.view;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.ResultReceiver;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
@@ -38,9 +39,9 @@ oneway interface IInputMethod {
void unbindInput();
- void startInput(in EditorInfo attribute);
+ void startInput(in IInputContext inputContext, in EditorInfo attribute);
- void restartInput(in EditorInfo attribute);
+ void restartInput(in IInputContext inputContext, in EditorInfo attribute);
void createSession(IInputMethodCallback callback);
@@ -48,7 +49,7 @@ oneway interface IInputMethod {
void revokeSession(IInputMethodSession session);
- void showSoftInput(boolean explicit);
+ void showSoftInput(int flags, in ResultReceiver resultReceiver);
- void hideSoftInput();
+ void hideSoftInput(int flags, in ResultReceiver resultReceiver);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 2a15bdb..adec0a7 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -16,6 +16,7 @@
package com.android.internal.view;
+import android.os.ResultReceiver;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.EditorInfo;
import com.android.internal.view.InputBindResult;
@@ -35,18 +36,22 @@ interface IInputMethodManager {
void removeClient(in IInputMethodClient client);
InputBindResult startInput(in IInputMethodClient client,
- in EditorInfo attribute, boolean initial, boolean needResult);
+ IInputContext inputContext, in EditorInfo attribute,
+ boolean initial, boolean needResult);
void finishInput(in IInputMethodClient client);
- void showSoftInput(in IInputMethodClient client, int flags);
- void hideSoftInput(in IInputMethodClient client, int flags);
- void windowGainedFocus(in IInputMethodClient client,
+ boolean showSoftInput(in IInputMethodClient client, int flags,
+ in ResultReceiver resultReceiver);
+ boolean hideSoftInput(in IInputMethodClient client, int flags,
+ in ResultReceiver resultReceiver);
+ void windowGainedFocus(in IInputMethodClient client, in IBinder windowToken,
boolean viewHasFocus, boolean isTextEditor,
int softInputMode, boolean first, int windowFlags);
void showInputMethodPickerFromClient(in IInputMethodClient client);
void setInputMethod(in IBinder token, String id);
void hideMySoftInput(in IBinder token, int flags);
- void updateStatusIcon(int iconId, String iconPackage);
+ void showMySoftInput(in IBinder token, int flags);
+ void updateStatusIcon(in IBinder token, String packageName, int iconId);
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 8a44976..a05ff14 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -46,4 +46,6 @@ oneway interface IInputMethodSession {
void dispatchTrackballEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
void appPrivateCommand(String action, in Bundle data);
+
+ void toggleSoftInput(int showFlags, int hideFlags);
}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index a9ba5f6..b92cb45 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -151,11 +151,11 @@ public class InputConnectionWrapper implements InputConnection {
mIInputContext = inputContext;
}
- public CharSequence getTextAfterCursor(int length) {
+ public CharSequence getTextAfterCursor(int length, int flags) {
CharSequence value = null;
try {
InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getTextAfterCursor(length, callback.mSeq, callback);
+ mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
synchronized (callback) {
callback.waitForResultLocked();
if (callback.mHaveValue) {
@@ -169,11 +169,11 @@ public class InputConnectionWrapper implements InputConnection {
return value;
}
- public CharSequence getTextBeforeCursor(int length) {
+ public CharSequence getTextBeforeCursor(int length, int flags) {
CharSequence value = null;
try {
InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getTextBeforeCursor(length, callback.mSeq, callback);
+ mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
synchronized (callback) {
callback.waitForResultLocked();
if (callback.mHaveValue) {
@@ -241,6 +241,33 @@ public class InputConnectionWrapper implements InputConnection {
}
}
+ public boolean setSelection(int start, int end) {
+ try {
+ mIInputContext.setSelection(start, end);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean performEditorAction(int actionCode) {
+ try {
+ mIInputContext.performEditorAction(actionCode);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean performContextMenuAction(int id) {
+ try {
+ mIInputContext.performContextMenuAction(id);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
public boolean setComposingText(CharSequence text, int newCursorPosition) {
try {
mIInputContext.setComposingText(text, newCursorPosition);
@@ -304,18 +331,9 @@ public class InputConnectionWrapper implements InputConnection {
}
}
- public boolean hideStatusIcon() {
- try {
- mIInputContext.showStatusIcon(null, 0);
- return true;
- } catch (RemoteException e) {
- return false;
- }
- }
-
- public boolean showStatusIcon(String packageName, int resId) {
+ public boolean reportFullscreenMode(boolean enabled) {
try {
- mIInputContext.showStatusIcon(packageName, resId);
+ mIInputContext.reportFullscreenMode(enabled);
return true;
} catch (RemoteException e) {
return false;
diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
index c16c165..9e4b4ce 100644
--- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -80,6 +80,11 @@ public final class ExpandedMenuView extends ListView implements ItemInvoker, Men
setChildrenDrawingCacheEnabled(false);
}
+ @Override
+ protected boolean recycleOnMeasure() {
+ return false;
+ }
+
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 558a4c3..9e1f2ae 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -142,9 +142,6 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
}
void setCaptionMode(boolean shortcut) {
-
- mShortcutCaptionMode = shortcut && (mItemData.shouldShowShortcut());
-
/*
* If there is no item model, don't do any of the below (for example,
* the 'More' item doesn't have a model)
@@ -153,6 +150,8 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
return;
}
+ mShortcutCaptionMode = shortcut && (mItemData.shouldShowShortcut());
+
CharSequence text = mItemData.getTitleForItemView(this);
if (mShortcutCaptionMode) {
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 32513cd..e155875 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -220,7 +220,7 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
mRadioButton =
(RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio,
this, false);
- addView(mRadioButton, 0);
+ addView(mRadioButton);
}
private void insertCheckBox() {
@@ -228,7 +228,7 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
mCheckBox =
(CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox,
this, false);
- addView(mCheckBox, 0);
+ addView(mCheckBox);
}
public boolean prefersCondensedTitle() {
diff --git a/core/java/com/android/internal/widget/EditStyledText.java b/core/java/com/android/internal/widget/EditStyledText.java
new file mode 100644
index 0000000..8a4675a
--- /dev/null
+++ b/core/java/com/android/internal/widget/EditStyledText.java
@@ -0,0 +1,931 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ImageSpan;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.widget.EditText;
+
+/**
+ * EditStyledText extends EditText for managing the flow and status to edit
+ * the styled text. This manages the states and flows of editing, supports
+ * inserting image, import/export HTML.
+ */
+public class EditStyledText extends EditText {
+
+ private static final String LOG_TAG = "EditStyledText";
+ private static final boolean DBG = true;
+
+ /**
+ * The modes of editing actions.
+ */
+ /** The mode that no editing action is done. */
+ public static final int MODE_NOTHING = 0;
+ /** The mode of copy. */
+ public static final int MODE_COPY = 1;
+ /** The mode of paste. */
+ public static final int MODE_PASTE = 2;
+ /** The mode of changing size. */
+ public static final int MODE_SIZE = 3;
+ /** The mode of changing color. */
+ public static final int MODE_COLOR = 4;
+ /** The mode of selection. */
+ public static final int MODE_SELECT = 5;
+
+ /**
+ * The state of selection.
+ */
+ /** The state that selection isn't started. */
+ public static final int STATE_SELECT_OFF = 0;
+ /** The state that selection is started. */
+ public static final int STATE_SELECT_ON = 1;
+ /** The state that selection is done, but not fixed. */
+ public static final int STATE_SELECTED = 2;
+ /** The state that selection is done and not fixed. */
+ public static final int STATE_SELECT_FIX = 3;
+
+ /**
+ * The help message strings.
+ */
+ public static final int HINT_MSG_NULL = 0;
+ public static final int HINT_MSG_COPY_BUF_BLANK = 1;
+ public static final int HINT_MSG_SELECT_START = 2;
+ public static final int HINT_MSG_SELECT_END = 3;
+ public static final int HINT_MSG_PUSH_COMPETE = 4;
+
+ /**
+ * EditStyledTextInterface provides functions for notifying messages to
+ * calling class.
+ */
+ public interface EditStyledTextNotifier {
+ public void notifyHintMsg(int msgId);
+ }
+
+ private EditStyledTextNotifier mESTInterface;
+
+ /**
+ * EditStyledTextEditorManager manages the flow and status of each
+ * function for editing styled text.
+ */
+ private EditorManager mManager;
+ private StyledTextConverter mConverter;
+ private StyledTextToast mToast;
+
+ /**
+ * EditStyledText extends EditText for managing flow of each editing
+ * action.
+ */
+ public EditStyledText(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ public EditStyledText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public EditStyledText(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Set Notifier.
+ */
+ public void setNotifier(EditStyledTextNotifier estInterface) {
+ mESTInterface = estInterface;
+ }
+
+ /**
+ * Set Builder for AlertDialog.
+ *
+ * @param builder
+ * Builder for opening Alert Dialog.
+ */
+ public void setBuilder(Builder builder) {
+ mToast.setBuilder(builder);
+ }
+
+ /**
+ * Set Parameters for ColorAlertDialog.
+ *
+ * @param colortitle
+ * Title for Alert Dialog.
+ * @param colornames
+ * List of name of selecting color.
+ * @param colorints
+ * List of int of color.
+ */
+ public void setColorAlertParams(CharSequence colortitle,
+ CharSequence[] colornames, CharSequence[] colorints) {
+ mToast.setColorAlertParams(colortitle, colornames, colorints);
+ }
+
+ /**
+ * Set Parameters for SizeAlertDialog.
+ *
+ * @param sizetitle
+ * Title for Alert Dialog.
+ * @param sizenames
+ * List of name of selecting size.
+ * @param sizedisplayints
+ * List of int of size displayed in TextView.
+ * @param sizesendints
+ * List of int of size exported to HTML.
+ */
+ public void setSizeAlertParams(CharSequence sizetitle,
+ CharSequence[] sizenames, CharSequence[] sizedisplayints,
+ CharSequence[] sizesendints) {
+ mToast.setSizeAlertParams(sizetitle, sizenames, sizedisplayints,
+ sizesendints);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final boolean superResult = super.onTouchEvent(event);
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onTouchEvent");
+ }
+ mManager.onTouchScreen();
+ }
+ return superResult;
+ }
+
+ /**
+ * Start editing. This function have to be called before other editing
+ * actions.
+ */
+ public void onStartEdit() {
+ mManager.onStartEdit();
+ }
+
+ /**
+ * End editing.
+ */
+ public void onEndEdit() {
+ mManager.onEndEdit();
+ }
+
+ /**
+ * Start "Copy" action.
+ */
+ public void onStartCopy() {
+ mManager.onStartCopy();
+ }
+
+ /**
+ * Start "Paste" action.
+ */
+ public void onStartPaste() {
+ mManager.onStartPaste();
+ }
+
+ /**
+ * Start changing "Size" action.
+ */
+ public void onStartSize() {
+ mManager.onStartSize();
+ }
+
+ /**
+ * Start changing "Color" action.
+ */
+ public void onStartColor() {
+ mManager.onStartColor();
+ }
+
+ /**
+ * Start "Select" action.
+ */
+ public void onStartSelect() {
+ mManager.onStartSelect();
+ }
+
+ /**
+ * Start "SelectAll" action.
+ */
+ public void onStartSelectAll() {
+ mManager.onStartSelectAll();
+ }
+
+ /**
+ * InsertImage to TextView by using URI
+ *
+ * @param uri
+ * URI of the iamge inserted to TextView.
+ */
+ public void onInsertImage(Uri uri) {
+ mManager.onInsertImage(uri);
+ }
+
+ /**
+ * InsertImage to TextView by using resource ID
+ *
+ * @param resId
+ * Resource ID of the iamge inserted to TextView.
+ */
+ public void onInsertImage(int resId) {
+ mManager.onInsertImage(resId);
+ }
+
+ /**
+ * Fix Selected Item.
+ */
+ public void fixSelectedItem() {
+ mManager.onFixSelectItem();
+ }
+
+ /**
+ * Set Size of the Item.
+ *
+ * @param size
+ * The size of the Item.
+ */
+ public void setItemSize(int size) {
+ mManager.setItemSize(size);
+ }
+
+ /**
+ * Set Color of the Item.
+ *
+ * @param color
+ * The color of the Item.
+ */
+ public void setItemColor(int color) {
+ mManager.setItemColor(color);
+ }
+
+ public void onShowColorAlert() {
+ mToast.onShowColorAlertDialog();
+ }
+
+ public void onShowSizeAlert() {
+ mToast.onShowSizeAlertDialog();
+ }
+
+ /**
+ * Check editing is started.
+ *
+ * @return Whether editing is started or not.
+ */
+ public boolean isEditting() {
+ return mManager.isEditting();
+ }
+
+ /**
+ * Get the mode of the action.
+ *
+ * @return The mode of the action.
+ */
+ public int getEditMode() {
+ return mManager.getEditMode();
+ }
+
+ /**
+ * Get the state of the selection.
+ *
+ * @return The state of the selection.
+ */
+ public int getSelectState() {
+ return mManager.getSelectState();
+ }
+
+ public String getBody() {
+ return mConverter.getConvertedBody();
+ }
+
+ /**
+ * Initialize members.
+ */
+ private void init() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- init");
+ requestFocus();
+ }
+ mManager = new EditorManager(this);
+ mConverter = new StyledTextConverter(this);
+ mToast = new StyledTextToast(this);
+ }
+
+ /**
+ * Notify hint messages what action is expected to calling class.
+ *
+ * @param msgId
+ * Id of the hint message.
+ */
+ private void setHintMessage(int msgId) {
+ if (mESTInterface != null) {
+ mESTInterface.notifyHintMsg(msgId);
+ }
+ }
+
+ @Override
+ public Bundle getInputExtras(boolean create) {
+ Bundle bundle = super.getInputExtras(create);
+ if (bundle != null) {
+ bundle.putBoolean("allowEmoji", true);
+ }
+ return bundle;
+ }
+
+ /**
+ * Object which manages the flow and status of editing actions.
+ */
+ private class EditorManager {
+ private boolean mEditFlag = false;
+ private int mMode = 0;
+ private int mState = 0;
+ private int mCurStart = 0;
+ private int mCurEnd = 0;
+ private EditStyledText mEST;
+ private Editable mTextSelectBuffer;
+ private CharSequence mTextCopyBufer;
+
+ EditorManager(EditStyledText est) {
+ mEST = est;
+ }
+
+ public void onStartEdit() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onEdit");
+ }
+ handleResetEdit();
+ }
+
+ public void onEndEdit() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickCancel");
+ }
+ handleCancel();
+ }
+
+ public void onStartCopy() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickCopy");
+ }
+ handleCopy();
+ }
+
+ public void onStartPaste() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickPaste");
+ }
+ handlePaste();
+ }
+
+ public void onStartSize() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickSize");
+ }
+ handleSize();
+ }
+
+ public void setItemSize(int size) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickSizeItem");
+ }
+ if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
+ changeSizeSelectedText(size);
+ handleResetEdit();
+ }
+ }
+
+ public void setItemColor(int color) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickColorItem");
+ }
+ if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
+ changeColorSelectedText(color);
+ handleResetEdit();
+ }
+ }
+
+ public void onStartColor() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickColor");
+ }
+ handleColor();
+ }
+
+ public void onStartSelect() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickSelect");
+ }
+ mMode = MODE_SELECT;
+ if (mState == STATE_SELECT_OFF) {
+ handleSelect();
+ } else {
+ offSelect();
+ handleSelect();
+ }
+ }
+
+ public void onStartSelectAll() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickSelectAll");
+ }
+ handleSelectAll();
+ }
+
+ public void onTouchScreen() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickView");
+ }
+ if (mState == STATE_SELECT_ON || mState == STATE_SELECTED) {
+ handleSelect();
+ }
+ }
+
+ public void onFixSelectItem() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickComplete");
+ }
+ handleComplete();
+ }
+
+ public void onInsertImage(Uri uri) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onInsertImage by URI: " + uri.getPath()
+ + "," + uri.toString());
+ }
+
+ mEST.getText().append("a");
+ mEST.getText().setSpan(new ImageSpan(mEST.getContext(), uri),
+ mEST.getText().length() - 1, mEST.getText().length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ public void onInsertImage(int resID) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onInsertImage by resID");
+ }
+ mEST.getText().append("b");
+ mEST.getText().setSpan(new ImageSpan(mEST.getContext(), resID),
+ mEST.getText().length() - 1, mEST.getText().length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ public boolean isEditting() {
+ return mEditFlag;
+ }
+
+ public int getEditMode() {
+ return mMode;
+ }
+
+ public int getSelectState() {
+ return mState;
+ }
+
+ private void handleCancel() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleCancel");
+ }
+ mMode = MODE_NOTHING;
+ mState = STATE_SELECT_OFF;
+ mEditFlag = false;
+ offSelect();
+ }
+
+ private void handleComplete() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleComplete");
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mState == STATE_SELECTED) {
+ mState = STATE_SELECT_FIX;
+ }
+ switch (mMode) {
+ case MODE_COPY:
+ handleCopy();
+ break;
+ case MODE_COLOR:
+ handleColor();
+ break;
+ case MODE_SIZE:
+ handleSize();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void handleCopy() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleCopy: " + mMode + "," + mState);
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
+ mMode = MODE_COPY;
+ if (mState == STATE_SELECTED) {
+ mState = STATE_SELECT_FIX;
+ storeSelectedText();
+ } else {
+ handleSelect();
+ }
+ } else if (mMode != MODE_COPY) {
+ handleCancel();
+ mMode = MODE_COPY;
+ handleCopy();
+ } else if (mState == STATE_SELECT_FIX) {
+ mEST.setHintMessage(HINT_MSG_NULL);
+ storeSelectedText();
+ handleResetEdit();
+ }
+ }
+
+ private void handlePaste() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handlePaste");
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mTextSelectBuffer != null && mTextCopyBufer.length() > 0) {
+ mTextSelectBuffer.insert(mEST.getSelectionStart(),
+ mTextCopyBufer);
+ } else {
+ mEST.setHintMessage(HINT_MSG_COPY_BUF_BLANK);
+ }
+ }
+
+ private void handleSize() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleSize: " + mMode + "," + mState);
+ }
+ if (!mEditFlag) {
+ Log.e(LOG_TAG, "--- Editing is not started for handlesize.");
+ return;
+ }
+ if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
+ mMode = MODE_SIZE;
+ if (mState == STATE_SELECTED) {
+ mState = STATE_SELECT_FIX;
+ handleSize();
+ } else {
+ handleSelect();
+ }
+ } else if (mMode != MODE_SIZE) {
+ handleCancel();
+ mMode = MODE_SIZE;
+ handleSize();
+ } else {
+ if (mState == STATE_SELECT_FIX) {
+ mEST.setHintMessage(HINT_MSG_NULL);
+ mEST.onShowSizeAlert();
+ } else {
+ Log.d(LOG_TAG, "--- handlesize: do nothing");
+ }
+ }
+ }
+
+ private void handleColor() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleSize: " + mMode + "," + mState);
+ }
+ if (!mEditFlag) {
+ Log.e(LOG_TAG, "--- Editing is not started for handlecolor.");
+ return;
+ }
+ if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
+ mMode = MODE_COLOR;
+ if (mState == STATE_SELECTED) {
+ mState = STATE_SELECT_FIX;
+ handleColor();
+ } else {
+ handleSelect();
+ }
+ } else if (mMode != MODE_COLOR) {
+ handleCancel();
+ mMode = MODE_COLOR;
+ handleSize();
+ } else {
+ if (mState == STATE_SELECT_FIX) {
+ mEST.setHintMessage(HINT_MSG_NULL);
+ mEST.onShowColorAlert();
+ } else {
+ Log.d(LOG_TAG, "--- handlecolor: do nothing");
+ }
+ }
+ }
+
+ private void handleSelect() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleSelect:" + mEditFlag + "," + mState);
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mState == STATE_SELECT_OFF) {
+ if (isTextSelected()) {
+ Log.e(LOG_TAG, "Selection is off, but selected");
+ }
+ setSelectStartPos();
+ mEST.setHintMessage(HINT_MSG_SELECT_END);
+ } else if (mState == STATE_SELECT_ON) {
+ if (isTextSelected()) {
+ Log.e(LOG_TAG, "Selection now start, but selected");
+ }
+ setSelectEndPos();
+ mEST.setHintMessage(HINT_MSG_PUSH_COMPETE);
+ doNextHandle();
+ } else if (mState == STATE_SELECTED) {
+ if (!isTextSelected()) {
+ Log.e(LOG_TAG, "Selection is done, but not selected");
+ }
+ setSelectEndPos();
+ doNextHandle();
+ }
+ }
+
+ private void handleSelectAll() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleSelectAll");
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ mEST.selectAll();
+ }
+
+ private void doNextHandle() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- doNextHandle: " + mMode + "," + mState);
+ }
+ switch (mMode) {
+ case MODE_COPY:
+ handleCopy();
+ break;
+ case MODE_PASTE:
+ handlePaste();
+ break;
+ case MODE_SIZE:
+ handleSize();
+ break;
+ case MODE_COLOR:
+ handleColor();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void handleResetEdit() {
+ if (DBG) {
+ Log.d(LOG_TAG, "Reset Editor");
+ }
+ handleCancel();
+ mEditFlag = true;
+ mEST.setHintMessage(HINT_MSG_SELECT_START);
+ }
+
+ // Methods of selection
+ private void onSelect() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onSelect:" + mCurStart + "," + mCurEnd);
+ }
+ if (mCurStart >= 0 && mCurStart <= mEST.getText().length()
+ && mCurEnd >= 0 && mCurEnd <= mEST.getText().length()) {
+ mEST.setSelection(mCurStart, mCurEnd);
+ mState = STATE_SELECTED;
+ } else {
+ Log.e(LOG_TAG,
+ "Select is on, but cursor positions are illigal.:"
+ + mEST.getText().length() + "," + mCurStart
+ + "," + mCurEnd);
+ }
+ }
+
+ private void offSelect() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- offSelect");
+ }
+ int currpos = mEST.getSelectionStart();
+ mEST.setSelection(currpos, currpos);
+ mState = STATE_SELECT_OFF;
+ }
+
+ private void setSelectStartPos() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- setSelectStartPos");
+ }
+ mCurStart = mEST.getSelectionStart();
+ mState = STATE_SELECT_ON;
+ }
+
+ private void setSelectEndPos() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- setSelectEndPos:"
+ + mEST.getSelectionStart());
+ }
+ int curpos = mEST.getSelectionStart();
+ if (curpos < mCurStart) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- setSelectEndPos: swap is done.");
+ }
+ mCurEnd = mCurStart;
+ mCurStart = curpos;
+ } else {
+ mCurEnd = curpos;
+ }
+ onSelect();
+ }
+
+ private boolean isTextSelected() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- isTextSelected:" + mCurStart + ","
+ + mCurEnd);
+ }
+ return (mCurStart != mCurEnd)
+ && (mState == STATE_SELECTED ||
+ mState == STATE_SELECT_FIX);
+ }
+
+ private void storeSelectedText() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- storeSelectedText");
+ }
+ mTextSelectBuffer = mEST.getText();
+ mTextCopyBufer = mTextSelectBuffer.subSequence(mCurStart, mCurEnd);
+ }
+
+ private void changeSizeSelectedText(int size) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- changeSizeSelectedText:" + size + ","
+ + mCurStart + "," + mCurEnd);
+ }
+ mEST.getText().setSpan(new AbsoluteSizeSpan(size), mCurStart,
+ mCurEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ private void changeColorSelectedText(int color) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- changeCollorSelectedText:" + color + ","
+ + mCurStart + "," + mCurEnd);
+ }
+ mEST.getText().setSpan(new ForegroundColorSpan(color), mCurStart,
+ mCurEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+
+ private class StyledTextConverter {
+ private EditStyledText mEST;
+
+ public StyledTextConverter(EditStyledText est) {
+ mEST = est;
+ }
+
+ public String getConvertedBody() {
+ String htmlBody = Html.toHtml(mEST.getText());
+ return htmlBody;
+ }
+ }
+
+ private class StyledTextToast {
+ Builder mBuilder;
+ CharSequence mColorTitle;
+ CharSequence mSizeTitle;
+ CharSequence[] mColorNames;
+ CharSequence[] mColorInts;
+ CharSequence[] mSizeNames;
+ CharSequence[] mSizeDisplayInts;
+ CharSequence[] mSizeSendInts;
+ EditStyledText mEST;
+
+ public StyledTextToast(EditStyledText est) {
+ mEST = est;
+ }
+
+ public void setBuilder(Builder builder) {
+ mBuilder = builder;
+ }
+
+ public void setColorAlertParams(CharSequence colortitle,
+ CharSequence[] colornames, CharSequence[] colorints) {
+ mColorTitle = colortitle;
+ mColorNames = colornames;
+ mColorInts = colorints;
+ }
+
+ public void setSizeAlertParams(CharSequence sizetitle,
+ CharSequence[] sizenames, CharSequence[] sizedisplayints,
+ CharSequence[] sizesendints) {
+ mSizeTitle = sizetitle;
+ mSizeNames = sizenames;
+ mSizeDisplayInts = sizedisplayints;
+ mSizeSendInts = sizesendints;
+ }
+
+ public boolean checkColorAlertParams() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- checkParams");
+ }
+ if (mBuilder == null) {
+ Log.e(LOG_TAG, "--- builder is null.");
+ return false;
+ } else if (mColorTitle == null || mColorNames == null
+ || mColorInts == null) {
+ Log.e(LOG_TAG, "--- color alert params are null.");
+ return false;
+ } else if (mColorNames.length != mColorInts.length) {
+ Log.e(LOG_TAG, "--- the length of color alert params are "
+ + "different.");
+ return false;
+ }
+ return true;
+ }
+
+ public boolean checkSizeAlertParams() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- checkParams");
+ }
+ if (mBuilder == null) {
+ Log.e(LOG_TAG, "--- builder is null.");
+ } else if (mSizeTitle == null || mSizeNames == null
+ || mSizeDisplayInts == null || mSizeSendInts == null) {
+ Log.e(LOG_TAG, "--- size alert params are null.");
+ } else if (mSizeNames.length != mSizeDisplayInts.length
+ && mSizeSendInts.length != mSizeDisplayInts.length) {
+ Log.e(LOG_TAG, "--- the length of size alert params are "
+ + "different.");
+ }
+ return true;
+ }
+
+ private void onShowColorAlertDialog() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onShowAlertDialog");
+ }
+ if (!checkColorAlertParams()) {
+ return;
+ }
+ mBuilder.setTitle(mColorTitle);
+ mBuilder.setIcon(0);
+ mBuilder.
+ setItems(mColorNames,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d("EETVM", "mBuilder.onclick:" + which);
+ int color = Integer.parseInt(
+ (String) mColorInts[which], 16) - 0x01000000;
+ mEST.setItemColor(color);
+ }
+ });
+ mBuilder.show();
+ }
+
+ private void onShowSizeAlertDialog() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onShowAlertDialog");
+ }
+ if (!checkColorAlertParams()) {
+ return;
+ }
+ mBuilder.setTitle(mSizeTitle);
+ mBuilder.setIcon(0);
+ mBuilder.
+ setItems(mSizeNames,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ Log.d("EETVM", "mBuilder.onclick:" + which);
+ int size = Integer
+ .parseInt((String) mSizeDisplayInts[which]);
+ mEST.setItemSize(size);
+ }
+ });
+ mBuilder.show();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index a2673a5..f2ec064 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -16,175 +16,87 @@
package com.android.internal.widget;
-import android.content.res.TypedArray;
import android.os.Bundle;
-import android.os.Handler;
import android.text.Editable;
-import android.text.Selection;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
import android.text.method.KeyListener;
import android.util.Log;
-import android.util.LogPrinter;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.widget.TextView;
-class ComposingText {
-}
-
public class EditableInputConnection extends BaseInputConnection {
private static final boolean DEBUG = false;
private static final String TAG = "EditableInputConnection";
- public static final Object COMPOSING = new ComposingText();
-
private final TextView mTextView;
- private Object[] mDefaultComposingSpans;
-
public EditableInputConnection(TextView textview) {
- super(textview);
+ super(textview, false);
mTextView = textview;
}
- 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);
- }
- }
+ public Editable getEditable() {
+ TextView tv = mTextView;
+ if (tv != null) {
+ return tv.getEditableText();
}
-
- text.setSpan(COMPOSING, 0, text.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
- }
-
- public static int getComposingSpanStart(Spannable text) {
- return text.getSpanStart(COMPOSING);
+ return null;
}
- public static int getComposingSpanEnd(Spannable text) {
- return text.getSpanEnd(COMPOSING);
+ public boolean beginBatchEdit() {
+ mTextView.beginBatchEdit();
+ return true;
}
- public boolean setComposingText(CharSequence text, int newCursorPosition) {
- if (DEBUG) Log.v(TAG, "setComposingText " + text);
- replaceText(text, newCursorPosition, true);
+ public boolean endBatchEdit() {
+ mTextView.endBatchEdit();
return true;
}
-
- public boolean finishComposingText() {
- if (DEBUG) Log.v(TAG, "finishComposingText");
+
+ public boolean clearMetaKeyStates(int states) {
final Editable content = getEditable();
- if (content != null) {
- removeComposingSpans(content);
+ if (content == null) return false;
+ KeyListener kl = mTextView.getKeyListener();
+ if (kl != null) {
+ try {
+ kl.clearMetaKeyState(mTextView, content, states);
+ } catch (AbstractMethodError e) {
+ // This is an old listener that doesn't implement the
+ // new method.
+ }
}
return true;
}
- public boolean commitText(CharSequence text, int newCursorPosition) {
- if (DEBUG) Log.v(TAG, "commitText " + text);
- replaceText(text, newCursorPosition, false);
- return true;
- }
-
public boolean commitCompletion(CompletionInfo text) {
if (DEBUG) Log.v(TAG, "commitCompletion " + text);
+ mTextView.beginBatchEdit();
mTextView.onCommitCompletion(text);
+ mTextView.endBatchEdit();
return true;
}
- public CharSequence getTextBeforeCursor(int length) {
- final Editable content = getEditable();
- if (content == null) return null;
-
- int a = Selection.getSelectionStart(content);
- int b = Selection.getSelectionEnd(content);
-
- if (a > b) {
- int tmp = a;
- a = b;
- b = tmp;
- }
-
- if (length > a) {
- length = a;
- }
-
- return content.subSequence(a - length, a);
- }
-
- public CharSequence getTextAfterCursor(int length) {
- final Editable content = getEditable();
- if (content == null) return null;
-
- int a = Selection.getSelectionStart(content);
- int b = Selection.getSelectionEnd(content);
-
- if (a > b) {
- int tmp = a;
- a = b;
- b = tmp;
- }
-
- if (b + length > content.length()) {
- length = content.length() - b;
- }
-
- return content.subSequence(b, b + length);
+ public boolean performEditorAction(int actionCode) {
+ if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
+ mTextView.onEditorAction(actionCode);
+ return true;
}
-
- public int getCursorCapsMode(int reqModes) {
- final Editable content = getEditable();
- if (content == null) return 0;
-
- int a = Selection.getSelectionStart(content);
- int b = Selection.getSelectionEnd(content);
-
- if (a > b) {
- int tmp = a;
- a = b;
- b = tmp;
- }
-
- return TextUtils.getCapsMode(content, a, reqModes);
+
+ public boolean performContextMenuAction(int id) {
+ if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
+ mTextView.beginBatchEdit();
+ mTextView.onTextContextMenuItem(id);
+ mTextView.endBatchEdit();
+ return true;
}
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
if (mTextView != null) {
ExtractedText et = new ExtractedText();
if (mTextView.extractText(request, et)) {
- if ((flags&EXTRACTED_TEXT_MONITOR) != 0) {
+ if ((flags&GET_EXTRACTED_TEXT_MONITOR) != 0) {
mTextView.setExtracting(request);
}
return et;
@@ -193,173 +105,25 @@ public class EditableInputConnection extends BaseInputConnection {
return null;
}
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength
- + " / " + rightLength);
- final Editable content = getEditable();
- if (content == null) return false;
-
- int a = Selection.getSelectionStart(content);
- int b = Selection.getSelectionEnd(content);
-
- if (a > b) {
- int tmp = a;
- a = b;
- b = tmp;
- }
-
- // ignore the composing text.
- int ca = content.getSpanStart(COMPOSING);
- int cb = content.getSpanEnd(COMPOSING);
- if (cb < ca) {
- int tmp = ca;
- ca = cb;
- cb = tmp;
- }
- if (ca != -1 && cb != -1) {
- if (ca < a) a = ca;
- if (cb > b) b = cb;
- }
-
- int deleted = 0;
-
- if (leftLength > 0) {
- int start = a - leftLength;
- if (start < 0) start = 0;
- content.delete(start, a);
- deleted = a - start;
- }
-
- if (rightLength > 0) {
- b = b - deleted;
-
- int end = b + rightLength;
- if (end > content.length()) end = content.length();
-
- content.delete(b, end);
- }
-
- return true;
- }
-
- public boolean beginBatchEdit() {
- if (mTextView == null) return false;
- mTextView.onBeginBatchEdit();
- return true;
- }
-
- public boolean endBatchEdit() {
- if (mTextView == null) return false;
- mTextView.onEndBatchEdit();
- return true;
- }
-
- public boolean clearMetaKeyStates(int states) {
- final Editable content = getEditable();
- if (content == null) return false;
- KeyListener kl = mTextView.getKeyListener();
- if (kl != null) kl.clearMetaKeyState(mTextView, content, states);
- return true;
- }
-
public boolean performPrivateCommand(String action, Bundle data) {
- if (mTextView == null) return false;
mTextView.onPrivateIMECommand(action, data);
return true;
}
-
- private Editable getEditable() {
- TextView tv = mTextView;
- if (tv != null) {
- return tv.getEditableText();
- }
- return null;
- }
-
- private void replaceText(CharSequence text, int newCursorPosition,
- boolean composing) {
- final Editable content = getEditable();
-
- // delete composing text set previously.
- int a = content.getSpanStart(COMPOSING);
- int b = content.getSpanEnd(COMPOSING);
- if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b);
-
- if (b < a) {
- int tmp = a;
- a = b;
- b = tmp;
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ if (mTextView == null) {
+ return super.commitText(text, newCursorPosition);
}
- if (a != -1 && b != -1) {
- removeComposingSpans(content);
- } else {
- a = Selection.getSelectionStart(content);
- b = Selection.getSelectionEnd(content);
- if (a >=0 && b>= 0 && a != b) {
- if (b < a) {
- int tmp = a;
- a = b;
- b = tmp;
- }
- }
- }
+ CharSequence errorBefore = mTextView.getError();
+ boolean success = super.commitText(text, newCursorPosition);
+ CharSequence errorAfter = mTextView.getError();
- if (composing) {
- Spannable sp = null;
- if (!(text instanceof Spannable)) {
- sp = new SpannableStringBuilder(text);
- text = sp;
- if (mDefaultComposingSpans == null) {
- TypedArray ta = mTextView.getContext().getTheme()
- .obtainStyledAttributes(new int[] {
- com.android.internal.R.attr.candidatesTextStyleSpans
- });
- CharSequence style = ta.getText(0);
- ta.recycle();
- if (style != null && style instanceof Spanned) {
- mDefaultComposingSpans = ((Spanned)style).getSpans(
- 0, style.length(), Object.class);
- }
- }
- if (mDefaultComposingSpans != null) {
- for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
- sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- } else {
- sp = (Spannable)text;
- }
- setComposingSpans(sp);
+ if (errorAfter != null && errorBefore == errorAfter) {
+ mTextView.setError(null, null);
}
-
- // Adjust newCursorPosition to be relative the start of the text.
- newCursorPosition += a;
- if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
- + text + "\", composing=" + composing
- + ", type=" + text.getClass().getCanonicalName());
-
- if (DEBUG) {
- LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
- lp.println("Current text:");
- TextUtils.dumpSpans(content, lp, " ");
- lp.println("Composing text:");
- TextUtils.dumpSpans(text, lp, " ");
- }
-
- content.replace(a, b, text);
- if (newCursorPosition < 0) newCursorPosition = 0;
- if (newCursorPosition > content.length())
- newCursorPosition = content.length();
- Selection.setSelection(content, newCursorPosition);
-
- if (DEBUG) {
- LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
- lp.println("Final text:");
- TextUtils.dumpSpans(content, lp, " ");
- }
+ return success;
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index ed1cd58..f0b311c 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -77,11 +77,11 @@ public class LockPatternUtils {
public static final int MIN_PATTERN_REGISTER_FAIL = 3;
private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
+ private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
+ private final static String PATTERN_EVER_CHOSEN = "lockscreen.patterneverchosen";
private final ContentResolver mContentResolver;
- private long mLockoutDeadline = 0;
-
private static String sLockPatternFilename;
/**
@@ -140,6 +140,16 @@ public class LockPatternUtils {
}
/**
+ * Return true if the user has ever chosen a pattern. This is true even if the pattern is
+ * currently cleared.
+ *
+ * @return True if the user has ever chosen a pattern.
+ */
+ public boolean isPatternEverChosen() {
+ return getBoolean(PATTERN_EVER_CHOSEN);
+ }
+
+ /**
* Save a lock pattern.
* @param pattern The new pattern to save.
*/
@@ -156,6 +166,7 @@ public class LockPatternUtils {
raf.write(hash, 0, hash.length);
}
raf.close();
+ setBoolean(PATTERN_EVER_CHOSEN, true);
} catch (FileNotFoundException fnfe) {
// Cant do much, unless we want to fail over to using the settings provider
Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
@@ -270,12 +281,14 @@ public class LockPatternUtils {
}
/**
- * Store the lockout deadline, meaning the user can't attempt his/her unlock
- * pattern until the deadline has passed. Does not persist across reboots.
- * @param deadline The elapsed real time in millis in future.
+ * Set and store the lockout deadline, meaning the user can't attempt his/her unlock
+ * pattern until the deadline has passed.
+ * @return the chosen deadline.
*/
- public void setLockoutAttemptDeadline(long deadline) {
- mLockoutDeadline = deadline;
+ public long setLockoutAttemptDeadline() {
+ final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS;
+ setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline);
+ return deadline;
}
/**
@@ -284,7 +297,12 @@ public class LockPatternUtils {
* enter a pattern.
*/
public long getLockoutAttemptDeadline() {
- return (mLockoutDeadline <= SystemClock.elapsedRealtime()) ? 0 : mLockoutDeadline;
+ final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L);
+ final long now = SystemClock.elapsedRealtime();
+ if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) {
+ return 0L;
+ }
+ return deadline;
}
/**
@@ -341,5 +359,13 @@ public class LockPatternUtils {
enabled ? 1 : 0);
}
+ private long getLong(String systemSettingKey, long def) {
+ return android.provider.Settings.System.getLong(mContentResolver, systemSettingKey, def);
+ }
+
+ private void setLong(String systemSettingKey, long value) {
+ android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value);
+ }
+
}
diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/com/android/internal/widget/NumberPicker.java
index 20ea6a6..2f08c8d 100644
--- a/core/java/com/android/internal/widget/NumberPicker.java
+++ b/core/java/com/android/internal/widget/NumberPicker.java
@@ -28,12 +28,9 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnLongClickListener;
-import android.view.animation.Animation;
-import android.view.animation.TranslateAnimation;
-import android.widget.EditText;
-import android.widget.LinearLayout;
import android.widget.TextView;
-import android.widget.ViewSwitcher;
+import android.widget.LinearLayout;
+import android.widget.EditText;
import com.android.internal.R;
@@ -71,25 +68,18 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
private final Runnable mRunnable = new Runnable() {
public void run() {
if (mIncrement) {
- changeCurrent(mCurrent + 1, mSlideUpInAnimation, mSlideUpOutAnimation);
+ changeCurrent(mCurrent + 1);
mHandler.postDelayed(this, mSpeed);
} else if (mDecrement) {
- changeCurrent(mCurrent - 1, mSlideDownInAnimation, mSlideDownOutAnimation);
+ changeCurrent(mCurrent - 1);
mHandler.postDelayed(this, mSpeed);
}
}
};
-
- private final LayoutInflater mInflater;
- private final TextView mText;
- private final InputFilter mInputFilter;
+
+ private final EditText mText;
private final InputFilter mNumberInputFilter;
-
- private final Animation mSlideUpOutAnimation;
- private final Animation mSlideUpInAnimation;
- private final Animation mSlideDownOutAnimation;
- private final Animation mSlideDownInAnimation;
-
+
private String[] mDisplayedValues;
private int mStart;
private int mEnd;
@@ -110,14 +100,14 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
this(context, attrs, 0);
}
- public NumberPicker(Context context, AttributeSet attrs,
- int defStyle) {
+ @SuppressWarnings({"UnusedDeclaration"})
+ public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
setOrientation(VERTICAL);
- mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mInflater.inflate(R.layout.number_picker, this, true);
+ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.number_picker, this, true);
mHandler = new Handler();
- mInputFilter = new NumberPickerInputFilter();
+ InputFilter inputFilter = new NumberPickerInputFilter();
mNumberInputFilter = new NumberRangeKeyListener();
mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
mIncrementButton.setOnClickListener(this);
@@ -128,32 +118,11 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
mDecrementButton.setOnLongClickListener(this);
mDecrementButton.setNumberPicker(this);
- mText = (TextView) findViewById(R.id.timepicker_input);
+ mText = (EditText) findViewById(R.id.timepicker_input);
mText.setOnFocusChangeListener(this);
- mText.setFilters(new InputFilter[] { mInputFilter });
+ mText.setFilters(new InputFilter[] {inputFilter});
mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
-
- mSlideUpOutAnimation = new TranslateAnimation(
- Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
- 0, Animation.RELATIVE_TO_SELF, 0,
- Animation.RELATIVE_TO_SELF, -100);
- mSlideUpOutAnimation.setDuration(200);
- mSlideUpInAnimation = new TranslateAnimation(
- Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
- 0, Animation.RELATIVE_TO_SELF, 100,
- Animation.RELATIVE_TO_SELF, 0);
- mSlideUpInAnimation.setDuration(200);
- mSlideDownOutAnimation = new TranslateAnimation(
- Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
- 0, Animation.RELATIVE_TO_SELF, 0,
- Animation.RELATIVE_TO_SELF, 100);
- mSlideDownOutAnimation.setDuration(200);
- mSlideDownInAnimation = new TranslateAnimation(
- Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
- 0, Animation.RELATIVE_TO_SELF, -100,
- Animation.RELATIVE_TO_SELF, 0);
- mSlideDownInAnimation.setDuration(200);
-
+
if (!isEnabled()) {
setEnabled(false);
}
@@ -220,17 +189,14 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
}
public void onClick(View v) {
-
- /* The text view may still have focus so clear it's focus which will
- * trigger the on focus changed and any typed values to be pulled.
- */
- mText.clearFocus();
+ validateInput(mText);
+ if (!mText.hasFocus()) mText.requestFocus();
// now perform the increment/decrement
if (R.id.increment == v.getId()) {
- changeCurrent(mCurrent + 1, mSlideUpInAnimation, mSlideUpOutAnimation);
+ changeCurrent(mCurrent + 1);
} else if (R.id.decrement == v.getId()) {
- changeCurrent(mCurrent - 1, mSlideDownInAnimation, mSlideDownOutAnimation);
+ changeCurrent(mCurrent - 1);
}
}
@@ -240,7 +206,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
: String.valueOf(value);
}
- private void changeCurrent(int current, Animation in, Animation out) {
+ private void changeCurrent(int current) {
// Wrap around the values if we go past the start or end
if (current > mEnd) {
@@ -271,6 +237,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
} else {
mText.setText(mDisplayedValues[mCurrent - mStart]);
}
+ mText.setSelection(mText.getText().length());
}
private void validateCurrentView(CharSequence str) {
@@ -289,16 +256,20 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
* has valid values.
*/
if (!hasFocus) {
- String str = String.valueOf(((TextView) v).getText());
- if ("".equals(str)) {
-
- // Restore to the old value as we don't allow empty values
- updateView();
- } else {
-
- // Check the new value and ensure it's in range
- validateCurrentView(str);
- }
+ validateInput(v);
+ }
+ }
+
+ private void validateInput(View v) {
+ String str = String.valueOf(((TextView) v).getText());
+ if ("".equals(str)) {
+
+ // Restore to the old value as we don't allow empty values
+ updateView();
+ } else {
+
+ // Check the new value and ensure it's in range
+ validateCurrentView(str);
}
}
diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java
new file mode 100644
index 0000000..aee7b76
--- /dev/null
+++ b/core/java/com/android/internal/widget/TextProgressBar.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Chronometer;
+import android.widget.Chronometer.OnChronometerTickListener;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.RemoteViews.RemoteView;
+
+/**
+ * Container that links together a {@link ProgressBar} and {@link Chronometer}
+ * as children. It subscribes to {@link Chronometer#OnChronometerTickListener}
+ * and updates the {@link ProgressBar} based on a preset finishing time.
+ * <p>
+ * This widget expects to contain two children with specific ids
+ * {@link android.R.id.progress} and {@link android.R.id.text1}.
+ * <p>
+ * If the {@link Chronometer} {@link android.R.attr#layout_width} is
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, then the
+ * {@link android.R.attr#gravity} will be used to automatically move it with
+ * respect to the {@link ProgressBar} position. For example, if
+ * {@link android.view.Gravity#LEFT} then the {@link Chronometer} will be placed
+ * just ahead of the leading edge of the {@link ProgressBar} position.
+ */
+@RemoteView
+public class TextProgressBar extends RelativeLayout implements OnChronometerTickListener {
+ public static final String TAG = "TextProgressBar";
+
+ static final int CHRONOMETER_ID = android.R.id.text1;
+ static final int PROGRESSBAR_ID = android.R.id.progress;
+
+ Chronometer mChronometer = null;
+ ProgressBar mProgressBar = null;
+
+ long mDurationBase = -1;
+ int mDuration = -1;
+
+ boolean mChronometerFollow = false;
+ int mChronometerGravity = Gravity.NO_GRAVITY;
+
+ public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public TextProgressBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TextProgressBar(Context context) {
+ super(context);
+ }
+
+ /**
+ * Catch any interesting children when they are added.
+ */
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ super.addView(child, index, params);
+
+ int childId = child.getId();
+ if (childId == CHRONOMETER_ID && child instanceof Chronometer) {
+ mChronometer = (Chronometer) child;
+ mChronometer.setOnChronometerTickListener(this);
+
+ // Check if Chronometer should move with with ProgressBar
+ mChronometerFollow = (params.width == ViewGroup.LayoutParams.WRAP_CONTENT);
+ mChronometerGravity = (mChronometer.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK);
+
+ } else if (childId == PROGRESSBAR_ID && child instanceof ProgressBar) {
+ mProgressBar = (ProgressBar) child;
+ }
+ }
+
+ /**
+ * Set the expected termination time of the running {@link Chronometer}.
+ * This value is used to adjust the {@link ProgressBar} against the elapsed
+ * time.
+ * <p>
+ * Call this <b>after</b> adjusting the {@link Chronometer} base, if
+ * necessary.
+ *
+ * @param durationBase Use the {@link SystemClock#elapsedRealtime} time
+ * base.
+ */
+ @android.view.RemotableViewMethod
+ public void setDurationBase(long durationBase) {
+ mDurationBase = durationBase;
+
+ if (mProgressBar == null || mChronometer == null) {
+ throw new RuntimeException("Expecting child ProgressBar with id " +
+ "'android.R.id.progress' and Chronometer id 'android.R.id.text1'");
+ }
+
+ // Update the ProgressBar maximum relative to Chronometer base
+ mDuration = (int) (durationBase - mChronometer.getBase());
+ if (mDuration <= 0) {
+ mDuration = 1;
+ }
+ mProgressBar.setMax(mDuration);
+ }
+
+ /**
+ * Callback when {@link Chronometer} changes, indicating that we should
+ * update the {@link ProgressBar} and change the layout if necessary.
+ */
+ public void onChronometerTick(Chronometer chronometer) {
+ if (mProgressBar == null) {
+ throw new RuntimeException(
+ "Expecting child ProgressBar with id 'android.R.id.progress'");
+ }
+
+ // Stop Chronometer if we're past duration
+ long now = SystemClock.elapsedRealtime();
+ if (now >= mDurationBase) {
+ mChronometer.stop();
+ }
+
+ // Update the ProgressBar status
+ int remaining = (int) (mDurationBase - now);
+ mProgressBar.setProgress(mDuration - remaining);
+
+ // Move the Chronometer if gravity is set correctly
+ if (mChronometerFollow) {
+ RelativeLayout.LayoutParams params;
+
+ // Calculate estimate of ProgressBar leading edge position
+ params = (RelativeLayout.LayoutParams) mProgressBar.getLayoutParams();
+ int contentWidth = mProgressBar.getWidth() - (params.leftMargin + params.rightMargin);
+ int leadingEdge = ((contentWidth * mProgressBar.getProgress()) /
+ mProgressBar.getMax()) + params.leftMargin;
+
+ // Calculate any adjustment based on gravity
+ int adjustLeft = 0;
+ int textWidth = mChronometer.getWidth();
+ if (mChronometerGravity == Gravity.RIGHT) {
+ adjustLeft = -textWidth;
+ } else if (mChronometerGravity == Gravity.CENTER_HORIZONTAL) {
+ adjustLeft = -(textWidth / 2);
+ }
+
+ // Limit margin to keep text inside ProgressBar bounds
+ leadingEdge += adjustLeft;
+ int rightLimit = contentWidth - params.rightMargin - textWidth;
+ if (leadingEdge < params.leftMargin) {
+ leadingEdge = params.leftMargin;
+ } else if (leadingEdge > rightLimit) {
+ leadingEdge = rightLimit;
+ }
+
+ params = (RelativeLayout.LayoutParams) mChronometer.getLayoutParams();
+ params.leftMargin = leadingEdge;
+
+ // Request layout to move Chronometer
+ mChronometer.requestLayout();
+
+ }
+ }
+}
diff --git a/core/java/com/google/android/gdata/client/AndroidGDataClient.java b/core/java/com/google/android/gdata/client/AndroidGDataClient.java
index 1d8e9c5..fe7d860 100644
--- a/core/java/com/google/android/gdata/client/AndroidGDataClient.java
+++ b/core/java/com/google/android/gdata/client/AndroidGDataClient.java
@@ -21,6 +21,7 @@ import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.AbstractHttpEntity;
import android.content.ContentResolver;
+import android.content.Context;
import android.net.http.AndroidHttpClient;
import android.text.TextUtils;
import android.util.Config;
@@ -117,10 +118,7 @@ public class AndroidGDataClient implements GDataClient {
}
/**
- * Creates a new AndroidGDataClient.
- *
- * @param resolver The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
+ * @deprecated Use AndroidGDAtaClient(Context) instead.
*/
public AndroidGDataClient(ContentResolver resolver) {
mHttpClient = new GoogleHttpClient(resolver, USER_AGENT_APP_VERSION,
@@ -129,6 +127,21 @@ public class AndroidGDataClient implements GDataClient {
mResolver = resolver;
}
+ /**
+ * Creates a new AndroidGDataClient.
+ *
+ * @param context The ContentResolver to get URL rewriting rules from
+ * through the Android proxy server, using null to indicate not using proxy.
+ * The context will also be used by GoogleHttpClient for configuration of
+ * SSL session persistence.
+ */
+ public AndroidGDataClient(Context context) {
+ mHttpClient = new GoogleHttpClient(context, USER_AGENT_APP_VERSION,
+ true /* gzip capable */);
+ mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
+ mResolver = context.getContentResolver();
+ }
+
public void close() {
mHttpClient.close();
}
diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java
index 4656aff..ac9ad73 100644
--- a/core/java/com/google/android/net/GoogleHttpClient.java
+++ b/core/java/com/google/android/net/GoogleHttpClient.java
@@ -16,44 +16,44 @@
package com.google.android.net;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.http.AndroidHttpClient;
+import android.os.Build;
+import android.os.NetStat;
+import android.os.SystemClock;
+import android.provider.Checkin;
+import android.util.Config;
+import android.util.Log;
+import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolException;
-import org.apache.http.impl.client.RequestWrapper;
-import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
-import org.apache.http.client.HttpClient;
import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
+import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
import java.io.IOException;
import java.net.URI;
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;
-
/**
* {@link AndroidHttpClient} wrapper that uses {@link UrlRules} to rewrite URLs
* and otherwise tweak HTTP requests.
*/
public class GoogleHttpClient implements HttpClient {
- private static final String TAG = "GoogleHttpClient";
- private final AndroidHttpClient mClient;
- private final ContentResolver mResolver;
- private final String mUserAgent;
+ private static final String TAG = "GoogleHttpClient";
/** Exception thrown when a request is blocked by the URL rules. */
public static class BlockedRequestException extends IOException {
@@ -64,11 +64,15 @@ public class GoogleHttpClient implements HttpClient {
}
}
+ private final AndroidHttpClient mClient;
+ private final ContentResolver mResolver;
+ private final String mUserAgent;
+
/**
* 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)}
+ * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)}
*/
public GoogleHttpClient(ContentResolver resolver, String userAgent) {
mClient = AndroidHttpClient.newInstance(userAgent);
@@ -77,6 +81,17 @@ public class GoogleHttpClient implements HttpClient {
}
/**
+ * GoogleHttpClient(Context, String, boolean) - without SSL session
+ * persistence.
+ *
+ * @deprecated use Context instead of ContentResolver.
+ */
+ public GoogleHttpClient(ContentResolver resolver, String appAndVersion,
+ boolean gzipCapable) {
+ this(resolver, null /* cache */, appAndVersion, gzipCapable);
+ }
+
+ /**
* Create an HTTP client. Normaly this client is shared throughout an app.
* The HTTP client will construct its User-Agent as follows:
*
@@ -85,7 +100,10 @@ public class GoogleHttpClient implements HttpClient {
* <appAndVersion> (<build device> <build id>); gzip
* (if gzip capable)
*
- * @param resolver to use for acccessing URL rewriting rules.
+ * The context has settings for URL rewriting rules and is used to enable
+ * SSL session persistence.
+ *
+ * @param context application context.
* @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
@@ -93,14 +111,19 @@ public class GoogleHttpClient implements HttpClient {
* 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 + ")";
+ public GoogleHttpClient(Context context, String appAndVersion,
+ boolean gzipCapable) {
+ this(context.getContentResolver(), SSLClientSessionCacheFactory.getCache(context),
+ appAndVersion, gzipCapable);
+ }
+
+ private GoogleHttpClient(ContentResolver resolver, SSLClientSessionCache cache,
+ String appAndVersion, boolean gzipCapable) {
+ String userAgent = appAndVersion + " (" + Build.DEVICE + " " + Build.ID + ")";
if (gzipCapable) {
userAgent = userAgent + "; gzip";
}
- mClient = AndroidHttpClient.newInstance(userAgent);
+ mClient = AndroidHttpClient.newInstance(userAgent, cache);
mResolver = resolver;
mUserAgent = userAgent;
}
@@ -120,8 +143,37 @@ public class GoogleHttpClient implements HttpClient {
String code = "Error";
long start = SystemClock.elapsedRealtime();
try {
- HttpResponse response = mClient.execute(request, context);
- code = Integer.toString(response.getStatusLine().getStatusCode());
+ HttpResponse response;
+ // TODO: if we're logging network stats, and if the apache library is configured
+ // to follow redirects, count each redirect as an additional round trip.
+
+ // see if we're logging network stats.
+ boolean logNetworkStats = NetworkStatsEntity.shouldLogNetworkStats();
+
+ if (logNetworkStats) {
+ int uid = android.os.Process.myUid();
+ long startTx = NetStat.getUidTxBytes(uid);
+ long startRx = NetStat.getUidRxBytes(uid);
+
+ response = mClient.execute(request, context);
+ code = Integer.toString(response.getStatusLine().getStatusCode());
+
+ HttpEntity origEntity = response == null ? null : response.getEntity();
+ if (origEntity != null) {
+ // yeah, we compute the same thing below. we do need to compute this here
+ // so we can wrap the HttpEntity in the response.
+ long now = SystemClock.elapsedRealtime();
+ long elapsed = now - start;
+ NetworkStatsEntity entity = new NetworkStatsEntity(origEntity,
+ mUserAgent, uid, startTx, startRx,
+ elapsed /* response latency */, now /* processing start time */);
+ response.setEntity(entity);
+ }
+ } else {
+ response = mClient.execute(request, context);
+ code = Integer.toString(response.getStatusLine().getStatusCode());
+ }
+
return response;
} catch (IOException e) {
code = "IOException";
diff --git a/core/java/com/google/android/net/NetStats.java b/core/java/com/google/android/net/NetStats.java
deleted file mode 100644
index fee8219..0000000
--- a/core/java/com/google/android/net/NetStats.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.google.android.net;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.NoSuchElementException;
-import java.util.StringTokenizer;
-
-/**
- * Gets network send/receive statistics for this process.
- * The statistics come from /proc/pid/stat, using the ATOP kernel modification.
- */
-public class NetStats {
- private static String statsFile = "/proc/" + android.os.Process.myPid() + "/stat";
-
- private static String TAG = "netstat";
-
- /**
- * Returns network stats for this process.
- * Returns stats of 0 if problem encountered.
- *
- * @return [bytes sent, bytes received]
- */
- public static long[] getStats() {
- long result[] = new long[2];
-
- try {
- BufferedReader br = new BufferedReader(new FileReader(statsFile), 512);
- String line = br.readLine(); // Skip first line
- line = br.readLine();
- StringTokenizer st = new StringTokenizer(line);
- st.nextToken(); // disk read
- st.nextToken(); // disk sectors
- st.nextToken(); // disk write
- st.nextToken(); // disk sectors
- st.nextToken(); // tcp send
- result[0] = Long.parseLong(st.nextToken()); // tcp bytes sent
- st.nextToken(); //tcp recv
- result[1] = Long.parseLong(st.nextToken()); // tcp bytes recv
- } catch (IOException e) {
- // Probably wrong kernel; no point logging exception
- } catch (NoSuchElementException e) {
- } catch (NullPointerException e) {
- }
- return result;
- }
-}
diff --git a/core/java/com/google/android/net/NetworkStatsEntity.java b/core/java/com/google/android/net/NetworkStatsEntity.java
new file mode 100644
index 0000000..f5d2349
--- /dev/null
+++ b/core/java/com/google/android/net/NetworkStatsEntity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.net;
+
+import android.os.NetStat;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.EventLog;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.entity.HttpEntityWrapper;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+public class NetworkStatsEntity extends HttpEntityWrapper {
+
+ private static final int HTTP_STATS_EVENT = 52001;
+
+ private class NetworkStatsInputStream extends FilterInputStream {
+
+ public NetworkStatsInputStream(InputStream wrapped) {
+ super(wrapped);
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ super.close();
+ } finally {
+ long processingTime = SystemClock.elapsedRealtime() - mProcessingStartTime;
+ long tx = NetStat.getUidTxBytes(mUid);
+ long rx = NetStat.getUidRxBytes(mUid);
+
+ EventLog.writeEvent(HTTP_STATS_EVENT, mUa, mResponseLatency, processingTime,
+ tx - mStartTx, rx - mStartRx);
+ }
+ }
+ }
+
+ private final String mUa;
+ private final int mUid;
+ private final long mStartTx;
+ private final long mStartRx;
+ private final long mResponseLatency;
+ private final long mProcessingStartTime;
+
+ public NetworkStatsEntity(HttpEntity orig, String ua,
+ int uid, long startTx, long startRx, long responseLatency,
+ long processingStartTime) {
+ super(orig);
+ this.mUa = ua;
+ this.mUid = uid;
+ this.mStartTx = startTx;
+ this.mStartRx = startRx;
+ this.mResponseLatency = responseLatency;
+ this.mProcessingStartTime = processingStartTime;
+ }
+
+ public static boolean shouldLogNetworkStats() {
+ return "1".equals(SystemProperties.get("googlehttpclient.logstats"));
+ }
+
+ @Override
+ public InputStream getContent() throws IOException {
+ InputStream orig = super.getContent();
+ return new NetworkStatsInputStream(orig);
+ }
+}
diff --git a/core/java/com/google/android/net/SSLClientSessionCacheFactory.java b/core/java/com/google/android/net/SSLClientSessionCacheFactory.java
new file mode 100644
index 0000000..6570a9bd
--- /dev/null
+++ b/core/java/com/google/android/net/SSLClientSessionCacheFactory.java
@@ -0,0 +1,62 @@
+package com.google.android.net;
+
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.android.internal.net.DbSSLSessionCache;
+
+/**
+ * Factory that returns the appropriate implementation of a {@link SSLClientSessionCache} based
+ * on gservices.
+ *
+ * @hide
+ */
+// TODO: return a proxied implementation that is updated as the gservices value changes.
+public final class SSLClientSessionCacheFactory {
+
+ private static final String TAG = "SSLClientSessionCacheFactory";
+
+ public static final String DB = "db";
+ public static final String FILE = "file";
+
+ // utility class
+ private SSLClientSessionCacheFactory() {}
+
+ /**
+ * Returns a new {@link SSLClientSessionCache} based on the persistent cache that's specified,
+ * if any, in gservices. If no cache is specified, returns null.
+ * @param context The application context used for the per-process persistent cache.
+ * @return A new {@link SSLClientSessionCache}, or null if no persistent cache is configured.
+ */
+ public static SSLClientSessionCache getCache(Context context) {
+ String type = Settings.Gservices.getString(context.getContentResolver(),
+ Settings.Gservices.SSL_SESSION_CACHE);
+
+ if (type != null) {
+ if (DB.equals(type)) {
+ return DbSSLSessionCache.getInstanceForPackage(context);
+ } else if (FILE.equals(type)) {
+ File dir = context.getFilesDir();
+ File cacheDir = new File(dir, "sslcache");
+ if (!cacheDir.exists()) {
+ cacheDir.mkdir();
+ }
+ try {
+ return FileClientSessionCache.usingDirectory(cacheDir);
+ } catch (IOException ioe) {
+ Log.w(TAG, "Unable to create FileClientSessionCache in " + cacheDir.getName(), ioe);
+ return null;
+ }
+ } else {
+ Log.w(TAG, "Ignoring unrecognized type: '" + type + "'");
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/java/com/google/android/util/GoogleWebContentHelper.java b/core/java/com/google/android/util/GoogleWebContentHelper.java
index 7500ec3..2911420 100644
--- a/core/java/com/google/android/util/GoogleWebContentHelper.java
+++ b/core/java/com/google/android/util/GoogleWebContentHelper.java
@@ -206,7 +206,7 @@ public class GoogleWebContentHelper {
WebSettings settings = mWebView.getSettings();
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
- mProgressBar = mLayout.findViewById(com.android.internal.R.id.progress);
+ mProgressBar = mLayout.findViewById(com.android.internal.R.id.progressContainer);
TextView message = (TextView) mProgressBar.findViewById(com.android.internal.R.id.message);
message.setText(com.android.internal.R.string.googlewebcontenthelper_loading);
diff --git a/core/java/com/google/android/util/SimplePullParser.java b/core/java/com/google/android/util/SimplePullParser.java
index 95f2ddb..031790b 100644
--- a/core/java/com/google/android/util/SimplePullParser.java
+++ b/core/java/com/google/android/util/SimplePullParser.java
@@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.Reader;
+import java.io.Closeable;
import android.util.Xml;
import android.util.Log;
@@ -41,6 +42,7 @@ public class SimplePullParser {
private String mLogTag = null;
private final XmlPullParser mParser;
+ private Closeable source;
private String mCurrentStartTag;
/**
@@ -56,6 +58,7 @@ public class SimplePullParser {
moveToStartDocument(parser);
mParser = parser;
mCurrentStartTag = null;
+ source = stream;
} catch (XmlPullParserException e) {
throw new ParseException(e);
}
@@ -68,6 +71,7 @@ public class SimplePullParser {
public SimplePullParser(XmlPullParser parser) {
mParser = parser;
mCurrentStartTag = null;
+ source = null;
}
/**
@@ -89,6 +93,7 @@ public class SimplePullParser {
moveToStartDocument(parser);
mParser = parser;
mCurrentStartTag = null;
+ source = reader;
} catch (XmlPullParserException e) {
throw new ParseException(e);
}
@@ -171,6 +176,12 @@ public class SimplePullParser {
}
if (eventType == XmlPullParser.END_DOCUMENT && parentDepth == 0) {
+ // we could just rely on the caller calling close(), which it should, but try
+ // to auto-close for clients that might have missed doing so.
+ if (source != null) {
+ source.close();
+ source = null;
+ }
return null;
}
@@ -333,6 +344,20 @@ public class SimplePullParser {
}
/**
+ * Close this SimplePullParser and any underlying resources (e.g., its InputStream or
+ * Reader source) used by this SimplePullParser.
+ */
+ public void close() {
+ if (source != null) {
+ try {
+ source.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
+ /**
* Returns the string value of the named attribute. An exception will
* be thrown if the attribute is not present or is not a valid long.
*